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
2 changes: 1 addition & 1 deletion src/mcp/server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createMcpHandler } from "agents/mcp";

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.

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

View check run for this annotation

Deleted GitHub App / Gittensory Context

PR author has maintainer association

This PR appears to come from a maintainer-associated account.
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 @@ -2325,7 +2325,7 @@
const draft = buildPublicPrBodyDraft(analysis);
// Human-readable summary carries the rendered markdown body; structured draft is returned as JSON.
return {
summary: `Public-safe PR body draft for ${analysis.repoFullName} (metadata only; private scoreability excluded).\n\n${draft.markdown}`,
summary: `Public-safe PR body draft for ${analysis.repoFullName} (metadata only; internal analysis context omitted).\n\n${draft.markdown}`,
data: draft as unknown as Record<string, unknown>,
};
}
Expand Down
26 changes: 13 additions & 13 deletions src/services/pr-body-draft.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { sanitizePublicComment } from "../github/commands";

Check notice on line 1 in src/services/pr-body-draft.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/services/pr-body-draft.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.

Check notice on line 1 in src/services/pr-body-draft.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

PR author has maintainer association

This PR appears to come from a maintainer-associated account.
import { PUBLIC_UNSAFE_TERMS } from "../signals/redaction";
import type { LocalDiffPreflightResult } from "../signals/engine";
import type { LocalBranchAnalysis } from "../signals/local-branch";
Expand All @@ -7,10 +7,10 @@
* Drafts a public-safe, copy/paste PR body from local branch metadata.
*
* The draft is built ONLY from already-public-safe slices of {@link LocalBranchAnalysis}
* (the prepared packet, base freshness, linked-issue and overlap metadata). Private
* scoreability, reward/risk, raw trust, and reviewability context are excluded by
* construction — their field names are listed in {@link EXCLUDED_PRIVATE_PR_BODY_FIELDS}
* — and every emitted line additionally passes through {@link sanitizePublicComment} and a
* (the prepared packet, base freshness, linked-issue and overlap metadata). Internal
* analysis context is excluded by construction — those categories are listed in
* {@link EXCLUDED_PRIVATE_PR_BODY_FIELDS} using public-safe labels — and every emitted
* line additionally passes through {@link sanitizePublicComment} and a
* forbidden-language filter, so no private/financial language reaches GitHub.
*
* Input is metadata only; source contents are never read or uploaded.
Expand All @@ -36,17 +36,17 @@
};

/**
* Categories of private analysis context that must never appear in a public PR body draft.
* Phrased as public-safe labels (no private/financial terms) so the list itself stays
* safe to surface; it documents that private scoreability/risk context is excluded.
* Categories of internal analysis context that must never appear in a public PR body draft.
* Labels intentionally avoid private/financial taxonomy because MCP clients may display
* the structured draft alongside the markdown.
*/
export const EXCLUDED_PRIVATE_PR_BODY_FIELDS = [
"private score preview",
"private scenario projections",
"private risk signals",
"private score-gate blockers",
"branch eligibility gate",
"private ranked next actions",
"omitted analysis details",
"omitted forecast details",
"omitted signal details",
"omitted blocker details",
"omitted readiness details",
"omitted follow-up details",
] as const;

// Residual private/financial terms that sanitizePublicComment does not rewrite on its own
Expand Down
39 changes: 39 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 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.

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

View check run for this annotation

Deleted GitHub App / Gittensory Context

PR author has maintainer association

This PR appears to come from a maintainer-associated account.
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { describe, expect, it } from "vitest";
import { persistSignalSnapshot, upsertBounty, upsertIssueFromGitHub, upsertRepositoryFromGitHub } from "../../src/db/repositories";
Expand Down Expand Up @@ -53,6 +53,45 @@
}
});


it("keeps draft PR body MCP text free of private scoring taxonomy", async () => {
const mcp = new GittensoryMcp(createTestEnv()) as unknown as {
analyzeLocalBranch: () => Promise<unknown>;
draftPrBody(input: Record<string, unknown>): Promise<{ summary: string; data: Record<string, unknown> }>;
toolResult(payload: { summary: string; data: Record<string, unknown> }): { content: Array<{ type: "text"; text: string }>; structuredContent: Record<string, unknown> };
};
mcp.analyzeLocalBranch = async () => ({
repoFullName: "octo/demo",
prPacket: {
titleSuggestion: "Fix cache refresh race",
bodySections: [{ heading: "Changed Paths", lines: ["- src/cache.ts (modified, +12/-3)"] }],
validationSummary: {
passed: 1,
failed: 0,
notRun: 0,
commands: [{ command: "npm run test:ci", status: "passed", summary: "all green" }],
},
publicSafeWarnings: [],
},
baseFreshness: {
status: "fresh",
changedFileCount: 1,
testFileCount: 0,
warnings: [],
recommendation: undefined,
},
manifestGuidance: { present: false, publicNextSteps: [] },
preflight: { linkedIssues: [42], collisions: [], reviewBurden: "low" },
});

const payload = await mcp.draftPrBody({});
const result = mcp.toolResult(payload);
const visibleText = result.content[0]?.text ?? "";
expect(visibleText).not.toMatch(/private scoreability|score preview|scenario projections|risk signals|score-gate blockers|branch eligibility gate|ranked next actions/i);
expect(JSON.stringify(result.structuredContent)).not.toMatch(/private scoreability|score preview|risk signals|score-gate blockers|branch eligibility gate|ranked next actions/i);
expect(visibleText).toContain("internal analysis context omitted");
});

it("exposes an outputSchema on EVERY registered tool (#550)", async () => {
const { client } = await connectTestClient();
const { tools } = await client.listTools();
Expand Down
4 changes: 2 additions & 2 deletions test/unit/pr-body-draft.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it } from "vitest";

Check notice on line 1 in test/unit/pr-body-draft.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/pr-body-draft.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.

Check notice on line 1 in test/unit/pr-body-draft.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

PR author has maintainer association

This PR appears to come from a maintainer-associated account.
import { buildPublicPrBodyDraft, EXCLUDED_PRIVATE_PR_BODY_FIELDS, type PrBodyDraftSource } from "../../src/services/pr-body-draft";

const LOCAL_PATH_SOURCE = String.raw`(?:(?<![A-Za-z0-9])[A-Za-z]:[\\/][^\s"';)]+|\\\\[^\s"';\\]+\\[^\s"';]+|(?<![/\\A-Za-z0-9._-])/[A-Za-z0-9._-]+(?:/[^\s"';)]+)*)`;
Expand Down Expand Up @@ -230,12 +230,12 @@
expect(section(draft, "Tests run").join(" ")).toContain("[local path]");
});

it("lists the private analysis fields it deliberately excludes", () => {
it("lists excluded internal analysis categories using public-safe labels", () => {
const draft = buildPublicPrBodyDraft(source());
expect(draft.excludedPrivateFields).toEqual([...EXCLUDED_PRIVATE_PR_BODY_FIELDS]);
// The exclusion list itself stays public-safe (no private/financial terms).
expect(JSON.stringify(draft.excludedPrivateFields)).not.toMatch(FORBIDDEN_PUBLIC_LANGUAGE);
expect(draft.excludedPrivateFields.join(" ")).toMatch(/score|risk|eligibility/i);
expect(draft.excludedPrivateFields.join(" ")).toMatch(/analysis|signals|readiness|actions/i);
});
});

Expand Down
Loading