Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/services/eligibility-plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
10 changes: 9 additions & 1 deletion test/unit/eligibility-plan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
6 changes: 4 additions & 2 deletions test/unit/eligibility-scenarios.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 3 additions & 2 deletions test/unit/mcp-eligibility-plan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion test/unit/mcp-output-schemas.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ describe("MCP tool calls return schema-valid structured content", () => {
});
expect(result.isError).toBeFalsy();
const data = result.structuredContent as Record<string, unknown>;
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);
Expand Down