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
16 changes: 16 additions & 0 deletions packages/gittensory-mcp/bin/gittensory-mcp.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env node

Check warning on line 1 in packages/gittensory-mcp/bin/gittensory-mcp.js

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Items reference the same linked issue #549.

Check warning on line 1 in packages/gittensory-mcp/bin/gittensory-mcp.js

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 8 meaningful terms.

Check notice on line 1 in packages/gittensory-mcp/bin/gittensory-mcp.js

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Open PR work references issue #549.

Check notice on line 1 in packages/gittensory-mcp/bin/gittensory-mcp.js

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 5 meaningful terms.

Check notice on line 1 in packages/gittensory-mcp/bin/gittensory-mcp.js

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.

Check notice on line 1 in packages/gittensory-mcp/bin/gittensory-mcp.js

View check run for this annotation

Deleted GitHub App / Gittensory Context

Open PR queue is busy

This repo has a busy open PR queue in the local Gittensory cache.
import { createHash } from "node:crypto";
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
Expand Down Expand Up @@ -139,6 +139,12 @@
plannedPaths: z.array(z.string()).optional(),
};

const lintPrTextShape = {
commitMessages: z.array(z.string()).max(50).optional(),
prBody: z.string().optional(),
linkedIssue: z.number().int().positive().optional(),
};

const preflightShape = {
repoFullName: z.string().min(3),
contributorLogin: z.string().min(1).optional(),
Expand Down Expand Up @@ -314,6 +320,16 @@
},
);

server.registerTool(
"gittensory_lint_pr_text",
{
description:
"Lint a commit message + PR body against the gittensor traceability/no-issue-rationale and Conventional Commit rubric before submitting. Returns a deterministic verdict (strong/adequate/weak) plus specific public-safe fixes. No source upload.",
inputSchema: lintPrTextShape,
},
async (input) => toolResult("Gittensory PR-text lint.", await apiPost("/v1/lint/pr-text", input)),
);

server.registerTool(
"gittensory_preflight_local_diff",
{
Expand Down
14 changes: 14 additions & 0 deletions src/api/routes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Hono, type Context } from "hono";

Check warning on line 1 in src/api/routes.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Items reference the same linked issue #549.

Check warning on line 1 in src/api/routes.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 8 meaningful terms.

Check notice on line 1 in src/api/routes.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Open PR work references issue #549.

Check notice on line 1 in src/api/routes.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 5 meaningful terms.

Check notice on line 1 in src/api/routes.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.

Check notice on line 1 in src/api/routes.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Open PR queue is busy

This repo has a busy open PR queue in the local Gittensory cache.
import { z } from "zod";
import { analyzePRQueue, type AuthorRole, type ChecksStatus } from "../queue-intelligence";
import { completeGitHubWebOAuth, createSessionFromGitHubToken, pollGitHubDeviceFlow, startGitHubDeviceFlow, startGitHubWebOAuth } from "../auth/github-oauth";
Expand Down Expand Up @@ -181,6 +181,7 @@
buildLaneAdvice,
buildLinkedIssueValidation,
buildLocalDiffPreflightResult,
buildPrTextLint,
buildMaintainerCutReadiness,
buildMaintainerLaneReport,
buildPullRequestMaintainerPacket,
Expand Down Expand Up @@ -352,6 +353,12 @@
plannedPaths: z.array(z.string().max(PREFLIGHT_LIMITS.changedFileChars)).max(PREFLIGHT_LIMITS.changedFiles).optional(),
});

const lintPrTextSchema = z.object({
commitMessages: z.array(z.string().max(PREFLIGHT_LIMITS.bodyChars)).max(50).optional(),
prBody: z.string().max(PREFLIGHT_LIMITS.bodyChars).optional(),
linkedIssue: z.number().int().positive().optional(),
});

const skippedPrAuditQuerySchema = z
.object({
limit: z.coerce.number().int().optional(),
Expand Down Expand Up @@ -2062,6 +2069,13 @@
});
});

app.post("/v1/lint/pr-text", async (c) => {
const body = await c.req.json().catch(() => null);
const parsed = lintPrTextSchema.safeParse(body);
if (!parsed.success) return c.json({ error: "invalid_lint_pr_text_request", issues: parsed.error.issues }, 400);
return c.json(buildPrTextLint(parsed.data));
});

