feat(mcp): register plan-DAG tools + local scorer in packages/loopover-mcp - #6526
Conversation
…r-mcp The miner-auto-dev profile's recommendedTools listed loopover_run_local_scorer/loopover_build_plan/loopover_plan_status/ loopover_record_step_result/loopover_predict_gate, but none were registered as local stdio tools -- only the string literals existed. - loopover_run_local_scorer: computeLocalScorerTokens imported directly from @loopover/engine (already exported), same pattern as the existing loopover_check_slop_risk/loopover_lint_pr_text pure in-process tools. - loopover_build_plan / loopover_plan_status / loopover_record_step_result: the plan-DAG state machine (src/services/plan-dag.ts) was never extracted to @loopover/engine's export map, so it's hand-duplicated here following the same MAINTAIN_ACTION_CLASSES/AUTONOMY_LEVELS precedent this file already uses for exactly this situation. Pure + stateless -- no DB, no network access. - loopover_predict_gate: cannot be pure-local (needs live repo/issue/ PR/manifest data only the server can assemble). Proxies to the existing POST /v1/local/branch-analysis route, which already computes predictedGate via buildPredictedGateVerdict -- the same logic the remote tool uses -- and returns it as a top-level field. No new backend endpoint needed. Metadata-only input (no git required), unlike the branch-analysis tools that shell out to git. Along the way, found and fixed a stale, non-symlinked packages/loopover-mcp/node_modules/@loopover/engine directory shadowing the correct root-level workspace symlink, which was breaking the CLI's own subpath imports (unrelated to this change -- confirmed pre-existing via git stash). Added test/unit/mcp-cli-plan-scorer-tools.test.ts (15 tests) covering all 5 tools' success + rejection paths, and a localBranchAnalysisStatus fixture-server option + predictedGate field on localBranchAnalysisFixture in test/unit/support/mcp-cli-harness.ts to test loopover_predict_gate's API-failure path, mirroring the existing intakeStatus pattern. Closes JSONbored#6150
JSONbored#6150 registered loopover_run_local_scorer, loopover_build_plan, loopover_plan_status, loopover_record_step_result, and loopover_predict_gate, taking the total loopover_-prefixed stdio tool count from 55 to 60. mcp-tool-rename-aliases.test.ts hardcodes this count as a regression guard against silent alias/registration drift; update it to match.
Both tests create a fake repo literally named "JSONbored/gittensory"
(the default self-repo identity test/helpers/d1.ts's createTestEnv()
uses) but never mock fetch, so the repo-settings resolver's manifest
loader fell through to a REAL network request to the real, live
JSONbored/gittensory GitHub repo's .loopover.yml. That real manifest
now carries autonomy: { merge: auto, ... } (JSONbored#773),
so the live-fetched content silently overrode the DB-only settings
these two tests exist to verify -- unrelated to and unaffected by this
branch's own diff, confirmed by reproducing the identical failure via
`git stash` and again in a clean upstream/main worktree.
Mock fetch to 404 (matching this file's established pattern for
network-adjacent tests) and keep the LOOPOVER_DRIFT_ISSUE_REPO
override the sibling tests in this file already use, so the fixture
repo name no longer collides with the real self-repo's live config or
its bundled fallback.
…etch Same root cause as the earlier backfill.test.ts fix: both tests create a fixture repo literally named "JSONbored/gittensory" without overriding LOOPOVER_DRIFT_ISSUE_REPO away from createTestEnv()'s default, so the repo-settings resolver's un-mocked (or 404-catch-all) manifest fetch falls through to the bundled self-repo manifest, which now carries real autonomy: auto config (JSONbored#773) and silently overrides these tests' DB-only settings. - test/integration/api.test.ts: "serves installation repair diagnostics and refreshes installation health" had no fetch mock at all for its first two /repair calls, so pull_requests came back "write" instead of the expected baseline "read". - test/unit/queue-5.test.ts: "the live slop gate fetches the PR's own commit messages" already mocked fetch with a 404 catch-all, but still matched the self-repo fallback via the env default, silently routing the PR through the self-repo's manifest instead of the DB settings under test -- surfaced as a null slopRisk instead of 15. Confirmed unrelated to this branch's own diff by reproducing both failures in a clean upstream/main checkout before fixing.
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #6526 +/- ##
=======================================
Coverage 95.56% 95.56%
=======================================
Files 589 589
Lines 47121 47121
Branches 14989 14989
=======================================
Hits 45032 45032
Misses 1297 1297
Partials 792 792
Flags with carried forward coverage won't be shown. Click here to find out more. |
|
Tip ✅ LoopOver review result - approve/merge recommendedReview updated: 2026-07-16 12:07:50 UTC
Review summary Nits — 5 non-blocking
Decision drivers
Context & advisory signals — never blocks the verdict
Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk 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.
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.
|
Closes #6150
Summary
src/mcp/server.tsregistersloopover_run_local_scorer,loopover_build_plan,loopover_plan_status,loopover_record_step_result, andloopover_predict_gateon the remote server, andpackages/loopover-mcp/bin/loopover-mcp.js'sminer-auto-devprofile listed all five inrecommendedTools— but none were actually registered as local stdio tools, only the string literals existed. A contributor relying on the local server for this profile couldn't invoke any of them.loopover_run_local_scorer:computeLocalScorerTokensimported directly from@loopover/engine(already exported at the package root) — same pattern as the existingloopover_check_slop_risk/loopover_lint_pr_textpure in-process tools. Pure, deterministic, no repo/network access.loopover_build_plan/loopover_plan_status/loopover_record_step_result: the plan-DAG state machine (src/services/plan-dag.ts) was never extracted to@loopover/engine's export map, so there's nothing to import — hand-duplicated here following the exact same precedent this file already uses forMAINTAIN_ACTION_CLASSES/AUTONOMY_LEVELSwhen the published package's export map doesn't cover something. Pure + stateless (no DB, no network) — the harness runs each step and callsloopover_record_step_resultto report it back.loopover_predict_gate: cannot be pure-local — it needs live repo/issue/PR/manifest data only the server can assemble (env.DB-backed). Proxies to the existingPOST /v1/local/branch-analysisroute, which already computespredictedGateviabuildPredictedGateVerdict(the identical logic the remote tool uses) and returns it as a top-level response field — no new backend endpoint needed. Uses a metadata-only input shape (no git/workspace context), unlike the sibling branch-analysis tools that shell out to git.Note on re-open (this is attempt #3)
#6462 and #6490 were both auto-closed by red CI, neither caused by this diff's own code:
test/unit/mcp-tool-rename-aliases.test.tshardcodes the exact count of registeredloopover_-prefixed stdio tools; this PR's 5 new tools take that count from 55 to 60, so the guard needed updating (this one genuinely is a consequence of this diff).maincommit (feat(agent): autonomy-levels framework (observe→…→auto) #773) gave the real, liveJSONbored/gittensoryrepo's.loopover.ymlgenuineautonomy: { merge: auto, ... }config. Several pre-existing tests across the suite create a fixture repo literally namedJSONbored/gittensorywithout mockingfetch, so their manifest-resolution calls silently hit that real, live config over the network instead of the DB-only settings under test —test/unit/backfill.test.ts(2 tests),test/unit/queue-5.test.ts(1 test), andtest/integration/api.test.ts(1 test) were all confirmed broken this way by reproducing the identical failures in an isolatedgit worktreeof cleanupstream/main, unrelated to this branch. Fixed the twoqueue-5.test.ts/api.test.tsinstances directly; the twobackfill.test.tsinstances were independently fixed by the maintainer upstream in the meantime (this branch is rebased on top of that fix, no conflict remains). The maintainer also fixed the actual root cause at the source afterward (test/helpers/d1.ts's default self-repo test identity no longer collides with the real repo name), so this class of failure shouldn't recur.main-level migration-number collision (0156grabbed by two different PRs) briefly brokevalidate-codeon the previous attempt; already fixed upstream and picked up by this branch's rebase.Incidental fix
While testing, found
packages/loopover-mcp/node_modules/@loopover/enginewas a stale, non-symlinked directory shadowing the correct root-level workspace symlink, breaking the CLI's own@loopover/engine/signals/slopetc. subpath imports — confirmed pre-existing and unrelated to this change viagit stashcomparison against a clean checkout. Removed it; the root symlink resolves correctly.Scope
type(scope): short summaryConventional Commit format.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Validation
git diff --checknpm run actionlintnpm run typecheck(root) — reliably OOMs on this shared sandbox regardless of what changed (reproduced repeatedly this session).packages/loopover-mcpis plain JS with its ownnpm run build(node --checkacross every lib/bin file) — ran it directly and it passes clean, and confirmed via direct execution thatloopover-mcp --helpandloopover-mcp tools --json(60 tools, count verified) both run without error.npm run test:coverage— not run repo-wide (same OOM risk). Instead: ran the full MCP CLI test suite (30 files, 230 tests),mcp-tool-rename-aliases.test.ts,backfill.test.ts,queue-5.test.ts(the one affected test), andapi.test.ts(the one affected test) together — 406 tests, all passing — after this branch's final rebase onto currentmain. Also ran a background full (non-coverage) sweep of the entiretest/unit+test/integrationsuite (908 files, 17,435 tests) specifically to rule out any further instances of the self-repo collision pattern beyond the 4 already found — none found.test/unit/mcp-cli-plan-scorer-tools.test.ts(15 new tests) covers registration + success/rejection paths for all 5 new tools, including the API-failure path forloopover_predict_gate.npm run test:workers— N/A, no Worker-facing code changed (this is the local CLI, notsrc/).npm run build:mcp/npm run test:mcp-pack— both run directly and pass clean.npm run ui:openapi:check/ui:lint/ui:typecheck/ui:build— N/A, noapps/loopover-uichanges.npm audit --audit-level=moderate— 0 vulnerabilities.localBranchAnalysisStatusfixture-server option added totest/unit/support/mcp-cli-harness.ts, mirroring the existingintakeStatuspattern.If any required check was skipped, explain why:
npm run typecheck/npm run test:coverage: reliably OOMs on this shared sandbox under memory pressure from concurrent sessions, independent of the diff. Substituted withpackages/loopover-mcp's own build (clean), direct CLI execution confirming all 5 tools register and respond correctly, the broader test suites listed above (all passing), and a full-suite non-coverage sweep specifically to de-risk the unrelated regression class that closed the two prior attempts.Safety
/v1/local/branch-analysisroute).UI Evidencesection below with screenshots. — N/A, no visible UI change (CLI tool registration only).CHANGELOG.mduntouched.Notes
loopover_build_plan/loopover_plan_status/loopover_record_step_result's hand-duplicated plan-DAG logic inloopover-mcp.jsis a deliberate architectural choice, not an oversight: this file already documents (in theMAINTAIN_ACTION_CLASSES/AUTONOMY_LEVELScomment block) that it resolves@loopover/enginethrough the published package, whose export map exposes only a curated set of subpaths — widening that public API is a separate, larger decision than "register these 5 tools locally," so this follows the existing precedent rather than introducing a new one.