From a9290a81f4615138ddf9c07eb344670807eb37ac Mon Sep 17 00:00:00 2001 From: philluiz2323 Date: Mon, 27 Jul 2026 09:17:20 -0700 Subject: [PATCH] feat(openapi): document GET pulls/:number/ai-review-findings (#9305) ai-review-findings is the only sibling of maintainer-packet and reviewability missing from the OpenAPI contract. Add PullRequestAiReviewFindingsSchema from prAiReviewFindingsOutputSchema, register the path, assert it in openapi.test, and regenerate apps/loopover-ui/public/openapi.json. Closes #9305 --- apps/loopover-ui/public/openapi.json | 138 +++++++++++++++++++++++++++ src/openapi/schemas.ts | 23 +++++ src/openapi/spec.ts | 20 ++++ test/unit/openapi.test.ts | 8 ++ 4 files changed, 189 insertions(+) diff --git a/apps/loopover-ui/public/openapi.json b/apps/loopover-ui/public/openapi.json index bf235246c0..1337df890e 100644 --- a/apps/loopover-ui/public/openapi.json +++ b/apps/loopover-ui/public/openapi.json @@ -14856,6 +14856,75 @@ "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" + ] } }, "parameters": {}, @@ -19336,6 +19405,75 @@ } ] } + }, + "/v1/repos/{owner}/{repo}/pulls/{number}/ai-review-findings": { + "get": { + "summary": "Contributor-owned AI review findings for a pull request", + "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 + }, + "required": true, + "name": "login", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Structured AI-review findings for the authenticated contributor's own PR (mirrors loopover_get_pr_ai_review_findings)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PullRequestAiReviewFindings" + } + } + } + }, + "400": { + "description": "Invalid pull number or missing login query" + }, + "403": { + "description": "Caller is not the PR author" + }, + "404": { + "description": "Pull request not found" + } + }, + "security": [ + { + "LoopOverBearer": [] + }, + { + "LoopOverSessionCookie": [] + } + ] + } } }, "servers": [ diff --git a/src/openapi/schemas.ts b/src/openapi/schemas.ts index 79085ab1e2..564e16a666 100644 --- a/src/openapi/schemas.ts +++ b/src/openapi/schemas.ts @@ -2822,6 +2822,29 @@ export const PullRequestReviewabilitySchema = z }) .openapi("PullRequestReviewability"); +// Field-level parity with `prAiReviewFindingsOutputSchema` in src/mcp/server.ts (#9305). +export const PullRequestAiReviewFindingsSchema = z + .object({ + status: z.enum(["ready", "not_found", "ai_review_off"]), + repoFullName: z.string().optional(), + pullNumber: z.number().optional(), + login: z.string().optional(), + 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(), + }), + ) + .optional(), + categoryCounts: z.record(z.string(), z.number()).optional(), + }) + .openapi("PullRequestAiReviewFindings"); + export const RegistryChangeReportSchema = z .object({ generatedAt: z.string(), diff --git a/src/openapi/spec.ts b/src/openapi/spec.ts index b5acd25bcb..7a48287bc6 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,24 @@ 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: "Contributor-owned AI review findings for a pull request", + request: { + params: z.object({ owner: z.string(), repo: z.string(), number: z.string() }), + query: z.object({ login: z.string().min(1) }), + }, + responses: { + 200: { + description: "Structured AI-review findings for the authenticated contributor's own PR (mirrors loopover_get_pr_ai_review_findings)", + content: { "application/json": { schema: PullRequestAiReviewFindingsSchema } }, + }, + 400: { description: "Invalid pull number or missing login query" }, + 403: { description: "Caller is not the PR author" }, + 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..3de3745280 100644 --- a/test/unit/openapi.test.ts +++ b/test/unit/openapi.test.ts @@ -21,6 +21,8 @@ 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/repos/{owner}/{repo}/pulls/{number}/ai-review-findings"]?.get).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 +102,12 @@ 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(); + // #9305: response schema keys must match prAiReviewFindingsOutputSchema in src/mcp/server.ts + expect(JSON.stringify(spec.components?.schemas?.PullRequestAiReviewFindings)).toContain("status"); + expect(JSON.stringify(spec.components?.schemas?.PullRequestAiReviewFindings)).toContain("findings"); + expect(JSON.stringify(spec.components?.schemas?.PullRequestAiReviewFindings)).toContain("categoryCounts"); + expect(JSON.stringify(spec.components?.schemas?.PullRequestAiReviewFindings)).toContain("ai_review_off"); expect(spec.components?.schemas?.LocalBranchAnalysis).toBeDefined(); expect(spec.components?.schemas?.RepoSettingsPreview).toBeDefined(); expect(spec.components?.schemas?.InstallationRepair).toBeDefined();