app.post("/v1/preflight/pr", async (c) => {
const body = await c.req.json().catch(() => null);
const parsed = preflightSchema.safeParse(body);
Expand Down
35 changes: 35 additions & 0 deletions src/mcp/server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createMcpHandler } from "agents/mcp";

Check warning on line 1 in src/mcp/server.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Items reference the same linked issue #549.

Check warning on line 1 in src/mcp/server.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 8 meaningful terms.

Check notice on line 1 in src/mcp/server.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Open PR work references issue #549.

Check notice on line 1 in src/mcp/server.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 5 meaningful terms.

Check notice on line 1 in src/mcp/server.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.

Check notice on line 1 in src/mcp/server.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Open PR queue is busy

This repo has a busy open PR queue in the local Gittensory cache.
import type { Context } from "hono";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
Expand Down Expand Up @@ -75,6 +75,7 @@
buildLocalDiffPreflightResult,
buildPreflightResult,
buildPreStartCheck,
buildPrTextLint,
buildQueueHealth,
buildRegistryChangeReport,
buildRoleContext,
Expand Down Expand Up @@ -142,6 +143,12 @@
plannedPaths: z.array(z.string().max(PREFLIGHT_LIMITS.changedFileChars)).max(PREFLIGHT_LIMITS.changedFiles).optional(),
};

const lintPrTextShape = {
commitMessages: z.array(z.string().max(PREFLIGHT_LIMITS.bodyChars)).max(50).optional(),
prBody: z.string().max(PREFLIGHT_LIMITS.bodyChars).optional(),
linkedIssue: z.number().int().positive().optional(),
};

const preflightShape = {
repoFullName: z.string().min(3).max(PREFLIGHT_LIMITS.repoFullNameChars),
contributorLogin: z.string().min(1).max(PREFLIGHT_LIMITS.contributorLoginChars).optional(),
Expand Down Expand Up @@ -508,6 +515,15 @@
report: z.unknown().optional(),
};

const lintPrTextOutputSchema = {
verdict: z.string().optional(),
score: z.number().optional(),
components: z.unknown().optional(),
fixes: z.unknown().optional(),
summary: z.string().optional(),
generatedAt: z.string().optional(),
};

export async function handleMcpRequest(c: AppContext): Promise<Response> {
if (c.req.method === "OPTIONS") return new Response(null, { status: 204 });
const identity = await authenticateMcpRequest(c);
Expand Down Expand Up @@ -786,6 +802,17 @@
async (input) => this.toolResult(await this.checkBeforeStart(input)),
);

server.registerTool(
"gittensory_lint_pr_text",
{
description:
"Lint a commit message + PR body against the gittensor traceability/no-issue-rationale and Conventional Commit rubric, before submitting. Returns a deterministic quality verdict (strong/adequate/weak) and specific public-safe fixes. Metadata only; no source upload, no GitHub writes.",
inputSchema: lintPrTextShape,
outputSchema: lintPrTextOutputSchema,
},
async (input) => this.toolResult(this.lintPrText(input)),
);

server.registerTool(
"gittensory_preflight_local_diff",
{
Expand Down Expand Up @@ -1201,6 +1228,14 @@
};
}

private lintPrText(input: { commitMessages?: string[] | undefined; prBody?: string | undefined; linkedIssue?: number | undefined }): ToolPayload {
const report = buildPrTextLint(input);
return {
summary: `Gittensory PR-text lint verdict: ${report.verdict}.`,
data: report as unknown as Record<string, unknown>,
};
}

