diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index d004dde944..8912a7284d 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -49,6 +49,13 @@ body: - ... 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: @@ -62,3 +69,5 @@ body: required: true - label: This does not auto-close, auto-merge, rewrite contributor work, or label PRs outside the confirmed-miner policy. required: true + - label: Public GitHub output will be tested against forbidden language (wallet, hotkey, raw trust score, payout, reward estimate, farming, private reviewability, public score estimate). + required: true diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 52bbf8c5d7..41f810604c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -13,7 +13,7 @@ - [ ] `git diff --check` - [ ] `npm run actionlint` - [ ] `npm run typecheck` -- [ ] `npm run test:coverage` +- [ ] `npm run test:coverage` locally; global coverage stays at or above **97%** for lines, statements, functions, and branches (aim for **98%+** branch coverage locally so CI variance does not fail near the threshold) - [ ] `npm run test:workers` - [ ] `npm run build:mcp` - [ ] `npm run test:mcp-pack` @@ -22,7 +22,7 @@ - [ ] `npm run ui:typecheck` - [ ] `npm run ui:build` - [ ] `npm audit --audit-level=moderate` -- [ ] Coverage remains at or above 97% for statements, branches, functions, and lines. +- [ ] New or changed behavior has unit/integration tests for new branches, fallback paths, and sanitizer boundaries If any required check was skipped, explain why: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4ab3d5a3a7..0324294051 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -86,8 +86,10 @@ npm run ui:build npm audit --audit-level=moderate ``` -`npm run test:ci` runs the normal combined gate. Coverage must stay at or above 97% for -statements, branches, functions, and lines. +`npm run test:ci` runs the normal combined gate. Coverage must stay at or above **97%** for +statements, branches, functions, and lines. Run `npm run test:coverage` locally when you change +behavior, and aim for **98%+ branch coverage** locally so small CI variance does not fail near the +threshold. Maintainer/release smoke checks: @@ -103,6 +105,13 @@ The browser smoke path is manual until it is stable enough to make required on e Tests should prove behavior, not just exercise lines for coverage. +- 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. +- 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). - Backend/API tests should cover success, denied, invalid input, missing auth, scoped auth, rate-limit, persistence, and error-shaping paths. - Auth tests should cover browser cookie sessions, bearer sessions, logout/revocation, GitHub @@ -146,6 +155,8 @@ MCP: - Preserve backwards compatibility where practical. - Keep CLI output stable and JSON output parseable. +- `gittensory-mcp init-client --print` supports `codex`, `claude`, `cursor`, and `mcp` (generic + JSON hosts that use the `mcpServers` shape). - MCP package releases are prepared separately and published from protected `mcp-vX.Y.Z` tags. Public GitHub surfaces: diff --git a/apps/gittensory-ui/src/routes/docs.mcp-clients.tsx b/apps/gittensory-ui/src/routes/docs.mcp-clients.tsx index 7178d0c75c..6dd633c603 100644 --- a/apps/gittensory-ui/src/routes/docs.mcp-clients.tsx +++ b/apps/gittensory-ui/src/routes/docs.mcp-clients.tsx @@ -32,6 +32,20 @@ function McpClients() { title="MCP client setup" description="Configure your coding agent to talk to the Gittensory MCP. Pick stdio for local agents, remote for cloud agents." > +

Generate config

+

These commands print config only. They do not mutate your local client files.

+ +

+ --print mcp uses the same JSON snippet as Claude Desktop and Cursor for other + stdio MCP hosts that expect the mcpServers shape. +

+

Codex (OpenAI)

[--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] @@ -852,7 +852,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) { @@ -897,7 +897,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 = { @@ -1067,7 +1067,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: { @@ -1081,7 +1081,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/test/unit/mcp-cli.test.ts b/test/unit/mcp-cli.test.ts index d22a39f32f..9db886b644 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 () => {