diff --git a/src/api/routes.ts b/src/api/routes.ts index c9ed0e7c2f..085a029ec5 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -437,6 +437,7 @@ const scorePreviewSchema = z.object({ openPrCount: z.number().int().min(0).optional(), credibility: z.number().min(0).max(1).optional(), changesRequestedCount: z.number().int().min(0).optional(), + duplicateRiskCount: z.number().int().min(0).optional(), fixedBaseScore: z.number().min(0).optional(), metadataOnly: z.boolean().default(false), pendingMergedPrCount: z.number().int().min(0).optional(), diff --git a/src/mcp/server.ts b/src/mcp/server.ts index 7c8f1d47c2..51389d5068 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -253,6 +253,7 @@ const scorePreviewShape = { openPrCount: z.number().int().min(0).optional(), credibility: z.number().min(0).max(1).optional(), changesRequestedCount: z.number().int().min(0).optional(), + duplicateRiskCount: z.number().int().min(0).optional(), metadataOnly: z.boolean().default(true), pendingMergedPrCount: z.number().int().min(0).optional(), pendingClosedPrCount: z.number().int().min(0).optional(), diff --git a/test/integration/api.test.ts b/test/integration/api.test.ts index 782f62021c..664630ae7d 100644 --- a/test/integration/api.test.ts +++ b/test/integration/api.test.ts @@ -1345,6 +1345,7 @@ describe("api routes", () => { totalTokenScore: 60, sourceLines: 40, openPrCount: 1, + duplicateRiskCount: 2, }), }, env, @@ -1353,7 +1354,12 @@ describe("api routes", () => { await expect(scorePreview.json()).resolves.toMatchObject({ repoFullName: "entrius/allways-ui", targetType: "planned_pr", - result: { privateOnly: true, scoringModelSnapshotId: "scoring-1" }, + input: { duplicateRiskCount: 2 }, + result: { + privateOnly: true, + scoringModelSnapshotId: "scoring-1", + blockedBy: expect.arrayContaining([expect.objectContaining({ code: "duplicate_risk", severity: "reducer" })]), + }, }); const noContributorScorePreview = await app.request( "/v1/scoring/preview", @@ -4334,6 +4340,39 @@ describe("api routes", () => { expect(callPayload.result.structuredContent.repoFullName).toBe("entrius/allways-ui"); expect(callPayload.result.content[0]?.text).not.toMatch(/reward|farming/i); + const scorePreviewCall = await app.request( + "/mcp", + { + method: "POST", + headers: mcpHeaders(env), + body: JSON.stringify({ + jsonrpc: "2.0", + id: "score-preview-duplicate-risk", + method: "tools/call", + params: { + name: "gittensory_preview_local_pr_score", + arguments: { + repoFullName: "entrius/allways-ui", + targetKey: "mcp-duplicate-risk", + sourceTokenScore: 40, + totalTokenScore: 60, + sourceLines: 42, + duplicateRiskCount: 2, + }, + }, + }), + }, + env, + ); + expect(scorePreviewCall.status).toBe(200); + const scorePreviewPayload = (await mcpJson(scorePreviewCall)) as { + result: { structuredContent: { input: { duplicateRiskCount?: number }; result: { blockedBy: Array<{ code: string; severity: string }> } } }; + }; + expect(scorePreviewPayload.result.structuredContent.input.duplicateRiskCount).toBe(2); + expect(scorePreviewPayload.result.structuredContent.result.blockedBy).toEqual( + expect.arrayContaining([expect.objectContaining({ code: "duplicate_risk", severity: "reducer" })]), + ); + const noTotalsContext = await app.request( "/mcp", {