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
2 changes: 1 addition & 1 deletion packages/loopover-mcp/bin/loopover-mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
14 changes: 0 additions & 14 deletions src/signals/issue-slop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
61 changes: 61 additions & 0 deletions test/unit/mcp-check-issue-slop-description.test.ts
Original file line number Diff line number Diff line change
@@ -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<void> };
};

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);
}
});
});