Skip to content

feat(mcp): REST route + CLI mirror for loopover_get_eligibility_plan - #6692

Merged
loopover-orb[bot] merged 1 commit into
JSONbored:mainfrom
dhgoal:feat/eligibility-plan-rest-cli-v3
Jul 16, 2026
Merged

feat(mcp): REST route + CLI mirror for loopover_get_eligibility_plan#6692
loopover-orb[bot] merged 1 commit into
JSONbored:mainfrom
dhgoal:feat/eligibility-plan-rest-cli-v3

Conversation

@dhgoal

@dhgoal dhgoal commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #6621

loopover_get_eligibility_plan existed only on the remote MCP server (src/mcp/server.ts) — the one tool in the preview / explain_breakdown / eligibility_plan trio with no REST route and no local CLI mirror. This adds both, modelled exactly on loopover_explain_score_breakdown's existing pair.

  • POST /v1/scoring/eligibility-plan (src/api/routes.ts), placed immediately after explain-breakdown and structured identically: same scorePreviewSchema, same repo/snapshot/evidence/contributorIssues fetch, same buildScorePreview(...). It returns deriveEligibilityPlan(preview) (reused as-is from src/services/eligibility-plan.ts, not reimplemented) and — like /v1/scoring/preview, and matching loopover_get_eligibility_plan's own MCP handler — treats contributorLogin as optional (conditional gate), not the unconditional requirement explain-breakdown uses.
  • loopover_get_eligibility_plan stdio tool in packages/loopover-mcp/bin/loopover-mcp.js, plus a STDIO_TOOL_DESCRIPTORS entry (category discovery, matching the server's MCP_TOOL_CATEGORIES). The local branch-metadata→request-body assembly it shares with loopover_explain_score_breakdown is factored into a buildLocalScoreRequestBody helper both call, per the issue's preferred option, so the two can't drift — the only difference is the apiPost path.

Scope

  • The PR title follows type(scope): short summary Conventional Commit format.
  • This PR is focused and does not mix unrelated backend, UI, MCP, docs, dependency, and deploy changes.
  • This follows CONTRIBUTING.md and does not reintroduce GitHub Pages, VitePress, site/, or CNAME.
  • I linked a currently open issue this PR resolves (Closes #6621).

Validation

  • git diff --check
  • npm run actionlint
  • npm run typecheck (see note — type-safe by construction; local root typecheck OOMs)
  • npm run test:coverage (the changed route's every branch is covered — see below)
  • npm run test:workers
  • npm run build:mcp
  • npm run test:mcp-pack
  • npm run ui:openapi:check
  • npm run ui:lint
  • npm run ui:typecheck
  • npm run ui:build
  • npm audit --audit-level=moderate
  • New or changed behavior has unit/integration tests for new branches, fallback paths, and sanitizer boundaries

If any required check was skipped, explain why:

  • Route coverage (the codecov/patch-gated surface). Ran the affected integration tests green: test/integration/api.test.ts "serves deterministic signal endpoints" now exercises the new route's authorized path (contributorLogin present → 200 + {eligible, branchEligibilityStatus, blockers, cleanupPaths}), the optional-login path (no contributorLogin → 200, the branch that differs from explain-breakdown), and the invalid-body 400 (added to the parametrized invalid_scoring_preview_request list). The contributor-gate 403 branch is covered in test/integration/routes-errors.test.ts, mirroring the existing victimScorePreview case (a session whose actor ≠ the requested contributor). That is every branch of the new route.
  • CLI coverage. packages/loopover-mcp/bin/loopover-mcp.js is not in vitest.config.ts's coverage.include, so it isn't codecov-gated. Its registration, description, and category are validated by the existing test/unit/mcp-cli-tools.test.ts (which boots the real stdio server and asserts descriptors match registered tools) — passing with the new tool. The request-body assembly is now the shared buildLocalScoreRequestBody helper, exercised through loopover_explain_score_breakdown's unchanged path; the sole delta is the apiPost endpoint string.
  • Typecheck. The route is byte-identical to the adjacent, type-checked explain-breakdown route except the terminal call; explainScoreBreakdown and deriveEligibilityPlan both take the same ScorePreviewResult, so passing the shared preview is type-safe by construction. The repo's root typecheck OOMs locally; an isolated tsc surfaces no error at the changed lines (only pre-existing ambient-type/config artifacts common to the whole file). CI runs the full typecheck.
  • OpenAPI. src/openapi/spec.ts is hand-curated and does not list explain-breakdown (or preview's siblings); the new route follows that precedent, so ui:openapi:check needs no regen.
  • Prettier is not run by CI (no format/format:check script, absent from test:ci); all four touched files are already non-prettier-clean on main, so no formatting churn was introduced.

Safety

  • No secrets, wallet details, hotkeys, coldkeys, user PATs, private keys, raw trust scores, private rankings, or private maintainer evidence are exposed.
  • Public GitHub text stays sanitized and low-noise.
  • Auth, cookie, CORS, GitHub App, Cloudflare, or session changes include negative-path tests.
  • API/OpenAPI/MCP behavior is updated and tested where needed.
  • UI changes use live API data or real empty/error/loading states, not production mock/demo fallbacks.
  • Visible UI changes include a UI Evidence section.
  • Public docs/changelogs are updated where needed.

The new route inherits the exact contributor gate of its siblings: requireContributorAccess is applied when a contributorLogin is supplied (403 forbidden_contributor on a mismatch — tested), and deriveEligibilityPlan returns only public-safe blocker/cleanup language (no scores or private counts) by construction.

Notes

Supersedes #6660 (auto-closed): that revision added the stdio tool but missed the hardcoded tool-count invariant in test/unit/mcp-tool-rename-aliases.test.ts (60→61). That one test failing in validate-tests (3) also aborted that shard's Codecov upload, which is why the same root cause surfaced as both a test failure and a codecov/patch drop. Fixed here; the count assertions (and their it titles + running comment) now read 61, verified locally (10/10).

Notes

deriveEligibilityPlan and the remote loopover_get_eligibility_plan tool are unchanged; this only adds the missing HTTP + CLI reach. The shared buildLocalScoreRequestBody extraction removes the copy-paste the issue flagged, so explain_score_breakdown and get_eligibility_plan stay in lockstep.

Closes #6621

loopover_get_eligibility_plan existed only on the remote MCP server — the one
tool in the preview/breakdown/eligibility trio with no REST route or local
CLI mirror. This adds both, following loopover_explain_score_breakdown's
established pair exactly.

- POST /v1/scoring/eligibility-plan (src/api/routes.ts), placed after
  explain-breakdown and structured identically: same scorePreviewSchema, same
  repo/snapshot/evidence fetch, same buildScorePreview. It returns
  deriveEligibilityPlan(preview) (reused as-is from services/eligibility-plan)
  and — like /v1/scoring/preview, and matching the tool's own handler — treats
  contributorLogin as optional rather than unconditionally required.
- loopover_get_eligibility_plan stdio tool + a STDIO_TOOL_DESCRIPTORS entry
  (category discovery, matching the server's MCP_TOOL_CATEGORIES). The local
  branch-metadata-to-request-body assembly it shares with
  loopover_explain_score_breakdown is factored into buildLocalScoreRequestBody
  so the two never drift; only the apiPost path differs.

Tests: route-level coverage for an authorized plan (contributorLogin present),
the anonymous/optional-login path, an invalid body (400), and the
contributor-gate 403 (routes-errors.test.ts, mirroring victimScorePreview).
CLI registration + descriptor + category are covered by the existing
mcp-cli-tools stdio-server tests.

Closes JSONbored#6621
@dhgoal
dhgoal requested a review from JSONbored as a code owner July 16, 2026 19:07
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.65%. Comparing base (58ab17e) to head (0c87556).
⚠️ Report is 8 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #6692   +/-   ##
=======================================
  Coverage   93.65%   93.65%           
=======================================
  Files         682      682           
  Lines       68112    68126   +14     
  Branches    18708    18713    +5     
=======================================
+ Hits        63788    63802   +14     
  Misses       3347     3347           
  Partials      977      977           
Flag Coverage Δ
shard-1 43.94% <7.14%> (+0.04%) ⬆️
shard-2 36.94% <7.14%> (-0.03%) ⬇️
shard-3 32.47% <28.57%> (+0.01%) ⬆️
shard-4 34.66% <7.14%> (+0.42%) ⬆️
shard-5 31.67% <7.14%> (+0.29%) ⬆️
shard-6 45.39% <92.85%> (-0.36%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/api/routes.ts 94.60% <100.00%> (+0.03%) ⬆️

... and 1 file with indirect coverage changes

@loopover-orb loopover-orb Bot added the gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier. label Jul 16, 2026
@loopover-orb

loopover-orb Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Tip

✅ LoopOver review result - approve/merge recommended

Review updated: 2026-07-16 19:46:11 UTC

5 files · 1 AI reviewer · no blockers · readiness 77/100 · CI green · unknown

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
This PR adds a REST route (POST /v1/scoring/eligibility-plan) and a stdio CLI/MCP tool (loopover_get_eligibility_plan) mirroring the existing explain-breakdown pattern, and factors the shared local-branch-metadata assembly into a new buildLocalScoreRequestBody helper used by both tools. The route reuses deriveEligibilityPlan and buildScorePreview exactly as the existing preview/explain-breakdown routes do, correctly makes the contributor gate conditional (matching /v1/scoring/preview), and is backed by integration tests covering the 200 path, the optional-contributor path, the 403 forbidden-contributor gate, and the invalid-request 400 path. The tool-count bump test (61→62) is updated consistently with the new registration.

Nits — 4 non-blocking
  • src/api/routes.ts:2121 — the extracted `400` status/`invalid_scoring_preview_request` error shape duplicates the pattern used by every other scoring route; consider a shared response helper if this recurs again, though it's consistent with existing style.
  • The size-smell flags on loopover-mcp.js and routes.ts reflect pre-existing file size, not growth introduced by this diff specifically — worth tracking separately but not a blocker here.
  • packages/loopover-mcp/bin/loopover-mcp.js — `buildLocalScoreRequestBody`'s destructured `estimatedSourceLines` fallback math (`diff.changedLineCount - diff.testFiles.length`) is copied verbatim from the pre-existing explain-breakdown logic; fine since it's unchanged behavior, but worth a shared comment if a third caller appears.
  • Consider adding a unit test directly for `buildLocalScoreRequestBody` in isolation (not just via the two tools that call it) to pin its contract now that it's shared.

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ✅ Gate result — Passing (No configured blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #6621
Related work ⚠️ 1 scoped overlap Top overlaps are listed below; lower-confidence bulk is hidden.
Change scope ❌ 8/20 High review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 128 registered-repo PR(s), 75 merged, 36 issue(s).
Contributor context ✅ Confirmed Gittensor contributor dhgoal; Gittensor profile; 128 PR(s), 36 issue(s).
Improvement ✅ Minor risk: clean · value: minor
Review context
  • Author: dhgoal
  • Role context: outside_contributor
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: not available
  • Official Gittensor activity: 128 PR(s), 36 issue(s).
  • Related work: Titles/paths share 7 meaningful terms. (issue #6593, issue #6621)
Contributor next steps
  • Start here: Review top overlaps.
  • Then work through the remaining 3 steps in the Signals table above.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask &lt;question&gt; answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat &lt;question&gt; answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@loopover-orb loopover-orb Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoopOver approves — the gate is satisfied and CI is green.

@loopover-orb
loopover-orb Bot merged commit bb028ec into JSONbored:main Jul 16, 2026
16 checks passed
This was referenced Jul 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP: add a REST route + CLI mirror for loopover_get_eligibility_plan

1 participant