Skip to content

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

g1t.sh
1,580 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.

A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were1//! REST: each route maps an HTTP request onto one operation.
2
3use serde_json::{Map, Value};
4
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb975use crate::about::AboutOp;
The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook.6use crate::run_artifacts::ArtifactsOp;
Merge GitHub pull request #8 (claude/cool-newton-7nm8r9): Docker deployments on g1t.page behind a switch that stays off until Cloudflare's container APIs are checked live, workflows a workspace manages in its repositories, tarball and zipball downloads, refs/pull/* kept to g1t alone, framework detection for g1t.page builds, and a workflow's event payload matching GitHub's.7use crate::archives::ArchiveOp;
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca8use crate::deploy_keys::DeployKeysOp;
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb979use crate::deployments::DeploymentsOp;
Projects have Observability: their requests, logs, traces, metrics and errors, from apps on g1t.page with nothing to set up, and from software anywhere else over OpenTelemetry.10use crate::telemetry::TelemetryOp;
Merge packages: roles, Actions access, source label, soft delete, API11use crate::packages::PackagesOp;
Merge main into Artifacts Phase 212use crate::folios::FoliosOp;
Merge branch 'worktree-agent-a6ec0a89d7fe7a9ae'13use crate::coordination::CoordinationOp;
Merge branch 'worktree-agent-a3abfcce648e87dca'14use crate::protection::ProtectionOp;
API and MCP for a workspace's personal access token rules, members' tokens and approvals15use crate::token_policy::TokenOp;
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were16use crate::operations::Op;
Merge checks: statuses and check runs on every commit17use crate::checks::ChecksOp;
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge18use crate::rules::RulesOp;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar19use crate::security::SecurityOp;
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were20
21pub struct Route {
22 pub method: &'static str,
23 /// Segments starting with `:` are parameters.
24 pub path: &'static str,
25 pub op: Op,
26 /// Query parameters the route reads, as `(name in the URL, input name)`.
27 pub query: &'static [(&'static str, &'static str)],
28}
29
30const fn route(
31 method: &'static str,
32 path: &'static str,
33 op: Op,
34 query: &'static [(&'static str, &'static str)],
35) -> Route {
36 Route {
37 method,
38 path,
39 op,
40 query,
41 }
42}
43
44pub const ROUTES: &[Route] = &[
45 route("GET", "/user", Op::Whoami, &[]),
46 route("POST", "/workspaces", Op::CreateWorkspace, &[]),
Merge branch 'worktree-agent-ad7c6d88d93adc817'47 route("GET", "/workspaces/:workspace", Op::GetWorkspace, &[]),
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were48 route("DELETE", "/workspaces/:workspace", Op::DeleteWorkspace, &[]),
49 route("GET", "/user/emails", Op::ListEmails, &[]),
50 route("POST", "/user/emails", Op::AddEmail, &[]),
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)51 route("POST", "/user/emails/confirm", Op::ConfirmEmail, &[]),
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were52 route("DELETE", "/user/emails/:email", Op::RemoveEmail, &[]),
53 route("PATCH", "/user/email-settings", Op::UpdateEmailSettings, &[]),
Merge workspace invitations: nobody joins a workspace without saying yes, people are found by username, your own invites can bring someone in, and nobody is left without a workspace (identity 0040)54 // Invitations to workspaces waiting for your answer.
55 route("GET", "/user/invitations", Op::ListInvitations, &[]),
56 route("POST", "/user/invitations/:id/accept", Op::AcceptInvitation, &[]),
57 route("POST", "/user/invitations/:id/decline", Op::DeclineInvitation, &[]),
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were58 route("GET", "/workspaces/:workspace/invitations", Op::ListWorkspaceInvites, &[]),
API and MCP for a workspace's personal access token rules, members' tokens and approvals59 // A workspace's rules for personal access tokens, and its members' tokens.
60 route("GET", "/workspaces/:workspace/personal-access-token-policy", Op::Tokens(TokenOp::GetTokenPolicy), &[]),
61 route("PATCH", "/workspaces/:workspace/personal-access-token-policy", Op::Tokens(TokenOp::SetTokenPolicy), &[]),
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers62 route("GET", "/workspaces/:workspace/personal-access-tokens", Op::Tokens(TokenOp::ListMemberTokens), &[]),
API and MCP for a workspace's personal access token rules, members' tokens and approvals63 route("POST", "/workspaces/:workspace/personal-access-tokens/:id", Op::Tokens(TokenOp::RevokeMemberToken), &[]),
64 route("GET", "/workspaces/:workspace/personal-access-token-requests", Op::Tokens(TokenOp::ListTokenRequests), &[]),
65 route("POST", "/workspaces/:workspace/personal-access-token-requests/:id", Op::Tokens(TokenOp::ReviewTokenRequest), &[]),
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were66 route("POST", "/workspaces/:workspace/invitations", Op::InviteMember, &[]),
67 route("DELETE", "/workspaces/:workspace/invitations/:id", Op::RevokeWorkspaceInvite, &[]),
68 // Who has access. GitHub's addresses, but for adding someone, which
69 // takes an email address as well as a username.
70 route("GET", "/repos/:owner/:name/collaborators", Op::ListCollaborators, &[]),
71 route("POST", "/repos/:owner/:name/collaborators", Op::AddCollaborator, &[]),
72 route("PATCH", "/repos/:owner/:name/collaborators/:username", Op::UpdateCollaborator, &[]),
73 route("DELETE", "/repos/:owner/:name/collaborators/:username", Op::RemoveCollaborator, &[]),
74 route(
75 "GET",
76 "/repos/:owner/:name/collaborators/:username/permission",
77 Op::GetCollaboratorPermission,
78 &[],
79 ),
80 route("GET", "/repos/:owner/:name/invitations", Op::ListRepoInvitations, &[]),
81 route("DELETE", "/repos/:owner/:name/invitations/:id", Op::RevokeRepoInvitation, &[]),
82 route("GET", "/user/repository_invitations", Op::ListMyRepoInvitations, &[]),
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca83 // Deploy keys, at GitHub's addresses.
84 route("GET", "/repos/:owner/:name/keys", Op::DeployKeys(DeployKeysOp::ListDeployKeys), &[]),
85 route("POST", "/repos/:owner/:name/keys", Op::DeployKeys(DeployKeysOp::CreateDeployKey), &[]),
86 route("GET", "/repos/:owner/:name/keys/:id", Op::DeployKeys(DeployKeysOp::GetDeployKey), &[]),
87 route("DELETE", "/repos/:owner/:name/keys/:id", Op::DeployKeys(DeployKeysOp::DeleteDeployKey), &[]),
API: notifications over REST and MCP, with notifications scopes88 // Your notifications: threads, marking them, and what you subscribe
89 // to and watch. GitHub's addresses, with g1t's saved and snoozed.
90 route("GET", "/notifications", Op::ListNotifications, &[("all", "all"), ("participating", "participating"), ("view", "view"), ("reason", "reason"), ("severity", "severity"), ("since", "since"), ("before", "before"), ("cursor", "cursor"), ("per_page", "per_page")]),
91 route("PUT", "/notifications", Op::MarkNotificationsRead, &[]),
92 route("GET", "/notifications/threads/:id", Op::GetNotificationThread, &[]),
93 route("PATCH", "/notifications/threads/:id", Op::MarkThreadRead, &[]),
94 route("DELETE", "/notifications/threads/:id", Op::MarkThreadDone, &[]),
95 route("PUT", "/notifications/threads/:id/saved", Op::SaveThread, &[]),
96 route("DELETE", "/notifications/threads/:id/saved", Op::SaveThread, &[]),
97 route("PUT", "/notifications/threads/:id/snooze", Op::SnoozeThread, &[]),
98 route("DELETE", "/notifications/threads/:id/snooze", Op::SnoozeThread, &[]),
99 route("GET", "/notifications/threads/:id/subscription", Op::GetThreadSubscription, &[]),
100 route("PUT", "/notifications/threads/:id/subscription", Op::SetThreadSubscription, &[]),
101 route("DELETE", "/notifications/threads/:id/subscription", Op::DeleteThreadSubscription, &[]),
102 route("GET", "/repos/:owner/:name/notifications", Op::ListNotifications, &[("all", "all"), ("participating", "participating"), ("view", "view"), ("reason", "reason"), ("severity", "severity"), ("since", "since"), ("before", "before"), ("cursor", "cursor"), ("per_page", "per_page")]),
103 route("PUT", "/repos/:owner/:name/notifications", Op::MarkNotificationsRead, &[]),
104 route("GET", "/repos/:owner/:name/subscription", Op::GetRepoSubscription, &[]),
105 route("PUT", "/repos/:owner/:name/subscription", Op::SetRepoSubscription, &[]),
106 route("DELETE", "/repos/:owner/:name/subscription", Op::DeleteRepoSubscription, &[]),
107 route("GET", "/repos/:owner/:name/issues/:number/subscription", Op::GetThreadSubscription, &[]),
108 route("PUT", "/repos/:owner/:name/issues/:number/subscription", Op::SetThreadSubscription, &[]),
109 route("DELETE", "/repos/:owner/:name/issues/:number/subscription", Op::DeleteThreadSubscription, &[]),
110 route("GET", "/user/subscriptions", Op::ListWatchedRepos, &[]),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97111 // Stars: yours, and who starred a repository.
112 route("GET", "/user/starred", Op::About(AboutOp::ListStarred), &[]),
113 route("GET", "/user/starred/:owner/:name", Op::About(AboutOp::CheckStarred), &[]),
114 route("PUT", "/user/starred/:owner/:name", Op::About(AboutOp::Star), &[]),
115 route("DELETE", "/user/starred/:owner/:name", Op::About(AboutOp::Unstar), &[]),
116 route("GET", "/repos/:owner/:name/stargazers", Op::About(AboutOp::ListStargazers), &[("page", "page")]),
117 // What the default branch says about a repository, kept by commit.
118 route("GET", "/repos/:owner/:name/languages", Op::About(AboutOp::GetLanguages), &[]),
119 route("GET", "/repos/:owner/:name/contributors", Op::About(AboutOp::ListContributors), &[]),
120 route("GET", "/repos/:owner/:name/license", Op::About(AboutOp::GetLicense), &[]),
121 // Releases: `latest` and `tags/…` before an id.
122 route("GET", "/repos/:owner/:name/releases", Op::About(AboutOp::ListReleases), &[]),
123 route("POST", "/repos/:owner/:name/releases", Op::About(AboutOp::CreateRelease), &[]),
124 route("GET", "/repos/:owner/:name/releases/latest", Op::About(AboutOp::GetLatestRelease), &[]),
125 route("GET", "/repos/:owner/:name/releases/tags/:tag", Op::About(AboutOp::GetReleaseByTag), &[]),
126 route("GET", "/repos/:owner/:name/releases/:id", Op::About(AboutOp::GetRelease), &[]),
127 route("PATCH", "/repos/:owner/:name/releases/:id", Op::About(AboutOp::UpdateRelease), &[]),
128 route("DELETE", "/repos/:owner/:name/releases/:id", Op::About(AboutOp::DeleteRelease), &[]),
API: pinned projects over REST and MCP129 // Your pinned projects in a workspace, in your order.
130 route("GET", "/user/pinned_projects/:workspace", Op::ListPinnedProjects, &[]),
131 route("PUT", "/user/pinned_projects/:workspace", Op::ReorderPinnedProjects, &[]),
132 route("PUT", "/user/pinned_projects/:workspace/:project", Op::PinProject, &[]),
133 route("DELETE", "/user/pinned_projects/:workspace/:project", Op::UnpinProject, &[]),
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were134 route("PATCH", "/user/repository_invitations/:id", Op::AcceptRepoInvitation, &[]),
135 route("DELETE", "/user/repository_invitations/:id", Op::DeclineRepoInvitation, &[]),
136 route("PATCH", "/workspaces/:workspace", Op::UpdateWorkspace, &[]),
Merge main (membership, two-factor, GitHub repo roles) into tokens137 // Members and owners: GitHub's organization members, by username.
138 route("GET", "/workspaces/:workspace/members", Op::ListMembers, &[]),
139 route("PATCH", "/workspaces/:workspace/members/:username", Op::UpdateMember, &[]),
140 route("DELETE", "/workspaces/:workspace/members/:username", Op::RemoveMember, &[]),
141 route("POST", "/workspaces/:workspace/transfer_ownership", Op::TransferOwnership, &[]),
142 route("DELETE", "/user/memberships/:workspace", Op::LeaveWorkspace, &[]),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97143 // A workspace's projects: what each is, where it runs, its links.
144 route("GET", "/workspaces/:workspace/projects", Op::ListProjects, &[]),
145 route("GET", "/workspaces/:workspace/projects/:project", Op::GetProject, &[]),
146 route("PATCH", "/workspaces/:workspace/projects/:project", Op::UpdateProject, &[]),
Projects have Observability: their requests, logs, traces, metrics and errors, from apps on g1t.page with nothing to set up, and from software anywhere else over OpenTelemetry.147 // A project's observability: what its software did, and how it is collected.
148 route(
149 "GET",
150 "/workspaces/:workspace/projects/:project/observability/overview",
151 Op::Telemetry(TelemetryOp::GetOverview),
152 &[("from", "from"), ("to", "to"), ("environment", "environment"), ("service", "service"), ("deployment_id", "deployment_id"), ("step", "step")],
153 ),
154 route(
155 "GET",
156 "/workspaces/:workspace/projects/:project/observability/requests",
157 Op::Telemetry(TelemetryOp::ListRequests),
158 &[
159 ("from", "from"),
160 ("to", "to"),
161 ("q", "q"),
162 ("status", "status"),
163 ("errors_only", "errors_only"),
164 ("route", "route"),
165 ("min_duration_ms", "min_duration_ms"),
166 ("environment", "environment"),
167 ("service", "service"),
168 ("deployment_id", "deployment_id"),
169 ("cursor", "cursor"),
170 ("limit", "limit"),
171 ],
172 ),
173 route("GET", "/workspaces/:workspace/projects/:project/observability/requests/:id", Op::Telemetry(TelemetryOp::GetRequest), &[]),
174 route(
175 "GET",
176 "/workspaces/:workspace/projects/:project/observability/logs",
177 Op::Telemetry(TelemetryOp::ListLogs),
178 &[
179 ("from", "from"),
180 ("to", "to"),
181 ("q", "q"),
182 ("level", "level"),
183 ("trace_id", "trace_id"),
184 ("request_id", "request_id"),
185 ("environment", "environment"),
186 ("service", "service"),
187 ("deployment_id", "deployment_id"),
188 ("cursor", "cursor"),
189 ("limit", "limit"),
190 ],
191 ),
192 route(
193 "GET",
194 "/workspaces/:workspace/projects/:project/observability/traces",
195 Op::Telemetry(TelemetryOp::ListTraces),
196 &[
197 ("from", "from"),
198 ("to", "to"),
199 ("q", "q"),
200 ("errors_only", "errors_only"),
201 ("min_duration_ms", "min_duration_ms"),
202 ("environment", "environment"),
203 ("service", "service"),
204 ("cursor", "cursor"),
205 ("limit", "limit"),
206 ],
207 ),
208 route("GET", "/workspaces/:workspace/projects/:project/observability/traces/:trace_id", Op::Telemetry(TelemetryOp::GetTrace), &[]),
209 route(
210 "GET",
211 "/workspaces/:workspace/projects/:project/observability/errors",
212 Op::Telemetry(TelemetryOp::ListErrors),
213 &[("state", "state"), ("environment", "environment"), ("service", "service"), ("from", "from"), ("to", "to")],
214 ),
215 route("PATCH", "/workspaces/:workspace/projects/:project/observability/errors/:fingerprint", Op::Telemetry(TelemetryOp::UpdateError), &[]),
216 route("GET", "/workspaces/:workspace/projects/:project/observability/metrics", Op::Telemetry(TelemetryOp::ListMetrics), &[]),
217 route(
218 "GET",
219 "/workspaces/:workspace/projects/:project/observability/metrics/:metric",
220 Op::Telemetry(TelemetryOp::GetMetric),
221 &[("from", "from"), ("to", "to"), ("aggregate", "aggregate"), ("group_by", "group_by"), ("step", "step"), ("service", "service"), ("environment", "environment")],
222 ),
223 route("GET", "/workspaces/:workspace/projects/:project/observability/settings", Op::Telemetry(TelemetryOp::GetSettings), &[]),
224 route("PATCH", "/workspaces/:workspace/projects/:project/observability/settings", Op::Telemetry(TelemetryOp::UpdateSettings), &[]),
225 route("GET", "/workspaces/:workspace/projects/:project/observability/tokens", Op::Telemetry(TelemetryOp::ListTokens), &[]),
226 route("POST", "/workspaces/:workspace/projects/:project/observability/tokens", Op::Telemetry(TelemetryOp::CreateToken), &[]),
227 route("DELETE", "/workspaces/:workspace/projects/:project/observability/tokens/:id", Op::Telemetry(TelemetryOp::DeleteToken), &[]),
228 route("GET", "/workspaces/:workspace/projects/:project/observability/exports", Op::Telemetry(TelemetryOp::ListExports), &[]),
229 route("POST", "/workspaces/:workspace/projects/:project/observability/exports", Op::Telemetry(TelemetryOp::CreateExport), &[]),
230 route("PATCH", "/workspaces/:workspace/projects/:project/observability/exports/:id", Op::Telemetry(TelemetryOp::UpdateExport), &[]),
231 route("DELETE", "/workspaces/:workspace/projects/:project/observability/exports/:id", Op::Telemetry(TelemetryOp::DeleteExport), &[]),
232 route("GET", "/workspaces/:workspace/projects/:project/observability/usage", Op::Telemetry(TelemetryOp::GetUsage), &[]),
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were233 route("PUT", "/workspaces/:workspace/base_permission", Op::SetBasePermission, &[]),
234 route(
235 "GET",
236 "/workspaces/:workspace/outside_collaborators",
237 Op::ListOutsideCollaborators,
238 &[],
239 ),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar240 // Teams: a workspace's groups of members, with roles on repositories.
241 route("GET", "/workspaces/:workspace/teams", Op::ListTeams, &[("q", "query")]),
242 route("POST", "/workspaces/:workspace/teams", Op::CreateTeam, &[]),
243 route("GET", "/workspaces/:workspace/teams/:team", Op::GetTeam, &[]),
244 route("PATCH", "/workspaces/:workspace/teams/:team", Op::UpdateTeam, &[]),
245 route("DELETE", "/workspaces/:workspace/teams/:team", Op::DeleteTeam, &[]),
246 route(
247 "GET",
248 "/workspaces/:workspace/teams/:team/members",
249 Op::ListTeamMembers,
250 &[("include_child_teams", "include_child_teams")],
251 ),
252 route("PUT", "/workspaces/:workspace/teams/:team/members/:username", Op::SetTeamMember, &[]),
253 route("DELETE", "/workspaces/:workspace/teams/:team/members/:username", Op::RemoveTeamMember, &[]),
254 route("GET", "/workspaces/:workspace/teams/:team/teams", Op::ListChildTeams, &[]),
255 route("GET", "/workspaces/:workspace/teams/:team/repos", Op::ListTeamRepos, &[]),
256 route("PUT", "/workspaces/:workspace/teams/:team/repos/:repo", Op::SetTeamRepo, &[]),
257 route("DELETE", "/workspaces/:workspace/teams/:team/repos/:repo", Op::RemoveTeamRepo, &[]),
258 route(
259 "PUT",
260 "/workspaces/:workspace/teams/:team/review_assignment",
261 Op::SetTeamReviewAssignment,
262 &[],
263 ),
264 route("GET", "/workspaces/:workspace/members/:username/teams", Op::ListUserTeams, &[]),
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.265 // A workspace's billing: usage, budget and invoices.
Usage, Billing settings and prepaid AI credit; fixes from the UX audit266 route(
267 "GET",
268 "/workspaces/:workspace/usage",
269 Op::GetUsage,
270 &[("from", "from"), ("until", "until"), ("products", "products"), ("projects", "projects"), ("group_by", "group_by")],
271 ),
272 route("GET", "/workspaces/:workspace/budget", Op::GetBudget, &[]),
273 route("PUT", "/workspaces/:workspace/budget", Op::SetBudget, &[]),
274 route("GET", "/workspaces/:workspace/invoices", Op::ListInvoices, &[]),
275 route("GET", "/workspaces/:workspace/billing_details", Op::GetBillingDetails, &[]),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar276 // Code owners: the CODEOWNERS file, checked.
277 route("GET", "/repos/:owner/:name/codeowners/errors", Op::GetCodeownersErrors, &[("ref", "ref")]),
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were278 // Security alerts: secrets and vulnerable dependencies.
279 route(
280 "GET",
281 "/repos/:owner/:name/security/alerts",
282 Op::ListSecurityAlerts,
283 &[("state", "state"), ("kind", "kind")],
284 ),
285 route("POST", "/repos/:owner/:name/security/alerts/:id/dismiss", Op::DismissSecurityAlert, &[]),
286 route("POST", "/repos/:owner/:name/security/alerts/:id/reopen", Op::ReopenSecurityAlert, &[]),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar287 // The security suite: secret scanning, code scanning, vulnerability
288 // alerts and the supply chain, at the common addresses.
289 route("GET", "/repos/:owner/:name/secret-scanning/alerts", Op::Security(SecurityOp::ListSecretAlerts), &[("state", "state"), ("secret_type", "secret_type"), ("validity", "validity"), ("bypassed", "bypassed")]),
290 route("GET", "/workspaces/:workspace/secret-scanning/alerts", Op::Security(SecurityOp::ListSecretAlerts), &[("state", "state"), ("secret_type", "secret_type"), ("validity", "validity"), ("bypassed", "bypassed")]),
291 route("GET", "/repos/:owner/:name/secret-scanning/alerts/:id", Op::Security(SecurityOp::GetSecretAlert), &[]),
292 route("PATCH", "/repos/:owner/:name/secret-scanning/alerts/:id", Op::Security(SecurityOp::UpdateSecretAlert), &[]),
293 route("GET", "/repos/:owner/:name/secret-scanning/alerts/:id/locations", Op::Security(SecurityOp::ListSecretLocations), &[]),
294 route("POST", "/repos/:owner/:name/secret-scanning/alerts/:id/bypass", Op::Security(SecurityOp::BypassPushProtection), &[]),
295 route("POST", "/repos/:owner/:name/secret-scanning/alerts/:id/validity", Op::Security(SecurityOp::CheckSecretValidity), &[]),
296 route("GET", "/workspaces/:workspace/secret-scanning/bypass-requests", Op::Security(SecurityOp::ListBypassRequests), &[("state", "state"), ("repo", "repo")]),
297 route("PATCH", "/workspaces/:workspace/secret-scanning/bypass-requests/:id", Op::Security(SecurityOp::ReviewBypassRequest), &[]),
298 route("POST", "/repos/:owner/:name/secret-scanning/custom-patterns/dry-run", Op::Security(SecurityOp::DryRunCustomPattern), &[]),
299 route("POST", "/workspaces/:workspace/secret-scanning/custom-patterns/dry-run", Op::Security(SecurityOp::DryRunCustomPattern), &[]),
300 route("GET", "/repos/:owner/:name/secret-scanning/custom-patterns", Op::Security(SecurityOp::ListCustomPatterns), &[]),
301 route("GET", "/workspaces/:workspace/secret-scanning/custom-patterns", Op::Security(SecurityOp::ListCustomPatterns), &[]),
302 route("POST", "/repos/:owner/:name/secret-scanning/custom-patterns", Op::Security(SecurityOp::CreateCustomPattern), &[]),
303 route("POST", "/workspaces/:workspace/secret-scanning/custom-patterns", Op::Security(SecurityOp::CreateCustomPattern), &[]),
304 route("PATCH", "/repos/:owner/:name/secret-scanning/custom-patterns/:id", Op::Security(SecurityOp::UpdateCustomPattern), &[]),
305 route("PATCH", "/workspaces/:workspace/secret-scanning/custom-patterns/:id", Op::Security(SecurityOp::UpdateCustomPattern), &[]),
306 route("DELETE", "/repos/:owner/:name/secret-scanning/custom-patterns/:id", Op::Security(SecurityOp::DeleteCustomPattern), &[]),
307 route("DELETE", "/workspaces/:workspace/secret-scanning/custom-patterns/:id", Op::Security(SecurityOp::DeleteCustomPattern), &[]),
308 route("GET", "/repos/:owner/:name/code-scanning/alerts", Op::Security(SecurityOp::ListCodeAlerts), &[("state", "state"), ("severity", "severity"), ("tool", "tool"), ("rule_id", "rule_id")]),
309 route("GET", "/workspaces/:workspace/code-scanning/alerts", Op::Security(SecurityOp::ListCodeAlerts), &[("state", "state"), ("severity", "severity"), ("tool", "tool"), ("rule_id", "rule_id")]),
310 route("GET", "/repos/:owner/:name/code-scanning/alerts/:number", Op::Security(SecurityOp::GetCodeAlert), &[]),
311 route("PATCH", "/repos/:owner/:name/code-scanning/alerts/:number", Op::Security(SecurityOp::UpdateCodeAlert), &[]),
312 route("GET", "/repos/:owner/:name/code-scanning/analyses", Op::Security(SecurityOp::ListAnalyses), &[]),
313 route("POST", "/repos/:owner/:name/code-scanning/sarifs", Op::Security(SecurityOp::UploadSarif), &[]),
314 route("GET", "/repos/:owner/:name/code-scanning/sarifs/:id", Op::Security(SecurityOp::GetSarifUpload), &[]),
315 route("GET", "/repos/:owner/:name/vulnerability-alerts", Op::Security(SecurityOp::ListVulnerabilityAlerts), &[("state", "state"), ("severity", "severity"), ("ecosystem", "ecosystem"), ("package", "package")]),
316 route("GET", "/workspaces/:workspace/vulnerability-alerts", Op::Security(SecurityOp::ListVulnerabilityAlerts), &[("state", "state"), ("severity", "severity"), ("ecosystem", "ecosystem"), ("package", "package")]),
317 route("GET", "/repos/:owner/:name/vulnerability-alerts/:id", Op::Security(SecurityOp::GetVulnerabilityAlert), &[]),
318 route("PATCH", "/repos/:owner/:name/vulnerability-alerts/:id", Op::Security(SecurityOp::UpdateVulnerabilityAlert), &[]),
319 route("GET", "/repos/:owner/:name/dependency-graph", Op::Security(SecurityOp::GetDependencyGraph), &[]),
320 route("GET", "/repos/:owner/:name/dependency-graph/sbom", Op::Security(SecurityOp::GetSbom), &[]),
321 route("GET", "/repos/:owner/:name/dependency-graph/compare/:basehead", Op::Security(SecurityOp::CompareDependencies), &[]),
322 route("GET", "/repos/:owner/:name/security/settings", Op::Security(SecurityOp::GetSettings), &[]),
323 route("PATCH", "/repos/:owner/:name/security/settings", Op::Security(SecurityOp::UpdateSettings), &[]),
324 route("GET", "/workspaces/:workspace/security/settings", Op::Security(SecurityOp::GetWorkspaceSettings), &[]),
325 route("PATCH", "/workspaces/:workspace/security/settings", Op::Security(SecurityOp::UpdateWorkspaceSettings), &[]),
326 route("GET", "/workspaces/:workspace/security/overview", Op::Security(SecurityOp::GetOverview), &[("days", "days")]),
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9327 // Reporting: a repository's advisories, and the vulnerability reports sent to its maintainers.
328 route("GET", "/repos/:owner/:name/security/advisories", Op::Security(SecurityOp::ListAdvisories), &[("state", "state")]),
329 route("POST", "/repos/:owner/:name/security/advisories", Op::Security(SecurityOp::CreateAdvisory), &[]),
330 route("GET", "/repos/:owner/:name/security/advisories/:id", Op::Security(SecurityOp::GetAdvisory), &[]),
331 route("PATCH", "/repos/:owner/:name/security/advisories/:id", Op::Security(SecurityOp::UpdateAdvisory), &[]),
332 route("POST", "/repos/:owner/:name/security/advisories/:id/state", Op::Security(SecurityOp::SetAdvisoryState), &[]),
333 route("GET", "/repos/:owner/:name/security/reports", Op::Security(SecurityOp::ListReports), &[("state", "state")]),
334 route("POST", "/repos/:owner/:name/security/reports", Op::Security(SecurityOp::ReportVulnerability), &[]),
335 route("PATCH", "/repos/:owner/:name/security/reports/:id", Op::Security(SecurityOp::ReviewReport), &[]),
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were336 route("GET", "/repos", Op::ListRepos, &[("q", "query")]),
337 route(
338 "GET",
339 "/search",
340 Op::Search,
341 &[("q", "query"), ("type", "type"), ("page", "page"), ("per_page", "per_page")],
342 ),
343 route("POST", "/repos", Op::CreateRepo, &[]),
344 route("GET", "/repos/:owner/:name", Op::GetRepo, &[]),
Merge GitHub pull request #8 (claude/cool-newton-7nm8r9): Docker deployments on g1t.page behind a switch that stays off until Cloudflare's container APIs are checked live, workflows a workspace manages in its repositories, tarball and zipball downloads, refs/pull/* kept to g1t alone, framework detection for g1t.page builds, and a workflow's event payload matching GitHub's.345 // A branch, tag or commit as one archive: a redirect to a link good for
346 // ten minutes, as GitHub's go to codeload.
347 route("GET", "/repos/:owner/:name/tarball/:ref", Op::Archives(ArchiveOp::DownloadTarball), &[]),
348 route("GET", "/repos/:owner/:name/zipball/:ref", Op::Archives(ArchiveOp::DownloadZipball), &[]),
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were349 route("PATCH", "/repos/:owner/:name", Op::UpdateRepo, &[]),
350 route("POST", "/repos/:owner/:name/transfer", Op::TransferRepo, &[]),
351 route("DELETE", "/repos/:owner/:name", Op::DeleteRepo, &[]),
352 route(
353 "GET",
354 "/workspaces/:workspace/repos/deleted",
355 Op::ListDeletedRepos,
356 &[],
357 ),
358 route("POST", "/repos/:owner/:name/restore", Op::RestoreRepo, &[]),
359 route("POST", "/repos/:owner/:name/purge", Op::PurgeRepo, &[]),
360 route("POST", "/repos/:owner/:name/rename", Op::RenameRepo, &[]),
361 route("POST", "/repos/:owner/:name/archive", Op::ArchiveRepo, &[]),
362 route("POST", "/repos/:owner/:name/unarchive", Op::UnarchiveRepo, &[]),
363 route(
364 "POST",
365 "/repos/:owner/:name/visibility",
366 Op::SetRepoVisibility,
367 &[],
368 ),
369 route(
370 "POST",
371 "/repos/:owner/:name/branches/:branch/rename",
372 Op::RenameBranch,
373 &[],
374 ),
The Branches page does things to branches now. It has tabs (Overview, Yours, Active, Stale, All, kept in the address as ?tab=) and search; Overview shows the default branch, then the first five of your branches, the active ones and the stale ones, each linking to the rest. Each branch is a row with its name in a code chip with a copy button, who made its latest commit and when, that commit's checks, behind and ahead counts with bars drawn against the largest shown, and its pull request (the open one, else the newest merged or closed) with its state; on a phone the columns fold under the name. Yours is branches whose latest commit is the viewer's; stale is no commit in 90 days.375 route("POST", "/repos/:owner/:name/branches", Op::CreateBranch, &[]),
376 route("DELETE", "/repos/:owner/:name/branches/:branch", Op::DeleteBranch, &[("head", "head")]),
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were377 route(
378 "GET",
379 "/repos/:owner/:name/settings",
380 Op::GetRepoSettings,
381 &[],
382 ),
383 route(
384 "PATCH",
385 "/repos/:owner/:name/settings",
386 Op::UpdateRepoSettings,
387 &[],
388 ),
389 route("GET", "/repos/:owner/:name/check-names", Op::ListCheckNames, &[]),
Merge checks: statuses and check runs on every commit390 // Checks: GitHub's addresses for statuses, check runs and check suites.
391 route("POST", "/repos/:owner/:name/statuses/:sha", Op::Checks(ChecksOp::CreateCommitStatus), &[]),
392 route("GET", "/repos/:owner/:name/commits/:ref/statuses", Op::Checks(ChecksOp::ListCommitStatuses), &[]),
393 route("GET", "/repos/:owner/:name/commits/:ref/status", Op::Checks(ChecksOp::GetCombinedStatus), &[]),
394 route(
395 "GET",
396 "/repos/:owner/:name/commits/:ref/check-runs",
397 Op::Checks(ChecksOp::ListCheckRunsForRef),
398 &[("check_name", "check_name"), ("status", "status"), ("app", "app"), ("filter", "filter")],
399 ),
400 route(
401 "GET",
402 "/repos/:owner/:name/commits/:ref/check-suites",
403 Op::Checks(ChecksOp::ListCheckSuitesForRef),
404 &[("app", "app"), ("check_name", "check_name")],
405 ),
406 route("POST", "/repos/:owner/:name/check-runs", Op::Checks(ChecksOp::CreateCheckRun), &[]),
407 route("GET", "/repos/:owner/:name/check-runs/:id", Op::Checks(ChecksOp::GetCheckRun), &[]),
408 route("PATCH", "/repos/:owner/:name/check-runs/:id", Op::Checks(ChecksOp::UpdateCheckRun), &[]),
409 route("GET", "/repos/:owner/:name/check-runs/:id/annotations", Op::Checks(ChecksOp::ListCheckRunAnnotations), &[]),
410 route("POST", "/repos/:owner/:name/check-runs/:id/rerequest", Op::Checks(ChecksOp::RerequestCheckRun), &[]),
411 route("GET", "/repos/:owner/:name/check-suites/:id", Op::Checks(ChecksOp::GetCheckSuite), &[]),
412 route("POST", "/repos/:owner/:name/check-suites/:id/rerequest", Op::Checks(ChecksOp::RerequestCheckSuite), &[]),
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge413 // Rulesets: a repository's, a workspace's, the rules of one branch,
414 // and how they judged pushes and merges.
415 route("GET", "/repos/:owner/:name/rulesets", Op::Rules(RulesOp::ListRepoRulesets), &[("include_parents", "include_parents")]),
416 route("POST", "/repos/:owner/:name/rulesets", Op::Rules(RulesOp::CreateRepoRuleset), &[]),
417 route("GET", "/repos/:owner/:name/rulesets/:id", Op::Rules(RulesOp::GetRepoRuleset), &[]),
418 route("PUT", "/repos/:owner/:name/rulesets/:id", Op::Rules(RulesOp::UpdateRepoRuleset), &[]),
419 route("DELETE", "/repos/:owner/:name/rulesets/:id", Op::Rules(RulesOp::DeleteRepoRuleset), &[]),
420 route("GET", "/repos/:owner/:name/rules/branches/:branch", Op::Rules(RulesOp::GetBranchRules), &[("target", "target")]),
421 route(
422 "GET",
423 "/repos/:owner/:name/rules/evaluations",
424 Op::Rules(RulesOp::ListRuleEvaluations),
425 &[("ruleset_id", "ruleset_id"), ("verdict", "verdict"), ("problems_only", "problems_only"), ("before", "before"), ("limit", "limit")],
426 ),
427 route("GET", "/workspaces/:workspace/rulesets", Op::Rules(RulesOp::ListWorkspaceRulesets), &[]),
428 route("POST", "/workspaces/:workspace/rulesets", Op::Rules(RulesOp::CreateWorkspaceRuleset), &[]),
429 route("GET", "/workspaces/:workspace/rulesets/:id", Op::Rules(RulesOp::GetWorkspaceRuleset), &[]),
430 route("PUT", "/workspaces/:workspace/rulesets/:id", Op::Rules(RulesOp::UpdateWorkspaceRuleset), &[]),
431 route("DELETE", "/workspaces/:workspace/rulesets/:id", Op::Rules(RulesOp::DeleteWorkspaceRuleset), &[]),
432 route(
433 "GET",
434 "/workspaces/:workspace/rules/evaluations",
435 Op::Rules(RulesOp::ListWorkspaceRuleEvaluations),
436 &[("ruleset_id", "ruleset_id"), ("verdict", "verdict"), ("problems_only", "problems_only"), ("before", "before"), ("limit", "limit")],
437 ),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97438 // Deployments wherever they run, their statuses, and environments.
439 route(
440 "GET",
441 "/repos/:owner/:name/deployments",
442 Op::Deployments(DeploymentsOp::ListDeployments),
443 &[("environment", "environment"), ("ref", "ref"), ("sha", "sha"), ("task", "task"), ("state", "state"), ("source", "source"), ("creator", "creator"), ("page", "page"), ("per_page", "per_page")],
444 ),
445 route("POST", "/repos/:owner/:name/deployments", Op::Deployments(DeploymentsOp::CreateDeployment), &[]),
Merge branch 'worktree-agent-a4d35ee70c50683d0' into claude/cool-newton-7nm8r9446 // A g1t Actions job opening a g1t.page build it runs itself.
447 route("POST", "/repos/:owner/:name/deployments/builds", Op::Deployments(DeploymentsOp::StartDeploymentBuild), &[]),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97448 route("GET", "/repos/:owner/:name/deployments/:id", Op::Deployments(DeploymentsOp::GetDeployment), &[]),
449 route("GET", "/repos/:owner/:name/deployments/:id/statuses", Op::Deployments(DeploymentsOp::ListDeploymentStatuses), &[]),
450 route("POST", "/repos/:owner/:name/deployments/:id/statuses", Op::Deployments(DeploymentsOp::CreateDeploymentStatus), &[]),
451 route("GET", "/repos/:owner/:name/environments", Op::Deployments(DeploymentsOp::ListEnvironments), &[]),
452 route("GET", "/repos/:owner/:name/environments/:environment", Op::Deployments(DeploymentsOp::GetEnvironment), &[]),
Merge branch 'worktree-agent-a3abfcce648e87dca'453 // Environments' protection rules, and the runs they hold.
454 route("PUT", "/repos/:owner/:name/environments/:environment", Op::Protection(ProtectionOp::UpdateEnvironment), &[]),
455 route("DELETE", "/repos/:owner/:name/environments/:environment", Op::Protection(ProtectionOp::DeleteEnvironment), &[]),
456 route(
457 "GET",
458 "/repos/:owner/:name/actions/runs/:id/pending_deployments",
459 Op::Protection(ProtectionOp::GetPendingDeployments),
460 &[],
461 ),
462 route(
463 "POST",
464 "/repos/:owner/:name/actions/runs/:id/pending_deployments",
465 Op::Protection(ProtectionOp::ReviewPendingDeployments),
466 &[],
467 ),
468 route("POST", "/repos/:owner/:name/actions/runs/:id/approve", Op::Protection(ProtectionOp::ApproveWorkflowRun), &[]),
469 route("GET", "/repos/:owner/:name/actions/permissions/workflow", Op::Protection(ProtectionOp::GetWorkflowPermissions), &[]),
470 route("PUT", "/repos/:owner/:name/actions/permissions/workflow", Op::Protection(ProtectionOp::SetWorkflowPermissions), &[]),
471 route(
472 "GET",
473 "/repos/:owner/:name/actions/permissions/fork-pr-contributor-approval",
474 Op::Protection(ProtectionOp::GetForkPrApproval),
475 &[],
476 ),
477 route(
478 "PUT",
479 "/repos/:owner/:name/actions/permissions/fork-pr-contributor-approval",
480 Op::Protection(ProtectionOp::SetForkPrApproval),
481 &[],
482 ),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts483 route("GET", "/repos/:owner/:name/actions/permissions/access", Op::Protection(ProtectionOp::GetActionsAccess), &[]),
484 route("PUT", "/repos/:owner/:name/actions/permissions/access", Op::Protection(ProtectionOp::SetActionsAccess), &[]),
Merge branch 'worktree-agent-a3abfcce648e87dca'485 route("POST", "/repos/:owner/:name/dispatches", Op::Protection(ProtectionOp::CreateRepositoryDispatch), &[]),
486 route(
487 "GET",
488 "/workspaces/:workspace/actions/permissions/workflow",
489 Op::Protection(ProtectionOp::GetWorkspaceWorkflowPermissions),
490 &[],
491 ),
492 route(
493 "PUT",
494 "/workspaces/:workspace/actions/permissions/workflow",
495 Op::Protection(ProtectionOp::SetWorkspaceWorkflowPermissions),
496 &[],
497 ),
Merge GitHub pull request #8 (claude/cool-newton-7nm8r9): Docker deployments on g1t.page behind a switch that stays off until Cloudflare's container APIs are checked live, workflows a workspace manages in its repositories, tarball and zipball downloads, refs/pull/* kept to g1t alone, framework detection for g1t.page builds, and a workflow's event payload matching GitHub's.498 route(
499 "GET",
500 "/workspaces/:workspace/actions/managed-workflows",
501 Op::Protection(ProtectionOp::ListWorkspaceManagedWorkflows),
502 &[],
503 ),
504 route(
505 "POST",
506 "/workspaces/:workspace/actions/managed-workflows",
507 Op::Protection(ProtectionOp::CreateWorkspaceManagedWorkflow),
508 &[],
509 ),
510 route(
511 "PATCH",
512 "/workspaces/:workspace/actions/managed-workflows/:workflow",
513 Op::Protection(ProtectionOp::UpdateWorkspaceManagedWorkflow),
514 &[],
515 ),
516 route(
517 "DELETE",
518 "/workspaces/:workspace/actions/managed-workflows/:workflow",
519 Op::Protection(ProtectionOp::DeleteWorkspaceManagedWorkflow),
520 &[],
521 ),
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were522 route("GET", "/repos/:owner/:name/queue", Op::GetMergeQueue, &[]),
523 route("POST", "/repos/:owner/:name/memory", Op::Remember, &[]),
524 route(
525 "GET",
526 "/repos/:owner/:name/memory",
527 Op::Recall,
528 &[("q", "query"), ("limit", "limit")],
529 ),
530 route(
531 "GET",
532 "/repos/:owner/:name/events",
533 Op::ListEvents,
Merge branch 'worktree-agent-ad531323a21f4c975'534 &[
535 ("before", "before"),
536 ("after", "after"),
537 ("types", "types"),
538 ("numbers", "numbers"),
539 ("since", "since"),
540 ("limit", "limit"),
541 ("wait", "wait"),
542 ],
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were543 ),
544 route("GET", "/repos/:owner/:name/labels", Op::ListLabels, &[]),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar545 route("POST", "/repos/:owner/:name/labels", Op::CreateLabel, &[]),
546 route("POST", "/repos/:owner/:name/labels/defaults", Op::AddDefaultLabels, &[]),
547 route("PATCH", "/repos/:owner/:name/labels/:label", Op::UpdateLabel, &[]),
548 route("DELETE", "/repos/:owner/:name/labels/:label", Op::DeleteLabel, &[]),
549 route("GET", "/repos/:owner/:name/issues/:number/labels", Op::ListIssueLabels, &[]),
550 route("POST", "/repos/:owner/:name/issues/:number/labels", Op::AddIssueLabels, &[]),
551 route("PUT", "/repos/:owner/:name/issues/:number/labels", Op::SetIssueLabels, &[]),
552 route("DELETE", "/repos/:owner/:name/issues/:number/labels", Op::RemoveIssueLabels, &[]),
553 route("DELETE", "/repos/:owner/:name/issues/:number/labels/:label", Op::RemoveIssueLabels, &[]),
554 route("GET", "/repos/:owner/:name/milestones", Op::ListMilestones, &[("state", "state")]),
555 route("POST", "/repos/:owner/:name/milestones", Op::CreateMilestone, &[]),
556 route("GET", "/repos/:owner/:name/milestones/:milestone", Op::GetMilestone, &[]),
557 route("PATCH", "/repos/:owner/:name/milestones/:milestone", Op::UpdateMilestone, &[]),
558 route("DELETE", "/repos/:owner/:name/milestones/:milestone", Op::DeleteMilestone, &[]),
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were559 route(
560 "GET",
561 "/repos/:owner/:name/issues",
562 Op::ListIssues,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar563 &[("state", "state"), ("label", "label"), ("milestone", "milestone")],
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were564 ),
565 route("POST", "/repos/:owner/:name/issues", Op::CreateIssue, &[]),
566 route(
567 "GET",
568 "/repos/:owner/:name/issues/:number",
569 Op::GetIssue,
570 &[],
571 ),
572 route(
573 "PATCH",
574 "/repos/:owner/:name/issues/:number",
575 Op::UpdateIssue,
576 &[],
577 ),
578 route(
579 "POST",
580 "/repos/:owner/:name/issues/:number/close",
581 Op::CloseIssue,
582 &[],
583 ),
584 route(
585 "POST",
586 "/repos/:owner/:name/issues/:number/reopen",
587 Op::ReopenIssue,
588 &[],
589 ),
590 route(
591 "POST",
592 "/repos/:owner/:name/issues/import",
593 Op::ImportIssue,
594 &[],
595 ),
Merge branch 'worktree-agent-a6ec0a89d7fe7a9ae'596 // Coordinating work: claims on issues, an issue's whole context, and
597 // sessions of people's own harnesses.
598 route("GET", "/repos/:owner/:name/issues/:number/work", Op::Coordination(CoordinationOp::GetWork), &[]),
599 route("POST", "/repos/:owner/:name/issues/:number/claim", Op::Coordination(CoordinationOp::ClaimIssue), &[]),
600 route("POST", "/repos/:owner/:name/issues/:number/release", Op::Coordination(CoordinationOp::ReleaseClaim), &[]),
601 route("POST", "/repos/:owner/:name/issues/:number/handoff", Op::Coordination(CoordinationOp::HandOffIssue), &[]),
602 route(
603 "GET",
604 "/repos/:owner/:name/claims",
605 Op::Coordination(CoordinationOp::ListClaims),
606 &[("state", "state"), ("issue", "issue")],
607 ),
608 route("POST", "/repos/:owner/:name/sessions", Op::Coordination(CoordinationOp::StartSession), &[]),
609 route(
610 "GET",
611 "/repos/:owner/:name/sessions",
612 Op::Coordination(CoordinationOp::ListSessions),
613 &[("state", "state"), ("issue", "issue"), ("pull", "pull")],
614 ),
615 route("GET", "/repos/:owner/:name/sessions/:id", Op::Coordination(CoordinationOp::GetSession), &[("after", "after")]),
616 route("PATCH", "/repos/:owner/:name/sessions/:id", Op::Coordination(CoordinationOp::UpdateSession), &[]),
617 route("POST", "/repos/:owner/:name/sessions/:id/end", Op::Coordination(CoordinationOp::EndSession), &[]),
618 route(
619 "POST",
620 "/repos/:owner/:name/sessions/:id/entries",
621 Op::Coordination(CoordinationOp::RecordSessionEntries),
622 &[],
623 ),
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were624 route(
625 "GET",
626 "/repos/:owner/:name/context",
627 Op::GetContext,
628 &[("reference", "reference")],
629 ),
630 route(
631 "GET",
632 "/workspaces/:workspace/integrations",
633 Op::ListIntegrations,
634 &[],
635 ),
636 route(
637 "POST",
638 "/workspaces/:workspace/integrations",
639 Op::ConnectIntegration,
640 &[],
641 ),
642 route(
643 "GET",
644 "/repos/:owner/:name/hooks",
645 Op::ListWebhooks,
646 &[],
647 ),
648 route(
649 "POST",
650 "/repos/:owner/:name/hooks",
651 Op::CreateWebhook,
652 &[],
653 ),
654 route(
655 "PATCH",
656 "/repos/:owner/:name/hooks/:id",
657 Op::UpdateWebhook,
658 &[],
659 ),
660 route(
661 "DELETE",
662 "/repos/:owner/:name/hooks/:id",
663 Op::DeleteWebhook,
664 &[],
665 ),
666 route(
667 "POST",
668 "/repos/:owner/:name/hooks/:id/pings",
669 Op::PingWebhook,
670 &[],
671 ),
672 route(
673 "GET",
674 "/repos/:owner/:name/hooks/:id/deliveries",
675 Op::ListWebhookDeliveries,
676 &[],
677 ),
678 route(
679 "POST",
680 "/repos/:owner/:name/hooks/:id/deliveries/:delivery/redeliver",
681 Op::RedeliverWebhook,
682 &[],
683 ),
684 route(
685 "GET",
686 "/workspaces/:workspace/hooks",
687 Op::ListWebhooks,
688 &[],
689 ),
690 route(
691 "POST",
692 "/workspaces/:workspace/hooks",
693 Op::CreateWebhook,
694 &[],
695 ),
696 route(
697 "PATCH",
698 "/workspaces/:workspace/hooks/:id",
699 Op::UpdateWebhook,
700 &[],
701 ),
702 route(
703 "DELETE",
704 "/workspaces/:workspace/hooks/:id",
705 Op::DeleteWebhook,
706 &[],
707 ),
708 route(
709 "POST",
710 "/workspaces/:workspace/hooks/:id/pings",
711 Op::PingWebhook,
712 &[],
713 ),
714 route(
715 "GET",
716 "/workspaces/:workspace/hooks/:id/deliveries",
717 Op::ListWebhookDeliveries,
718 &[],
719 ),
720 route(
721 "POST",
722 "/workspaces/:workspace/hooks/:id/deliveries/:delivery/redeliver",
723 Op::RedeliverWebhook,
724 &[],
725 ),
726 route(
AI Gateway: OpenAI's format, open models, and your own providers727 "PATCH",
728 "/workspaces/:workspace/integrations/:id",
729 Op::UpdateIntegration,
730 &[],
731 ),
732 route(
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were733 "DELETE",
734 "/workspaces/:workspace/integrations/:id",
735 Op::DisconnectIntegration,
736 &[],
737 ),
738 route(
739 "POST",
740 "/workspaces/:workspace/integrations/:id/test",
741 Op::TestIntegration,
742 &[],
743 ),
744 route(
745 "GET",
746 "/repos/:owner/:name/actions/workflows",
747 Op::ListWorkflows,
748 &[],
749 ),
750 route(
751 "GET",
752 "/repos/:owner/:name/actions/workflows/:workflow/runs",
753 Op::ListWorkflowRuns,
754 &[("branch", "branch"), ("event", "event"), ("per_page", "limit")],
755 ),
756 route(
757 "POST",
758 "/repos/:owner/:name/actions/workflows/:workflow/dispatches",
759 Op::DispatchWorkflow,
760 &[],
761 ),
762 route(
763 "PATCH",
764 "/repos/:owner/:name/actions/workflows/:workflow",
765 Op::UpdateWorkflow,
766 &[],
767 ),
768 route(
769 "PUT",
770 "/repos/:owner/:name/actions/workflows/:workflow/enable",
771 Op::UpdateWorkflow,
772 &[],
773 ),
774 route(
775 "PUT",
776 "/repos/:owner/:name/actions/workflows/:workflow/disable",
777 Op::UpdateWorkflow,
778 &[],
779 ),
780 route(
781 "GET",
782 "/repos/:owner/:name/actions/runs",
783 Op::ListWorkflowRuns,
784 &[("workflow", "workflow"), ("branch", "branch"), ("event", "event"), ("pull", "pull"), ("head_sha", "sha"), ("per_page", "limit")],
785 ),
786 route(
787 "GET",
788 "/repos/:owner/:name/actions/runs/:id",
789 Op::GetWorkflowRun,
790 &[],
791 ),
792 route(
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)793 "GET",
794 "/repos/:owner/:name/actions/runs/:id/attempts/:attempt",
795 Op::GetWorkflowRun,
796 &[],
797 ),
798 route(
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were799 "POST",
800 "/repos/:owner/:name/actions/runs/:id/cancel",
801 Op::CancelWorkflowRun,
802 &[],
803 ),
804 route(
805 "POST",
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)806 "/repos/:owner/:name/actions/runs/:id/force-cancel",
807 Op::CancelWorkflowRun,
808 &[],
809 ),
810 route(
811 "POST",
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were812 "/repos/:owner/:name/actions/runs/:id/rerun",
813 Op::RerunWorkflowRun,
814 &[],
815 ),
816 route(
817 "POST",
818 "/repos/:owner/:name/actions/runs/:id/rerun-failed-jobs",
819 Op::RerunWorkflowRun,
820 &[],
821 ),
822 route(
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)823 "POST",
824 "/repos/:owner/:name/actions/jobs/:job/rerun",
825 Op::RerunWorkflowRun,
826 &[],
827 ),
828 route(
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were829 "GET",
830 "/repos/:owner/:name/actions/jobs/:job/logs",
831 Op::GetJobLogs,
832 &[("after", "after")],
833 ),
Merge packages: roles, Actions access, source label, soft delete, API834 // Packages, at GitHub's addresses with the workspace in place of the
835 // organization. A name with a slash is one URL-encoded segment.
836 route("GET", "/workspaces/:workspace/packages", Op::Packages(PackagesOp::ListPackages), &[("package_type", "package_type"), ("q", "q"), ("state", "state")]),
837 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name", Op::Packages(PackagesOp::GetPackage), &[]),
838 route("PATCH", "/workspaces/:workspace/packages/:package_type/:package_name", Op::Packages(PackagesOp::UpdatePackage), &[]),
839 route("DELETE", "/workspaces/:workspace/packages/:package_type/:package_name", Op::Packages(PackagesOp::DeletePackage), &[]),
840 route("POST", "/workspaces/:workspace/packages/:package_type/:package_name/restore", Op::Packages(PackagesOp::RestorePackage), &[]),
841 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name/versions", Op::Packages(PackagesOp::ListVersions), &[("state", "state")]),
842 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name/versions/:version_id", Op::Packages(PackagesOp::GetVersion), &[]),
843 route("DELETE", "/workspaces/:workspace/packages/:package_type/:package_name/versions/:version_id", Op::Packages(PackagesOp::DeleteVersion), &[]),
844 route("POST", "/workspaces/:workspace/packages/:package_type/:package_name/versions/:version_id/restore", Op::Packages(PackagesOp::RestoreVersion), &[]),
845 route("PUT", "/workspaces/:workspace/packages/:package_type/:package_name/repository", Op::Packages(PackagesOp::LinkPackage), &[]),
846 route("DELETE", "/workspaces/:workspace/packages/:package_type/:package_name/repository", Op::Packages(PackagesOp::UnlinkPackage), &[]),
847 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name/access", Op::Packages(PackagesOp::ListAccess), &[]),
848 route("PUT", "/workspaces/:workspace/packages/:package_type/:package_name/access", Op::Packages(PackagesOp::SetAccess), &[]),
849 route("DELETE", "/workspaces/:workspace/packages/:package_type/:package_name/access", Op::Packages(PackagesOp::RemoveAccess), &[("username", "username"), ("team", "team")]),
850 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name/actions-access", Op::Packages(PackagesOp::ListActionsAccess), &[]),
851 route("PUT", "/workspaces/:workspace/packages/:package_type/:package_name/actions-access", Op::Packages(PackagesOp::SetActionsAccess), &[]),
852 route("DELETE", "/workspaces/:workspace/packages/:package_type/:package_name/actions-access/:repository", Op::Packages(PackagesOp::RemoveActionsAccess), &[]),
Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2853 // Artifacts, at GitHub's addresses. `…/zip` answers with a redirect to
854 // a signed link (lib.rs).
855 route("GET", "/repos/:owner/:name/actions/artifacts", Op::Artifacts(ArtifactsOp::ListArtifacts), &[("name", "name"), ("page", "page"), ("per_page", "per_page")]),
856 route("GET", "/repos/:owner/:name/actions/runs/:id/artifacts", Op::Artifacts(ArtifactsOp::ListRunArtifacts), &[("name", "name")]),
857 route("GET", "/repos/:owner/:name/actions/artifacts/:id", Op::Artifacts(ArtifactsOp::GetArtifact), &[]),
858 route("GET", "/repos/:owner/:name/actions/artifacts/:id/zip", Op::Artifacts(ArtifactsOp::DownloadArtifact), &[]),
859 route("DELETE", "/repos/:owner/:name/actions/artifacts/:id", Op::Artifacts(ArtifactsOp::DeleteArtifact), &[]),
860 route("GET", "/repos/:owner/:name/actions/permissions/artifact-and-log-retention", Op::Artifacts(ArtifactsOp::GetArtifactRetention), &[]),
861 route("PUT", "/repos/:owner/:name/actions/permissions/artifact-and-log-retention", Op::Artifacts(ArtifactsOp::SetArtifactRetention), &[]),
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were862 route(
863 "GET",
864 "/repos/:owner/:name/actions/secrets",
865 Op::ListActionsSecrets,
866 &[],
867 ),
868 route(
869 "PUT",
870 "/repos/:owner/:name/actions/secrets/:setting",
871 Op::SetActionsSecret,
872 &[],
873 ),
874 route(
875 "DELETE",
876 "/repos/:owner/:name/actions/secrets/:setting",
877 Op::DeleteActionsSecret,
878 &[],
879 ),
880 route(
881 "GET",
882 "/repos/:owner/:name/actions/variables",
883 Op::ListActionsVariables,
884 &[],
885 ),
886 route(
887 "POST",
888 "/repos/:owner/:name/actions/variables",
889 Op::SetActionsVariable,
890 &[],
891 ),
892 route(
893 "PATCH",
894 "/repos/:owner/:name/actions/variables/:setting",
895 Op::SetActionsVariable,
896 &[],
897 ),
898 route(
899 "DELETE",
900 "/repos/:owner/:name/actions/variables/:setting",
901 Op::DeleteActionsVariable,
902 &[],
903 ),
904 route(
905 "GET",
906 "/workspaces/:workspace/actions/secrets",
907 Op::ListActionsSecrets,
908 &[],
909 ),
910 route(
911 "PUT",
912 "/workspaces/:workspace/actions/secrets/:setting",
913 Op::SetActionsSecret,
914 &[],
915 ),
916 route(
917 "DELETE",
918 "/workspaces/:workspace/actions/secrets/:setting",
919 Op::DeleteActionsSecret,
920 &[],
921 ),
922 route(
923 "GET",
924 "/workspaces/:workspace/actions/variables",
925 Op::ListActionsVariables,
926 &[],
927 ),
928 route(
929 "POST",
930 "/workspaces/:workspace/actions/variables",
931 Op::SetActionsVariable,
932 &[],
933 ),
934 route(
935 "PATCH",
936 "/workspaces/:workspace/actions/variables/:setting",
937 Op::SetActionsVariable,
938 &[],
939 ),
940 route(
941 "DELETE",
942 "/workspaces/:workspace/actions/variables/:setting",
943 Op::DeleteActionsVariable,
944 &[],
945 ),
946 // Self-hosted runners: a repository's own, or a workspace's.
947 route("GET", "/repos/:owner/:name/actions/runners", Op::ListRunners, &[]),
948 route("POST", "/repos/:owner/:name/actions/runners/registration-token", Op::CreateRunnerRegistrationToken, &[]),
949 route("DELETE", "/repos/:owner/:name/actions/runners/:id", Op::RemoveRunner, &[]),
950 route("GET", "/repos/:owner/:name/actions/runner-settings", Op::GetRunnerSettings, &[]),
951 route("PATCH", "/repos/:owner/:name/actions/runner-settings", Op::UpdateRunnerSettings, &[]),
952 route("GET", "/workspaces/:workspace/actions/runners", Op::ListRunners, &[]),
953 route("POST", "/workspaces/:workspace/actions/runners/registration-token", Op::CreateRunnerRegistrationToken, &[]),
954 route("DELETE", "/workspaces/:workspace/actions/runners/:id", Op::RemoveRunner, &[]),
955 route("GET", "/workspaces/:workspace/actions/runner-settings", Op::GetRunnerSettings, &[]),
956 route("PATCH", "/workspaces/:workspace/actions/runner-settings", Op::UpdateRunnerSettings, &[]),
957 route("GET", "/workspaces/:workspace/actions/runner-groups", Op::ListRunnerGroups, &[]),
958 route("POST", "/workspaces/:workspace/actions/runner-groups", Op::CreateRunnerGroup, &[]),
959 route("PATCH", "/workspaces/:workspace/actions/runner-groups/:id", Op::UpdateRunnerGroup, &[]),
960 route("DELETE", "/workspaces/:workspace/actions/runner-groups/:id", Op::DeleteRunnerGroup, &[]),
961 route(
962 "POST",
963 "/repos/:owner/:name/issues/:number/comments",
964 Op::AddComment,
965 &[],
966 ),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts967 route("PATCH", "/repos/:owner/:name/issues/comments/:comment_id", Op::EditComment, &[]),
968 route("DELETE", "/repos/:owner/:name/issues/comments/:comment_id", Op::DeleteComment, &[]),
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were969 route(
970 "GET",
971 "/repos/:owner/:name/pulls",
972 Op::ListPullRequests,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar973 &[("state", "state"), ("label", "label"), ("milestone", "milestone"), ("base", "base")],
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were974 ),
975 route(
976 "POST",
977 "/repos/:owner/:name/pulls",
978 Op::CreatePullRequest,
979 &[],
980 ),
981 route(
982 "GET",
983 "/repos/:owner/:name/pulls/:number",
984 Op::GetPullRequest,
985 &[],
986 ),
987 route(
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar988 "PATCH",
989 "/repos/:owner/:name/pulls/:number",
990 Op::UpdatePullRequest,
991 &[],
992 ),
993 route(
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were994 "GET",
995 "/repos/:owner/:name/pulls/:number/changes",
996 Op::GetPullRequestChanges,
997 &[],
998 ),
999 route(
1000 "POST",
1001 "/repos/:owner/:name/pulls/:number/reviews",
1002 Op::ReviewPullRequest,
1003 &[],
1004 ),
1005 route(
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1006 "POST",
1007 "/repos/:owner/:name/pulls/:number/requested_reviewers",
1008 Op::RequestReviewers,
1009 &[],
1010 ),
1011 route(
1012 "DELETE",
1013 "/repos/:owner/:name/pulls/:number/requested_reviewers",
1014 Op::RemoveRequestedReviewers,
1015 &[],
1016 ),
1017 route(
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were1018 "GET",
1019 "/repos/:owner/:name/pulls/:number/session",
1020 Op::ReadSession,
1021 &[("after", "after")],
1022 ),
1023 route(
1024 "POST",
1025 "/repos/:owner/:name/pulls/:number/session",
1026 Op::RecordSession,
1027 &[],
1028 ),
Merge branch 'worktree-agent-ab8033ab973f8d99a'1029 // Decision records, on a pull request or an issue (with its pull
1030 // requests'), and why a line is the way it is.
1031 route("GET", "/repos/:owner/:name/pulls/:number/decisions", Op::ListDecisions, &[]),
1032 route("POST", "/repos/:owner/:name/pulls/:number/decisions", Op::RecordDecision, &[]),
1033 route("GET", "/repos/:owner/:name/issues/:number/decisions", Op::ListDecisions, &[]),
1034 route("GET", "/repos/:owner/:name/why", Op::Why, &[("path", "path"), ("line", "line"), ("ref", "ref")]),
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were1035 route(
1036 "POST",
1037 "/repos/:owner/:name/pulls/:number/ready",
1038 Op::MarkPullRequestReady,
1039 &[],
1040 ),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1041 route("POST", "/repos/:owner/:name/pulls/:number/draft", Op::ConvertPullRequestToDraft, &[]),
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were1042 route(
1043 "POST",
1044 "/repos/:owner/:name/pulls/:number/close",
1045 Op::ClosePullRequest,
1046 &[],
1047 ),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1048 route("POST", "/repos/:owner/:name/pulls/:number/reopen", Op::ReopenPullRequest, &[]),
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were1049 route(
1050 "POST",
1051 "/repos/:owner/:name/pulls/:number/merge",
1052 Op::MergePullRequest,
1053 &[],
1054 ),
Merge main into Artifacts Phase 21055 // Artifacts mode's docs, slides, designs and dashboards. Search comes
1056 // before an artifact by id, which it would otherwise match.
1057 route("GET", "/workspaces/:workspace/artifacts", Op::Folios(FoliosOp::List), &[("tab", "tab"), ("kind", "kind"), ("space", "space"), ("project", "project"), ("q", "q"), ("state", "state"), ("cursor", "cursor"), ("limit", "limit")]),
1058 route("POST", "/workspaces/:workspace/artifacts", Op::Folios(FoliosOp::Create), &[]),
1059 route("GET", "/workspaces/:workspace/artifacts/search", Op::Folios(FoliosOp::Search), &[("q", "q"), ("kind", "kind"), ("space", "space"), ("project", "project"), ("limit", "limit")]),
1060 route("GET", "/workspaces/:workspace/artifacts/:artifact_id", Op::Folios(FoliosOp::Get), &[]),
1061 route("PATCH", "/workspaces/:workspace/artifacts/:artifact_id", Op::Folios(FoliosOp::Update), &[]),
1062 route("DELETE", "/workspaces/:workspace/artifacts/:artifact_id", Op::Folios(FoliosOp::Trash), &[]),
1063 route("POST", "/workspaces/:workspace/artifacts/:artifact_id/restore", Op::Folios(FoliosOp::Restore), &[]),
1064 route("POST", "/workspaces/:workspace/artifacts/:artifact_id/purge", Op::Folios(FoliosOp::Purge), &[]),
1065 route("GET", "/workspaces/:workspace/artifacts/:artifact_id/content", Op::Folios(FoliosOp::GetContent), &[]),
1066 route("PUT", "/workspaces/:workspace/artifacts/:artifact_id/content", Op::Folios(FoliosOp::Edit), &[]),
1067 route("GET", "/workspaces/:workspace/artifacts/:artifact_id/access", Op::Folios(FoliosOp::GetAccess), &[]),
1068 route("PUT", "/workspaces/:workspace/artifacts/:artifact_id/access", Op::Folios(FoliosOp::SetAccess), &[]),
1069 route("GET", "/workspaces/:workspace/artifacts/:artifact_id/versions", Op::Folios(FoliosOp::ListVersions), &[]),
1070 route("POST", "/workspaces/:workspace/artifacts/:artifact_id/versions/:version_id/restore", Op::Folios(FoliosOp::RestoreVersion), &[]),
1071 route("GET", "/workspaces/:workspace/artifact-templates", Op::Folios(FoliosOp::ListTemplates), &[("kind", "kind")]),
1072 route("GET", "/workspaces/:workspace/artifact-spaces", Op::Folios(FoliosOp::ListSpaces), &[]),
1073 route("POST", "/workspaces/:workspace/datasets/query", Op::Folios(FoliosOp::QueryDataset), &[]),
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were1074];
1075
1076impl Route {
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1077 /// Whether the route answers success with `204` and no body, as
1078 /// GitHub's address for the same does. MCP still answers `true`.
1079 pub fn no_content(&self) -> bool {
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r91080 matches!(
1081 self.op,
1082 Op::DeleteComment
1083 )
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1084 }
1085
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were1086 /// The names of the route's path parameters, in order.
1087 pub fn params(&self) -> impl Iterator<Item = &'static str> {
1088 self.path
1089 .split('/')
1090 .filter_map(|segment| segment.strip_prefix(':'))
1091 }
1092
1093 /// The values of the path parameters, if `path` is this route's.
1094 fn matches<'a>(&self, path: &'a str) -> Option<Vec<(&'static str, &'a str)>> {
1095 let mut values = Vec::new();
1096 let mut actual = path.trim_end_matches('/').split('/');
1097 for expected in self.path.split('/') {
1098 let segment = actual.next()?;
1099 match expected.strip_prefix(':') {
1100 Some(name) if !segment.is_empty() => values.push((name, segment)),
1101 Some(_) => return None,
1102 None if expected == segment => {}
1103 None => return None,
1104 }
1105 }
1106 actual.next().is_none().then_some(values)
1107 }
1108}
1109
1110/// A path segment with its `%XX` escapes decoded; as given when that is not
1111/// UTF-8.
1112fn percent_decoded(segment: &str) -> String {
1113 let bytes = segment.as_bytes();
1114 let mut out = Vec::with_capacity(bytes.len());
1115 let mut i = 0;
1116 while i < bytes.len() {
1117 let escaped = (bytes[i] == b'%')
1118 .then(|| segment.get(i + 1..i + 3))
1119 .flatten()
1120 .filter(|hex| hex.bytes().all(|byte| byte.is_ascii_hexdigit()))
1121 .and_then(|hex| u8::from_str_radix(hex, 16).ok());
1122 match escaped {
1123 Some(byte) => {
1124 out.push(byte);
1125 i += 3;
1126 }
1127 None => {
1128 out.push(bytes[i]);
1129 i += 1;
1130 }
1131 }
1132 }
1133 String::from_utf8(out).unwrap_or_else(|_| segment.to_owned())
1134}
1135
1136/// The route for a request, and the operation input it describes.
1137///
1138/// The input is the JSON body, overlaid with the query parameters the route
1139/// reads and then with what the path names: `owner` and `name` become
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1140/// `repo`, as does a team's `repo` with its `workspace`, and `number`
1141/// becomes an integer.
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were1142pub fn resolve(
1143 method: &str,
1144 path: &str,
1145 query: &[(String, String)],
1146 body: Value,
1147) -> Option<(&'static Route, Value)> {
1148 let (route, params) = ROUTES
1149 .iter()
1150 .filter(|route| route.method == method)
1151 .find_map(|route| Some((route, route.matches(path)?)))?;
1152
1153 let mut input = match body {
1154 Value::Object(fields) => fields,
1155 _ => Map::new(),
1156 };
1157 for (name, key) in route.query {
1158 if let Some((_, value)) = query.iter().find(|(query_name, _)| query_name == name) {
1159 input.insert((*key).to_owned(), Value::String(value.clone()));
1160 }
1161 }
1162 let param = |wanted: &str| {
1163 params
1164 .iter()
1165 .find(|(name, _)| *name == wanted)
1166 .map(|(_, value)| *value)
1167 };
1168 if let (Some(owner), Some(name)) = (param("owner"), param("name")) {
1169 input.insert("repo".to_owned(), Value::String(format!("{owner}/{name}")));
1170 }
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971171 // Every other name the path gives, under that name; the ones below that
1172 // need more (a repository, a number, an encoded name) are set after.
1173 for (key, value) in &params {
1174 if !matches!(*key, "owner" | "name") {
1175 input.insert((*key).to_owned(), Value::String(percent_decoded(value)));
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were1176 }
1177 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1178 // A repository of a team's workspace, named by itself.
1179 if let (Some(workspace), Some(name)) = (param("workspace"), param("repo")) {
1180 input.insert("repo".to_owned(), Value::String(format!("{workspace}/{name}")));
1181 }
1182 // A branch name may hold slashes, sent URL-encoded as one segment, and
1183 // a label's name spaces.
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were1184 if let Some(branch) = param("branch") {
1185 input.insert("branch".to_owned(), Value::String(percent_decoded(branch)));
1186 }
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971187 // An environment's name may hold slashes and spaces, URL-encoded.
1188 if let Some(environment) = param("environment") {
1189 input.insert("environment".to_owned(), Value::String(percent_decoded(environment)));
1190 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1191 if let Some(label) = param("label") {
1192 input.insert("label".to_owned(), Value::String(percent_decoded(label)));
1193 }
1194 if let Some(milestone) = param("milestone") {
1195 // Not a number: zero, which no milestone has.
1196 input.insert("milestone".to_owned(), milestone.parse::<u32>().unwrap_or(0).into());
1197 }
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were1198 // GitHub says some things with the path alone.
1199 if route.path.ends_with("/enable") || route.path.ends_with("/disable") {
1200 input.insert("enabled".to_owned(), Value::Bool(route.path.ends_with("/enable")));
1201 }
1202 if route.path.ends_with("/rerun-failed-jobs") {
1203 input.insert("failed_only".to_owned(), Value::Bool(true));
1204 }
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)1205 if route.path.ends_with("/force-cancel") {
1206 input.insert("force".to_owned(), Value::Bool(true));
1207 }
API: notifications over REST and MCP, with notifications scopes1208 // Unsaving and waking a thread are a DELETE of what PUT made.
1209 if route.method == "DELETE" && route.path.ends_with("/saved") {
1210 input.insert("saved".to_owned(), Value::Bool(false));
1211 }
1212 if route.method == "DELETE" && route.path.ends_with("/snooze") {
1213 input.remove("until");
1214 }
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were1215 if let Some(number) = param("number") {
1216 // Not a number: zero, which no issue or pull request has.
1217 input.insert(
1218 "number".to_owned(),
1219 number.parse::<u32>().unwrap_or(0).into(),
1220 );
1221 }
1222 Some((route, Value::Object(input)))
1223}
1224
1225#[cfg(test)]
1226mod tests {
1227 use serde_json::json;
1228
1229 use super::*;
1230
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971231 /// Every name a route's path gives reaches the operation: a name left
1232 /// off the lists above is dropped, and the operation answers that it
1233 /// was not given (the project routes were, until this test).
1234 #[test]
1235 fn every_path_parameter_reaches_the_input() {
1236 for route in ROUTES.iter() {
1237 let names: Vec<&str> = route.path.split('/').filter_map(|part| part.strip_prefix(':')).collect();
1238 if names.is_empty() {
1239 continue;
1240 }
1241 let path: String = route
1242 .path
1243 .split('/')
1244 .map(|part| match part.strip_prefix(':') {
1245 Some("number" | "milestone") => "7".to_owned(),
1246 Some(name) => format!("{name}-x"),
1247 None => part.to_owned(),
1248 })
1249 .collect::<Vec<_>>()
1250 .join("/");
1251 let (found, input) = resolve(route.method, &path, &[], json!({})).unwrap();
1252 // A path two routes could take is checked under the first.
1253 if found.path != route.path {
1254 continue;
1255 }
1256 for name in names {
1257 let key = match name {
1258 "owner" | "name" => "repo",
1259 "repo" if names_has_workspace(route.path) => "repo",
1260 other => other,
1261 };
1262 assert!(
1263 input.get(key).is_some_and(|value| !value.is_null()),
1264 "{} {}: :{name} does not reach the input",
1265 route.method,
1266 route.path
1267 );
1268 }
1269 }
1270 }
1271
1272 fn names_has_workspace(path: &str) -> bool {
1273 path.split('/').any(|part| part == ":workspace")
1274 }
1275
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were1276 #[test]
1277 fn a_path_resolves_to_its_operation_and_input() {
1278 let (route, input) = resolve(
1279 "POST",
1280 "/repos/flagon-io/hello/pulls/14/merge",
1281 &[],
1282 json!({ "keep_issue_open": true, "number": 99, "repo": "someone/else" }),
1283 )
1284 .unwrap();
1285 assert_eq!(route.op, Op::MergePullRequest);
1286 // What the path names wins over the body.
1287 assert_eq!(
1288 input,
1289 json!({ "keep_issue_open": true, "number": 14, "repo": "flagon-io/hello" })
1290 );
1291 }
1292
1293 #[test]
1294 fn a_branch_with_slashes_is_one_encoded_segment() {
1295 let (route, input) = resolve(
1296 "POST",
1297 "/repos/flagon-io/hello/branches/feature%2Flogin/rename",
1298 &[],
1299 json!({ "new_name": "feature/sign-in" }),
1300 )
1301 .unwrap();
1302 assert_eq!(route.op, Op::RenameBranch);
1303 assert_eq!(
1304 input,
1305 json!({ "new_name": "feature/sign-in", "branch": "feature/login", "repo": "flagon-io/hello" })
1306 );
1307 assert_eq!(percent_decoded("100%"), "100%");
1308 assert_eq!(percent_decoded("a%2bb%zz"), "a+b%zz");
1309 }
1310
1311 #[test]
1312 fn a_collaborator_is_named_by_username() {
1313 let (route, input) = resolve(
1314 "PATCH",
1315 "/repos/flagon-io/hello/collaborators/ada",
1316 &[],
1317 json!({ "role": "maintain" }),
1318 )
1319 .unwrap();
1320 assert_eq!(route.op, Op::UpdateCollaborator);
1321 assert_eq!(input, json!({ "role": "maintain", "username": "ada", "repo": "flagon-io/hello" }));
1322 let (route, input) = resolve("DELETE", "/user/repository_invitations/rin_1", &[], Value::Null).unwrap();
1323 assert_eq!(route.op, Op::DeclineRepoInvitation);
1324 assert_eq!(input, json!({ "id": "rin_1" }));
1325 }
1326
1327 #[test]
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1328 fn teams_are_addressed_by_workspace_and_slug() {
1329 let query = [("q".to_owned(), "back".to_owned())];
1330 let (route, input) = resolve("GET", "/workspaces/acme/teams", &query, Value::Null).unwrap();
1331 assert_eq!(route.op, Op::ListTeams);
1332 assert_eq!(input, json!({ "query": "back", "workspace": "acme" }));
1333 let (route, input) = resolve("PATCH", "/workspaces/acme/teams/backend", &[], json!({ "name": "Back end" })).unwrap();
1334 assert_eq!(route.op, Op::UpdateTeam);
1335 assert_eq!(input, json!({ "name": "Back end", "workspace": "acme", "team": "backend" }));
1336 let (route, input) =
1337 resolve("PUT", "/workspaces/acme/teams/backend/members/ana", &[], json!({ "role": "maintainer" })).unwrap();
1338 assert_eq!(route.op, Op::SetTeamMember);
1339 assert_eq!(input, json!({ "role": "maintainer", "workspace": "acme", "team": "backend", "username": "ana" }));
1340 let query = [("include_child_teams".to_owned(), "true".to_owned())];
1341 let (route, input) = resolve("GET", "/workspaces/acme/teams/backend/members", &query, Value::Null).unwrap();
1342 assert_eq!(route.op, Op::ListTeamMembers);
1343 assert_eq!(input, json!({ "include_child_teams": "true", "workspace": "acme", "team": "backend" }));
1344 // A repository is named by itself, in the team's workspace.
1345 let (route, input) =
1346 resolve("PUT", "/workspaces/acme/teams/backend/repos/rocket", &[], json!({ "role": "write" })).unwrap();
1347 assert_eq!(route.op, Op::SetTeamRepo);
1348 assert_eq!(input, json!({ "role": "write", "workspace": "acme", "team": "backend", "repo": "acme/rocket" }));
1349 let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op;
1350 assert_eq!(op("DELETE", "/workspaces/acme/teams/backend/repos/rocket"), Op::RemoveTeamRepo);
1351 assert_eq!(op("GET", "/workspaces/acme/teams/backend/teams"), Op::ListChildTeams);
1352 assert_eq!(op("GET", "/workspaces/acme/teams/backend/repos"), Op::ListTeamRepos);
1353 assert_eq!(op("PUT", "/workspaces/acme/teams/backend/review_assignment"), Op::SetTeamReviewAssignment);
1354 assert_eq!(op("DELETE", "/workspaces/acme/teams/backend"), Op::DeleteTeam);
1355 assert_eq!(op("POST", "/workspaces/acme/teams"), Op::CreateTeam);
1356 assert_eq!(op("GET", "/workspaces/acme/teams/backend"), Op::GetTeam);
1357 assert_eq!(op("DELETE", "/workspaces/acme/teams/backend/members/ana"), Op::RemoveTeamMember);
1358 let (route, input) = resolve("GET", "/workspaces/acme/members/ana/teams", &[], Value::Null).unwrap();
1359 assert_eq!(route.op, Op::ListUserTeams);
1360 assert_eq!(input, json!({ "workspace": "acme", "username": "ana" }));
1361 }
1362
1363 #[test]
1364 fn reviewers_are_requested_and_code_owners_checked_on_a_repository() {
1365 let body = json!({ "reviewers": ["ana"], "team_reviewers": ["backend"] });
1366 let (route, input) = resolve("POST", "/repos/acme/rocket/pulls/7/requested_reviewers", &[], body.clone()).unwrap();
1367 assert_eq!(route.op, Op::RequestReviewers);
1368 assert_eq!(
1369 input,
1370 json!({ "reviewers": ["ana"], "team_reviewers": ["backend"], "repo": "acme/rocket", "number": 7 })
1371 );
1372 let (route, _) = resolve("DELETE", "/repos/acme/rocket/pulls/7/requested_reviewers", &[], body).unwrap();
1373 assert_eq!(route.op, Op::RemoveRequestedReviewers);
1374 let query = [("ref".to_owned(), "main".to_owned())];
1375 let (route, input) = resolve("GET", "/repos/acme/rocket/codeowners/errors", &query, Value::Null).unwrap();
1376 assert_eq!(route.op, Op::GetCodeownersErrors);
1377 assert_eq!(input, json!({ "ref": "main", "repo": "acme/rocket" }));
1378 }
1379
1380 #[test]
Merge branch 'worktree-agent-ad531323a21f4c975'1381 fn events_are_followed_with_a_cursor_filters_and_a_wait() {
1382 let query = [
1383 ("after".to_owned(), "evt_1".to_owned()),
1384 ("types".to_owned(), "work.claimed,integration.failed".to_owned()),
1385 ("numbers".to_owned(), "12,14".to_owned()),
1386 ("wait".to_owned(), "25".to_owned()),
1387 ("limit".to_owned(), "20".to_owned()),
1388 ("since".to_owned(), "2026-10-12T00:00:00Z".to_owned()),
1389 ];
1390 let (route, input) = resolve("GET", "/repos/acme/rocket/events", &query, Value::Null).unwrap();
1391 assert_eq!(route.op, Op::ListEvents);
1392 let asked = crate::follow::asked(&input).unwrap();
1393 assert_eq!(asked.after.as_deref(), Some("evt_1"));
1394 assert_eq!(asked.types, vec!["integration.failed", "work.claimed"]);
1395 assert_eq!(asked.numbers, vec![12, 14]);
1396 assert_eq!(asked.wait_ms, 25_000);
1397 assert_eq!(asked.limit, Some(20));
1398 assert_eq!(asked.since.as_deref(), Some("2026-10-12T00:00:00Z"));
1399 }
1400
1401 #[test]
API: notifications over REST and MCP, with notifications scopes1402 fn notifications_are_addressed_as_threads_and_by_issue() {
1403 let (route, input) = resolve("DELETE", "/notifications/threads/ntf_1", &[], Value::Null).unwrap();
1404 assert_eq!(route.op, Op::MarkThreadDone);
1405 assert_eq!(input, json!({ "id": "ntf_1" }));
1406 let (route, input) = resolve("DELETE", "/notifications/threads/ntf_1/saved", &[], Value::Null).unwrap();
1407 assert_eq!(route.op, Op::SaveThread);
1408 assert_eq!(input, json!({ "id": "ntf_1", "saved": false }));
1409 let (route, input) = resolve("DELETE", "/notifications/threads/ntf_1/snooze", &[], json!({ "until": "x" })).unwrap();
1410 assert_eq!(route.op, Op::SnoozeThread);
1411 assert_eq!(input, json!({ "id": "ntf_1" }));
1412 let query = [("all".to_owned(), "true".to_owned()), ("per_page".to_owned(), "50".to_owned())];
1413 let (route, input) = resolve("GET", "/repos/acme/rocket/notifications", &query, Value::Null).unwrap();
1414 assert_eq!(route.op, Op::ListNotifications);
1415 assert_eq!(input, json!({ "all": "true", "per_page": "50", "repo": "acme/rocket" }));
1416 let (route, input) = resolve("PUT", "/repos/acme/rocket/issues/7/subscription", &[], json!({ "ignored": true })).unwrap();
1417 assert_eq!(route.op, Op::SetThreadSubscription);
1418 assert_eq!(input, json!({ "ignored": true, "number": 7, "repo": "acme/rocket" }));
1419 assert_eq!(resolve("GET", "/user/subscriptions", &[], Value::Null).unwrap().0.op, Op::ListWatchedRepos);
1420 }
1421
1422 #[test]
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1423 fn labels_and_milestones_are_named_in_the_path() {
1424 let (route, input) = resolve("PATCH", "/repos/acme/web/labels/good%20first%20issue", &[], json!({ "color": "7057ff" })).unwrap();
1425 assert_eq!(route.op, Op::UpdateLabel);
1426 assert_eq!(input, json!({ "color": "7057ff", "label": "good first issue", "repo": "acme/web" }));
1427 let (route, input) = resolve("DELETE", "/repos/acme/web/issues/7/labels/bug", &[], Value::Null).unwrap();
1428 assert_eq!(route.op, Op::RemoveIssueLabels);
1429 assert_eq!(input, json!({ "label": "bug", "number": 7, "repo": "acme/web" }));
1430 let (route, input) = resolve("DELETE", "/repos/acme/web/issues/7/labels", &[], Value::Null).unwrap();
1431 assert_eq!(route.op, Op::RemoveIssueLabels);
1432 assert_eq!(input, json!({ "number": 7, "repo": "acme/web" }));
1433 let (route, _) = resolve("POST", "/repos/acme/web/labels/defaults", &[], Value::Null).unwrap();
1434 assert_eq!(route.op, Op::AddDefaultLabels);
1435 let (route, input) = resolve("PATCH", "/repos/acme/web/milestones/3", &[], json!({ "state": "closed" })).unwrap();
1436 assert_eq!(route.op, Op::UpdateMilestone);
1437 assert_eq!(input, json!({ "state": "closed", "milestone": 3, "repo": "acme/web" }));
1438 let (route, input) = resolve("PATCH", "/repos/acme/web/pulls/9", &[], json!({ "base": "release" })).unwrap();
1439 assert_eq!(route.op, Op::UpdatePullRequest);
1440 assert_eq!(input, json!({ "base": "release", "number": 9, "repo": "acme/web" }));
1441 }
1442
1443 #[test]
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1444 fn pull_requests_reopen_and_turn_draft_and_comments_are_named_by_id() {
1445 let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op;
1446 assert_eq!(op("POST", "/repos/acme/web/pulls/9/reopen"), Op::ReopenPullRequest);
1447 assert_eq!(op("POST", "/repos/acme/web/pulls/9/draft"), Op::ConvertPullRequestToDraft);
1448 assert_eq!(op("POST", "/repos/acme/web/pulls/9/close"), Op::ClosePullRequest);
1449 // Reopening and closing with a PATCH, as `state`.
1450 let (route, input) = resolve("PATCH", "/repos/acme/web/pulls/9", &[], json!({ "state": "open" })).unwrap();
1451 assert_eq!(route.op, Op::UpdatePullRequest);
1452 assert_eq!(input, json!({ "state": "open", "number": 9, "repo": "acme/web" }));
1453 let (route, input) =
1454 resolve("PATCH", "/repos/acme/web/issues/comments/cmt_1", &[], json!({ "body": "Better" })).unwrap();
1455 assert_eq!(route.op, Op::EditComment);
1456 assert_eq!(input, json!({ "body": "Better", "comment_id": "cmt_1", "repo": "acme/web" }));
1457 let (route, input) = resolve("DELETE", "/repos/acme/web/issues/comments/cmt_1", &[], Value::Null).unwrap();
1458 assert_eq!(route.op, Op::DeleteComment);
1459 assert_eq!(input, json!({ "comment_id": "cmt_1", "repo": "acme/web" }));
1460 // Still an issue's comments, labels and subscription.
1461 assert_eq!(op("POST", "/repos/acme/web/issues/7/comments"), Op::AddComment);
1462 assert_eq!(op("DELETE", "/repos/acme/web/issues/7/labels"), Op::RemoveIssueLabels);
1463 assert_eq!(op("DELETE", "/repos/acme/web/issues/7/subscription"), Op::DeleteThreadSubscription);
1464 }
1465
1466 #[test]
Projects have Observability: their requests, logs, traces, metrics and errors, from apps on g1t.page with nothing to set up, and from software anywhere else over OpenTelemetry.1467 fn observability_is_addressed_by_workspace_and_project() {
1468 let query = [("level".to_owned(), "error,warn".to_owned()), ("q".to_owned(), "timeout".to_owned())];
1469 let (route, input) = resolve("GET", "/workspaces/acme/projects/web/observability/logs", &query, Value::Null).unwrap();
1470 assert_eq!(route.op, Op::Telemetry(TelemetryOp::ListLogs));
1471 assert_eq!(input, json!({ "level": "error,warn", "q": "timeout", "workspace": "acme", "project": "web" }));
1472 let (route, input) =
1473 resolve("GET", "/workspaces/acme/projects/web/observability/metrics/http.server.request.duration", &[], Value::Null).unwrap();
1474 assert_eq!(route.op, Op::Telemetry(TelemetryOp::GetMetric));
1475 assert_eq!(input["metric"], json!("http.server.request.duration"));
1476 let (route, input) =
1477 resolve("PATCH", "/workspaces/acme/projects/web/observability/errors/f00d", &[], json!({ "state": "resolved" })).unwrap();
1478 assert_eq!(route.op, Op::Telemetry(TelemetryOp::UpdateError));
1479 assert_eq!(input, json!({ "state": "resolved", "workspace": "acme", "project": "web", "fingerprint": "f00d" }));
1480 }
1481
1482 #[test]
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1483 fn billing_is_addressed_by_workspace() {
1484 let query = [("from".to_owned(), "2026-10-01".to_owned()), ("products".to_owned(), "agent,sandboxes".to_owned())];
1485 let (route, input) = resolve("GET", "/workspaces/acme/usage", &query, Value::Null).unwrap();
1486 assert_eq!(route.op, Op::GetUsage);
1487 assert_eq!(input, json!({ "from": "2026-10-01", "products": "agent,sandboxes", "workspace": "acme" }));
1488 let (route, input) = resolve("PUT", "/workspaces/acme/budget", &[], json!({ "alerts": [50] })).unwrap();
1489 assert_eq!(route.op, Op::SetBudget);
1490 assert_eq!(input, json!({ "alerts": [50], "workspace": "acme" }));
1491 let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op;
1492 assert_eq!(op("GET", "/workspaces/acme/budget"), Op::GetBudget);
1493 assert_eq!(op("GET", "/workspaces/acme/invoices"), Op::ListInvoices);
1494 assert_eq!(op("GET", "/workspaces/acme/billing_details"), Op::GetBillingDetails);
1495 }
1496
1497 #[test]
Merge checks: statuses and check runs on every commit1498 fn checks_are_at_githubs_addresses() {
1499 let sha = "a".repeat(40);
1500 let body = json!({ "state": "success", "context": "ci/build" });
1501 let (route, input) = resolve("POST", &format!("/repos/acme/web/statuses/{sha}"), &[], body).unwrap();
1502 assert_eq!(route.op, Op::Checks(ChecksOp::CreateCommitStatus));
1503 assert_eq!(input, json!({ "state": "success", "context": "ci/build", "sha": sha, "repo": "acme/web" }));
1504 let (route, input) = resolve("GET", "/repos/acme/web/commits/release%2F1.x/status", &[], Value::Null).unwrap();
1505 assert_eq!(route.op, Op::Checks(ChecksOp::GetCombinedStatus));
1506 assert_eq!(input, json!({ "ref": "release/1.x", "repo": "acme/web" }));
1507 let query = [("check_name".to_owned(), "lint".to_owned())];
1508 let (route, input) = resolve("GET", "/repos/acme/web/commits/main/check-runs", &query, Value::Null).unwrap();
1509 assert_eq!(route.op, Op::Checks(ChecksOp::ListCheckRunsForRef));
1510 assert_eq!(input, json!({ "check_name": "lint", "ref": "main", "repo": "acme/web" }));
1511 let (route, input) = resolve("PATCH", "/repos/acme/web/check-runs/cr_1", &[], json!({ "conclusion": "success" })).unwrap();
1512 assert_eq!(route.op, Op::Checks(ChecksOp::UpdateCheckRun));
1513 assert_eq!(input, json!({ "conclusion": "success", "id": "cr_1", "repo": "acme/web" }));
1514 let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op;
1515 assert_eq!(op("POST", "/repos/acme/web/check-runs"), Op::Checks(ChecksOp::CreateCheckRun));
1516 assert_eq!(op("GET", "/repos/acme/web/check-runs/cr_1/annotations"), Op::Checks(ChecksOp::ListCheckRunAnnotations));
1517 assert_eq!(op("POST", "/repos/acme/web/check-suites/cs_1/rerequest"), Op::Checks(ChecksOp::RerequestCheckSuite));
1518 assert_eq!(op("GET", "/repos/acme/web/commits/main/check-suites"), Op::Checks(ChecksOp::ListCheckSuitesForRef));
1519 }
1520
1521 #[test]
Merge GitHub pull request #8 (claude/cool-newton-7nm8r9): Docker deployments on g1t.page behind a switch that stays off until Cloudflare's container APIs are checked live, workflows a workspace manages in its repositories, tarball and zipball downloads, refs/pull/* kept to g1t alone, framework detection for g1t.page builds, and a workflow's event payload matching GitHub's.1522 fn archives_are_at_githubs_addresses_and_redirect() {
1523 let sha = "4807077b296e6edbf410d55e72749d3e1170c291";
1524 let (route, input) = resolve("GET", &format!("/repos/acme/web/tarball/{sha}"), &[], Value::Null).unwrap();
1525 assert_eq!(route.op, Op::Archives(ArchiveOp::DownloadTarball));
1526 assert_eq!(input, json!({ "ref": sha, "repo": "acme/web" }));
1527 // A branch with slashes, URL-encoded as octokit sends it.
1528 let (route, input) = resolve("GET", "/repos/acme/web/zipball/release%2F1.x", &[], Value::Null).unwrap();
1529 assert_eq!(route.op, Op::Archives(ArchiveOp::DownloadZipball));
1530 assert_eq!(input, json!({ "ref": "release/1.x", "repo": "acme/web" }));
1531 // Both answer with a redirect to their link, as the artifact download does.
1532 assert!(route.op.redirects());
1533 assert!(Op::Archives(ArchiveOp::DownloadTarball).redirects());
1534 assert!(Op::Artifacts(ArtifactsOp::DownloadArtifact).redirects());
1535 assert!(!Op::GetRepo.redirects());
1536 // Anyone may ask for a public repository's.
1537 assert!(!route.op.needs_user());
1538 }
1539
1540 #[test]
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were1541 fn query_parameters_are_renamed() {
1542 let query = [
1543 ("q".to_owned(), "parser".to_owned()),
1544 ("x".to_owned(), "y".to_owned()),
1545 ];
1546 let (route, input) = resolve("GET", "/repos", &query, Value::Null).unwrap();
1547 assert_eq!(route.op, Op::ListRepos);
1548 assert_eq!(input, json!({ "query": "parser" }));
1549 }
1550
1551 #[test]
1552 fn method_and_shape_must_match() {
1553 assert!(resolve("GET", "/repos/a/b/issues/1/close", &[], Value::Null).is_none());
1554 assert!(resolve("GET", "/repos/a", &[], Value::Null).is_none());
1555 assert!(resolve("GET", "/repos/a/b/issues/1/extra", &[], Value::Null).is_none());
1556 assert!(resolve("GET", "/repos/a/b/", &[], Value::Null).is_some());
1557 }
1558
1559 #[test]
1560 fn every_parameter_and_query_name_is_an_input() {
1561 for route in ROUTES {
1562 let properties = route.op.properties();
1563 for (_, key) in route.query {
1564 assert!(properties.contains_key(*key), "{}: {key}", route.path);
1565 }
1566 for name in route.params() {
1567 let covered = matches!(name, "owner" | "name") && properties.contains_key("repo")
1568 || properties.contains_key(name);
1569 assert!(covered, "{}: {name}", route.path);
1570 }
1571 }
1572 }
1573
1574 #[test]
1575 fn every_operation_has_a_route() {
1576 for op in Op::ALL {
1577 assert!(ROUTES.iter().any(|route| route.op == op), "{}", op.name());
1578 }
1579 }
1580}

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