From b8c7409371853e62e7f5a302f35e5f79d107b9a4 Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Sun, 14 Jun 2026 13:09:01 -0700 Subject: [PATCH 1/2] feat(mcp): slop-risk + issue-slop self-check tools in the npm package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Surface the deterministic slop self-checks (already live on the hosted /mcp endpoint) to npm-package (stdio) users: - New REST routes /v1/lint/slop-risk + /v1/lint/issue-slop mirroring the gittensory_check_slop_risk / gittensory_check_issue_slop MCP tools — pure local-metadata (paths + line counts / issue title+body), no repo data or secrets, so they sit in the session path-allowlist like /v1/lint/pr-text. Each returns slopRisk/band/findings + the rubric. - Register both tools in the package bin (calling the new routes). - Integration coverage: happy path, schema-invalid, malformed-JSON, and session-token access for both routes. --- packages/gittensory-mcp/bin/gittensory-mcp.js | 35 ++++++++++++++++ src/api/routes.ts | 36 ++++++++++++++++- test/integration/api.test.ts | 40 +++++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) diff --git a/packages/gittensory-mcp/bin/gittensory-mcp.js b/packages/gittensory-mcp/bin/gittensory-mcp.js index 37308b3da1..d79ad689bb 100755 --- a/packages/gittensory-mcp/bin/gittensory-mcp.js +++ b/packages/gittensory-mcp/bin/gittensory-mcp.js @@ -145,6 +145,21 @@ const lintPrTextShape = { linkedIssue: z.number().int().positive().optional(), }; +const checkSlopRiskShape = { + changedFiles: z + .array(z.object({ path: z.string().min(1).max(400), additions: z.number().int().min(0).optional(), deletions: z.number().int().min(0).optional() })) + .max(2000) + .optional(), + description: z.string().max(20000).optional(), + tests: z.array(z.string().max(400)).max(2000).optional(), + testFiles: z.array(z.string().max(400)).max(2000).optional(), +}; + +const checkIssueSlopShape = { + title: z.string().max(500).optional(), + body: z.string().max(40000).optional(), +}; + const preflightShape = { repoFullName: z.string().min(3), contributorLogin: z.string().min(1).optional(), @@ -330,6 +345,26 @@ server.registerTool( async (input) => toolResult("Gittensory PR-text lint.", await apiPost("/v1/lint/pr-text", input)), ); +server.registerTool( + "gittensory_check_slop_risk", + { + description: + "Assess the deterministic slop risk of a planned change from local diff metadata (paths + line counts) + the PR description — an agent-native, source-free quality self-check. Returns slopRisk (0-100), band, findings, and the rubric. No repo data needed.", + inputSchema: checkSlopRiskShape, + }, + async (input) => toolResult("Gittensory slop-risk self-check.", await apiPost("/v1/lint/slop-risk", input)), +); + +server.registerTool( + "gittensory_check_issue_slop", + { + 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.", + inputSchema: checkIssueSlopShape, + }, + async (input) => toolResult("Gittensory issue-slop self-check.", await apiPost("/v1/lint/issue-slop", input)), +); + server.registerTool( "gittensory_preflight_local_diff", { diff --git a/src/api/routes.ts b/src/api/routes.ts index b2ec5e23f7..d89041e58b 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -198,6 +198,7 @@ import { attachDataQuality, buildCoreSignalFidelity, buildFreshnessSloReport, bu import { buildContributorOpenPrMonitor } from "../signals/contributor-open-pr-monitor"; import { buildPullRequestReviewability, type PullRequestReviewability } from "../signals/reward-risk"; import { buildLocalBranchAnalysis, findCurrentBranchPullRequest } from "../signals/local-branch"; +import { buildSlopAssessment, buildIssueSlopAssessment, SLOP_RUBRIC_MARKDOWN, ISSUE_SLOP_RUBRIC_MARKDOWN } from "../signals/slop"; import { buildPredictedGateVerdict } from "../rules/predicted-gate"; import { buildMaintainerActivationPreview, recommendedAdvisoryActivationSettings } from "../services/maintainer-activation"; import { buildRepoOutcomeCalibration } from "../services/outcome-calibration"; @@ -361,6 +362,22 @@ const lintPrTextSchema = z.object({ linkedIssue: z.number().int().positive().optional(), }); +// Pure local-metadata slop self-checks (no repo data, no secrets) — mirror the gittensory_check_slop_risk / +// gittensory_check_issue_slop MCP tools so the npm package can offer the same agent-native self-check. +const slopRiskSchema = z.object({ + changedFiles: z + .array(z.object({ path: z.string().min(1).max(400), additions: z.number().int().min(0).optional(), deletions: z.number().int().min(0).optional() })) + .max(2000) + .optional(), + description: z.string().max(20000).optional(), + tests: z.array(z.string().max(400)).max(2000).optional(), + testFiles: z.array(z.string().max(400)).max(2000).optional(), +}); +const issueSlopSchema = z.object({ + title: z.string().max(500).optional(), + body: z.string().max(40000).optional(), +}); + const skippedPrAuditQuerySchema = z .object({ limit: z.coerce.number().int().optional(), @@ -2113,6 +2130,21 @@ export function createApp() { return c.json(buildPrTextLint(parsed.data)); }); + // Agent-native slop self-checks (#530/#533): pure local-metadata, mirroring the MCP tools of the same name. + app.post("/v1/lint/slop-risk", async (c) => { + const body = await c.req.json().catch(() => null); + const parsed = slopRiskSchema.safeParse(body); + if (!parsed.success) return c.json({ error: "invalid_slop_risk_request", issues: parsed.error.issues }, 400); + return c.json({ ...buildSlopAssessment(parsed.data), rubric: SLOP_RUBRIC_MARKDOWN }); + }); + + app.post("/v1/lint/issue-slop", async (c) => { + const body = await c.req.json().catch(() => null); + const parsed = issueSlopSchema.safeParse(body); + if (!parsed.success) return c.json({ error: "invalid_issue_slop_request", issues: parsed.error.issues }, 400); + return c.json({ ...buildIssueSlopAssessment(parsed.data), rubric: ISSUE_SLOP_RUBRIC_MARKDOWN }); + }); + app.post("/v1/preflight/pr", async (c) => { const body = await c.req.json().catch(() => null); const parsed = preflightSchema.safeParse(body); @@ -3973,6 +4005,8 @@ function contributorEvidenceFromProfile(profile: { const EXTENSION_PULL_CONTEXT_PATH = "/v1/extension/pull-context"; const EXTENSION_PULL_CONTEXT_SCOPE = "extension:pull_context"; const LINT_PR_TEXT_PATH = "/v1/lint/pr-text"; +const LINT_SLOP_RISK_PATH = "/v1/lint/slop-risk"; +const LINT_ISSUE_SLOP_PATH = "/v1/lint/issue-slop"; type ProtectedRouteContext = { env: Env; @@ -4013,7 +4047,7 @@ function canSessionAccessPath(env: Env, identity: Extract { const invalidLintPrText = await app.request("/v1/lint/pr-text", { method: "POST", headers: apiHeaders(env), body: JSON.stringify({ linkedIssue: -1 }) }, env); expect(invalidLintPrText.status).toBe(400); + // Agent-native slop self-checks (mirror the gittensory_check_slop_risk / gittensory_check_issue_slop MCP tools). + const slopRisk = await app.request( + "/v1/lint/slop-risk", + { method: "POST", headers: apiHeaders(env), body: JSON.stringify({ changedFiles: [{ path: "src/widget.ts", additions: 80, deletions: 2 }], description: "" }) }, + env, + ); + expect(slopRisk.status).toBe(200); + const slopRiskBody = await slopRisk.json(); + expect(slopRiskBody).toMatchObject({ slopRisk: expect.any(Number), band: expect.stringMatching(/clean|low|elevated|high/), findings: expect.any(Array), rubric: expect.any(String) }); + expect(JSON.stringify(slopRiskBody)).not.toMatch(/hotkey|coldkey|wallet|payout|reward/i); + + // Session identities (not just the API token) can reach it — it is allowlisted like the other local self-checks. + const { token: slopSessionToken } = await createSessionForGitHubUser(env, { login: "slop-mcp-user", id: 4343 }); + const sessionSlopRisk = await app.request( + "/v1/lint/slop-risk", + { method: "POST", headers: { authorization: `Bearer ${slopSessionToken}`, "content-type": "application/json" }, body: JSON.stringify({ changedFiles: [] }) }, + env, + ); + expect(sessionSlopRisk.status).toBe(200); + + const invalidSlopRisk = await app.request("/v1/lint/slop-risk", { method: "POST", headers: apiHeaders(env), body: JSON.stringify({ changedFiles: [{ path: "", additions: -1 }] }) }, env); + expect(invalidSlopRisk.status).toBe(400); + + const issueSlop = await app.request( + "/v1/lint/issue-slop", + { method: "POST", headers: apiHeaders(env), body: JSON.stringify({ title: "Add retries", body: "" }) }, + env, + ); + expect(issueSlop.status).toBe(200); + await expect(issueSlop.json()).resolves.toMatchObject({ slopRisk: expect.any(Number), band: expect.stringMatching(/clean|low|elevated|high/), rubric: expect.any(String) }); + + const invalidIssueSlop = await app.request("/v1/lint/issue-slop", { method: "POST", headers: apiHeaders(env), body: JSON.stringify({ title: 123 }) }, env); + expect(invalidIssueSlop.status).toBe(400); + + // Malformed (unparseable) JSON bodies fall through the catch to a 400, not a 500. + const malformedSlopRisk = await app.request("/v1/lint/slop-risk", { method: "POST", headers: apiHeaders(env), body: "{not json" }, env); + expect(malformedSlopRisk.status).toBe(400); + const malformedIssueSlop = await app.request("/v1/lint/issue-slop", { method: "POST", headers: apiHeaders(env), body: "{not json" }, env); + expect(malformedIssueSlop.status).toBe(400); + const queueIntelligence = await app.request( "/v1/internal/queue-intelligence", { From 4dc0c17b6504e7f2ca46cf3ae7b08da3adaa0a90 Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Sun, 14 Jun 2026 13:14:45 -0700 Subject: [PATCH 2/2] chore(release): @jsonbored/gittensory-mcp v0.6.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cut the 0.6.0 release (last published 0.5.0, 2026-06-12). Brings npm-package (stdio) users up to date with the miner-facing tools added since: the new slop self-checks plus check_before_start, validate_linked_issue, lint_pr_text, output-schema hardening on every tool, and the scope/bound fixes — see packages/gittensory-mcp/CHANGELOG.md (generated). - packages/gittensory-mcp/package.json: 0.5.0 -> 0.6.0 - CHANGELOG.md: generated mcp-v0.6.0 section - version pins bumped to latest=0.6.0 (minimum stays 0.5.0 — minor, backward compatible): apps/gittensory-ui/src/lib/mcp-package.ts + src/services/mcp-compatibility.ts (the /v1/mcp/compatibility surface) - api.test.ts: assert latestRecommended/latestPackage = 0.6.0 Release-candidate gate (npm run mcp:release-candidate) PASS on all checks (tag<->version, changelog section, tarball allowlist + secret scan, CLI smoke, tokenless trusted publishing). Tag mcp-v0.6.0 triggers the publish. --- apps/gittensory-ui/src/lib/mcp-package.ts | 2 +- packages/gittensory-mcp/CHANGELOG.md | 36 +++++++++++++++++++++++ packages/gittensory-mcp/package.json | 2 +- src/services/mcp-compatibility.ts | 2 +- test/integration/api.test.ts | 10 +++---- 5 files changed, 44 insertions(+), 8 deletions(-) diff --git a/apps/gittensory-ui/src/lib/mcp-package.ts b/apps/gittensory-ui/src/lib/mcp-package.ts index 9315c51341..e09d6e228a 100644 --- a/apps/gittensory-ui/src/lib/mcp-package.ts +++ b/apps/gittensory-ui/src/lib/mcp-package.ts @@ -6,7 +6,7 @@ export const MCP_PACKAGE_NAME = "@jsonbored/gittensory-mcp"; export const MCP_PACKAGE_ENCODED_NAME = "@jsonbored%2fgittensory-mcp"; export const MCP_PACKAGE_REGISTRY_URL = `https://registry.npmjs.org/${MCP_PACKAGE_ENCODED_NAME}`; export const MCP_PACKAGE_NPM_URL = `https://www.npmjs.com/package/${MCP_PACKAGE_NAME}`; -export const MCP_PACKAGE_KNOWN_LATEST_VERSION = "0.5.0"; +export const MCP_PACKAGE_KNOWN_LATEST_VERSION = "0.6.0"; export const MCP_MINIMUM_SUPPORTED_VERSION = "0.5.0"; export type NpmPackageMetadata = { diff --git a/packages/gittensory-mcp/CHANGELOG.md b/packages/gittensory-mcp/CHANGELOG.md index 3b94d6da37..c095c4d8c0 100644 --- a/packages/gittensory-mcp/CHANGELOG.md +++ b/packages/gittensory-mcp/CHANGELOG.md @@ -1,5 +1,41 @@ # Changelog +## mcp-v0.6.0 - 2026-06-14 + +### Features +- Add missing test evidence slop signal (#616) +- Gittensory_validate_linked_issue linked-issue multiplier validator (#622) +- Gittensory_check_before_start pre-start duplicate/solvability check (#621) +- Authoritative .gittensory.yml gate config (config-as-code) (#647) +- Dual-AI maintainer review + BYOK (Phase C) (#652) +- Config-as-code provider/model + maintainer self-serve BYOK routes (#664) +- Event→subscription→delivery service + MCP badge feed (closes #536, advances #535) (#707) +- Policy-pack-pluggable gate — gittensor + oss-anti-slop packs (#692) (#710) +- Agent-native pre-submission self-check as a general product (#693) (#712) +- Surface the earn-on-Gittensor path for oss-anti-slop adopters (#694) (#713) +- Miner-facing post-merge reward & outcome attribution (#702) (#714) +- Wire + modernize the deterministic slop score into the gate, context & MCP (#530/#531/#532) (#716) +- AI-assisted advisory slop layer (advisory-only, never blocks) (#724) +- Issue-side slop triage (#533) (#729) +- Model upstream time-decay, applied behind a default-off flag (#703) (#731) +- Gittensory_lint_pr_text commit/PR-body rubric linter (#634) +- Per-repo time-decay hyperparameters + go live (#703) (#733) +- Active issue-watch monitor — gittensory_watch_issues (#699 path B) (#735) +- Slop-risk + issue-slop self-check tools in the npm package + +### Fixes +- Bound local branch scenario inputs (#614) +- Harden manifest public-safe filter (#659) +- Enforce repo scope for gate prediction (#717) +- Bound mark-read ids (#718) +- Visibility-aware access gate for subscriptions + fan-out (#742) + +### Refactors +- Shared public-safe redaction module (#542) (#743) + +### Chores +- Add outputSchema to every tool that lacks it (#637) + ## mcp-v0.5.0 - 2026-06-12 ### Features diff --git a/packages/gittensory-mcp/package.json b/packages/gittensory-mcp/package.json index 5afbd18a76..7169e2872b 100644 --- a/packages/gittensory-mcp/package.json +++ b/packages/gittensory-mcp/package.json @@ -1,6 +1,6 @@ { "name": "@jsonbored/gittensory-mcp", - "version": "0.5.0", + "version": "0.6.0", "license": "AGPL-3.0-only", "type": "module", "description": "Local stdio MCP wrapper for the Gittensory Gittensor base-agent.", diff --git a/src/services/mcp-compatibility.ts b/src/services/mcp-compatibility.ts index cf3e8f01a8..12e38a9bfb 100644 --- a/src/services/mcp-compatibility.ts +++ b/src/services/mcp-compatibility.ts @@ -1,7 +1,7 @@ export const GITTENSORY_API_VERSION = "0.1.0"; export const GITTENSORY_MCP_PACKAGE_NAME = "@jsonbored/gittensory-mcp"; export const MINIMUM_SUPPORTED_MCP_VERSION = "0.5.0"; -export const LATEST_RECOMMENDED_MCP_VERSION = "0.5.0"; +export const LATEST_RECOMMENDED_MCP_VERSION = "0.6.0"; export type McpCompatibilityStatus = "current" | "stale" | "incompatible" | "unknown"; diff --git a/test/integration/api.test.ts b/test/integration/api.test.ts index b8286f2b36..faad53c53e 100644 --- a/test/integration/api.test.ts +++ b/test/integration/api.test.ts @@ -88,7 +88,7 @@ describe("api routes", () => { status: "ok", service: "gittensory-api", minMcpVersion: "0.5.0", - latestRecommendedMcpVersion: "0.5.0", + latestRecommendedMcpVersion: "0.6.0", }); const compatibility = await app.request("/v1/mcp/compatibility", {}, env); @@ -101,8 +101,8 @@ describe("api routes", () => { mcp: { packageName: "@jsonbored/gittensory-mcp", minimumSupportedVersion: "0.5.0", - latestRecommendedVersion: "0.5.0", - latestPackageVersion: "0.5.0", + latestRecommendedVersion: "0.6.0", + latestPackageVersion: "0.6.0", }, compatibilityWarnings: [], breakingChanges: [], @@ -3438,7 +3438,7 @@ describe("api routes", () => { expect(mcpCompatibilityBody).toMatchObject({ adoption: expect.objectContaining({ minimumSupportedVersion: "0.5.0", - latestRecommendedVersion: "0.5.0", + latestRecommendedVersion: "0.6.0", staleEvents: 1, incompatibleEvents: 3, totalEvents: 4, @@ -5147,7 +5147,7 @@ describe("api routes", () => { protocolVersion: "2025-03-26", compatibilityStatus: "incompatible", minimumSupportedVersion: "0.5.0", - latestRecommendedVersion: "0.5.0", + latestRecommendedVersion: "0.6.0", }), }), ]),