@arii/boomtick-mcp 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.mcp/schemas/github.get_pr_commits.json +32 -0
- package/.mcp/schemas/github.get_pr_reviews.json +32 -0
- package/.mcp/schemas/github_get_pr_commits.json +32 -0
- package/.mcp/schemas/github_get_pr_reviews.json +32 -0
- package/.mcp/schemas/repo.get_workflow_runs.json +50 -0
- package/.mcp/schemas/repo.logs.json +14 -0
- package/.mcp/schemas/repo.read_ci_logs.json +14 -0
- package/.mcp/schemas/repo_get_workflow_runs.json +50 -0
- package/.mcp/schemas/repo_logs.json +14 -0
- package/.mcp/schemas/repo_read_ci_logs.json +14 -0
- package/CHANGELOG.md +7 -0
- package/README.md +0 -1
- package/actions/impact-analysis/action.yml +4 -16
- package/actions/run-project-gate/action.yml +2 -9
- package/actions/test-build/action.yml +1 -19
- package/dist/lib/git.js +44 -0
- package/dist/lib/git.test.js +82 -0
- package/dist/mcp/definitions.js +16 -39
- package/dist/mcp/server.js +0 -46
- package/dist/mcp/server.test.js +0 -33
- package/dist/tools/contract.js +135 -2
- package/dist/tools/github.get_merge_conflict_files.js +10 -18
- package/dist/tools/github.get_pr_commits.js +21 -0
- package/dist/tools/github.get_pr_reviews.js +21 -0
- package/dist/tools/github.get_pr_reviews.test.js +99 -0
- package/dist/tools/jules/create-session.js +1 -2
- package/dist/tools/jules/get-pr.js +1 -2
- package/dist/tools/jules/get-session.js +1 -2
- package/dist/tools/jules/shared.js +24 -0
- package/dist/tools/repo.get_changed_files.js +2 -13
- package/dist/tools/repo.get_workflow_runs.js +29 -0
- package/dist/tools/repo.logs.js +5 -1
- package/dist/tools/repo.read_agent_context.js +27 -1
- package/dist/tools/repo.read_agent_context.test.js +1 -1
- package/dist/tools/repo.read_ci_logs.js +5 -1
- package/dist/tools/repo.run_tests.js +1 -1
- package/package.json +3 -6
- package/src/agents/verifier-agent.prompt.md +0 -4
- package/src/lib/git.test.ts +110 -0
- package/src/lib/git.ts +75 -0
- package/src/mcp/definitions.ts +18 -42
- package/src/mcp/server.test.ts +0 -42
- package/src/mcp/server.ts +0 -45
- package/src/tools/contract.ts +147 -2
- package/src/tools/github.get_merge_conflict_files.ts +10 -20
- package/src/tools/github.get_pr_commits.ts +27 -0
- package/src/tools/github.get_pr_reviews.test.ts +110 -0
- package/src/tools/github.get_pr_reviews.ts +27 -0
- package/src/tools/jules/create-session.ts +1 -2
- package/src/tools/jules/get-pr.ts +1 -2
- package/src/tools/jules/get-session.ts +1 -2
- package/src/tools/jules/shared.ts +35 -0
- package/src/tools/repo.get_changed_files.ts +2 -16
- package/src/tools/repo.get_workflow_runs.ts +36 -0
- package/src/tools/repo.logs.ts +5 -1
- package/src/tools/repo.read_agent_context.test.ts +1 -1
- package/src/tools/repo.read_agent_context.ts +35 -1
- package/src/tools/repo.read_ci_logs.ts +5 -1
- package/src/tools/repo.run_tests.ts +1 -1
- package/.mcp/schemas/agent.search_ddgs.json +0 -31
- package/.mcp/schemas/agent_search_ddgs.json +0 -31
- package/.mcp/schemas/repo.get_route_map.json +0 -23
- package/.mcp/schemas/repo.run_lighthouse.json +0 -29
- package/.mcp/schemas/repo.run_playwright.json +0 -36
- package/.mcp/schemas/repo_get_route_map.json +0 -23
- package/.mcp/schemas/repo_run_lighthouse.json +0 -29
- package/.mcp/schemas/repo_run_playwright.json +0 -36
- package/dist/lib/td-cli.js +0 -54
- package/dist/tools/ddgs.search.js +0 -50
- package/dist/tools/ddgs.search.test.js +0 -67
- package/dist/tools/ddgs_search.py +0 -23
- package/dist/tools/repo.get_route_map.js +0 -37
- package/dist/tools/repo.run_lighthouse.js +0 -37
- package/dist/tools/repo.run_playwright.js +0 -34
- package/scripts/export-mcp-schema.ts +0 -7
- package/src/lib/td-cli.ts +0 -73
- package/src/tools/ddgs.search.test.ts +0 -92
- package/src/tools/ddgs.search.ts +0 -58
- package/src/tools/ddgs_search.py +0 -23
- package/src/tools/repo.get_route_map.ts +0 -41
- package/src/tools/repo.run_lighthouse.ts +0 -40
- package/src/tools/repo.run_playwright.ts +0 -42
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
-
import { ddgsSearchHandler } from "./ddgs.search.js";
|
|
3
|
-
import { runCommand } from "../lib/shell.js";
|
|
4
|
-
|
|
5
|
-
vi.mock("../lib/shell.js");
|
|
6
|
-
|
|
7
|
-
describe("ddgsSearchHandler", () => {
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
vi.resetAllMocks();
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it("should parse arguments, sanitize query, and return search results", async () => {
|
|
13
|
-
const mockOutput = JSON.stringify([
|
|
14
|
-
{ title: "Result 1", href: "https://example.com/1", body: "Description 1" },
|
|
15
|
-
{ title: "Result 2", href: "https://example.com/2", body: "Description 2" },
|
|
16
|
-
]);
|
|
17
|
-
|
|
18
|
-
vi.mocked(runCommand).mockResolvedValue({
|
|
19
|
-
stdout: mockOutput,
|
|
20
|
-
stderr: "",
|
|
21
|
-
exitCode: 0,
|
|
22
|
-
durationMs: 100,
|
|
23
|
-
command: "mock python3",
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
const result = await ddgsSearchHandler({ query: "test\nquery\x00", maxResults: 2 });
|
|
27
|
-
|
|
28
|
-
expect(runCommand).toHaveBeenCalledTimes(1);
|
|
29
|
-
expect(runCommand).toHaveBeenCalledWith(
|
|
30
|
-
"python3",
|
|
31
|
-
[expect.stringContaining("ddgs_search.py"), "test query", "2"]
|
|
32
|
-
);
|
|
33
|
-
expect(result.results).toHaveLength(2);
|
|
34
|
-
expect(result.results[0].title).toBe("Result 1");
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("should handle raw error when python script exits with non-zero code and non-JSON output", async () => {
|
|
38
|
-
vi.mocked(runCommand).mockResolvedValue({
|
|
39
|
-
stdout: "",
|
|
40
|
-
stderr: "Python script failed",
|
|
41
|
-
exitCode: 1,
|
|
42
|
-
durationMs: 100,
|
|
43
|
-
command: "mock python3",
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
await expect(ddgsSearchHandler({ query: "test query", maxResults: 2 })).rejects.toThrow(
|
|
47
|
-
"Failed to search ddgs: Python script failed"
|
|
48
|
-
);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("should handle parsed JSON error when python script exits with non-zero code", async () => {
|
|
52
|
-
vi.mocked(runCommand).mockResolvedValue({
|
|
53
|
-
stdout: "",
|
|
54
|
-
stderr: JSON.stringify({ error: "Structured JSON error" }),
|
|
55
|
-
exitCode: 1,
|
|
56
|
-
durationMs: 100,
|
|
57
|
-
command: "mock python3",
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
await expect(ddgsSearchHandler({ query: "test query", maxResults: 2 })).rejects.toThrow(
|
|
61
|
-
"Failed to search ddgs: Structured JSON error"
|
|
62
|
-
);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("should handle error when python output is not an array", async () => {
|
|
66
|
-
vi.mocked(runCommand).mockResolvedValue({
|
|
67
|
-
stdout: JSON.stringify({ error: "Something went wrong" }),
|
|
68
|
-
stderr: "",
|
|
69
|
-
exitCode: 0,
|
|
70
|
-
durationMs: 100,
|
|
71
|
-
command: "mock python3",
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
await expect(ddgsSearchHandler({ query: "test query", maxResults: 2 })).rejects.toThrow(
|
|
75
|
-
"Expected an array of search results"
|
|
76
|
-
);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("should handle error when python output cannot be parsed", async () => {
|
|
80
|
-
vi.mocked(runCommand).mockResolvedValue({
|
|
81
|
-
stdout: "invalid json",
|
|
82
|
-
stderr: "",
|
|
83
|
-
exitCode: 0,
|
|
84
|
-
durationMs: 100,
|
|
85
|
-
command: "mock python3",
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
await expect(ddgsSearchHandler({ query: "test query", maxResults: 2 })).rejects.toThrow(
|
|
89
|
-
"Failed to parse ddgs search results"
|
|
90
|
-
);
|
|
91
|
-
});
|
|
92
|
-
});
|
package/src/tools/ddgs.search.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { runCommand } from "../lib/shell.js";
|
|
3
|
-
|
|
4
|
-
export const DdgsSearchInputSchema = z.object({
|
|
5
|
-
query: z.string(),
|
|
6
|
-
maxResults: z.number().optional().default(5),
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
import path from "path";
|
|
10
|
-
import { fileURLToPath } from "url";
|
|
11
|
-
|
|
12
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
-
const __dirname = path.dirname(__filename);
|
|
14
|
-
|
|
15
|
-
export async function ddgsSearchHandler(args: z.infer<typeof DdgsSearchInputSchema>) {
|
|
16
|
-
const scriptPath = path.join(__dirname, "ddgs_search.py");
|
|
17
|
-
|
|
18
|
-
// Sanitize query by removing control characters, newlines, and null bytes
|
|
19
|
-
// eslint-disable-next-line no-control-regex
|
|
20
|
-
const safeQuery = args.query.replace(/[\x00-\x1F\x7F-\x9F]/g, " ").trim();
|
|
21
|
-
if (!safeQuery) {
|
|
22
|
-
throw new Error("Query cannot be empty after sanitization.");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const result = await runCommand("python3", [scriptPath, safeQuery, (args.maxResults ?? 5).toString()]);
|
|
26
|
-
|
|
27
|
-
// duckduckgo_search logs annoying deprecation warnings to stderr we want to ignore
|
|
28
|
-
// if it really failed, exitCode will be non-zero and we'll have stdout/stderr
|
|
29
|
-
if (result.exitCode !== 0) {
|
|
30
|
-
let parsedError = result.stderr || result.stdout;
|
|
31
|
-
|
|
32
|
-
const lines = parsedError.split("\n");
|
|
33
|
-
for (const line of lines) {
|
|
34
|
-
try {
|
|
35
|
-
const json = JSON.parse(line);
|
|
36
|
-
if (json.error) {
|
|
37
|
-
parsedError = json.error;
|
|
38
|
-
break;
|
|
39
|
-
}
|
|
40
|
-
} catch {
|
|
41
|
-
// continue
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
throw new Error(`Failed to search ddgs: ${parsedError}`);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
const data = JSON.parse(result.stdout);
|
|
50
|
-
if (!Array.isArray(data)) {
|
|
51
|
-
throw new Error("Expected an array of search results");
|
|
52
|
-
}
|
|
53
|
-
return { results: data };
|
|
54
|
-
} catch (e) {
|
|
55
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
56
|
-
throw new Error(`Failed to parse ddgs search results. Error: ${message}. Output was: ${result.stdout}`);
|
|
57
|
-
}
|
|
58
|
-
}
|
package/src/tools/ddgs_search.py
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# pylint: disable=line-too-long
|
|
2
|
-
"""DuckDuckGo Search tool."""
|
|
3
|
-
import json
|
|
4
|
-
import sys
|
|
5
|
-
|
|
6
|
-
try:
|
|
7
|
-
from duckduckgo_search import DDGS
|
|
8
|
-
except ImportError:
|
|
9
|
-
print(json.dumps({"error": "duckduckgo_search package not found. Please install it using 'pip install duckduckgo-search' (preferably in a virtual environment)."}), file=sys.stderr)
|
|
10
|
-
sys.exit(1)
|
|
11
|
-
|
|
12
|
-
if len(sys.argv) != 3:
|
|
13
|
-
print(json.dumps({"error": f"Invalid argument count. Expected 2 (query, max_results), got {len(sys.argv) - 1}."}), file=sys.stderr)
|
|
14
|
-
sys.exit(1)
|
|
15
|
-
|
|
16
|
-
try:
|
|
17
|
-
query = sys.argv[1]
|
|
18
|
-
max_results = int(sys.argv[2])
|
|
19
|
-
results = DDGS().text(query, max_results=max_results)
|
|
20
|
-
print(json.dumps(list(results)))
|
|
21
|
-
except Exception as e:
|
|
22
|
-
print(json.dumps({"error": str(e)}), file=sys.stderr)
|
|
23
|
-
sys.exit(1)
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { runCommand } from "../lib/shell.js";
|
|
3
|
-
import { config } from "../config.js";
|
|
4
|
-
import path from "path";
|
|
5
|
-
|
|
6
|
-
export const GetRouteMapInputSchema = z.object({
|
|
7
|
-
includeStatic: z.boolean().optional(),
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
export async function getRouteMapHandler(args: z.infer<typeof GetRouteMapInputSchema>) {
|
|
11
|
-
GetRouteMapInputSchema.parse(args);
|
|
12
|
-
// Logic based on tech-dancer repo structure: src/config/routes.ts and content/
|
|
13
|
-
const routesPath = path.join(config.repoPath, "src/config/routes.ts");
|
|
14
|
-
|
|
15
|
-
// For simplicity in MVP, we'll try to find routes by listing content files
|
|
16
|
-
// and reading the main routes file if it exists.
|
|
17
|
-
let routeMap: Record<string, string> = {};
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
const gitFiles = await runCommand("git", ["ls-files", "content/"]);
|
|
21
|
-
const contentFiles = gitFiles.stdout.trim().split("\n");
|
|
22
|
-
|
|
23
|
-
for (const file of contentFiles) {
|
|
24
|
-
if (file.endsWith(".md")) {
|
|
25
|
-
const slug = path.basename(file, ".md");
|
|
26
|
-
if (file.includes("/posts/")) {
|
|
27
|
-
routeMap[`/blog/${slug}`] = file;
|
|
28
|
-
} else if (file.includes("/resources/")) {
|
|
29
|
-
routeMap[`/resources/${slug}`] = file;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
} catch (error) {
|
|
34
|
-
// If no content files, that's fine
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (args.includeStatic) {
|
|
38
|
-
routeMap["/favicon.ico"] = "public/favicon.ico";
|
|
39
|
-
}
|
|
40
|
-
return { routeMap };
|
|
41
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { runCommand } from "../lib/shell.js";
|
|
3
|
-
|
|
4
|
-
export const RunLighthouseInputSchema = z.object({
|
|
5
|
-
route: z.string().optional().default("/"),
|
|
6
|
-
worktreePath: z.string().optional(),
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
export async function runLighthouseHandler(args: z.infer<typeof RunLighthouseInputSchema>) {
|
|
10
|
-
const results = await runCommand("pnpm", ["lhci", "autorun", "--json"], { cwd: args.worktreePath });
|
|
11
|
-
|
|
12
|
-
let summary = {};
|
|
13
|
-
const failures: string[] = [];
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
// Attempt to parse summary from LHCI output if available
|
|
17
|
-
// For MVP, we'll provide a structured placeholder if actual JSON isn't present
|
|
18
|
-
if (results.stdout.includes("{")) {
|
|
19
|
-
const possibleJson = results.stdout.substring(results.stdout.indexOf("{"));
|
|
20
|
-
summary = JSON.parse(possibleJson);
|
|
21
|
-
} else {
|
|
22
|
-
summary = {
|
|
23
|
-
performance: 0,
|
|
24
|
-
accessibility: 0,
|
|
25
|
-
bestPractices: 0,
|
|
26
|
-
seo: 0
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
} catch (e) {
|
|
30
|
-
failures.push("Failed to parse Lighthouse report");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
success: results.exitCode === 0,
|
|
35
|
-
route: args.route,
|
|
36
|
-
summary,
|
|
37
|
-
failures,
|
|
38
|
-
command: results.command
|
|
39
|
-
};
|
|
40
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { runCommand } from "../lib/shell.js";
|
|
3
|
-
|
|
4
|
-
export const RunPlaywrightInputSchema = z.object({
|
|
5
|
-
grep: z.string().optional(),
|
|
6
|
-
worktreePath: z.string().optional(),
|
|
7
|
-
timeoutSeconds: z.number().optional().default(180),
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
export type RunPlaywrightInput = z.input<typeof RunPlaywrightInputSchema>;
|
|
11
|
-
|
|
12
|
-
export async function runPlaywrightHandler(args: RunPlaywrightInput) {
|
|
13
|
-
const parsed = RunPlaywrightInputSchema.parse(args || {});
|
|
14
|
-
const tdArgs = ["repo", "run-playwright"];
|
|
15
|
-
if (parsed.grep) {
|
|
16
|
-
tdArgs.push("--grep", parsed.grep);
|
|
17
|
-
}
|
|
18
|
-
if (parsed.worktreePath) {
|
|
19
|
-
tdArgs.push("--worktree", parsed.worktreePath);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const result = await runCommand("td-cli", tdArgs, {
|
|
23
|
-
cwd: parsed.worktreePath,
|
|
24
|
-
timeout: parsed.timeoutSeconds * 1000,
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
if (result.exitCode !== 0) {
|
|
28
|
-
// Playwright might exit with non-zero if tests fail, but our td-cli should ideally handle it
|
|
29
|
-
// and return a JSON with success: false.
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const output = JSON.parse(result.stdout);
|
|
33
|
-
if (output.status === "error") {
|
|
34
|
-
throw new Error(`Failed to run Playwright: ${output.message}`);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
success: output.success,
|
|
39
|
-
command: output.command,
|
|
40
|
-
failedTests: output.failedTests
|
|
41
|
-
};
|
|
42
|
-
}
|