diff --git a/src/api/routes.ts b/src/api/routes.ts index 92aa9dfea3..a9a8e1af3d 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -2142,6 +2142,7 @@ export function createApp() { pullRequests, bounties, issueQuality: issueQuality?.report, + confirmedContributor: Boolean(context.gittensorSnapshot), }); const response = { ...analysis, predictedGate, dataQuality: await loadRepoDataQuality(c.env, parsed.data.repoFullName) }; await persistSignal(c.env, "local-branch-analysis", `${parsed.data.login}:${parsed.data.repoFullName}:${parsed.data.branchName ?? parsed.data.headRef ?? "local"}`, parsed.data.repoFullName, response as unknown as Record, analysis.generatedAt); diff --git a/src/rules/predicted-gate.ts b/src/rules/predicted-gate.ts index 51e307ed30..f9799a6604 100644 --- a/src/rules/predicted-gate.ts +++ b/src/rules/predicted-gate.ts @@ -91,19 +91,6 @@ export function buildPredictedGateVerdict(args: { const { input, manifest, repo, issues, pullRequests } = args; const gate = manifest.gate; - // A synthetic open PR from the local branch metadata — fed to the SAME advisory builder as a real PR. - const syntheticPr: PullRequestRecord = { - repoFullName: input.repoFullName, - number: 0, - title: input.title, - state: "open", - authorLogin: input.contributorLogin, - authorAssociation: input.authorAssociation ?? null, - body: input.body ?? null, - labels: input.labels ?? [], - linkedIssues: input.linkedIssues ?? [], - }; - const preflight = buildPreflightResult( { repoFullName: input.repoFullName, @@ -120,6 +107,21 @@ export function buildPredictedGateVerdict(args: { args.bounties ?? [], args.issueQuality, ); + + // A synthetic open PR from the local branch metadata — fed to the SAME advisory builder as a real PR. + // Use preflight's normalized linked issues so body references like "Closes #7" match real PR parity. + const syntheticPr: PullRequestRecord = { + repoFullName: input.repoFullName, + number: 0, + title: input.title, + state: "open", + authorLogin: input.contributorLogin, + authorAssociation: input.authorAssociation ?? null, + body: input.body ?? null, + labels: input.labels ?? [], + linkedIssues: preflight.linkedIssues, + }; + const collisions = buildCollisionReport(input.repoFullName, issues, pullRequests); const queueHealth = buildQueueHealth(repo, issues, pullRequests, collisions); const readiness = buildPublicReadinessScore({ diff --git a/test/unit/predicted-gate.test.ts b/test/unit/predicted-gate.test.ts index 85c25d7631..bb8131ea75 100644 --- a/test/unit/predicted-gate.test.ts +++ b/test/unit/predicted-gate.test.ts @@ -66,6 +66,12 @@ describe("buildPredictedGateVerdict", () => { expect(advisory.blockers.some((b) => b.code === "missing_linked_issue")).toBe(false); }); + it("uses linked issues inferred from the body for gate advisory parity", () => { + const result = verdict({ gate: { linkedIssue: "block" }, input: { body: "Closes #7", linkedIssues: [] } }); + expect(result.conclusion).toBe("success"); + expect(result.blockers.some((b) => b.code === "missing_linked_issue")).toBe(false); + }); + it("forces a neutral prediction for a self-declared non-confirmed contributor", () => { const result = buildPredictedGateVerdict({ input: { ...BASE_INPUT, body: "no issue", linkedIssues: [] },