diff --git a/src/services/eligibility-plan.ts b/src/services/eligibility-plan.ts index 3b55939bba..254af93019 100644 --- a/src/services/eligibility-plan.ts +++ b/src/services/eligibility-plan.ts @@ -112,7 +112,13 @@ export function deriveEligibilityPlan(result: ScorePreviewResult): EligibilityPl // Only affirm eligibility when the branch is positively confirmed (eligible or not required); // "unknown" / missing metadata is treated as not-yet-eligible so the plan never overpromises. const branchConfirmed = branchEligibilityStatus === "eligible" || branchEligibilityStatus === "not_required"; - const eligible = result.linkedIssueMultiplier.eligible && branchConfirmed; + // When no linked issue was required, linkedIssueMultiplier.eligible is meaninglessly false, so a confirmed branch + // must not be gated on it (#7809) -- mirror the same special case eligibilityStatusKey already applies for + // "not_required", so the structured `eligible` boolean agrees with the "not required" publicSummary text. + const eligible = + linkedIssueStatus === "not_required" + ? branchConfirmed + : result.linkedIssueMultiplier.eligible && branchConfirmed; const blockers = result.blockedBy .filter((b) => ELIGIBILITY_BLOCKER_CODES.has(b.code)) diff --git a/test/unit/eligibility-plan.test.ts b/test/unit/eligibility-plan.test.ts index 7d3941c8ca..33c60b6b02 100644 --- a/test/unit/eligibility-plan.test.ts +++ b/test/unit/eligibility-plan.test.ts @@ -116,10 +116,18 @@ describe("deriveEligibilityPlan (#2092)", () => { }); } - it("no linked issue + non-required branch → not_required summary, no projection", () => { + it("no linked issue + non-required branch → not_required summary, no projection, eligible:true (#7809)", () => { const plan = planFor({ liStatus: "not_required", brStatus: "not_required" }); expect(plan.publicSummary).toContain("not required for this contribution type"); expect(plan.linkedIssueProjection).toBeNull(); + // The structured boolean must agree with the "not required" text: a confirmed branch with no required linked + // issue is eligible, not gated on the meaningless linkedIssueMultiplier.eligible (which is false here). + expect(plan.eligible).toBe(true); + }); + + it("no linked issue required + an eligible branch is also eligible:true (#7809)", () => { + const plan = planFor({ liStatus: "not_required", brStatus: "eligible" }); + expect(plan.eligible).toBe(true); }); it("validated link + eligible branch → eligible summary, eligible:true", () => { diff --git a/test/unit/eligibility-scenarios.test.ts b/test/unit/eligibility-scenarios.test.ts index 27a14a756a..23b55928d7 100644 --- a/test/unit/eligibility-scenarios.test.ts +++ b/test/unit/eligibility-scenarios.test.ts @@ -105,9 +105,11 @@ describe("eligible branch with validated linked issue", () => { describe("unlinked — no linked issue configured", () => { const result = preview({ linkedIssueMode: "none" }); - it("derives eligible:false and not_required status when mode is none", () => { + it("derives eligible:true and not_required status when mode is none (#7809)", () => { const plan = deriveEligibilityPlan(result); - expect(plan.eligible).toBe(false); + // A confirmed branch with no linked issue required is eligible -- the structured boolean now agrees with the + // "not required" summary instead of contradicting it (#7809). + expect(plan.eligible).toBe(true); expect(plan.linkedIssueStatus).toBe("not_required"); expect(plan.branchEligibilityStatus).toBe("not_required"); expect(plan.blockers).toHaveLength(0); diff --git a/test/unit/mcp-eligibility-plan.test.ts b/test/unit/mcp-eligibility-plan.test.ts index d53c6e76bf..76882238c6 100644 --- a/test/unit/mcp-eligibility-plan.test.ts +++ b/test/unit/mcp-eligibility-plan.test.ts @@ -58,13 +58,14 @@ describe("MCP loopover_get_eligibility_plan (#2222)", () => { expect(typeof plan.publicSummary).toBe("string"); }); - it("returns not_required statuses when linked-issue mode is none", async () => { + it("returns not_required statuses (and eligible:true) when linked-issue mode is none (#7809)", async () => { const client = await connect(); const plan = await callPlan(client, { ...BASE_ARGS, linkedIssueMode: "none", }); - expect(plan.eligible).toBe(false); + // eligible now agrees with the "not required" summary rather than contradicting it (#7809). + expect(plan.eligible).toBe(true); expect(plan.linkedIssueStatus).toBe("not_required"); expect(plan.branchEligibilityStatus).toBe("not_required"); expect(plan.publicSummary).toMatch(/not required/i); diff --git a/test/unit/mcp-output-schemas.test.ts b/test/unit/mcp-output-schemas.test.ts index 3599c4a92d..55e958a34f 100644 --- a/test/unit/mcp-output-schemas.test.ts +++ b/test/unit/mcp-output-schemas.test.ts @@ -488,7 +488,7 @@ describe("MCP tool calls return schema-valid structured content", () => { }); expect(result.isError).toBeFalsy(); const data = result.structuredContent as Record; - expect(data.eligible).toBe(false); + expect(data.eligible).toBe(true); // not_required + confirmed branch is eligible (#7809) expect(data.linkedIssueStatus).toBe("not_required"); expect(data.branchEligibilityStatus).toBe("not_required"); expect(Array.isArray(data.blockers)).toBe(true);