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
35 changes: 35 additions & 0 deletions packages/gittensory-mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,46 @@ The same capabilities are exposed to MCP clients as:
- `GITHUB_TOKEN` for non-interactive login bootstrap
- `GITTENSOR_SCORE_PREVIEW_CMD`
- `GITTENSOR_ROOT`
- `GITTENSOR_SCORE_PREVIEW_TIMEOUT_MS` (default `15000`)
- `GITTENSORY_UPLOAD_SOURCE=false`
- `GITTENSORY_SKIP_NPM_VERSION_CHECK=true`

`GITTENSORY_UPLOAD_SOURCE=true` is not supported and fails closed.

### Local score preview adapter

Branch analysis can call a local scorer command that reads branch metadata JSON from stdin and prints one JSON object to stdout. Gittensory never uploads source contents; the scorer runs on your machine.

Metadata-only fallback is used when the command is missing or fails. Run `gittensory-mcp doctor` for setup diagnostics.

Reference wrappers ship with the package:

```sh
export GITTENSOR_SCORE_PREVIEW_CMD="node $(npm root -g)/@jsonbored/gittensory-mcp/scripts/gittensor-score-preview.mjs"
```

For tree-sitter scoring with a local [entrius/gittensor](https://github.com/entrius/gittensor) checkout:

```sh
export GITTENSOR_ROOT=/path/to/gittensor
export GITTENSOR_SCORE_PREVIEW_CMD="python3 $(npm root -g)/@jsonbored/gittensory-mcp/scripts/gittensor-score-preview.py"
```

Expected stdout shape:

```json
{
"sourceTokenScore": 42,
"totalTokenScore": 58,
"sourceLines": 40,
"testTokenScore": 16,
"nonCodeTokenScore": 0,
"warnings": []
}
```

Snake_case aliases such as `source_token_score` are also accepted.

## Release Notes

The package ships with `CHANGELOG.md`. Run:
Expand Down
39 changes: 35 additions & 4 deletions packages/gittensory-mcp/bin/gittensory-mcp.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { delimiter, dirname, join } from "node:path";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { buildBranchAnalysisPayload, collectLocalDiff, collectLocalBranchMetadata, setupGuidanceForLocalScorer } from "../lib/local-branch.js";
import { buildBranchAnalysisPayload, collectLocalDiff, collectLocalBranchMetadata, probeLocalScorer, referenceScorePreviewExample, resolveScorePreviewCommand, sanitizeLocalScorerStatus, setupGuidanceForLocalScorer } from "../lib/local-branch.js";

const defaultApiUrl = "https://gittensory-api.aethereal.dev";
const legacyDefaultApiUrls = new Set(["https://gittensory-api.zeronode.workers.dev"]);
Expand Down Expand Up @@ -496,6 +496,12 @@ async function runCli(args) {
}
process.stdout.write(`Preflight: ${result.analysis.preflight.status}\n`);
process.stdout.write(`Source upload: disabled\n`);
if (result.local?.localScorerStatus?.ok === false) {
process.stdout.write(`Local scorer: ${result.local.localScorerStatus.code ?? "metadata_only"}\n`);
for (const line of result.local.setupGuidance ?? setupGuidanceForLocalScorer(result.local.localScorerStatus)) {
process.stdout.write(`- ${line}\n`);
}
}
}

async function runAgentCli(args) {
Expand Down Expand Up @@ -599,6 +605,7 @@ Environment:
GITHUB_TOKEN for non-interactive login bootstrap
GITTENSOR_SCORE_PREVIEW_CMD
GITTENSOR_ROOT
GITTENSOR_SCORE_PREVIEW_TIMEOUT_MS
GITTENSORY_UPLOAD_SOURCE=false
`);
}
Expand Down Expand Up @@ -833,7 +840,31 @@ 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 Cursor config.");
else add("client_path", "warn", "gittensory-mcp was not found on PATH.", "Use an absolute command path in Codex, Claude, or MCP client config.");

const scorerCommand = resolveScorePreviewCommand();
if (!scorerCommand) {
add(
"local_scorer",
"warn",
"GITTENSOR_SCORE_PREVIEW_CMD is not configured; branch analysis will fall back to metadata-only scoring.",
`Example: export GITTENSOR_SCORE_PREVIEW_CMD="${referenceScorePreviewExample("metadata")}"`,
);
} else {
const probe = probeLocalScorer(scorerCommand);
if (probe.ok) {
add("local_scorer", "pass", `Configured scorer responded in ${probe.durationMs ?? 0}ms.`);
} else {
const remediation = setupGuidanceForLocalScorer(probe).slice(1).join(" ");
add("local_scorer", "warn", `Configured scorer failed (${probe.code ?? "scorer_failed"}): ${probe.reason}`, remediation || "Run gittensory-mcp doctor --json for structured diagnostics.");
}
}

if (process.env.GITTENSOR_ROOT) {
add("gittensor_root", "pass", "GITTENSOR_ROOT is configured.");
} else if (scorerCommand?.includes("gittensor-score-preview.py")) {
add("gittensor_root", "warn", "Python gittensor scorer is configured but GITTENSOR_ROOT is unset.", "Set GITTENSOR_ROOT to a local entrius/gittensor checkout.");
}

const payload = {
status: checks.some((check) => check.status === "fail") ? "needs_attention" : checks.some((check) => check.status === "warn") ? "warnings" : "ok",
Expand Down Expand Up @@ -1146,7 +1177,7 @@ async function analyzeCurrentBranch(input) {
changedFileCount: body.changedFiles?.length ?? 0,
testFileCount: body.changedFiles?.filter((file) => /(^|\/)(test|tests|spec|__tests__)\/|(^|\/)src\/test\/|(^|\/)[^/]+_test\.(go|py|rb)$|(^|\/)[^/]+_spec\.rb$|\.(test|spec)\.(ts|tsx|js|jsx|py|rb|rs)$/i.test(file.path)).length ?? 0,
passedValidationCount: body.validation?.filter((entry) => entry.status === "passed").length ?? 0,
localScorerStatus,
localScorerStatus: sanitizeLocalScorerStatus(localScorerStatus),
setupGuidance: setupGuidanceForLocalScorer(localScorerStatus),
},
analysis,
Expand Down Expand Up @@ -1195,7 +1226,7 @@ async function previewLocalScore(input) {
codeFiles: diff.codeFiles,
commitMessage: input.commitMessage ?? diff.commitMessage,
},
upstreamPreview,
upstreamPreview: sanitizeLocalScorerStatus(upstreamPreview),
remotePreview: await apiPost("/v1/scoring/preview", body),
setupGuidance: upstreamPreview.ok
? []
Expand Down
Loading