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
9 changes: 9 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
4 changes: 2 additions & 2 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -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:

Expand Down
15 changes: 13 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down
14 changes: 14 additions & 0 deletions apps/gittensory-ui/src/routes/docs.mcp-clients.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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."
>
<h2>Generate config</h2>
<p>These commands print config only. They do not mutate your local client files.</p>
<CodeBlock
lang="bash"
code={`gittensory-mcp init-client --print codex
gittensory-mcp init-client --print claude
gittensory-mcp init-client --print cursor
gittensory-mcp init-client --print mcp`}
/>
<p>
<code>--print mcp</code> uses the same JSON snippet as Claude Desktop and Cursor for other
stdio MCP hosts that expect the <code>mcpServers</code> shape.
</p>

<h2>Codex (OpenAI)</h2>
<CodeBlock
filename="~/.codex/config.toml"
Expand Down
10 changes: 5 additions & 5 deletions packages/gittensory-mcp/bin/gittensory-mcp.js
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,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 <github-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 <github-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 <github-login> [--repo owner/repo] [--json]
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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: {
Expand All @@ -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) {
Expand Down
6 changes: 6 additions & 0 deletions test/unit/mcp-cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down