From 872e873f5af0c666ab098ed8a3d6fb18b3a2bf5a Mon Sep 17 00:00:00 2001 From: bitfathers94 <237535319+bitfathers94@users.noreply.github.com> Date: Sun, 26 Jul 2026 14:00:54 +0000 Subject: [PATCH] fix(mcp): correct loopover_check_issue_slop description and drop the dead issue-slop rubric MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The registered loopover_check_issue_slop stdio tool description still promised "slopRisk (0-100), band, findings, and the rubric", but the /v1/lint/issue-slop route strips its response to {band, findings} by design (#6990) — the claim was never updated. Match the description to the actual response shape. ISSUE_SLOP_RUBRIC_MARKDOWN in src/signals/issue-slop.ts had zero consumers (not even a test); unlike the PR-side SLOP_RUBRIC_MARKDOWN it was never wired into an in-process self-check, so delete it rather than leave a dead constant behind. Add an in-process regression test that reads the registered description and asserts it describes {band, findings} without the stale slopRisk/rubric claims. Closes #8907 --- packages/loopover-mcp/bin/loopover-mcp.ts | 2 +- src/signals/issue-slop.ts | 14 ----- .../mcp-check-issue-slop-description.test.ts | 61 +++++++++++++++++++ 3 files changed, 62 insertions(+), 15 deletions(-) create mode 100644 test/unit/mcp-check-issue-slop-description.test.ts diff --git a/packages/loopover-mcp/bin/loopover-mcp.ts b/packages/loopover-mcp/bin/loopover-mcp.ts index acd1e842ac..e0d80d8e5a 100644 --- a/packages/loopover-mcp/bin/loopover-mcp.ts +++ b/packages/loopover-mcp/bin/loopover-mcp.ts @@ -1274,7 +1274,7 @@ const STDIO_TOOL_DESCRIPTORS = [ { name: "loopover_check_issue_slop", category: "review", - description: "Assess the deterministic slop risk of an issue from its title + body alone (no repo data) — flags clearly low-effort issues (empty body, an unfilled template) for triage. Returns slopRisk (0-100), band, findings, and the rubric. Advisory-only.", + description: "Assess the deterministic slop risk of an issue from its title + body alone (no repo data) — flags clearly low-effort issues (empty body, an unfilled template) for triage. Returns band and findings. Advisory-only: issues never block.", }, // #6150 — the miner-auto-dev profile's plan-DAG + local-scorer + gate-prediction tools, previously listed in // recommendedTools below but never actually registered. diff --git a/src/signals/issue-slop.ts b/src/signals/issue-slop.ts index 47e638dcb6..caf2bbb4e3 100644 --- a/src/signals/issue-slop.ts +++ b/src/signals/issue-slop.ts @@ -23,20 +23,6 @@ export const ISSUE_SLOP_WEIGHTS = { titleRestatement: 35, } as const; -export const ISSUE_SLOP_RUBRIC_MARKDOWN = [ - "# LoopOver issue slop triage rubric", - "", - "- `clean`: 0", - "- `low`: 1-30", - "- `elevated`: 31-59", - "- `high`: 60-100", - "", - "Advisory-only (issues never block). Current deterministic signals:", - "- empty issue body", - "- issue template opened but left unfilled", - "- issue body only restates the title (no added detail)", -].join("\n"); - export function buildIssueSlopAssessment(input: IssueSlopAssessmentInput): SlopAssessment { const findings: SignalFinding[] = []; const emptyBodyFinding = buildEmptyIssueBodyFinding(input); diff --git a/test/unit/mcp-check-issue-slop-description.test.ts b/test/unit/mcp-check-issue-slop-description.test.ts new file mode 100644 index 0000000000..1ec3073eb1 --- /dev/null +++ b/test/unit/mcp-check-issue-slop-description.test.ts @@ -0,0 +1,61 @@ +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import { closeFixtureServer, startFixtureServer } from "./support/mcp-cli-harness"; + +// #8907: the stdio loopover_check_issue_slop tool description used to promise "slopRisk (0-100), band, +// findings, and the rubric", but the /v1/lint/issue-slop route strips the response to {band, findings} +// by design (#6990). Import the bin .ts + InMemoryTransport so the assertion reads the actual registered +// description, guarding the fix against a future regression. +const MODULE = "../../packages/loopover-mcp/bin/loopover-mcp.ts"; + +type BinModule = { + server: { connect: (transport: unknown) => Promise }; +}; + +let tempDir = ""; +let mod: BinModule; + +beforeAll(async () => { + tempDir = mkdtempSync(join(tmpdir(), "loopover-issue-slop-desc-")); + const apiUrl = await startFixtureServer(); + process.env.LOOPOVER_API_URL = apiUrl; + process.env.LOOPOVER_API_TOKEN = "in-process-token"; + process.env.LOOPOVER_API_TIMEOUT_MS = "2000"; + process.env.LOOPOVER_CONFIG_DIR = tempDir; + process.env.LOOPOVER_SKIP_NPM_VERSION_CHECK = "1"; + mod = (await import(MODULE)) as unknown as BinModule; +}, 120_000); + +afterAll(async () => { + await closeFixtureServer(); + if (tempDir) rmSync(tempDir, { recursive: true, force: true }); + delete process.env.LOOPOVER_API_URL; + delete process.env.LOOPOVER_API_TOKEN; + delete process.env.LOOPOVER_API_TIMEOUT_MS; + delete process.env.LOOPOVER_CONFIG_DIR; + delete process.env.LOOPOVER_SKIP_NPM_VERSION_CHECK; +}); + +describe("bin loopover_check_issue_slop description (#8907)", () => { + it("describes the {band, findings} response shape without stale slopRisk/rubric claims", async () => { + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + await mod.server.connect(serverTransport); + const client = new Client({ name: "issue-slop-desc-test", version: "0.1.0" }, { capabilities: {} }); + await client.connect(clientTransport); + try { + const { tools } = await client.listTools(); + const tool = tools.find((entry) => entry.name === "loopover_check_issue_slop"); + expect(tool).toBeDefined(); + const description = tool?.description ?? ""; + expect(description).toContain("band and findings"); + expect(description).not.toContain("slopRisk"); + expect(description).not.toContain("the rubric"); + } finally { + await client.close().catch(() => undefined); + } + }); +});