g1t/crates/contracts/src/work.rs

1,782 lines59,203 bytesCodeBlame
1//! 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
14/// Labels every repository starts with. Any other label comes into being
15/// the first time it is put on an issue.
16pub const DEFAULT_LABELS: [&str; 5] = ["bug", "feature", "docs", "chore", "question"];
17
18/// `open` or `closed`: the filter on lists of issues and pull requests. An
19/// open pull request is a draft or one ready for review; a closed one was
20/// merged or closed without merging.
21#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(rename_all = "lowercase")]
23pub enum State {
24 Open,
25 Closed,
26}
27
28/// Why an issue was closed.
29#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
30#[serde(rename_all = "snake_case")]
31pub enum IssueReason {
32 /// The work was done. If a pull request did it, `resolved_by` names it.
33 Completed,
34 NotPlanned,
35}
36
37impl IssueReason {
38 pub fn as_str(self) -> &'static str {
39 match self {
40 IssueReason::Completed => "completed",
41 IssueReason::NotPlanned => "not_planned",
42 }
43 }
44}
45
46/// Something that should change in a repository: a bug, a feature, a
47/// question. Opened by a person, an agent or an integration. Pull requests
48/// are made against it; the one that is merged resolves it.
49#[derive(Clone, Debug, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase")]
51pub struct Issue {
52 pub id: String,
53 pub repo_id: String,
54 /// Shown as `#12`.
55 pub number: u32,
56 pub title: String,
57 /// Markdown. Also what an agent is given to work from.
58 pub body: String,
59 pub labels: Vec<String>,
60 /// Commands that must pass for a pull request to be accepted.
61 pub checks: Vec<String>,
62 pub state: State,
63 /// Set when closed.
64 pub reason: Option<IssueReason>,
65 /// The number of the pull request whose merge closed this issue.
66 pub resolved_by: Option<u32>,
67 pub author: User,
68 /// RFC 3339.
69 pub created_at: String,
70 /// RFC 3339.
71 pub updated_at: String,
72 /// RFC 3339.
73 pub closed_at: Option<String>,
74 /// Pull requests made against this issue, in any state.
75 pub pull_count: u32,
76 pub comment_count: u32,
77 /// Usernames of the people it is assigned to.
78 #[serde(default)]
79 pub assignees: Vec<String>,
80 /// The numbers of the issues that have to be merged before this one is
81 /// worked on.
82 #[serde(default)]
83 pub blocked_by: Vec<u32>,
84 /// Whether a g1t agent takes it as soon as it can: at once, or when
85 /// what it is blocked by has merged.
86 #[serde(default)]
87 pub queued: bool,
88 /// The agent working on it now: the one behind its newest pull request
89 /// that is still in progress in a fork, such as `g1t-agent`.
90 #[serde(default)]
91 pub agent: Option<String>,
92}
93
94#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
95#[serde(rename_all = "lowercase")]
96pub enum PullStatus {
97 /// Still being worked on.
98 Draft,
99 /// Ready for review.
100 Open,
101 Merged,
102 /// Closed without merging.
103 Closed,
104}
105
106impl PullStatus {
107 pub fn as_str(self) -> &'static str {
108 match self {
109 PullStatus::Draft => "draft",
110 PullStatus::Open => "open",
111 PullStatus::Merged => "merged",
112 PullStatus::Closed => "closed",
113 }
114 }
115
116 /// Whether the pull request can still be changed or merged.
117 pub fn is_active(self) -> bool {
118 matches!(self, PullStatus::Draft | PullStatus::Open)
119 }
120}
121
122/// Where the agent runs: on g1t's sandboxes, or in someone's own session.
123#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
124#[serde(rename_all = "lowercase")]
125pub enum Runtime {
126 Hosted,
127 External,
128}
129
130/// A proposed change. It is made either in a fork created for it, which is
131/// how agents work, or on a branch pushed to the repository itself.
132#[derive(Clone, Debug, Serialize, Deserialize)]
133#[serde(rename_all = "camelCase")]
134pub struct Pull {
135 pub id: String,
136 pub repo_id: String,
137 /// Shown as `#12`.
138 pub number: u32,
139 /// The number of the issue this is for, if any.
140 pub issue: Option<u32>,
141 pub title: String,
142 /// Markdown: what changed and why. Set when marked ready.
143 pub body: Option<String>,
144 /// A label for the agent doing the work, e.g. `claude-code`.
145 pub agent: String,
146 pub runtime: Runtime,
147 pub status: PullStatus,
148 /// The fork holding the change, unless it is on a branch.
149 pub fork: Option<RepoPath>,
150 /// The fork's repository id.
151 pub fork_repo_id: Option<String>,
152 /// The branch of the repository holding the change, unless it is in a
153 /// fork.
154 pub branch: Option<String>,
155 pub head_commit: Option<String>,
156 /// For a merged pull request, what the branch pointed to before the
157 /// merge. Comparing against it shows what the pull request changed.
158 pub merge_base: Option<String>,
159 /// Username of whoever merged it.
160 pub merged_by: Option<String>,
161 /// RFC 3339.
162 pub merged_at: Option<String>,
163 /// Set on a pull request closed because another one for the same issue
164 /// was merged: that one's number.
165 pub superseded_by: Option<u32>,
166 /// Where the latest run of the issue's acceptance checks stands, if
167 /// there has been one against the current head.
168 pub check_status: Option<CheckStatus>,
169 /// The files it changes, as of its latest push.
170 #[serde(default)]
171 pub files: Vec<ChangedFile>,
172 /// Usernames of the people it is assigned to.
173 #[serde(default)]
174 pub assignees: Vec<String>,
175 /// Those whose review was asked for: usernames, and `g1t-agent` when a
176 /// g1t agent was asked.
177 #[serde(default)]
178 pub reviewers: Vec<String>,
179 pub author: User,
180 /// RFC 3339.
181 pub created_at: String,
182 /// RFC 3339.
183 pub updated_at: String,
184 /// How sure g1t is of a g1t agent's change, from what it can observe,
185 /// once the agent has finished it. Absent before then, and on changes
186 /// g1t is not seeing through.
187 #[serde(default)]
188 pub confidence: Option<Confidence>,
189}
190
191/// How sure g1t is that an agent's change is right. Low is below medium,
192/// which is below high, so the lower of two is their minimum.
193#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
194#[serde(rename_all = "lowercase")]
195pub enum ConfidenceLevel {
196 Low,
197 Medium,
198 High,
199}
200
201impl ConfidenceLevel {
202 pub fn as_str(self) -> &'static str {
203 match self {
204 ConfidenceLevel::Low => "low",
205 ConfidenceLevel::Medium => "medium",
206 ConfidenceLevel::High => "high",
207 }
208 }
209
210 pub fn parse(value: &str) -> Option<ConfidenceLevel> {
211 match value.trim().to_ascii_lowercase().as_str() {
212 "low" => Some(ConfidenceLevel::Low),
213 "medium" => Some(ConfidenceLevel::Medium),
214 "high" => Some(ConfidenceLevel::High),
215 _ => None,
216 }
217 }
218}
219
220/// How sure g1t is of a change an agent made, worked out from what can be
221/// observed: its checks, how often it was sent back, the reviewer agent's
222/// verdict, whether it touched tests, its size, where it reached, how close
223/// it came to its guardrails, and what it asked and was not answered. The
224/// agent may say how sure it is too; what g1t observes can only lower that.
225#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
226#[serde(rename_all = "camelCase")]
227pub struct Confidence {
228 pub level: ConfidenceLevel,
229 /// A few words each, most telling first: what lowered it, or for
230 /// `high`, what it rests on.
231 pub reasons: Vec<String>,
232 /// What the agent said of its own change, if it said.
233 #[serde(default)]
234 pub self_reported: Option<ConfidenceLevel>,
235 /// What the agent said it was unsure about.
236 #[serde(default)]
237 pub uncertain_about: Vec<String>,
238 /// The agent run it was worked out after.
239 #[serde(default)]
240 pub run_id: Option<String>,
241 /// RFC 3339.
242 pub assessed_at: String,
243}
244
245/// One file a pull request changes, and by how much.
246#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
247pub struct ChangedFile {
248 pub path: String,
249 pub additions: u32,
250 pub deletions: u32,
251}
252
253/// Another pull request in progress that changes some of the same files.
254/// Two for the same issue are alternatives; two for different issues are
255/// heading for a conflict.
256#[derive(Clone, Debug, Serialize, Deserialize)]
257pub struct Overlap {
258 pub number: u32,
259 pub title: String,
260 /// The number of the issue the other pull request is for.
261 pub issue: Option<u32>,
262 /// The files both change.
263 pub paths: Vec<String>,
264}
265
266impl Pull {
267 /// What to ask the repos service to see what this pull request changes.
268 ///
269 /// A fork is compared as a whole. A branch is compared by name while
270 /// the pull request is open, and by the commit it was merged or closed
271 /// at afterwards, so later pushes to the branch do not change the record.
272 pub fn comparison(&self, viewer: &Viewer) -> CompareArgs {
273 let settled = matches!(self.status, PullStatus::Merged | PullStatus::Closed);
274 let (repo_id, head) = match &self.fork_repo_id {
275 Some(fork) => (fork.clone(), None),
276 None => (
277 self.repo_id.clone(),
278 self.head_commit
279 .clone()
280 .filter(|_| settled)
281 .or_else(|| self.branch.clone()),
282 ),
283 };
284 CompareArgs {
285 repo_id,
286 viewer: viewer.clone(),
287 base: self.merge_base.clone(),
288 head,
289 }
290 }
291}
292
293#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
294#[serde(rename_all = "lowercase")]
295pub enum CheckStatus {
296 /// Waiting for a sandbox.
297 Queued,
298 Running,
299 Passed,
300 Failed,
301 /// The checks could not be run at all.
302 Errored,
303}
304
305impl CheckStatus {
306 pub fn as_str(self) -> &'static str {
307 match self {
308 CheckStatus::Queued => "queued",
309 CheckStatus::Running => "running",
310 CheckStatus::Passed => "passed",
311 CheckStatus::Failed => "failed",
312 CheckStatus::Errored => "errored",
313 }
314 }
315}
316
317/// How one acceptance check went.
318#[derive(Clone, Debug, Serialize, Deserialize)]
319#[serde(rename_all = "camelCase")]
320pub struct CheckResult {
321 pub command: String,
322 pub passed: bool,
323 /// Absent when the command was stopped for taking too long.
324 #[serde(default)]
325 pub exit_code: Option<i32>,
326 /// What the command printed, standard output and error together. The
327 /// end of it, when there was a lot.
328 #[serde(default)]
329 pub output: String,
330 #[serde(default)]
331 pub duration_ms: u64,
332}
333
334/// One run of an issue's acceptance checks against a pull request's head,
335/// in a sandbox that holds nothing but that commit.
336#[derive(Clone, Debug, Serialize, Deserialize)]
337#[serde(rename_all = "camelCase")]
338pub struct CheckRun {
339 pub id: String,
340 /// The commit that was checked.
341 pub head_commit: String,
342 pub status: CheckStatus,
343 pub results: Vec<CheckResult>,
344 /// Why the checks could not be run, when `status` is `errored`.
345 pub error: Option<String>,
346 /// RFC 3339.
347 pub created_at: String,
348 /// RFC 3339.
349 pub finished_at: Option<String>,
350}
351
352/// A reviewer's decision on a pull request.
353#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
354#[serde(rename_all = "snake_case")]
355pub enum Verdict {
356 Approve,
357 RequestChanges,
358}
359
360impl Verdict {
361 pub fn as_str(self) -> &'static str {
362 match self {
363 Verdict::Approve => "approve",
364 Verdict::RequestChanges => "request_changes",
365 }
366 }
367}
368
369/// What an entry in a conversation is.
370#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
371#[serde(rename_all = "lowercase")]
372pub enum CommentKind {
373 /// Something a person or an agent wrote.
374 #[default]
375 Comment,
376 /// Something that happened: an assignment, a review asked for, a close.
377 Event,
378}
379
380/// A comment on an issue or a pull request. On a pull request it can sit
381/// on one line of the change, and it can carry a reviewer's verdict.
382#[derive(Clone, Debug, Serialize, Deserialize)]
383#[serde(rename_all = "camelCase")]
384pub struct Comment {
385 pub id: String,
386 /// Something a person wrote, or something that happened.
387 #[serde(default)]
388 pub kind: CommentKind,
389 pub author: User,
390 /// Markdown. For an event, what its author did, as the rest of a
391 /// sentence that starts with their name: "assigned ana".
392 pub body: String,
393 /// The file commented on, for a comment on a line.
394 pub path: Option<String>,
395 /// The line of that file, as numbered after the change.
396 pub line: Option<u32>,
397 pub verdict: Option<Verdict>,
398 /// RFC 3339.
399 pub created_at: String,
400}
401
402#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
403#[serde(rename_all = "snake_case")]
404pub enum SessionEntryKind {
405 Prompt,
406 Message,
407 ToolCall,
408 ToolResult,
409 Note,
410}
411
412/// One step of an agent's session: the "why" behind a pull request's commits.
413#[derive(Clone, Debug, Serialize, Deserialize)]
414pub struct SessionEntry {
415 pub seq: u32,
416 pub kind: SessionEntryKind,
417 pub text: String,
418 /// For tool calls and results.
419 pub tool: Option<String>,
420 /// The fork's head commit when this entry was recorded, if known.
421 pub commit: Option<String>,
422 /// RFC 3339.
423 pub at: String,
424}
425
426#[derive(Clone, Debug, Serialize, Deserialize)]
427pub struct NewSessionEntry {
428 pub kind: SessionEntryKind,
429 pub text: String,
430 #[serde(default)]
431 pub tool: Option<String>,
432 #[serde(default)]
433 pub commit: Option<String>,
434}
435
436#[derive(Clone, Debug, Serialize, Deserialize)]
437pub struct IssueDetail {
438 pub issue: Issue,
439 /// Every pull request made against it, oldest first.
440 pub pulls: Vec<Pull>,
441 pub comments: Vec<Comment>,
442}
443
444#[derive(Clone, Debug, Serialize, Deserialize)]
445pub struct PullDetail {
446 pub pull: Pull,
447 /// The issue it is for, if any.
448 pub issue: Option<Issue>,
449 pub comments: Vec<Comment>,
450 /// The latest run of the issue's acceptance checks.
451 pub checks: Option<CheckRun>,
452 /// Other pull requests in progress that change the same files.
453 #[serde(default)]
454 pub overlaps: Vec<Overlap>,
455 /// Whether the branch it would merge into has moved on without it, so
456 /// that it has to catch up before it can merge.
457 #[serde(default)]
458 pub behind: bool,
459 /// Whether a g1t agent is reviewing it right now.
460 #[serde(default)]
461 pub review_pending: bool,
462 /// Where it stands on its way to being merged, for a pull request g1t
463 /// is seeing through. Absent on anyone else's.
464 #[serde(default)]
465 pub lifecycle: Option<Lifecycle>,
466 /// A merge was asked for while it was behind: g1t is bringing it up to
467 /// date and will then land it.
468 #[serde(default)]
469 pub landing: bool,
470 /// Why g1t stopped working on it, if it did: a catch-up that could not
471 /// be completed, for example.
472 #[serde(default)]
473 pub stalled: Option<String>,
474 /// Messages people sent the agent while it worked, oldest first.
475 #[serde(default)]
476 pub messages: Vec<AgentMessage>,
477 /// What workflow runs said about its head commit, one per workflow.
478 #[serde(default)]
479 pub statuses: Vec<CommitStatus>,
480 /// Whether it merges cleanly into the branch it targets, worked out
481 /// ahead of time whenever either side moves.
482 #[serde(default)]
483 pub mergeable: Mergeable,
484 /// When `mergeable` is `conflicting`: the files that conflict.
485 #[serde(default)]
486 pub conflicts: Vec<String>,
487 /// Earlier runs of its acceptance checks, newest first, without their
488 /// output.
489 #[serde(default)]
490 pub earlier_checks: Vec<CheckRun>,
491}
492
493/// Whether a pull request's change merges cleanly into the branch it
494/// targets.
495#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
496#[serde(rename_all = "lowercase")]
497pub enum Mergeable {
498 /// It merges without conflicts.
499 Clean,
500 /// Some files conflict: see `PullDetail::conflicts`.
501 Conflicting,
502 /// Not known: never worked out, or it could not be.
503 #[default]
504 Unknown,
505 /// Being worked out now.
506 Checking,
507}
508
509impl Mergeable {
510 pub fn as_str(self) -> &'static str {
511 match self {
512 Mergeable::Clean => "clean",
513 Mergeable::Conflicting => "conflicting",
514 Mergeable::Unknown => "unknown",
515 Mergeable::Checking => "checking",
516 }
517 }
518
519 pub fn parse(value: Option<&str>) -> Mergeable {
520 match value {
521 Some("clean") => Mergeable::Clean,
522 Some("conflicting") => Mergeable::Conflicting,
523 Some("checking") => Mergeable::Checking,
524 _ => Mergeable::Unknown,
525 }
526 }
527}
528
529/// `start_mergecheck`: claims the probe of whether a pull request merges
530/// cleanly, which the work service asked for with a `pull.mergecheck`
531/// event. Called by the runner service, which starts the sandbox. Refused
532/// when it is no longer wanted, or when the repository already has as many
533/// probes running as it may. Returns `Outcome<MergecheckJob>`.
534#[derive(Debug, Serialize, Deserialize)]
535#[serde(rename_all = "camelCase")]
536pub struct StartMergecheckArgs {
537 pub pull_id: String,
538}
539
540/// What a sandbox needs to find out whether a pull request merges cleanly.
541#[derive(Debug, Serialize, Deserialize)]
542#[serde(rename_all = "camelCase")]
543pub struct MergecheckJob {
544 pub pull_id: String,
545 /// Lets the sandbox, and nothing else, report this probe.
546 pub token: String,
547 pub repo: RepoPath,
548 pub number: u32,
549 pub default_branch: String,
550 /// The default branch's commit to merge into.
551 pub base: String,
552 /// The repository holding the change: its fork, or the repository.
553 pub source: RepoPath,
554 /// The branch of `source` holding it.
555 pub branch: String,
556 /// The change's commit.
557 pub head: String,
558 /// Who opened the pull request, and so can read its source.
559 pub author: User,
560}
561
562/// `report_mergecheck`: what a sandbox found. Returns `Outcome<Mergeable>`.
563#[derive(Debug, Serialize, Deserialize)]
564#[serde(rename_all = "camelCase")]
565pub struct ReportMergecheckArgs {
566 pub pull_id: String,
567 pub token: String,
568 /// The files that conflict; empty when it merges cleanly.
569 #[serde(default)]
570 pub conflicts: Vec<String>,
571 /// Why it could not be found out.
572 #[serde(default)]
573 pub error: Option<String>,
574}
575
576/// What a workflow run (or another tool) says about a commit.
577#[derive(Clone, Debug, Serialize, Deserialize)]
578#[serde(rename_all = "camelCase")]
579pub struct CommitStatus {
580 /// What reported it, such as `CI / push`.
581 pub context: String,
582 /// `pending`, `success`, `failure` or `error`.
583 pub state: String,
584 pub description: Option<String>,
585 /// Where to see more, such as the run's page.
586 pub target_url: Option<String>,
587 pub updated_at: String,
588}
589
590/// `set_commit_status`: for services only. Returns `Outcome<bool>`.
591#[derive(Debug, Serialize, Deserialize)]
592#[serde(rename_all = "camelCase")]
593pub struct SetCommitStatusArgs {
594 pub repo_id: String,
595 pub sha: String,
596 pub context: String,
597 pub state: String,
598 #[serde(default)]
599 pub description: Option<String>,
600 #[serde(default)]
601 pub target_url: Option<String>,
602}
603
604/// A message a person sent an agent at work on a pull request. The agent
605/// receives it at its next step.
606#[derive(Clone, Debug, Serialize, Deserialize)]
607#[serde(rename_all = "camelCase")]
608pub struct AgentMessage {
609 pub id: String,
610 pub author: String,
611 pub body: String,
612 /// RFC 3339.
613 pub created_at: String,
614 /// RFC 3339. When the agent received it; null until then.
615 pub delivered_at: Option<String>,
616 /// `message` from a person, or from another pull request's agent a
617 /// `question`, a `handoff` of work, or the `answer` to one.
618 #[serde(default = "message_kind")]
619 pub kind: String,
620 /// The pull request whose agent sent it, when an agent did.
621 #[serde(default)]
622 pub from_number: Option<u32>,
623 /// The pull request it was sent to.
624 #[serde(default)]
625 pub to_number: u32,
626 /// For a question or handoff: the reply, once there is one.
627 #[serde(default)]
628 pub answer: Option<String>,
629 /// For a handoff: whether it was declined.
630 #[serde(default)]
631 pub declined: bool,
632 /// For the agent that sent it: what to expect, when the agent it asked
633 /// is not at work and will not answer soon.
634 #[serde(default, skip_serializing_if = "Option::is_none")]
635 pub hint: Option<String>,
636}
637
638fn message_kind() -> String {
639 "message".to_owned()
640}
641
642/// `message_agent`: sends the agent working on a pull request a message.
643/// The pull request's author and members of the workspace may. Returns
644/// `Outcome<AgentMessage>`.
645#[derive(Debug, Serialize, Deserialize)]
646pub struct MessageAgentArgs {
647 pub actor: User,
648 pub repo: RepoPath,
649 pub number: u32,
650 pub body: String,
651 /// For an agent: `question` or `handoff`; a person's is a `message`.
652 #[serde(default)]
653 pub kind: Option<String>,
654 /// For an agent: the pull request it is working on, which the reply
655 /// goes back to.
656 #[serde(default)]
657 pub from_number: Option<u32>,
658}
659
660/// `answer_message`: replies to a question or a handoff an agent received,
661/// accepting or declining a handoff. The reply reaches the asking agent at
662/// its next step. Returns `Outcome<AgentMessage>`, the message answered.
663#[derive(Debug, Serialize, Deserialize)]
664pub struct AnswerMessageArgs {
665 pub actor: User,
666 pub repo: RepoPath,
667 pub id: String,
668 pub body: String,
669 #[serde(default)]
670 pub decline: bool,
671}
672
673/// `take_messages`: the messages not yet delivered to the agent working on
674/// a pull request, marked delivered. Only g1t's agents may. Returns
675/// `Outcome<Vec<AgentMessage>>`.
676#[derive(Debug, Serialize, Deserialize)]
677pub struct TakeMessagesArgs {
678 pub actor: User,
679 pub repo: RepoPath,
680 pub number: u32,
681}
682
683/// A step on the way from an assigned issue to a pull request that is ready
684/// to merge. g1t takes each one without being asked.
685#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
686#[serde(rename_all = "snake_case")]
687pub enum Stage {
688 /// The agent is making the change.
689 Working,
690 /// The issue's acceptance checks are running against it.
691 Checking,
692 /// A g1t agent is reviewing it.
693 Reviewing,
694 /// The agent is addressing failed checks or a review.
695 Revising,
696 /// The agent is merging in the branch it would land on, which moved.
697 CatchingUp,
698 /// Woken to answer a question another agent asked it, or a handoff.
699 Answering,
700 /// In the repository's merge queue, being tested with what is ahead of
701 /// it before it lands.
702 Queued,
703 /// Checks passed, reviewed and approved, up to date. A person merges.
704 Ready,
705 /// g1t has stopped and a person has to decide what happens next.
706 NeedsYou,
707}
708
709/// Where a pull request made by a g1t agent stands. See [`Stage`].
710#[derive(Clone, Debug, Serialize, Deserialize)]
711pub struct Lifecycle {
712 pub stage: Stage,
713 /// One sentence saying what is happening, or why it stopped.
714 pub detail: String,
715 /// How many times the agent has been sent back to revise it.
716 pub revisions: u32,
717}
718
719/// `advance`: works out the next step for a pull request g1t is seeing
720/// through and, if there is one to take now, claims it, so that it is
721/// taken once however many times this is called. Called by the runner
722/// service, which carries the step out. Returns `Advance`.
723#[derive(Debug, Serialize, Deserialize)]
724#[serde(rename_all = "camelCase")]
725pub struct AdvanceArgs {
726 pub pull_id: String,
727}
728
729#[derive(Debug, Serialize, Deserialize)]
730#[serde(tag = "action", rename_all = "snake_case")]
731pub enum Advance {
732 /// Nothing to do now: a step is under way, or it is a person's turn.
733 None,
734 /// Have a g1t agent review it.
735 Review { job: LifecycleJob },
736 /// Send the agent back to address `job.feedback`.
737 Revise { job: LifecycleJob },
738 /// Merge in the branch it would land on.
739 CatchUp { job: LifecycleJob },
740}
741
742/// What the runner needs to carry out a step of a pull request's lifecycle.
743#[derive(Debug, Serialize, Deserialize)]
744#[serde(rename_all = "camelCase")]
745pub struct LifecycleJob {
746 pub pull_id: String,
747 pub repo: RepoPath,
748 pub number: u32,
749 /// Who the pull request belongs to. Sandboxes act as them.
750 pub author: User,
751 /// The repository holding the change: its fork, or the repository
752 /// itself for one made on a branch.
753 pub source: RepoPath,
754 /// The branch of the source holding the change; its default branch
755 /// when absent.
756 #[serde(default)]
757 pub branch: Option<String>,
758 pub default_branch: String,
759 pub title: String,
760 pub description: String,
761 pub issue: Option<Issue>,
762 /// For a revision: the failed checks or the review to address.
763 pub feedback: String,
764 /// For a revision: which one this is, from 1.
765 pub round: u32,
766}
767
768/// How a repository wants its pull requests handled. A repository that has
769/// changed nothing has the defaults.
770#[derive(Clone, Debug, Serialize, Deserialize)]
771#[serde(rename_all = "camelCase", default)]
772pub struct RepoSettings {
773 /// Land a g1t agent's pull request without a person once it is ready:
774 /// checks passed and approved as the settings below require.
775 pub auto_merge: bool,
776 /// Refuse to merge a pull request that does not contain the default
777 /// branch's latest commits, so that what merges is what was checked.
778 /// When off, merging one that is behind brings it up to date first.
779 pub require_up_to_date: bool,
780 /// How many approving reviews a pull request needs before it may
781 /// merge. A reviewer who has since asked for changes blocks it.
782 pub required_approvals: u32,
783 /// Whether a g1t agent's approval counts towards `required_approvals`.
784 pub count_agent_approvals: bool,
785 /// Whether a member may merge although the acceptance checks did not
786 /// pass.
787 pub allow_ignoring_checks: bool,
788 /// Whether a g1t agent's pull request is reviewed by a second agent
789 /// without being asked.
790 pub agent_review: bool,
791 /// How many times a g1t agent is sent back to its pull request before
792 /// a person is asked instead.
793 pub max_revisions: u32,
794 /// Merge through a queue: pull requests are tested together with those
795 /// ahead of them, and only a combination that passed reaches the default
796 /// branch.
797 pub merge_queue: bool,
798 /// Ask a person before merging a g1t agent's change whose confidence is
799 /// low: auto-merge and the merge queue leave it, and it needs someone,
800 /// until a person approves it.
801 pub hold_low_confidence: bool,
802 /// Username of the member who last changed the settings, if anyone has.
803 pub updated_by: Option<String>,
804 /// RFC 3339.
805 pub updated_at: Option<String>,
806}
807
808impl Default for RepoSettings {
809 fn default() -> Self {
810 RepoSettings {
811 auto_merge: false,
812 require_up_to_date: false,
813 required_approvals: 0,
814 count_agent_approvals: true,
815 allow_ignoring_checks: true,
816 agent_review: true,
817 max_revisions: 2,
818 merge_queue: false,
819 hold_low_confidence: true,
820 updated_by: None,
821 updated_at: None,
822 }
823 }
824}
825
826/// `update_settings`: replaces a repository's settings. Members of its
827/// workspace only. Returns `Outcome<RepoSettings>`. `get_settings` takes
828/// `ViewArgs` and returns the same.
829#[derive(Debug, Serialize, Deserialize)]
830pub struct UpdateSettingsArgs {
831 pub actor: User,
832 pub repo: RepoPath,
833 /// Who changed them and when are filled in by the service.
834 pub settings: RepoSettings,
835}
836
837/// `catch_up_job`: what the runner needs to bring a pull request up to date
838/// because a merge of it was asked for. Null if none was. Returns
839/// `Option<LifecycleJob>`.
840#[derive(Debug, Serialize, Deserialize)]
841#[serde(rename_all = "camelCase")]
842pub struct CatchUpJobArgs {
843 pub pull_id: String,
844}
845
846/// `wake_for_messages`: the agent on a pull request was asked a question
847/// or handed work while it was not at work. Claims a short step for it to
848/// answer, and hands over what it was sent, marked read. Null when there
849/// is nothing waiting, or the pull request cannot take a step now.
850/// Returns `Option<Wake>`.
851#[derive(Debug, Serialize, Deserialize)]
852#[serde(rename_all = "camelCase")]
853pub struct WakeForMessagesArgs {
854 pub pull_id: String,
855}
856
857/// What an agent woken to answer needs: its pull request, and what it was
858/// sent, oldest first.
859#[derive(Debug, Serialize, Deserialize)]
860#[serde(rename_all = "camelCase")]
861pub struct Wake {
862 pub job: LifecycleJob,
863 pub messages: Vec<AgentMessage>,
864}
865
866/// `stall`: records that a step could not be carried out, so that g1t
867/// stops and a person is asked. Returns `bool`.
868#[derive(Debug, Serialize, Deserialize)]
869#[serde(rename_all = "camelCase")]
870pub struct StallArgs {
871 pub pull_id: String,
872 pub reason: String,
873}
874
875/// `managed_pulls`: ids of the open pull requests g1t is seeing through,
876/// in one repository or in all of them. Returns `Vec<String>`.
877#[derive(Debug, Default, Serialize, Deserialize)]
878#[serde(rename_all = "camelCase")]
879pub struct ManagedPullsArgs {
880 #[serde(default)]
881 pub repo_id: Option<String>,
882}
883
884/// `open_issue`. Returns `Outcome<Issue>`.
885#[derive(Debug, Serialize, Deserialize)]
886pub struct OpenIssueArgs {
887 pub actor: User,
888 pub repo: RepoPath,
889 pub title: String,
890 #[serde(default)]
891 pub body: String,
892 #[serde(default)]
893 pub labels: Vec<String>,
894 #[serde(default)]
895 pub checks: Vec<String>,
896}
897
898/// `delegate_issue`: opens an issue to put g1t-agent on at once, refused
899/// before anything is opened unless `actor` may put agents to work in the
900/// repository (Run, which the Write role has). The runner service's
901/// `delegate` calls it and then starts the agent. Returns `Outcome<Issue>`.
902#[derive(Debug, Serialize, Deserialize)]
903pub struct DelegateIssueArgs {
904 pub actor: User,
905 pub repo: RepoPath,
906 pub title: String,
907 #[serde(default)]
908 pub body: String,
909 #[serde(default)]
910 pub labels: Vec<String>,
911 #[serde(default)]
912 pub checks: Vec<String>,
913}
914
915/// What became of the agent when an issue was opened and handed to it in
916/// one step.
917#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
918#[serde(rename_all = "snake_case")]
919pub enum AgentStartStatus {
920 /// It is at work on the issue's pull request.
921 Started,
922 /// Every agent slot of the workspace is busy: it starts on its own when
923 /// one frees up.
924 Queued,
925 /// It did not start, and will not until someone fixes what `code` says.
926 NotStarted,
927}
928
929/// Whether the agent started, and if not, why and what fixes it.
930#[derive(Clone, Debug, Serialize, Deserialize)]
931#[serde(rename_all = "camelCase")]
932pub struct AgentStart {
933 pub status: AgentStartStatus,
934 /// Why it did not start: `not_paid`, `trial_used`, `limit`, `paused`,
935 /// `issue_cap`, `billing_unavailable` or `no_model`; `waiting` when
936 /// queued.
937 #[serde(default)]
938 pub code: Option<String>,
939 /// What happened, in a sentence or two, with what to do.
940 #[serde(default)]
941 pub message: Option<String>,
942 /// Where the fix is: the workspace's billing or model settings.
943 #[serde(default)]
944 pub fix_url: Option<String>,
945}
946
947/// The runner service's `delegate`: the issue opened, and the agent put on
948/// it. The issue exists whatever became of the agent.
949#[derive(Clone, Debug, Serialize, Deserialize)]
950pub struct Delegated {
951 pub issue: Issue,
952 /// The pull request the agent opened, when it started.
953 #[serde(default)]
954 pub pull: Option<Pull>,
955 pub agent: AgentStart,
956}
957
958/// `report_confidence`: what the agent of a run says of its own change,
959/// with the run's own token. Kept with the run, and the pull request's
960/// confidence is worked out again with it. Returns `Outcome<bool>`.
961#[derive(Debug, Serialize, Deserialize)]
962#[serde(rename_all = "camelCase")]
963pub struct ReportConfidenceArgs {
964 pub run_id: String,
965 pub token: String,
966 /// `high`, `medium` or `low`.
967 pub confidence: String,
968 #[serde(default)]
969 pub uncertain_about: Vec<String>,
970}
971
972/// `list_issues`, newest first. Returns `Outcome<Vec<Issue>>`.
973#[derive(Debug, Serialize, Deserialize)]
974pub struct ListIssuesArgs {
975 pub repo: RepoPath,
976 pub viewer: Viewer,
977 #[serde(default)]
978 pub state: Option<State>,
979 /// Only issues carrying this label.
980 #[serde(default)]
981 pub label: Option<String>,
982}
983
984/// `list_pulls`, newest first. Returns `Outcome<Vec<Pull>>`.
985#[derive(Debug, Serialize, Deserialize)]
986pub struct ListPullsArgs {
987 pub repo: RepoPath,
988 pub viewer: Viewer,
989 #[serde(default)]
990 pub state: Option<State>,
991}
992
993/// `get_issue` (`Outcome<IssueDetail>`), `get_pull` (`Outcome<PullDetail>`),
994/// `read_session` (`Outcome<Vec<SessionEntry>>`), `list_labels`
995/// (`Outcome<Vec<String>>`) and `counts` (`Outcome<Counts>`). The last two
996/// ignore `number`.
997#[derive(Debug, Serialize, Deserialize)]
998#[serde(rename_all = "camelCase")]
999pub struct ViewArgs {
1000 pub repo: RepoPath,
1001 #[serde(default)]
1002 pub number: u32,
1003 pub viewer: Viewer,
1004 /// For `read_session`: only entries after this sequence number.
1005 #[serde(default)]
1006 pub after_seq: u32,
1007}
1008
1009/// How many issues and pull requests are open on a repository.
1010#[derive(Debug, Serialize, Deserialize)]
1011pub struct Counts {
1012 pub issues: u32,
1013 pub pulls: u32,
1014}
1015
1016/// `update_issue`: changes whichever fields are given. The author or a
1017/// member of the workspace may. Returns `Outcome<Issue>`.
1018#[derive(Debug, Serialize, Deserialize)]
1019pub struct UpdateIssueArgs {
1020 pub actor: User,
1021 pub repo: RepoPath,
1022 pub number: u32,
1023 #[serde(default)]
1024 pub title: Option<String>,
1025 #[serde(default)]
1026 pub body: Option<String>,
1027 #[serde(default)]
1028 pub labels: Option<Vec<String>>,
1029 /// Usernames of the people it is assigned to; replaces the whole set.
1030 /// Assigning it to the g1t agent is the runner's `run`, not this.
1031 #[serde(default)]
1032 pub assignees: Option<Vec<String>>,
1033}
1034
1035/// `close_issue` and `reopen_issue`. Each returns `Outcome<Issue>`.
1036#[derive(Debug, Serialize, Deserialize)]
1037pub struct IssueActionArgs {
1038 pub actor: User,
1039 pub repo: RepoPath,
1040 pub number: u32,
1041 /// For `close_issue`; `completed` if left out.
1042 #[serde(default)]
1043 pub reason: Option<IssueReason>,
1044}
1045
1046/// `add_comment`, on an issue or a pull request. On a pull request it may
1047/// name a line of the change, and may carry a verdict; nobody can give a
1048/// verdict on their own pull request. Returns `Outcome<Comment>`.
1049#[derive(Debug, Serialize, Deserialize)]
1050pub struct AddCommentArgs {
1051 pub actor: User,
1052 pub repo: RepoPath,
1053 pub number: u32,
1054 /// May be empty when approving.
1055 #[serde(default)]
1056 pub body: String,
1057 #[serde(default)]
1058 pub path: Option<String>,
1059 #[serde(default)]
1060 pub line: Option<u32>,
1061 #[serde(default)]
1062 pub verdict: Option<Verdict>,
1063}
1064
1065/// `open_pull`. Without `branch`, forks the repo and returns a draft pull
1066/// request to push to. With it, opens a pull request, ready for review,
1067/// for a branch already pushed to the repo. Returns `Outcome<Pull>`.
1068#[derive(Debug, Serialize, Deserialize)]
1069pub struct OpenPullArgs {
1070 pub actor: User,
1071 pub repo: RepoPath,
1072 /// The issue this is for.
1073 #[serde(default)]
1074 pub issue: Option<u32>,
1075 /// Defaults to the issue's title; required without an issue.
1076 #[serde(default)]
1077 pub title: String,
1078 /// What changed and why. Usually set later, when a draft is marked ready.
1079 #[serde(default)]
1080 pub body: String,
1081 /// A branch of the repository that already holds the change.
1082 #[serde(default)]
1083 pub branch: Option<String>,
1084 #[serde(default)]
1085 pub agent: String,
1086 pub runtime: Runtime,
1087}
1088
1089/// `ready_pull`, `close_pull` and `merge_pull`. Each returns `Outcome<Pull>`.
1090///
1091/// Also `catch_up_pull`: brings the pull request up to date with the
1092/// default branch without a sandbox where that is safe, as the repos
1093/// service's `update_pull_branch` does, after checking that `actor` may
1094/// update it: whoever opened it for a fork, any member for a branch.
1095/// Returns `Outcome<repos::PullBranchUpdate>`; on `needs_agent` nothing was
1096/// pushed and the runner's `update` is the way on.
1097#[derive(Debug, Serialize, Deserialize)]
1098#[serde(rename_all = "camelCase")]
1099pub struct PullActionArgs {
1100 pub actor: User,
1101 pub repo: RepoPath,
1102 pub number: u32,
1103 /// For `ready_pull`: what changed and why.
1104 #[serde(default)]
1105 pub summary: String,
1106 /// For `merge_pull`: leave the issue open and the other pull requests
1107 /// for it untouched, because this one is only part of the work.
1108 #[serde(default)]
1109 pub keep_issue_open: bool,
1110 /// For `merge_pull`: merge although the acceptance checks have not
1111 /// passed.
1112 #[serde(default)]
1113 pub ignore_checks: bool,
1114}
1115
1116/// Where a plan stands.
1117#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1118#[serde(rename_all = "lowercase")]
1119pub enum PlanStatus {
1120 /// An agent is reading the repository and writing it.
1121 Planning,
1122 /// Written, and waiting for a person to read and apply it.
1123 Ready,
1124 /// It could not be written.
1125 Failed,
1126 /// Its issues have been opened.
1127 Applied,
1128}
1129
1130/// One issue a plan proposes.
1131#[derive(Clone, Debug, Default, Serialize, Deserialize)]
1132#[serde(rename_all = "camelCase", default)]
1133pub struct PlannedIssue {
1134 pub title: String,
1135 /// Markdown: what to change, where, and why.
1136 pub body: String,
1137 pub labels: Vec<String>,
1138 /// Commands that must pass once the change is made.
1139 pub checks: Vec<String>,
1140 /// The files it will most likely change.
1141 pub files: Vec<String>,
1142 /// The positions, counting from 1, of earlier issues in the plan that
1143 /// have to be merged first. An agent writes this as `depends_on`.
1144 #[serde(alias = "depends_on")]
1145 pub depends_on: Vec<u32>,
1146 /// Its number, once the plan has been applied and it was kept.
1147 pub number: Option<u32>,
1148}
1149
1150/// An outcome someone wrote, and the issues an agent proposes to get there.
1151#[derive(Clone, Debug, Serialize, Deserialize)]
1152#[serde(rename_all = "camelCase")]
1153pub struct Plan {
1154 pub id: String,
1155 pub repo_id: String,
1156 /// The outcome wanted, as written.
1157 pub brief: String,
1158 pub status: PlanStatus,
1159 /// The agent's account of how it split the work.
1160 pub summary: String,
1161 pub issues: Vec<PlannedIssue>,
1162 /// Why it could not be written, when `status` is `failed`.
1163 pub error: Option<String>,
1164 pub author: User,
1165 /// RFC 3339.
1166 pub created_at: String,
1167 /// RFC 3339.
1168 pub finished_at: Option<String>,
1169 /// Once applied: where each issue it opened stands now, in plan order.
1170 /// Filled in by `get_plan` only.
1171 #[serde(default)]
1172 pub progress: Vec<IssueProgress>,
1173 /// Questions and handoffs between the agents on its pull requests,
1174 /// newest first. Filled in by `get_plan` only.
1175 #[serde(default)]
1176 pub exchanges: Vec<AgentMessage>,
1177}
1178
1179/// Where one issue of an applied plan stands.
1180#[derive(Clone, Debug, Serialize, Deserialize)]
1181#[serde(rename_all = "camelCase")]
1182pub struct IssueProgress {
1183 pub number: u32,
1184 pub title: String,
1185 /// `blocked` (waiting on issues it depends on), `waiting` (for an
1186 /// agent), `open` (nobody on it), one of the lifecycle stages
1187 /// (`working`, `checking`, `reviewing`, `revising`, `catching_up`,
1188 /// `answering`, `queued`, `ready`, `needs_you`), `landed` or `closed`.
1189 pub state: String,
1190 /// One sentence about where it stands.
1191 pub detail: String,
1192 /// The issues it is waiting on that are still open.
1193 pub blocked_by: Vec<u32>,
1194 /// The pull request carrying it, the newest if several.
1195 pub pull: Option<u32>,
1196 /// Who or what is working on it, e.g. `g1t-agent`.
1197 pub agent: Option<String>,
1198}
1199
1200/// `start_plan`: records an outcome to plan for. Members of the
1201/// repository's workspace only. Called by the runner service, which starts
1202/// the sandbox. Returns `Outcome<PlanJob>`.
1203#[derive(Debug, Serialize, Deserialize)]
1204pub struct StartPlanArgs {
1205 pub actor: User,
1206 pub repo: RepoPath,
1207 pub brief: String,
1208}
1209
1210/// What a sandbox needs to write a plan.
1211#[derive(Debug, Serialize, Deserialize)]
1212#[serde(rename_all = "camelCase")]
1213pub struct PlanJob {
1214 pub plan_id: String,
1215 /// Lets the sandbox, and nothing else, report this plan.
1216 pub token: String,
1217 pub brief: String,
1218 pub repo: RepoPath,
1219}
1220
1221/// `report_plan`: the plan a sandbox's agent wrote, or why it could not
1222/// write one. Returns `Outcome<bool>`.
1223#[derive(Debug, Serialize, Deserialize)]
1224#[serde(rename_all = "camelCase")]
1225pub struct ReportPlanArgs {
1226 pub plan_id: String,
1227 pub token: String,
1228 #[serde(default)]
1229 pub summary: String,
1230 #[serde(default)]
1231 pub issues: Vec<PlannedIssue>,
1232 #[serde(default)]
1233 pub error: Option<String>,
1234}
1235
1236/// `get_plan`. Members only. Returns `Outcome<Plan>`. `list_plans` takes
1237/// `ViewArgs` and returns `Outcome<Vec<Plan>>`, newest first.
1238#[derive(Debug, Serialize, Deserialize)]
1239pub struct PlanArgs {
1240 pub repo: RepoPath,
1241 pub viewer: Viewer,
1242 pub id: String,
1243}
1244
1245/// `apply_plan`: opens a plan's issues, each blocked by the ones it depends
1246/// on. Members only, and once. Returns `Outcome<Plan>`, its issues now
1247/// carrying their numbers.
1248#[derive(Debug, Serialize, Deserialize)]
1249pub struct ApplyPlanArgs {
1250 pub actor: User,
1251 pub repo: RepoPath,
1252 pub id: String,
1253 /// Queue every issue for a g1t agent.
1254 #[serde(default)]
1255 pub assign: bool,
1256 /// The positions, counting from 1, of the issues to open. All of them
1257 /// when absent.
1258 #[serde(default)]
1259 pub keep: Option<Vec<u32>>,
1260}
1261
1262/// `queue_issue`: asks for a g1t agent to take an issue as soon as it can,
1263/// or withdraws that. The author or a member may. Returns `Outcome<bool>`.
1264#[derive(Debug, Serialize, Deserialize)]
1265pub struct QueueIssueArgs {
1266 pub actor: User,
1267 pub repo: RepoPath,
1268 pub number: u32,
1269 pub queued: bool,
1270}
1271
1272/// `ready_issues`: issues waiting for a g1t agent that can be given one
1273/// now, in one repository or in all. Called by the runner service. Returns
1274/// `Vec<ReadyIssue>`.
1275#[derive(Debug, Default, Serialize, Deserialize)]
1276#[serde(rename_all = "camelCase")]
1277pub struct ReadyIssuesArgs {
1278 #[serde(default)]
1279 pub repo_id: Option<String>,
1280}
1281
1282#[derive(Debug, Serialize, Deserialize)]
1283pub struct ReadyIssue {
1284 pub repo: RepoPath,
1285 pub number: u32,
1286 /// Who queued it, on whose say-so the agent works.
1287 pub actor: User,
1288}
1289
1290/// `update_pull`: changes who a pull request is assigned to and whose
1291/// review is asked for. Each list given replaces the whole set. Whoever
1292/// opened it, or a member of the workspace, may. Returns `Outcome<Pull>`.
1293#[derive(Debug, Serialize, Deserialize)]
1294pub struct UpdatePullArgs {
1295 pub actor: User,
1296 pub repo: RepoPath,
1297 pub number: u32,
1298 #[serde(default)]
1299 pub assignees: Option<Vec<String>>,
1300 /// May include `g1t-agent`. Asking for its review does not by itself
1301 /// start one; the runner's `review` does.
1302 #[serde(default)]
1303 pub reviewers: Option<Vec<String>>,
1304}
1305
1306/// `start_checks`: begins a run of the acceptance checks for a pull request
1307/// that is ready for review. Called by the runner service, which starts the
1308/// sandbox. Returns `Outcome<CheckJob>`.
1309#[derive(Debug, Serialize, Deserialize)]
1310#[serde(rename_all = "camelCase")]
1311pub struct StartChecksArgs {
1312 pub pull_id: String,
1313}
1314
1315/// What a sandbox needs to carry out a check run.
1316#[derive(Debug, Serialize, Deserialize)]
1317#[serde(rename_all = "camelCase")]
1318pub struct CheckJob {
1319 pub run_id: String,
1320 /// Lets the sandbox, and nothing else, report this run's results.
1321 pub token: String,
1322 pub commands: Vec<String>,
1323 /// The repository holding the commit: the fork, or the repository itself.
1324 pub source: RepoPath,
1325 pub commit: String,
1326 /// Who opened the pull request, and so can read its source.
1327 pub author: User,
1328 /// Username of whoever wrote the checks: the issue's author.
1329 pub requested_by: String,
1330 pub repo: RepoPath,
1331 pub number: u32,
1332}
1333
1334/// `report_checks`: what a sandbox says about its run. With no results and
1335/// no error it has started. `skip` forgets the run, for one that will not
1336/// be carried out. Returns `Outcome<CheckRun>`.
1337#[derive(Debug, Serialize, Deserialize)]
1338#[serde(rename_all = "camelCase")]
1339pub struct ReportChecksArgs {
1340 pub run_id: String,
1341 pub token: String,
1342 #[serde(default)]
1343 pub results: Vec<CheckResult>,
1344 #[serde(default)]
1345 pub error: Option<String>,
1346 #[serde(default)]
1347 pub skip: bool,
1348}
1349
1350/// A pull request in progress, with where it lives.
1351#[derive(Debug, Serialize, Deserialize)]
1352pub struct ActivePull {
1353 pub pull: Pull,
1354 pub issue: Option<Issue>,
1355 /// Where it stands, for one g1t is seeing through.
1356 #[serde(default)]
1357 pub lifecycle: Option<Lifecycle>,
1358}
1359
1360/// `list_active_pulls`: drafts and open pull requests the viewer started,
1361/// most recently active first. Returns `Vec<ActivePull>`. Also
1362/// `list_assigned_issues`: open issues assigned to the viewer, most
1363/// recently changed first. Returns `Vec<Issue>`.
1364#[derive(Debug, Serialize, Deserialize)]
1365pub struct ViewerArgs {
1366 pub viewer: Viewer,
1367}
1368
1369/// `append_session`. Returns `Outcome<Appended>`.
1370#[derive(Debug, Serialize, Deserialize)]
1371pub struct AppendSessionArgs {
1372 pub actor: User,
1373 pub repo: RepoPath,
1374 pub number: u32,
1375 pub entries: Vec<NewSessionEntry>,
1376}
1377
1378#[derive(Debug, Serialize, Deserialize)]
1379pub struct Appended {
1380 pub count: u32,
1381}
1382
1383/// `start_review`: begins a review of a pull request by a g1t agent. Called
1384/// by the runner service, which starts the sandbox. Returns
1385/// `Outcome<ReviewJob>`.
1386#[derive(Debug, Serialize, Deserialize)]
1387#[serde(rename_all = "camelCase")]
1388pub struct StartReviewArgs {
1389 pub pull_id: String,
1390}
1391
1392/// What a sandbox needs to review a pull request.
1393#[derive(Debug, Serialize, Deserialize)]
1394#[serde(rename_all = "camelCase")]
1395pub struct ReviewJob {
1396 pub run_id: String,
1397 /// Lets the sandbox, and nothing else, report this review.
1398 pub token: String,
1399 /// The repository holding the commit: the fork, or the repository itself.
1400 pub source: RepoPath,
1401 pub commit: String,
1402 pub repo: RepoPath,
1403 pub default_branch: String,
1404 pub number: u32,
1405 pub title: String,
1406 pub description: String,
1407 /// The issue the pull request is for, which says what it should achieve.
1408 pub issue: Option<Issue>,
1409 /// Who opened the pull request, and so can read its source.
1410 pub author: User,
1411}
1412
1413/// A comment on one line, as a reviewing agent reports it.
1414#[derive(Debug, Serialize, Deserialize)]
1415pub struct ReviewComment {
1416 pub path: String,
1417 #[serde(default)]
1418 pub line: u32,
1419 pub body: String,
1420}
1421
1422/// `report_review`: the review a sandbox's agent wrote, or why it could not
1423/// write one. Returns `Outcome<bool>`.
1424#[derive(Debug, Serialize, Deserialize)]
1425#[serde(rename_all = "camelCase")]
1426pub struct ReportReviewArgs {
1427 pub run_id: String,
1428 pub token: String,
1429 #[serde(default)]
1430 pub verdict: Option<Verdict>,
1431 #[serde(default)]
1432 pub body: String,
1433 #[serde(default)]
1434 pub comments: Vec<ReviewComment>,
1435 /// The model that wrote it, by its public name.
1436 #[serde(default)]
1437 pub model: Option<String>,
1438 #[serde(default)]
1439 pub error: Option<String>,
1440}
1441
1442/// Lowercases, trims and de-duplicates labels, dropping empty ones.
1443/// Returns `None` if there are too many or one is too long.
1444pub fn normalize_labels(labels: &[String]) -> Option<Vec<String>> {
1445 const MAX_LABELS: usize = 10;
1446 const MAX_LABEL_CHARS: usize = 40;
1447 let mut normalized: Vec<String> = Vec::new();
1448 for label in labels {
1449 let label = label
1450 .split_whitespace()
1451 .collect::<Vec<_>>()
1452 .join(" ")
1453 .to_lowercase();
1454 if label.is_empty() || normalized.contains(&label) {
1455 continue;
1456 }
1457 if label.chars().count() > MAX_LABEL_CHARS {
1458 return None;
1459 }
1460 normalized.push(label);
1461 }
1462 (normalized.len() <= MAX_LABELS).then_some(normalized)
1463}
1464
1465#[cfg(test)]
1466mod tests {
1467 use super::normalize_labels;
1468
1469 fn labels(names: &[&str]) -> Vec<String> {
1470 names.iter().map(|name| (*name).to_owned()).collect()
1471 }
1472
1473 #[test]
1474 fn labels_are_lowercased_trimmed_and_unique() {
1475 assert_eq!(
1476 normalize_labels(&labels(&[" Bug ", "bug", "", "Good First Issue"])),
1477 Some(labels(&["bug", "good first issue"]))
1478 );
1479 }
1480
1481 #[test]
1482 fn too_long_or_too_many_labels_are_refused() {
1483 assert_eq!(normalize_labels(&["x".repeat(41)]), None);
1484 let many: Vec<String> = (0..11).map(|i| format!("label-{i}")).collect();
1485 assert_eq!(normalize_labels(&many), None);
1486 }
1487}
1488
1489
1490// --- Merge queue ----------------------------------------------------------
1491
1492/// Where a pull request in a merge queue stands.
1493#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1494#[serde(rename_all = "snake_case")]
1495pub enum QueueState {
1496 /// Waiting for its turn to be tested.
1497 Waiting,
1498 /// Its combined state is being built and checked.
1499 Testing,
1500 /// Its combined state passed; it lands once everything ahead has.
1501 Passed,
1502 /// Its combined state failed, or would not merge. It left the queue.
1503 Failed,
1504 /// On the default branch.
1505 Landed,
1506 /// Taken out of the queue by a person, or closed.
1507 Removed,
1508}
1509
1510impl QueueState {
1511 pub fn as_str(self) -> &'static str {
1512 match self {
1513 QueueState::Waiting => "waiting",
1514 QueueState::Testing => "testing",
1515 QueueState::Passed => "passed",
1516 QueueState::Failed => "failed",
1517 QueueState::Landed => "landed",
1518 QueueState::Removed => "removed",
1519 }
1520 }
1521
1522 /// Still in the queue.
1523 pub fn is_active(self) -> bool {
1524 matches!(
1525 self,
1526 QueueState::Waiting | QueueState::Testing | QueueState::Passed
1527 )
1528 }
1529}
1530
1531/// One pull request's place in a merge queue.
1532#[derive(Clone, Debug, Serialize, Deserialize)]
1533#[serde(rename_all = "camelCase")]
1534pub struct QueueEntry {
1535 pub id: String,
1536 pub number: u32,
1537 pub title: String,
1538 /// Who or what made the pull request, e.g. `g1t-agent`.
1539 pub agent: String,
1540 pub state: QueueState,
1541 /// The pull requests merged ahead of it in the state being tested, in
1542 /// queue order. Empty when it was tested on the default branch alone.
1543 pub ahead: Vec<u32>,
1544 /// The default branch's commit the tested state was built on.
1545 pub base_commit: Option<String>,
1546 /// The tested state: the default branch with everything ahead and this.
1547 pub combined_commit: Option<String>,
1548 /// Why it failed: a merge conflict or what could not be run.
1549 pub error: Option<String>,
1550 /// The checks run against the tested state.
1551 pub results: Vec<CheckResult>,
1552 /// Username of whoever merged it into the queue: a person, or `g1t`.
1553 pub enqueued_by: String,
1554 /// RFC 3339.
1555 pub created_at: String,
1556 /// RFC 3339. When it landed or left.
1557 pub finished_at: Option<String>,
1558}
1559
1560/// A repository's merge queue: what is in it, in order, and what recently
1561/// left it.
1562#[derive(Clone, Debug, Serialize, Deserialize)]
1563#[serde(rename_all = "camelCase")]
1564pub struct QueueView {
1565 /// Whether the repository merges through the queue.
1566 pub enabled: bool,
1567 pub active: Vec<QueueEntry>,
1568 /// Newest first.
1569 pub recent: Vec<QueueEntry>,
1570}
1571
1572/// `queue`: a repository's merge queue. Returns `Outcome<QueueView>`.
1573#[derive(Debug, Serialize, Deserialize)]
1574pub struct QueueArgs {
1575 pub repo: RepoPath,
1576 pub viewer: Viewer,
1577}
1578
1579/// `queue_build`: the next batch to test for a repository, if nothing is
1580/// being tested now. Returns `Vec<QueueJob>`, one per entry, each testing
1581/// the default branch with that entry and everything ahead of it.
1582#[derive(Debug, Serialize, Deserialize)]
1583#[serde(rename_all = "camelCase")]
1584pub struct QueueBuildArgs {
1585 pub repo_id: String,
1586}
1587
1588/// One pull request in a state being tested: where its change is.
1589#[derive(Clone, Debug, Serialize, Deserialize)]
1590#[serde(rename_all = "camelCase")]
1591pub struct QueueStackItem {
1592 pub number: u32,
1593 pub title: String,
1594 /// The repository holding the change: its fork, or the repository.
1595 pub source: RepoPath,
1596 /// The branch of `source` holding it.
1597 pub branch: String,
1598 pub commit: String,
1599}
1600
1601/// What a sandbox needs to build and check one combined state.
1602#[derive(Clone, Debug, Serialize, Deserialize)]
1603#[serde(rename_all = "camelCase")]
1604pub struct QueueJob {
1605 pub entry_id: String,
1606 /// Lets the sandbox, and nothing else, report this state's result.
1607 pub token: String,
1608 pub repo: RepoPath,
1609 pub default_branch: String,
1610 /// The default branch's commit to build on.
1611 pub base_commit: String,
1612 /// Where to push the tested state, in the repository itself.
1613 pub branch: String,
1614 /// The pull requests to merge in, in order; the last is the entry.
1615 pub stack: Vec<QueueStackItem>,
1616 /// Every acceptance check of every pull request in the stack.
1617 pub checks: Vec<String>,
1618 /// The checks of issues already completed: the default branch's
1619 /// contract. One that fails on the base alone is not held against the
1620 /// entry.
1621 #[serde(default)]
1622 pub contract_checks: Vec<String>,
1623 /// Who the sandbox acts as: a member who can push the tested state.
1624 pub actor: User,
1625}
1626
1627/// `report_queue`: a sandbox's result for one combined state. Returns
1628/// `Outcome<QueueState>`.
1629#[derive(Debug, Serialize, Deserialize)]
1630#[serde(rename_all = "camelCase")]
1631pub struct ReportQueueArgs {
1632 pub entry_id: String,
1633 pub token: String,
1634 #[serde(default)]
1635 pub combined_commit: Option<String>,
1636 #[serde(default)]
1637 pub results: Vec<CheckResult>,
1638 /// Set when the state could not be built or checked.
1639 #[serde(default)]
1640 pub error: Option<String>,
1641 /// For a merge conflict: the pull request whose change it collided with.
1642 #[serde(default)]
1643 pub conflict_with: Option<u32>,
1644 /// For a merge conflict: the files that conflicted.
1645 #[serde(default)]
1646 pub conflicts: Vec<String>,
1647}
1648
1649
1650/// `locate_pull`: where a pull request lives, by its id, for a tool that
1651/// knows only the fork it is working in (`g1t.sh/pulls/<id>`). Returns
1652/// `Outcome<LocatedPull>`; not found for anyone who cannot see it.
1653#[derive(Debug, Serialize, Deserialize)]
1654pub struct LocatePullArgs {
1655 pub id: String,
1656 pub viewer: Viewer,
1657}
1658
1659#[derive(Clone, Debug, Serialize, Deserialize)]
1660#[serde(rename_all = "camelCase")]
1661pub struct LocatedPull {
1662 pub repo: RepoPath,
1663 pub number: u32,
1664 pub title: String,
1665 pub status: PullStatus,
1666}
1667
1668// --- A person's work -------------------------------------------------------
1669
1670/// Issues or pull requests, on a person's profile.
1671#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1672#[serde(rename_all = "lowercase")]
1673pub enum AuthoredKind {
1674 Issue,
1675 Pull,
1676}
1677
1678/// The state filter on a person's work. `Closed` takes in merged pull
1679/// requests too; `Merged` is only those.
1680#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1681#[serde(rename_all = "lowercase")]
1682pub enum AuthoredState {
1683 Open,
1684 Closed,
1685 Merged,
1686}
1687
1688/// How a person's work is ordered.
1689#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1690#[serde(rename_all = "lowercase")]
1691pub enum AuthoredSort {
1692 /// Newest first.
1693 #[default]
1694 Created,
1695 /// Most recently changed first.
1696 Updated,
1697 /// Oldest first.
1698 Oldest,
1699}
1700
1701/// The most items one `by_author` page holds.
1702pub const AUTHORED_PAGE: u32 = 25;
1703
1704/// `by_author`: the issues and pull requests a person opened, only on
1705/// repositories `viewer` may read, so a private title never reaches anyone
1706/// who could not open it. Returns `Outcome<Authored>`; not found for an
1707/// account that does not exist.
1708#[derive(Debug, Serialize, Deserialize)]
1709pub struct ByAuthorArgs {
1710 pub username: String,
1711 pub viewer: Viewer,
1712 #[serde(default)]
1713 pub kind: Option<AuthoredKind>,
1714 #[serde(default)]
1715 pub state: Option<AuthoredState>,
1716 /// Only work on this repository: `namespace/name`.
1717 #[serde(default)]
1718 pub repo: Option<String>,
1719 #[serde(default)]
1720 pub sort: AuthoredSort,
1721 /// The `next` of the page before, to read on from there.
1722 #[serde(default)]
1723 pub before: Option<String>,
1724 /// At most [`AUTHORED_PAGE`]; that when absent.
1725 #[serde(default)]
1726 pub limit: Option<u32>,
1727}
1728
1729/// One issue or pull request a person opened.
1730#[derive(Clone, Debug, Serialize, Deserialize)]
1731#[serde(rename_all = "camelCase")]
1732pub struct AuthoredItem {
1733 pub kind: AuthoredKind,
1734 pub repo: RepoPath,
1735 pub number: u32,
1736 pub title: String,
1737 /// Open or closed; a merged pull request is closed.
1738 pub state: State,
1739 /// A pull request's own status.
1740 pub status: Option<PullStatus>,
1741 /// Why an issue was closed.
1742 pub reason: Option<IssueReason>,
1743 pub draft: bool,
1744 pub merged: bool,
1745 /// RFC 3339.
1746 pub created_at: String,
1747 /// RFC 3339.
1748 pub updated_at: String,
1749 /// When a pull request was merged. RFC 3339.
1750 pub merged_at: Option<String>,
1751}
1752
1753/// What a person has done, as far as the viewer may see.
1754#[derive(Clone, Debug, Default, Serialize, Deserialize)]
1755#[serde(rename_all = "camelCase")]
1756pub struct AuthoredCounts {
1757 pub pulls_merged: u32,
1758 pub pulls_open: u32,
1759 pub pulls: u32,
1760 pub issues: u32,
1761 pub issues_open: u32,
1762}
1763
1764/// A repository a person has opened work on, with how much.
1765#[derive(Clone, Debug, Serialize, Deserialize)]
1766pub struct AuthoredRepo {
1767 pub repo: RepoPath,
1768 pub count: u32,
1769}
1770
1771/// A page of a person's work.
1772#[derive(Clone, Debug, Default, Serialize, Deserialize)]
1773#[serde(rename_all = "camelCase")]
1774pub struct Authored {
1775 pub items: Vec<AuthoredItem>,
1776 /// Pass as `before` for the next page; null on the last.
1777 pub next: Option<String>,
1778 /// Over every repository the viewer may read, whatever the filters.
1779 pub counts: AuthoredCounts,
1780 /// Those repositories, most work first.
1781 pub repos: Vec<AuthoredRepo>,
1782}