Context
deriveEligibilityPlan's eligible boolean (src/services/eligibility-plan.ts:115) is wrong whenever no linked-issue multiplier was requested — the common case for any ordinary contribution that doesn't reference a linked issue:
const branchConfirmed = branchEligibilityStatus === "eligible" || branchEligibilityStatus === "not_required";
const eligible = result.linkedIssueMultiplier.eligible && branchConfirmed;
ScorePreviewInput.linkedIssueMode defaults to "none" (packages/loopover-engine/src/scoring/preview.ts:408). For that mode, decideLinkedIssueMultiplier (same file, :975-987) unconditionally returns { status: "not_required", eligible: false }. So branchConfirmed is true, but eligible = false && true = false — always, regardless of whether the branch itself is fine.
This contradicts the field's own doc comment (eligibility-plan.ts:13-18): "branch eligible AND linked issue validated when standard mode is requested." The file's own eligibilityStatusKey helper already special-cases this correctly (line 64: if (linkedIssueStatus === "not_required" && branchEligibilityStatus === "not_required") return "not_required"), producing a correct human-readable publicSummary ("...not required for this contribution type") — but the separate structured eligible: boolean field is never given the same special case, so it silently reads false in the same payload where the text reads "not required."
Verified empirically by extracting the exact logic paths and running them — confirmed eligible === false for the mode="none" case. Coverage gap confirmed: test/unit/eligibility-plan.test.ts:119-123's "no linked issue + non-required branch" test is the one case exercising this path, and — unlike every sibling test in the same describe block — never asserts plan.eligible, only publicSummary/linkedIssueProjection.
Consequences are real: EligibilityPlan (including the raw eligible boolean) is returned directly as structured JSON by the loopover_get_eligibility_plan MCP tool (src/mcp/server.ts:4072-4076), the POST API route (src/api/routes.ts:2412), and embedded in buildLocalBranchAnalysis's payload (src/signals/local-branch.ts:361,403) — any client reading the boolean (rather than parsing the prose) is told an otherwise-clean branch/PR is ineligible.
Requirements
Apply the same special case eligibilityStatusKey already uses to the eligible boolean itself: when linkedIssueStatus === "not_required" and branchEligibilityStatus is "eligible" or "not_required", eligible must be true (not gated on linkedIssueMultiplier.eligible, which is meaningless when no linked issue was required in the first place).
Deliverables
Test Coverage Requirements
src/services/** is under the top-level 99% patch coverage gate — the fixed branch and its test assertion must both be covered.
Expected Outcome
loopover_get_eligibility_plan (MCP tool, API route, and embedded local-branch-analysis payload) reports eligible: true for an otherwise-clean contribution that simply didn't reference a linked issue, matching its own publicSummary text instead of contradicting it.
Links & Resources
src/services/eligibility-plan.ts:13-18 (doc comment), :64 (eligibilityStatusKey's correct existing special case), :115 (the bug)
packages/loopover-engine/src/scoring/preview.ts:408,975-987,1128
test/unit/eligibility-plan.test.ts:119-123
- Consumers:
src/mcp/server.ts:4072-4076, src/api/routes.ts:2412, src/signals/local-branch.ts:361,403
Context
deriveEligibilityPlan'seligibleboolean (src/services/eligibility-plan.ts:115) is wrong whenever no linked-issue multiplier was requested — the common case for any ordinary contribution that doesn't reference a linked issue:ScorePreviewInput.linkedIssueModedefaults to"none"(packages/loopover-engine/src/scoring/preview.ts:408). For that mode,decideLinkedIssueMultiplier(same file,:975-987) unconditionally returns{ status: "not_required", eligible: false }. SobranchConfirmedistrue, buteligible = false && true = false— always, regardless of whether the branch itself is fine.This contradicts the field's own doc comment (
eligibility-plan.ts:13-18): "branch eligible AND linked issue validated when standard mode is requested." The file's owneligibilityStatusKeyhelper already special-cases this correctly (line 64:if (linkedIssueStatus === "not_required" && branchEligibilityStatus === "not_required") return "not_required"), producing a correct human-readablepublicSummary("...not required for this contribution type") — but the separate structuredeligible: booleanfield is never given the same special case, so it silently readsfalsein the same payload where the text reads "not required."Verified empirically by extracting the exact logic paths and running them — confirmed
eligible === falsefor themode="none"case. Coverage gap confirmed:test/unit/eligibility-plan.test.ts:119-123's "no linked issue + non-required branch" test is the one case exercising this path, and — unlike every sibling test in the samedescribeblock — never assertsplan.eligible, onlypublicSummary/linkedIssueProjection.Consequences are real:
EligibilityPlan(including the raweligibleboolean) is returned directly as structured JSON by theloopover_get_eligibility_planMCP tool (src/mcp/server.ts:4072-4076), thePOSTAPI route (src/api/routes.ts:2412), and embedded inbuildLocalBranchAnalysis's payload (src/signals/local-branch.ts:361,403) — any client reading the boolean (rather than parsing the prose) is told an otherwise-clean branch/PR is ineligible.Requirements
Apply the same special case
eligibilityStatusKeyalready uses to theeligibleboolean itself: whenlinkedIssueStatus === "not_required"andbranchEligibilityStatusis"eligible"or"not_required",eligiblemust betrue(not gated onlinkedIssueMultiplier.eligible, which is meaningless when no linked issue was required in the first place).Deliverables
deriveEligibilityPlan'seligibleboolean insrc/services/eligibility-plan.tsmatcheseligibilityStatusKey's existing semantics for the "linked issue not required" case.test/unit/eligibility-plan.test.ts's "no linked issue + non-required branch" test assertsplan.eligible === true, matching its sibling tests in the samedescribeblock.Test Coverage Requirements
src/services/**is under the top-level 99% patch coverage gate — the fixed branch and its test assertion must both be covered.Expected Outcome
loopover_get_eligibility_plan(MCP tool, API route, and embedded local-branch-analysis payload) reportseligible: truefor an otherwise-clean contribution that simply didn't reference a linked issue, matching its ownpublicSummarytext instead of contradicting it.Links & Resources
src/services/eligibility-plan.ts:13-18(doc comment),:64(eligibilityStatusKey's correct existing special case),:115(the bug)packages/loopover-engine/src/scoring/preview.ts:408,975-987,1128test/unit/eligibility-plan.test.ts:119-123src/mcp/server.ts:4072-4076,src/api/routes.ts:2412,src/signals/local-branch.ts:361,403