Skip to content
Closed
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
21 changes: 21 additions & 0 deletions packages/loopover-mcp/bin/loopover-mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,12 @@ const STDIO_TOOL_DESCRIPTORS = [
category: "maintainer",
description: "Return the maintainer queue-noise triage report for a repo: a noise score/level, the specific noise sources to clear first, and recommended maintainer actions. Maintainer-authenticated; advisory only.",
},
{
name: "loopover_refresh_repo_docs",
category: "maintainer",
description:
"Force an immediate repo-doc refresh (AGENTS.md/CLAUDE.md, and a skill file when warranted) for one repo, without waiting for the scheduled interval. Only ever opens a pull request -- never a direct commit -- and only when repoDocGeneration is enabled for this repo and the generated content actually changed. Maintainer access required.",
},
{
name: "loopover_get_ams_miner_cohort",
category: "maintainer",
Expand Down Expand Up @@ -1671,6 +1677,21 @@ registerStdioTool(
},
);

// #7754: stdio mirror of the remote loopover_refresh_repo_docs + the `maintain refresh-docs` CLI. Thin POST
// proxy of the same {repoBase}/repo-docs/refresh route (empty body -- the route only ever opens a PR, never
// merges/commits, so there is no create-safety flag to forward). Same ownerRepoShape pattern as maintainer_noise.
registerStdioTool(
"loopover_refresh_repo_docs",
{
description: stdioToolDescription("loopover_refresh_repo_docs"),
inputSchema: ownerRepoShape,
},
async ({ owner, repo }: any) => {
const prefix = `/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`;
return toolResult(`LoopOver repo-doc refresh for ${owner}/${repo}.`, await apiPost(`${prefix}/repo-docs/refresh`, {}));
},
);

registerStdioTool(
"loopover_get_ams_miner_cohort",
{
Expand Down
72 changes: 72 additions & 0 deletions test/unit/mcp-cli-refresh-repo-docs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { closeFixtureServer, startFixtureServer } from "./support/mcp-cli-harness";

// #7754: in-process coverage for the loopover_refresh_repo_docs stdio tool. Same #7764 entrypoint-guard
// pattern as mcp-cli-repo-focus-manifest -- import the .ts, hold the exported `server`, connect an
// InMemoryTransport so v8/Codecov attributes the registerStdioTool block (a subprocess spawn can't be
// instrumented). The tool is a thin POST proxy, so one call exercises the whole handler.
const MODULES = ["../../packages/loopover-mcp/bin/loopover-mcp.ts"] as const;

type BinModule = {
server: { connect: (transport: unknown) => Promise<void> };
};

let tempDir = "";
const refreshCalls: Array<{ url: string; method: string }> = [];
const loaded = new Map<string, BinModule>();

beforeAll(async () => {
tempDir = mkdtempSync(join(tmpdir(), "loopover-refresh-repo-docs-"));
const apiUrl = await startFixtureServer({
onApiRequest: (r) => {
if (r.url && r.url.includes("/repo-docs/refresh")) refreshCalls.push({ url: r.url ?? "", method: r.method ?? "" });
},
});
process.env.LOOPOVER_API_URL = apiUrl;
process.env.LOOPOVER_API_TOKEN = "in-process-token";
process.env.LOOPOVER_API_TIMEOUT_MS = "2000";
process.env.LOOPOVER_CONFIG_DIR = tempDir;
process.env.LOOPOVER_SKIP_NPM_VERSION_CHECK = "1";
for (const specifier of MODULES) {
loaded.set(specifier, (await import(specifier)) as unknown as BinModule);
}
}, 120_000);

afterAll(async () => {
await closeFixtureServer();
if (tempDir) rmSync(tempDir, { recursive: true, force: true });
delete process.env.LOOPOVER_API_URL;
delete process.env.LOOPOVER_API_TOKEN;
delete process.env.LOOPOVER_CONFIG_DIR;
delete process.env.LOOPOVER_SKIP_NPM_VERSION_CHECK;
});

describe("bin loopover_refresh_repo_docs stdio tool (in-process, #7754)", () => {
it.each(MODULES)("proxies POST .../repo-docs/refresh and returns the PR result — %s", async (specifier) => {
refreshCalls.length = 0;
const mod = loaded.get(specifier)!;
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
await mod.server.connect(serverTransport);
const client = new Client({ name: "refresh-repo-docs-test", version: "0.1.0" }, { capabilities: {} });
await client.connect(clientTransport);
try {
const tool = (await client.listTools()).tools.find((entry) => entry.name === "loopover_refresh_repo_docs");
expect(tool).toBeDefined();
expect(tool?.description).toMatch(/repo-doc refresh|opens a pull request/i);

const result = await client.callTool({ name: "loopover_refresh_repo_docs", arguments: { owner: "owner", repo: "repo" } });
expect(result.isError).toBeFalsy();
expect(refreshCalls).toEqual([{ url: "/v1/repos/owner/repo/repo-docs/refresh", method: "POST" }]);
const text = JSON.stringify(result);
expect(text).toContain("opened");
expect(text).toContain("pullNumber");
} finally {
await client.close().catch(() => undefined);
}
});
});
2 changes: 1 addition & 1 deletion test/unit/mcp-tool-rename-aliases.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
// (#7763 registered the loopover_watch_issues stdio tool, taking the count from 91 to 92.)
// (#7759 registered the loopover_check_improvement_potential stdio tool, taking the count from 92 to 93.)
// (#7761 registered the loopover_list_notifications stdio tool, taking the count from 93 to 94.)
// (#7752 registered the loopover_get_automation_state stdio tool, taking the count from 94 to 95.)
// (#7754 registered the loopover_refresh_repo_docs stdio tool, taking the count from 94 to 95.)
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { mkdtempSync, rmSync } from "node:fs";
Expand Down
Loading