From 7f1cfe1983c07cf0c97a1eccee17a4819b273a68 Mon Sep 17 00:00:00 2001 From: monsterdavidliu-ux Date: Sun, 31 May 2026 14:02:39 +0000 Subject: [PATCH] docs(ci): add coverage buffer and contributor test-quality guidance Document the 95% global coverage gate and 96%+ local branch buffer in CONTRIBUTING and the submission checklist. Require test scenarios and public/private safety boundaries in feature issues. Enforce contributor testing phrases via scripts/check-docs.mjs. Keep Cursor in init-client and MCP setup docs; add --print mcp as a compatibility alias for generic JSON MCP hosts alongside cursor. Fixes #33 --- .github/ISSUE_TEMPLATE/feature_request.yml | 8 +++++++ .github/pull_request_template.md | 2 ++ CONTRIBUTING.md | 23 ++++++++++--------- packages/gittensory-mcp/bin/gittensory-mcp.js | 10 ++++---- scripts/check-docs.mjs | 15 ++++++++++++ site/guide/mcp.md | 2 ++ test/unit/mcp-cli.test.ts | 6 +++++ 7 files changed, 50 insertions(+), 16 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 192dada787..0620a0bf88 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -18,6 +18,13 @@ body: description: Describe the backend behavior, signal, or API shape. validations: required: true + - type: textarea + id: test_scenarios + attributes: + label: Test scenarios + description: List unit/integration cases for new branches, fallback paths, sanitizer rules, and regressions. Call out public/private safety boundaries if GitHub comments or MCP output changes. + validations: + required: true - type: checkboxes id: boundaries attributes: @@ -27,3 +34,4 @@ body: - label: This does not require storing user PATs. - label: This does not expose wallet details, raw trust scores, or private rankings publicly. - label: This does not auto-close, auto-merge, rewrite contributor work, or label PRs outside the confirmed-miner policy. + - label: Public GitHub output will be tested against forbidden language (wallet, hotkey, raw trust score, payout, reward estimate, farming, private reviewability, public score estimate). diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 30162ecf3d..0c128afcd0 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -5,6 +5,8 @@ ## Validation - [ ] `npm run test:ci` +- [ ] `npm run test:coverage` locally; global coverage stays at or above **95%** for lines, statements, functions, and branches (aim for **96%+** branch coverage locally so CI variance does not fail near the threshold) +- [ ] New or changed behavior has unit/integration tests for new branches, fallback paths, and sanitizer boundaries - [ ] Changelog updated only if this is a release-prep change ## Safety diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 543ba4f68e..080bc412f1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,19 +25,20 @@ Out of scope: ## Quality Bar -- Run `npm run test:ci` before opening a PR. -- Add or update tests for behavior changes. +- Run `npm run test:ci` before submitting changes. +- Run `npm run test:coverage` locally when you change behavior. CI enforces **95%** global coverage for lines, statements, functions, and branches. +- Aim for **96%+ branch coverage** locally so small CI variance does not fail near the threshold. +- Add or update tests for every behavior change: new branches, fallback paths, sanitizer rules, and regressions. +- Add invariant or property-style tests when behavior depends on sorting, gating, public/private boundaries, scoring, queue pressure, or source-upload safety. +- Keep changes backend-only. +- Tests must cover new behavior or regressions, including fallback paths and sanitizer boundaries when touched. +- Public GitHub comments must be tested against forbidden language when comment text changes (wallet, hotkey, raw trust score, payout, reward estimate, farming, private reviewability, public score estimate). - Keep API and MCP responses structured and machine-readable. - Keep public GitHub comments advisory, sanitized, and non-spammy. - Keep GitHub App labels limited to configured labels for officially confirmed Gittensor miner PRs. +- Public surfaces must not expose secrets, wallet details, raw trust scores, or private rankings. +- Public text must avoid compensation-seeking or optimization-tactic language. +- OpenAPI and MCP schemas must stay aligned with behavior. - Prefer deterministic, evidence-based rules over opaque scoring. - Use Conventional Commit style for release-quality changelog output. -- Do not update changelogs in ordinary PRs unless the PR is explicitly preparing a release. - -## Pull Request Checklist - -- The change is backend-only. -- Tests cover the new behavior or regression. -- Public surfaces do not expose secrets, wallet details, raw trust scores, or private rankings. -- Public text avoids compensation-seeking or optimization-tactic language. -- OpenAPI and MCP schemas stay aligned with behavior. +- Do not update changelogs unless you are explicitly preparing a release. diff --git a/packages/gittensory-mcp/bin/gittensory-mcp.js b/packages/gittensory-mcp/bin/gittensory-mcp.js index c71bc77b80..f206789aea 100755 --- a/packages/gittensory-mcp/bin/gittensory-mcp.js +++ b/packages/gittensory-mcp/bin/gittensory-mcp.js @@ -592,7 +592,7 @@ function printHelp() { gittensory-mcp status [--json] gittensory-mcp changelog [--json] gittensory-mcp doctor [--cwd path] [--json] - gittensory-mcp init-client --print codex|claude|cursor [--json] + gittensory-mcp init-client --print codex|claude|cursor|mcp [--json] gittensory-mcp analyze-branch --login [--repo owner/repo] [--base origin/main] [--pending-merged-prs 3] [--expected-open-prs 0] [--projected-credibility 0.8] [--scenario-note "..."] [--validation "passed|npm test|summary"] [--json] gittensory-mcp preflight --login [--repo owner/repo] [--base origin/main] [--pending-merged-prs 3] [--expected-open-prs 0] [--projected-credibility 0.8] [--validation "passed|npm test|summary"] [--json] gittensory-mcp agent plan --login [--repo owner/repo] [--json] @@ -842,7 +842,7 @@ async function doctor(options) { const commandPath = findExecutable("gittensory-mcp"); if (commandPath) add("client_path", "pass", "gittensory-mcp is visible on PATH."); - else add("client_path", "warn", "gittensory-mcp was not found on PATH.", "Use an absolute command path in Codex, Claude, or MCP client config."); + else add("client_path", "warn", "gittensory-mcp was not found on PATH.", "Use an absolute command path in your MCP client config."); const scorerCommand = resolveScorePreviewCommand(); if (!scorerCommand) { @@ -887,7 +887,7 @@ async function doctor(options) { function initClient(options) { const client = String(options.print ?? options.client ?? "").toLowerCase(); - if (!client) throw new Error("Pass --print codex, --print claude, or --print cursor."); + if (!client) throw new Error("Pass --print codex, --print claude, --print cursor, or --print mcp."); const command = options.command ?? "gittensory-mcp"; const snippet = clientSnippet(client, command); const payload = { @@ -1057,7 +1057,7 @@ function redactPrivateValidationMetrics(text) { function clientSnippet(client, command) { if (client === "codex") return `[mcp_servers.gittensory]\ncommand = ${JSON.stringify(command)}\nargs = ["--stdio"]`; - if (client === "claude" || client === "cursor") { + if (client === "claude" || client === "cursor" || client === "mcp") { return JSON.stringify( { mcpServers: { @@ -1071,7 +1071,7 @@ function clientSnippet(client, command) { 2, ); } - throw new Error(`Unsupported client: ${client}. Use codex, claude, or cursor.`); + throw new Error(`Unsupported client: ${client}. Use codex, claude, cursor, or mcp.`); } function findExecutable(name) { diff --git a/scripts/check-docs.mjs b/scripts/check-docs.mjs index 935e644af9..ce9f51f5c0 100644 --- a/scripts/check-docs.mjs +++ b/scripts/check-docs.mjs @@ -58,6 +58,21 @@ for (const required of ["SUPPORT.md", "site/security/privacy.md", "site/security } } +const contributing = readFileSync(join(root, "CONTRIBUTING.md"), "utf8"); +for (const phrase of ["npm run test:ci", "npm run test:coverage", "95%", "96%+ branch coverage"]) { + if (!contributing.includes(phrase)) failures.push(`CONTRIBUTING.md: missing contributor testing phrase ${JSON.stringify(phrase)}`); +} + +const submissionTemplate = readFileSync(join(root, ".github/pull_request_template.md"), "utf8"); +for (const phrase of ["npm run test:ci", "npm run test:coverage", "95%", "sanitizer boundaries"]) { + if (!submissionTemplate.includes(phrase)) failures.push(`.github/pull_request_template.md: missing submission checklist phrase ${JSON.stringify(phrase)}`); +} + +const featureTemplate = readFileSync(join(root, ".github/ISSUE_TEMPLATE/feature_request.yml"), "utf8"); +for (const phrase of ["Test scenarios", "public/private safety", "forbidden language"]) { + if (!featureTemplate.includes(phrase)) failures.push(`.github/ISSUE_TEMPLATE/feature_request.yml: missing feature template phrase ${JSON.stringify(phrase)}`); +} + if (failures.length > 0) { console.error(failures.join("\n")); process.exit(1); diff --git a/site/guide/mcp.md b/site/guide/mcp.md index 2cd2b29101..815f667f26 100644 --- a/site/guide/mcp.md +++ b/site/guide/mcp.md @@ -25,6 +25,8 @@ gittensory-mcp init-client --print claude gittensory-mcp init-client --print cursor ``` +`--print mcp` prints the same JSON snippet for other stdio MCP hosts that use the `mcpServers` shape. + These commands print config only. They do not mutate your local client files. ## Codex diff --git a/test/unit/mcp-cli.test.ts b/test/unit/mcp-cli.test.ts index 92db7cd002..e853ee4356 100644 --- a/test/unit/mcp-cli.test.ts +++ b/test/unit/mcp-cli.test.ts @@ -26,6 +26,12 @@ describe("gittensory-mcp CLI", () => { const claude = JSON.parse(run(["init-client", "--print", "claude", "--json"])) as { snippet: string }; expect(claude.snippet).toContain('"mcpServers"'); expect(claude.snippet).toContain('"gittensory"'); + + const cursor = JSON.parse(run(["init-client", "--print", "cursor", "--json"])) as { snippet: string }; + expect(cursor.snippet).toBe(claude.snippet); + + const generic = JSON.parse(run(["init-client", "--print", "mcp", "--json"])) as { snippet: string }; + expect(generic.snippet).toBe(claude.snippet); }); it("runs doctor against a local health/session fixture", async () => {