diff --git a/src/mcp/server.ts b/src/mcp/server.ts index 87a2e2af99..46932d8a89 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -155,7 +155,6 @@ import { loadUpstreamStatus } from "../upstream/ruleset"; import { simulateOpenPrPressure, type OpenPrPressureInput } from "../services/open-pr-pressure-scenarios"; import { buildFindingTaxonomyDocument, FINDING_TAXONOMY_URI } from "../review/finding-taxonomy"; import { buildEnrichmentAnalyzersTaxonomyDocument, ENRICHMENT_ANALYZERS_URI } from "../review/enrichment-analyzers-taxonomy"; -import { buildSlopRulesDocument, SLOP_RULES_URI } from "../review/slop-rules-taxonomy"; type AppContext = Context<{ Bindings: Env }>; type ToolPayload = { @@ -2208,26 +2207,6 @@ export class GittensoryMcp { }), ); - // #2237 — read-only catalog of the deterministic slop rule codes + score bands for MCP discovery. - server.registerResource( - "gittensory_slop_rules", - SLOP_RULES_URI, - { - title: "Gittensory Slop Rules", - description: "Deterministic slop-signal catalog: rule codes with their point weights (PR + issue) and the clean/low/elevated/high score bands.", - mimeType: "application/json", - }, - async () => ({ - contents: [ - { - uri: SLOP_RULES_URI, - mimeType: "application/json", - text: JSON.stringify(buildSlopRulesDocument(), null, 2), - }, - ], - }), - ); - return server; } diff --git a/src/review/slop-rules-taxonomy.ts b/src/review/slop-rules-taxonomy.ts deleted file mode 100644 index cbcbb1384b..0000000000 --- a/src/review/slop-rules-taxonomy.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { ISSUE_SLOP_WEIGHTS, SLOP_WEIGHTS, type SlopBand } from "../signals/slop"; - -/** MCP resource URI for the deterministic slop-rule catalog (#2237). */ -export const SLOP_RULES_URI = "gittensory://slop-rules" as const; - -export interface SlopBandRange { - band: SlopBand; - /** Inclusive slopRisk (0-100) range that maps to this band. */ - range: string; -} - -export interface SlopRuleEntry { - /** Deterministic signal code — a key of SLOP_WEIGHTS / ISSUE_SLOP_WEIGHTS. */ - code: string; - /** slopRisk points this signal contributes when it fires. */ - weight: number; -} - -export interface SlopRulesDocument { - bands: readonly SlopBandRange[]; - pullRequestRules: readonly SlopRuleEntry[]; - issueRules: readonly SlopRuleEntry[]; -} - -// The fixed score bands, matching slopBandFor's documented cut-points in src/signals/slop.ts (clean=0, -// low=1-30, elevated=31-59, high=60-100). Typed as SlopBand so a renamed or removed band fails the build -// here rather than drifting silently from the detector's own union. -const SLOP_BANDS: readonly SlopBandRange[] = [ - { band: "clean", range: "0" }, - { band: "low", range: "1-30" }, - { band: "elevated", range: "31-59" }, - { band: "high", range: "60-100" }, -]; - -/** Project the deterministic slop-signal weight maps (SLOP_WEIGHTS + ISSUE_SLOP_WEIGHTS — the single source - * of truth in src/signals/slop.ts) into a static, read-only catalog of rule codes, their point weights, and - * the score bands, so an agent can pre-plan against the detector without triggering a scoring call. Codes - * and weights are read straight off the const maps (no duplicated literals); adding a signal there surfaces - * it here automatically. */ -export function buildSlopRulesDocument(): SlopRulesDocument { - return { - bands: SLOP_BANDS, - pullRequestRules: Object.entries(SLOP_WEIGHTS).map(([code, weight]) => ({ code, weight })), - issueRules: Object.entries(ISSUE_SLOP_WEIGHTS).map(([code, weight]) => ({ code, weight })), - }; -} diff --git a/test/unit/mcp-discovery.test.ts b/test/unit/mcp-discovery.test.ts index 9757759f6b..58f9a6b3f5 100644 --- a/test/unit/mcp-discovery.test.ts +++ b/test/unit/mcp-discovery.test.ts @@ -97,6 +97,12 @@ describe("MCP resource discovery", () => { } }); + it("does not expose deterministic slop weights as a discoverable resource", async () => { + const { resources } = await client.listResources(); + const uris = resources.map((r) => r.uri); + expect(uris).not.toContain("gittensory://slop-rules"); + }); + it("can read the changelog resource without authentication", async () => { const result = await client.readResource({ uri: "gittensory://changelog" }); expect(result.contents).toHaveLength(1); diff --git a/test/unit/mcp-slop-rules.test.ts b/test/unit/mcp-slop-rules.test.ts deleted file mode 100644 index 96776da534..0000000000 --- a/test/unit/mcp-slop-rules.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; -import { describe, expect, it } from "vitest"; -import { GittensoryMcp } from "../../src/mcp/server"; -import { SLOP_RULES_URI } from "../../src/review/slop-rules-taxonomy"; -import { ISSUE_SLOP_WEIGHTS, SLOP_WEIGHTS } from "../../src/signals/slop"; -import { createTestEnv } from "../helpers/d1"; - -async function connectTestClient() { - const mcpServer = new GittensoryMcp(createTestEnv()).createServer(); - const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); - await mcpServer.connect(serverTransport); - const client = new Client({ name: "gittensory-slop-rules-test", version: "0.1.0" }, { capabilities: {} }); - await client.connect(clientTransport); - return { client, mcpServer }; -} - -describe("MCP slop-rules resource (#2237)", () => { - it("discovers the slop-rules resource", async () => { - const { client } = await connectTestClient(); - const { resources } = await client.listResources(); - expect(resources.map((r) => r.uri)).toContain(SLOP_RULES_URI); - }); - - it("returns every rule code and score band as JSON", async () => { - const { client } = await connectTestClient(); - const result = await client.readResource({ uri: SLOP_RULES_URI }); - expect(result.contents).toHaveLength(1); - const content = result.contents[0]; - expect(content?.mimeType).toBe("application/json"); - if (!content || !("text" in content)) throw new Error("expected text content"); - const body = JSON.parse(content.text ?? "") as { - bands: Array<{ band: string; range: string }>; - pullRequestRules: Array<{ code: string; weight: number }>; - issueRules: Array<{ code: string; weight: number }>; - }; - const prCodes = body.pullRequestRules.map((rule) => rule.code); - for (const code of Object.keys(SLOP_WEIGHTS)) expect(prCodes).toContain(code); - const issueCodes = body.issueRules.map((rule) => rule.code); - for (const code of Object.keys(ISSUE_SLOP_WEIGHTS)) expect(issueCodes).toContain(code); - expect(body.bands.map((band) => band.band)).toEqual(["clean", "low", "elevated", "high"]); - }); -}); diff --git a/test/unit/slop-rules-taxonomy.test.ts b/test/unit/slop-rules-taxonomy.test.ts deleted file mode 100644 index 1c0832ec41..0000000000 --- a/test/unit/slop-rules-taxonomy.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { buildSlopRulesDocument, SLOP_RULES_URI } from "../../src/review/slop-rules-taxonomy"; -import { ISSUE_SLOP_WEIGHTS, SLOP_WEIGHTS } from "../../src/signals/slop"; - -describe("slop-rules taxonomy document (#2237)", () => { - it("projects every SLOP_WEIGHTS code + weight into pullRequestRules (single source of truth)", () => { - const doc = buildSlopRulesDocument(); - const expected = Object.entries(SLOP_WEIGHTS); - expect(doc.pullRequestRules).toHaveLength(expected.length); - for (const [code, weight] of expected) { - expect(doc.pullRequestRules).toContainEqual({ code, weight }); - } - }); - - it("projects every ISSUE_SLOP_WEIGHTS code + weight into issueRules", () => { - const doc = buildSlopRulesDocument(); - const expected = Object.entries(ISSUE_SLOP_WEIGHTS); - expect(doc.issueRules).toHaveLength(expected.length); - for (const [code, weight] of expected) { - expect(doc.issueRules).toContainEqual({ code, weight }); - } - }); - - it("enumerates the four score bands with their ranges", () => { - const doc = buildSlopRulesDocument(); - expect(doc.bands.map((b) => b.band)).toEqual(["clean", "low", "elevated", "high"]); - expect(doc.bands).toContainEqual({ band: "elevated", range: "31-59" }); - }); - - it("exposes the stable resource URI", () => { - expect(SLOP_RULES_URI).toBe("gittensory://slop-rules"); - }); -});