diff --git a/apps/loopover-ui/public/openapi.json b/apps/loopover-ui/public/openapi.json index bdb2032d4..72c2eacc4 100644 --- a/apps/loopover-ui/public/openapi.json +++ b/apps/loopover-ui/public/openapi.json @@ -14652,6 +14652,80 @@ "privateSummary" ] }, + "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" + ] + }, "FindingTaxonomyDocument": { "type": "object", "properties": { @@ -17050,6 +17124,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": [] + } + ] + } + }, "/v1/contributors/{login}/profile": { "get": { "summary": "Contributor evidence profile", diff --git a/src/openapi/schemas.ts b/src/openapi/schemas.ts index 79085ab1e..4b5ef6a09 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 67f085e8c..67b747dd9 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 f4afc434c..341be49a0 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();