From 308dc8fac6a20c35efab370fb44bc0f196d764c3 Mon Sep 17 00:00:00 2001 From: nghequyettien Date: Mon, 27 Jul 2026 23:39:33 +0700 Subject: [PATCH] fix(api): document ai-review-findings pull request route in openapi spec Closes #9305 The GET /v1/repos/{owner}/{repo}/pulls/{number}/ai-review-findings route existed and was live, but was never registered in the OpenAPI spec, unlike its maintainer-packet and reviewability siblings. Adds PullRequestAiReviewFindingsSchema, registers the path (including its login query param), and regenerates apps/loopover-ui/public/openapi.json. --- apps/loopover-ui/public/openapi.json | 145 +++++++++++++++++++++++++++ src/openapi/schemas.ts | 20 ++++ src/openapi/spec.ts | 22 ++++ test/unit/openapi.test.ts | 3 + 4 files changed, 190 insertions(+) diff --git a/apps/loopover-ui/public/openapi.json b/apps/loopover-ui/public/openapi.json index eb25b146c2..d1d0898451 100644 --- a/apps/loopover-ui/public/openapi.json +++ b/apps/loopover-ui/public/openapi.json @@ -14856,6 +14856,80 @@ "recommendation", "summary" ] + }, + "PullRequestAiReviewFindings": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "ready", + "not_found", + "ai_review_off" + ] + }, + "repoFullName": { + "type": "string" + }, + "pullNumber": { + "type": "number" + }, + "login": { + "type": "string" + }, + "headSha": { + "type": "string", + "nullable": true + }, + "findings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string" + }, + "path": { + "type": "string" + }, + "severity": { + "type": "string", + "enum": [ + "blocker", + "nit" + ] + }, + "line": { + "type": "number" + }, + "body": { + "type": "string" + } + }, + "required": [ + "category", + "path", + "severity", + "line", + "body" + ] + } + }, + "categoryCounts": { + "type": "object", + "additionalProperties": { + "type": "number" + } + } + }, + "required": [ + "status", + "repoFullName", + "pullNumber", + "login", + "findings", + "categoryCounts" + ] } }, "parameters": {}, @@ -19370,6 +19444,77 @@ } ] } + }, + "/v1/repos/{owner}/{repo}/pulls/{number}/ai-review-findings": { + "get": { + "summary": "A PR author's own structured, published AI-review findings", + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": true, + "name": "owner", + "in": "path" + }, + { + "schema": { + "type": "string" + }, + "required": true, + "name": "repo", + "in": "path" + }, + { + "schema": { + "type": "string" + }, + "required": true, + "name": "number", + "in": "path" + }, + { + "schema": { + "type": "string", + "minLength": 1, + "example": "jsonbored" + }, + "required": true, + "description": "GitHub login of the pull request's author -- the caller must be this same login.", + "name": "login", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Structured, published AI-review findings for the caller's own pull request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PullRequestAiReviewFindings" + } + } + } + }, + "400": { + "description": "Missing login" + }, + "403": { + "description": "The pull request belongs to a different contributor" + }, + "404": { + "description": "Pull request not found" + } + }, + "security": [ + { + "LoopOverBearer": [] + }, + { + "LoopOverSessionCookie": [] + } + ] + } } }, "servers": [ diff --git a/src/openapi/schemas.ts b/src/openapi/schemas.ts index 79085ab1e2..4b5ef6a09c 100644 --- a/src/openapi/schemas.ts +++ b/src/openapi/schemas.ts @@ -2822,6 +2822,26 @@ export const PullRequestReviewabilitySchema = z }) .openapi("PullRequestReviewability"); +export const PullRequestAiReviewFindingsSchema = z + .object({ + status: z.enum(["ready", "not_found", "ai_review_off"]), + repoFullName: z.string(), + pullNumber: z.number(), + login: z.string(), + headSha: z.string().nullable().optional(), + findings: z.array( + z.object({ + category: z.string(), + path: z.string(), + severity: z.enum(["blocker", "nit"]), + line: z.number(), + body: z.string(), + }), + ), + categoryCounts: z.record(z.string(), z.number()), + }) + .openapi("PullRequestAiReviewFindings"); + export const RegistryChangeReportSchema = z .object({ generatedAt: z.string(), diff --git a/src/openapi/spec.ts b/src/openapi/spec.ts index b8bfea05be..2e69536efe 100644 --- a/src/openapi/spec.ts +++ b/src/openapi/spec.ts @@ -48,6 +48,7 @@ import { MaintainerNoiseReportSchema, AmsMinerCohortComparisonSchema, McpCompatibilitySchema, + PullRequestAiReviewFindingsSchema, PullRequestMaintainerPacketSchema, PullRequestReviewIntelligenceSchema, PullRequestReviewabilitySchema, @@ -173,6 +174,7 @@ export function buildOpenApiSpec() { registry.register("MaintainerNoiseReport", MaintainerNoiseReportSchema); registry.register("AmsMinerCohortComparison", AmsMinerCohortComparisonSchema); registry.register("PullRequestReviewability", PullRequestReviewabilitySchema); + registry.register("PullRequestAiReviewFindings", PullRequestAiReviewFindingsSchema); registry.registerPath({ method: "get", @@ -758,6 +760,26 @@ export function buildOpenApiSpec() { 200: { description: "Private PR reviewability score and maintainer action", content: { "application/json": { schema: PullRequestReviewabilitySchema } } }, }, }); + registry.registerPath({ + method: "get", + path: "/v1/repos/{owner}/{repo}/pulls/{number}/ai-review-findings", + summary: "A PR author's own structured, published AI-review findings", + request: { + params: z.object({ owner: z.string(), repo: z.string(), number: z.string() }), + query: z.object({ + login: z.string().min(1).openapi({ + param: { description: "GitHub login of the pull request's author -- the caller must be this same login." }, + example: "jsonbored", + }), + }), + }, + responses: { + 200: { description: "Structured, published AI-review findings for the caller's own pull request", content: { "application/json": { schema: PullRequestAiReviewFindingsSchema } } }, + 400: { description: "Missing login" }, + 403: { description: "The pull request belongs to a different contributor" }, + 404: { description: "Pull request not found" }, + }, + }); registry.registerPath({ method: "get", path: "/v1/contributors/{login}/profile", diff --git a/test/unit/openapi.test.ts b/test/unit/openapi.test.ts index f4afc434cf..341be49a0f 100644 --- a/test/unit/openapi.test.ts +++ b/test/unit/openapi.test.ts @@ -21,6 +21,7 @@ describe("OpenAPI contract", () => { expect(spec.paths["/v1/repos/{owner}/{repo}/gittensor-config-recommendation"]).toBeDefined(); expect(spec.paths["/v1/repos/{owner}/{repo}/pulls/{number}/maintainer-packet"]).toBeDefined(); expect(spec.paths["/v1/repos/{owner}/{repo}/pulls/{number}/reviewability"]).toBeDefined(); + expect(spec.paths["/v1/repos/{owner}/{repo}/pulls/{number}/ai-review-findings"]).toBeDefined(); expect(spec.paths["/v1/contributors/{login}/profile"]).toBeDefined(); expect(spec.paths["/v1/contributors/{login}/decision-pack"]).toBeDefined(); expect(spec.paths["/v1/contributors/{login}/open-pr-monitor"]).toBeDefined(); @@ -100,6 +101,8 @@ describe("OpenAPI contract", () => { expect(spec.components?.schemas?.GittensorConfigRecommendation).toBeDefined(); expect(spec.components?.schemas?.PullRequestMaintainerPacket).toBeDefined(); expect(spec.components?.schemas?.PullRequestReviewability).toBeDefined(); + expect(spec.components?.schemas?.PullRequestAiReviewFindings).toBeDefined(); + expect(JSON.stringify(spec.components?.schemas?.PullRequestAiReviewFindings)).toContain("categoryCounts"); expect(spec.components?.schemas?.LocalBranchAnalysis).toBeDefined(); expect(spec.components?.schemas?.RepoSettingsPreview).toBeDefined(); expect(spec.components?.schemas?.InstallationRepair).toBeDefined();