private async canAccessRepo(fullName: string): Promise<boolean> {
if (this.identity.kind !== "session") return true;
const [scope, repo] = await Promise.all([this.loadSessionAccessScope(), getRepository(this.env, fullName)]);
Expand Down
151 changes: 150 additions & 1 deletion src/signals/engine.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {

Check warning on line 1 in src/signals/engine.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Items reference the same linked issue #549.

Check warning on line 1 in src/signals/engine.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 8 meaningful terms.

Check notice on line 1 in src/signals/engine.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Open PR work references issue #549.

Check notice on line 1 in src/signals/engine.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 5 meaningful terms.

Check notice on line 1 in src/signals/engine.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.

Check notice on line 1 in src/signals/engine.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Open PR queue is busy

This repo has a busy open PR queue in the local Gittensory cache.
AdvisoryFinding,
BountyRecord,
CheckSummaryRecord,
Expand Down Expand Up @@ -4293,7 +4293,156 @@
return 3;
}

function hasClearNoIssueRationale(pr: PullRequestRecord): boolean {
export type PrTextLintInput = {
commitMessages?: string[] | undefined;
prBody?: string | undefined;
linkedIssue?: number | undefined;
};

export type PrTextLintComponent = {
key: "traceability" | "commit_message" | "pr_body";
label: string;
status: "ok" | "weak";
evidence: string;
fix?: string | undefined;
};

export type PrTextLintReport = {
generatedAt: string;
verdict: "strong" | "adequate" | "weak";
/**
* 0-100 PR-text quality score from the deterministic rubric (sum of per-component weights; weak
* components score 25% of their weight). Advisory sub-signal only — `verdict` is authoritative.
* Because traceability is a hard gate for the verdict but only one weighted component of the score,
* the two can rank-disagree (e.g. a strong commit + body with no linked issue scores ~78 yet the
* verdict is "weak"). Rank by `verdict`, not `score`. Not a Gittensor reward/trust score.
*/
score: number;
components: PrTextLintComponent[];
fixes: string[];
summary: string;
};

const GENERIC_COMMIT_PATTERN = /^(?:wip|fix(?:es|ed|ing)?|updat(?:e|es|ed|ing)|change[sd]?|edit[sd]?|patch|minor|tweak[sd]?|misc|cleanup|chore|stuff|temp|tmp|test|final|done|commit|asdf+|\.+)\b[\s.!]*$/i;
// Conventional Commit subject: one of CONTRIBUTING's allowed types, optional `(scope)`, optional `!`,
// then `: ` and a non-empty summary (e.g. `feat(api): add cursor pagination`). Single source of truth
// with CONTRIBUTING.md "Commit And PR Titles".
const CONVENTIONAL_COMMIT_PATTERN = /^(?:feat|fix|test|docs|refactor|build|ci|chore|revert)(?:\([^()\r\n]+\))?!?:\s+\S/i;
const PR_TEXT_LINT_WEIGHTS = { traceability: 30, commit_message: 35, pr_body: 35 } as const;

function stripPrBodyScaffolding(body: string): string {
return body
.replace(/<!--[\s\S]*?-->/g, " ")
.replace(/^#{1,6}\s.*$/gm, " ")
.replace(/^\s*[-*]\s*\[[ xX]\]/gm, " ")
.replace(/[#>*_`[\]()]/g, " ")
.replace(/\s+/g, " ")
.trim();
}

/**
* Deterministic commit-message + PR-body rubric linter. Catches generic/empty AI-slop text before
* submit and returns a quality verdict plus specific, public-safe fixes. Reuses the gittensor
* traceability/no-issue-rationale rubric ({@link hasClearNoIssueRationale}, {@link tokenize},
* {@link STOPWORDS}) shared with the public readiness score. All output is routed through
* {@link sanitizePublicComment}; no private scoring is exposed.
*/
export function buildPrTextLint(input: PrTextLintInput): PrTextLintReport {
const commitMessages = (input.commitMessages ?? []).map((message) => message.trim()).filter((message) => message.length > 0);
const prBody = (input.prBody ?? "").trim();
const linkedIssue = typeof input.linkedIssue === "number" && input.linkedIssue > 0 ? input.linkedIssue : undefined;

const hasRationale = hasClearNoIssueRationale({ title: "", body: prBody });
const traceabilityOk = linkedIssue !== undefined || hasRationale;
const traceability: PrTextLintComponent = traceabilityOk
? {
key: "traceability",
label: "Traceability",
status: "ok",
evidence: linkedIssue !== undefined ? `Linked issue #${linkedIssue}.` : "PR body includes a no-issue rationale.",
}
: {
key: "traceability",
label: "Traceability",
status: "weak",
evidence: "No linked issue and no no-issue rationale in the PR body.",
fix: 'Link the issue this PR resolves (e.g. "Fixes #123"), or explain in the body why no issue applies.',
};

const primaryCommit = commitMessages[0] ?? "";
const commitTokens = tokenize(commitMessages.join(" "));
const commitGeneric = primaryCommit.length > 0 && GENERIC_COMMIT_PATTERN.test(primaryCommit);
// The `^`-anchored pattern matches against the subject line at the start of the message.
const commitConventional = CONVENTIONAL_COMMIT_PATTERN.test(primaryCommit);
const commitOk = commitConventional && primaryCommit.length >= 15 && commitTokens.length >= 2 && !commitGeneric;
const commitMessage: PrTextLintComponent = commitOk
? { key: "commit_message", label: "Commit message", status: "ok", evidence: "Commit message is specific and follows Conventional Commit format." }
: {
key: "commit_message",
label: "Commit message",
status: "weak",
evidence:
commitMessages.length === 0
? "No commit message was provided."
: commitGeneric
? "Commit message is generic (e.g. update/fix/wip)."
: !commitConventional
? "Commit message does not follow Conventional Commit format (type(scope): summary)."
: "Commit message is too short or lacks specific detail.",
fix: "Use a Conventional Commit subject (type(scope): summary, e.g. feat(api): add cursor pagination) that names what changed and why; avoid generic words like update, fix, or wip on their own.",
};

const strippedBody = stripPrBodyScaffolding(prBody);
const bodyTokens = tokenize(strippedBody);
const bodyLooksTemplated = prBody.length > 0 && /\[[ xX]\]|<!--/.test(prBody);
// tokenize() only counts ASCII word tokens, so a fully non-Latin (CJK/Cyrillic/…) body yields 0
// tokens and would be mislabelled "thin". Fall back to a Unicode-aware letter density check so
// substantive non-Latin prose is recognised before we flag a body as low-effort.
const bodyNonWhitespace = strippedBody.replace(/\s+/g, "");
const bodyLetterCount = (bodyNonWhitespace.match(/\p{L}/gu) ?? []).length;
const bodyLetterDense = bodyNonWhitespace.length >= 24 && bodyLetterCount / bodyNonWhitespace.length >= 0.6;
const bodyOk = strippedBody.length >= 40 && (bodyTokens.length >= 5 || bodyLetterDense);
const prBodyComponent: PrTextLintComponent = bodyOk
? {
key: "pr_body",
label: "PR body",
status: "ok",
evidence: hasValidationNote(prBody) ? "PR body describes the change and includes validation notes." : "PR body describes the change with specific detail.",
}
: {
key: "pr_body",
label: "PR body",
status: "weak",
evidence: prBody.length === 0 ? "PR body is empty." : bodyLooksTemplated ? "PR body looks like an unfilled template." : "PR body is thin and lacks specific detail about the change.",
fix: "Describe what changed, why, and how it was validated; fill in or remove unused template sections.",
};

const components = [traceability, commitMessage, prBodyComponent];
const score = components.reduce((sum, component) => sum + (component.status === "ok" ? PR_TEXT_LINT_WEIGHTS[component.key] : Math.round(PR_TEXT_LINT_WEIGHTS[component.key] * 0.25)), 0);
const weakCount = components.filter((component) => component.status === "weak").length;
const verdict: PrTextLintReport["verdict"] = weakCount === 0 ? "strong" : traceabilityOk && weakCount === 1 ? "adequate" : "weak";
const summary =
verdict === "strong"
? "PR text is traceable, specific, and ready to submit."
: verdict === "adequate"
? "PR text is acceptable but has one area to tighten before submitting."
: "PR text reads as low-effort; address the flagged items before submitting.";

return {
generatedAt: nowIso(),
verdict,
score,
components: components.map((component) => ({
...component,
evidence: sanitizePublicComment(component.evidence),
...(component.fix === undefined ? {} : { fix: sanitizePublicComment(component.fix) }),
})),
fixes: components.flatMap((component) => (component.fix === undefined ? [] : [sanitizePublicComment(component.fix)])),
summary: sanitizePublicComment(summary),
};
}

function hasClearNoIssueRationale(pr: Pick<PullRequestRecord, "title" | "body">): boolean {
return /\b(no issue\s*(?:because|:)|no linked issue\s*(?:because|:)|no ticket\s*(?:because|:)|maintenance|docs? only|typo|chore|cleanup)\b/i.test([pr.title, pr.body ?? ""].join(" "));
}

Expand Down
13 changes: 13 additions & 0 deletions test/integration/api.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

Check warning on line 1 in test/integration/api.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Items reference the same linked issue #549.

Check warning on line 1 in test/integration/api.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 8 meaningful terms.

Check notice on line 1 in test/integration/api.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Open PR work references issue #549.

Check notice on line 1 in test/integration/api.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 5 meaningful terms.

Check notice on line 1 in test/integration/api.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.

Check notice on line 1 in test/integration/api.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Open PR queue is busy

This repo has a busy open PR queue in the local Gittensory cache.
import { createSessionForGitHubUser, hashToken } from "../../src/auth/security";
import {
upsertBounty,
Expand Down Expand Up @@ -932,6 +932,19 @@
const invalidLocalDiff = await app.request("/v1/preflight/local-diff", { method: "POST", headers: apiHeaders(env), body: JSON.stringify({}) }, env);
expect(invalidLocalDiff.status).toBe(400);

const lintPrText = await app.request(
"/v1/lint/pr-text",
{ method: "POST", headers: apiHeaders(env), body: JSON.stringify({ commitMessages: ["wip"], prBody: "" }) },
env,
);
expect(lintPrText.status).toBe(200);
const lintPrTextBody = await lintPrText.json();
expect(lintPrTextBody).toMatchObject({ verdict: "weak", fixes: expect.any(Array) });
expect(JSON.stringify(lintPrTextBody)).not.toMatch(/hotkey|coldkey|wallet|payout|reward/i);

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);

const queueIntelligence = await app.request(
"/v1/internal/queue-intelligence",
{
Expand Down
21 changes: 21 additions & 0 deletions test/unit/mcp-output-schemas.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Client } from "@modelcontextprotocol/sdk/client/index.js";

Check warning on line 1 in test/unit/mcp-output-schemas.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Items reference the same linked issue #549.

Check warning on line 1 in test/unit/mcp-output-schemas.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 8 meaningful terms.

Check notice on line 1 in test/unit/mcp-output-schemas.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Open PR work references issue #549.

Check notice on line 1 in test/unit/mcp-output-schemas.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 5 meaningful terms.

Check notice on line 1 in test/unit/mcp-output-schemas.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.

Check notice on line 1 in test/unit/mcp-output-schemas.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Open PR queue is busy

This repo has a busy open PR queue in the local Gittensory cache.
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { describe, expect, it } from "vitest";
import { persistSignalSnapshot, upsertIssueFromGitHub, upsertRepositoryFromGitHub } from "../../src/db/repositories";
Expand All @@ -20,6 +20,7 @@
"gittensory_get_issue_quality",
"gittensory_validate_linked_issue",
"gittensory_check_before_start",
"gittensory_lint_pr_text",
"gittensory_get_registry_changes",
"gittensory_get_upstream_drift",
"gittensory_local_status",
Expand Down Expand Up @@ -175,6 +176,26 @@
expect(JSON.stringify(data)).not.toMatch(/hotkey|coldkey|wallet|payout|reward/i);
});

it("gittensory_lint_pr_text returns a deterministic verdict and fixes", async () => {
const { client } = await connectTestClient();
const weak = await client.callTool({ name: "gittensory_lint_pr_text", arguments: { commitMessages: ["wip"], prBody: "" } });
expect(weak.isError).toBeFalsy();
const weakData = weak.structuredContent as Record<string, unknown>;
expect(weakData.verdict).toBe("weak");
expect(Array.isArray(weakData.fixes)).toBe(true);
expect(JSON.stringify(weakData)).not.toMatch(/hotkey|coldkey|wallet|payout|reward/i);

const strong = await client.callTool({
name: "gittensory_lint_pr_text",
arguments: {
commitMessages: ["feat(api): add cursor pagination to the labels endpoint for large repositories"],
prBody: "Adds cursor-based pagination to the labels endpoint so labels beyond the first cached page are returned. Tested with vitest.",
linkedIssue: 160,
},
});
expect((strong.structuredContent as Record<string, unknown>).verdict).toBe("strong");
});

it("gittensory_get_repo_outcome_patterns reports not-found, computed, and cached outcomes", async () => {
const env = createTestEnv();
await upsertRepositoryFromGitHub(env, { name: "computed", full_name: "owner/computed", private: false, owner: { login: "owner" }, default_branch: "main" });
Expand Down
Loading
Loading