@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.
Files changed (82) hide show
  1. package/.mcp/schemas/github.get_pr_commits.json +32 -0
  2. package/.mcp/schemas/github.get_pr_reviews.json +32 -0
  3. package/.mcp/schemas/github_get_pr_commits.json +32 -0
  4. package/.mcp/schemas/github_get_pr_reviews.json +32 -0
  5. package/.mcp/schemas/repo.get_workflow_runs.json +50 -0
  6. package/.mcp/schemas/repo.logs.json +14 -0
  7. package/.mcp/schemas/repo.read_ci_logs.json +14 -0
  8. package/.mcp/schemas/repo_get_workflow_runs.json +50 -0
  9. package/.mcp/schemas/repo_logs.json +14 -0
  10. package/.mcp/schemas/repo_read_ci_logs.json +14 -0
  11. package/CHANGELOG.md +7 -0
  12. package/README.md +0 -1
  13. package/actions/impact-analysis/action.yml +4 -16
  14. package/actions/run-project-gate/action.yml +2 -9
  15. package/actions/test-build/action.yml +1 -19
  16. package/dist/lib/git.js +44 -0
  17. package/dist/lib/git.test.js +82 -0
  18. package/dist/mcp/definitions.js +16 -39
  19. package/dist/mcp/server.js +0 -46
  20. package/dist/mcp/server.test.js +0 -33
  21. package/dist/tools/contract.js +135 -2
  22. package/dist/tools/github.get_merge_conflict_files.js +10 -18
  23. package/dist/tools/github.get_pr_commits.js +21 -0
  24. package/dist/tools/github.get_pr_reviews.js +21 -0
  25. package/dist/tools/github.get_pr_reviews.test.js +99 -0
  26. package/dist/tools/jules/create-session.js +1 -2
  27. package/dist/tools/jules/get-pr.js +1 -2
  28. package/dist/tools/jules/get-session.js +1 -2
  29. package/dist/tools/jules/shared.js +24 -0
  30. package/dist/tools/repo.get_changed_files.js +2 -13
  31. package/dist/tools/repo.get_workflow_runs.js +29 -0
  32. package/dist/tools/repo.logs.js +5 -1
  33. package/dist/tools/repo.read_agent_context.js +27 -1
  34. package/dist/tools/repo.read_agent_context.test.js +1 -1
  35. package/dist/tools/repo.read_ci_logs.js +5 -1
  36. package/dist/tools/repo.run_tests.js +1 -1
  37. package/package.json +3 -6
  38. package/src/agents/verifier-agent.prompt.md +0 -4
  39. package/src/lib/git.test.ts +110 -0
  40. package/src/lib/git.ts +75 -0
  41. package/src/mcp/definitions.ts +18 -42
  42. package/src/mcp/server.test.ts +0 -42
  43. package/src/mcp/server.ts +0 -45
  44. package/src/tools/contract.ts +147 -2
  45. package/src/tools/github.get_merge_conflict_files.ts +10 -20
  46. package/src/tools/github.get_pr_commits.ts +27 -0
  47. package/src/tools/github.get_pr_reviews.test.ts +110 -0
  48. package/src/tools/github.get_pr_reviews.ts +27 -0
  49. package/src/tools/jules/create-session.ts +1 -2
  50. package/src/tools/jules/get-pr.ts +1 -2
  51. package/src/tools/jules/get-session.ts +1 -2
  52. package/src/tools/jules/shared.ts +35 -0
  53. package/src/tools/repo.get_changed_files.ts +2 -16
  54. package/src/tools/repo.get_workflow_runs.ts +36 -0
  55. package/src/tools/repo.logs.ts +5 -1
  56. package/src/tools/repo.read_agent_context.test.ts +1 -1
  57. package/src/tools/repo.read_agent_context.ts +35 -1
  58. package/src/tools/repo.read_ci_logs.ts +5 -1
  59. package/src/tools/repo.run_tests.ts +1 -1
  60. package/.mcp/schemas/agent.search_ddgs.json +0 -31
  61. package/.mcp/schemas/agent_search_ddgs.json +0 -31
  62. package/.mcp/schemas/repo.get_route_map.json +0 -23
  63. package/.mcp/schemas/repo.run_lighthouse.json +0 -29
  64. package/.mcp/schemas/repo.run_playwright.json +0 -36
  65. package/.mcp/schemas/repo_get_route_map.json +0 -23
  66. package/.mcp/schemas/repo_run_lighthouse.json +0 -29
  67. package/.mcp/schemas/repo_run_playwright.json +0 -36
  68. package/dist/lib/td-cli.js +0 -54
  69. package/dist/tools/ddgs.search.js +0 -50
  70. package/dist/tools/ddgs.search.test.js +0 -67
  71. package/dist/tools/ddgs_search.py +0 -23
  72. package/dist/tools/repo.get_route_map.js +0 -37
  73. package/dist/tools/repo.run_lighthouse.js +0 -37
  74. package/dist/tools/repo.run_playwright.js +0 -34
  75. package/scripts/export-mcp-schema.ts +0 -7
  76. package/src/lib/td-cli.ts +0 -73
  77. package/src/tools/ddgs.search.test.ts +0 -92
  78. package/src/tools/ddgs.search.ts +0 -58
  79. package/src/tools/ddgs_search.py +0 -23
  80. package/src/tools/repo.get_route_map.ts +0 -41
  81. package/src/tools/repo.run_lighthouse.ts +0 -40
  82. package/src/tools/repo.run_playwright.ts +0 -42
@@ -14,14 +14,11 @@ import { getChangedFilesHandler, GetChangedFilesInputSchema } from "../tools/rep
14
14
  import { readAgentContextHandler, ReadAgentContextInputSchema } from "../tools/repo.read_agent_context.js";
15
15
  import { getCommandSchemaHandler, GetCommandSchemaInputSchema } from "../tools/repo.get_command_schema.js";
16
16
  import { getPackageScriptsHandler, GetPackageScriptsInputSchema } from "../tools/repo.get_package_scripts.js";
17
- import { getRouteMapHandler, GetRouteMapInputSchema } from "../tools/repo.get_route_map.js";
18
17
  import { readCiLogsHandler, ReadCiLogsInputSchema } from "../tools/repo.read_ci_logs.js";
19
18
  import { repoLogsHandler, RepoLogsInputSchema } from "../tools/repo.logs.js";
20
19
  import { createRepairBranchHandler, CreateRepairBranchInputSchema } from "../tools/repo.create_repair_branch.js";
21
20
  import { createBranchHandler, CreateBranchInputSchema } from "../tools/repo.create_branch.js";
22
21
  import { runTestsHandler, RunTestsInputSchema } from "../tools/repo.run_tests.js";
23
- import { runLighthouseHandler, RunLighthouseInputSchema } from "../tools/repo.run_lighthouse.js";
24
- import { runPlaywrightHandler, RunPlaywrightInputSchema } from "../tools/repo.run_playwright.js";
25
22
  import { commitPatchHandler, CommitPatchInputSchema } from "../tools/repo.commit_patch.js";
26
23
  import { openReplacementPrHandler, OpenReplacementPrInputSchema } from "../tools/github.open_replacement_pr.js";
27
24
  import { commentTriageSummaryHandler, CommentTriageSummaryInputSchema } from "../tools/github.comment_triage_summary.js";
@@ -39,12 +36,10 @@ import { listJulesSessionsHandler, ListJulesSessionsInputSchema } from "../tools
39
36
  import { cancelJulesSessionHandler, CancelJulesSessionInputSchema } from "../tools/jules/cancel-session.js";
40
37
  import { getJulesPullRequestHandler, GetJulesPullRequestInputSchema } from "../tools/jules/get-pr.js";
41
38
  import { triggerJulesFeedbackHandler, TriggerJulesFeedbackInputSchema } from "../tools/jules/trigger-feedback.js";
42
- import { ddgsSearchHandler, DdgsSearchInputSchema } from "../tools/ddgs.search.js";
43
39
  import fs from "fs/promises";
44
40
  import { existsSync, readFileSync } from "fs";
45
41
  import path from "path";
46
42
  import { spawnSync } from "child_process";
47
- import { fileURLToPath } from "url";
48
43
  export class BoomtickMCPServer {
49
44
  server;
50
45
  constructor() {
@@ -106,12 +101,6 @@ export class BoomtickMCPServer {
106
101
  contents: [{ uri, mimeType: "application/json", text: content }],
107
102
  };
108
103
  }
109
- if (uri === "repo://routes") {
110
- const routeMap = await getRouteMapHandler({});
111
- return {
112
- contents: [{ uri, mimeType: "application/json", text: JSON.stringify(routeMap, null, 2) }],
113
- };
114
- }
115
104
  if (uri === "repo://design-tokens") {
116
105
  const tokensPath = path.join(config.repoPath, "src/styles/design-tokens.ts");
117
106
  const content = await fs.readFile(tokensPath, "utf-8");
@@ -133,20 +122,6 @@ export class BoomtickMCPServer {
133
122
  contents: [{ uri, mimeType: "application/json", text: JSON.stringify(logs, null, 2) }],
134
123
  };
135
124
  }
136
- if (uri.startsWith("repo://lighthouse/")) {
137
- const branch = uri.split("/").pop() || "";
138
- const report = await runLighthouseHandler({ route: "/", worktreePath: `../boomtick-mcp-rescue-${branch}` });
139
- return {
140
- contents: [{ uri, mimeType: "application/json", text: JSON.stringify(report, null, 2) }],
141
- };
142
- }
143
- if (uri.startsWith("repo://playwright/")) {
144
- const branch = uri.split("/").pop() || "";
145
- const report = await runPlaywrightHandler({ worktreePath: `../boomtick-mcp-rescue-${branch}` });
146
- return {
147
- contents: [{ uri, mimeType: "application/json", text: JSON.stringify(report, null, 2) }],
148
- };
149
- }
150
125
  throw new Error(`Resource not found: ${uri}`);
151
126
  });
152
127
  }
@@ -187,9 +162,6 @@ export class BoomtickMCPServer {
187
162
  case "repo.get.package.scripts":
188
163
  case "repo.get_package_scripts":
189
164
  return createSuccessResult(await getPackageScriptsHandler(GetPackageScriptsInputSchema.parse(normArgs)));
190
- case "repo.get.route.map":
191
- case "repo.get_route_map":
192
- return createSuccessResult(await getRouteMapHandler(GetRouteMapInputSchema.parse(normArgs)));
193
165
  case "repo.read.ci.logs":
194
166
  case "repo.read_ci_logs":
195
167
  return createSuccessResult(await readCiLogsHandler(ReadCiLogsInputSchema.parse(normArgs)));
@@ -204,12 +176,6 @@ export class BoomtickMCPServer {
204
176
  case "repo.run.tests":
205
177
  case "repo.run_tests":
206
178
  return createSuccessResult(await runTestsHandler(RunTestsInputSchema.parse(normArgs)));
207
- case "repo.run.lighthouse":
208
- case "repo.run_lighthouse":
209
- return createSuccessResult(await runLighthouseHandler(RunLighthouseInputSchema.parse(normArgs)));
210
- case "repo.run.playwright":
211
- case "repo.run_playwright":
212
- return createSuccessResult(await runPlaywrightHandler(RunPlaywrightInputSchema.parse(normArgs)));
213
179
  case "repo.commit.patch":
214
180
  case "repo.commit_patch":
215
181
  return createSuccessResult(await commitPatchHandler(CommitPatchInputSchema.parse(normArgs)));
@@ -261,9 +227,6 @@ export class BoomtickMCPServer {
261
227
  case "jules.trigger.feedback":
262
228
  case "jules.trigger_feedback":
263
229
  return createSuccessResult(await triggerJulesFeedbackHandler(TriggerJulesFeedbackInputSchema.parse(normArgs)));
264
- case "agent.search.ddgs":
265
- case "agent.search_ddgs":
266
- return createSuccessResult(await ddgsSearchHandler(DdgsSearchInputSchema.parse(normArgs)));
267
230
  default:
268
231
  return createErrorResult(`Tool not found: ${request.params.name}`);
269
232
  }
@@ -337,15 +300,6 @@ export class BoomtickMCPServer {
337
300
  console.error("❌ Fatal: td-cli pre-flight check failed:", error instanceof Error ? error.message : String(error));
338
301
  process.exit(1);
339
302
  }
340
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
341
- const ddgsScriptPath = path.join(__dirname, "..", "tools", "ddgs_search.py");
342
- try {
343
- await fs.access(ddgsScriptPath);
344
- }
345
- catch (err) {
346
- console.error(`❌ Fatal: ddgs_search.py not found at ${ddgsScriptPath}`);
347
- process.exit(1);
348
- }
349
303
  const transport = new StdioServerTransport();
350
304
  await this.server.connect(transport);
351
305
  console.error("Boomtick MCP Server running on stdio");
@@ -2,11 +2,8 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
2
2
  import { BoomtickMCPServer } from "./server.js";
3
3
  import fs from "fs/promises";
4
4
  import * as healthModule from "./tools.js";
5
- import * as routeMapModule from "../tools/repo.get_route_map.js";
6
5
  import * as prDiffModule from "../tools/github.get_pr_diff.js";
7
6
  import * as ciLogsModule from "../tools/repo.read_ci_logs.js";
8
- import * as lighthouseModule from "../tools/repo.run_lighthouse.js";
9
- import * as playwrightModule from "../tools/repo.run_playwright.js";
10
7
  vi.mock("fs/promises");
11
8
  vi.mock("../config.js", () => ({
12
9
  config: {
@@ -99,16 +96,6 @@ describe("BoomtickMCPServer", () => {
99
96
  contents: [{ uri: "repo://package-json", mimeType: "application/json", text: '{"name": "test"}' }],
100
97
  });
101
98
  });
102
- it("should handle repo://routes", async () => {
103
- const handler = requestHandlers.get("resources/read");
104
- vi.spyOn(routeMapModule, "getRouteMapHandler").mockResolvedValue({ routes: ["/"] });
105
- const result = await handler({
106
- method: "resources/read",
107
- params: { uri: "repo://routes" },
108
- });
109
- expect(result.contents[0].uri).toBe("repo://routes");
110
- expect(JSON.parse(result.contents[0].text)).toEqual({ routes: ["/"] });
111
- });
112
99
  it("should handle repo://design-tokens", async () => {
113
100
  const handler = requestHandlers.get("resources/read");
114
101
  vi.mocked(fs.readFile).mockResolvedValue("export const tokens = {};");
@@ -141,26 +128,6 @@ describe("BoomtickMCPServer", () => {
141
128
  expect(result.contents[0].uri).toBe("repo://ci/42");
142
129
  expect(JSON.parse(result.contents[0].text)).toEqual({ status: "success" });
143
130
  });
144
- it("should handle repo://lighthouse/:branch", async () => {
145
- const handler = requestHandlers.get("resources/read");
146
- vi.spyOn(lighthouseModule, "runLighthouseHandler").mockResolvedValue({ score: 100 });
147
- const result = await handler({
148
- method: "resources/read",
149
- params: { uri: "repo://lighthouse/feature-x" },
150
- });
151
- expect(result.contents[0].uri).toBe("repo://lighthouse/feature-x");
152
- expect(JSON.parse(result.contents[0].text)).toEqual({ score: 100 });
153
- });
154
- it("should handle repo://playwright/:branch", async () => {
155
- const handler = requestHandlers.get("resources/read");
156
- vi.spyOn(playwrightModule, "runPlaywrightHandler").mockResolvedValue({ passed: true });
157
- const result = await handler({
158
- method: "resources/read",
159
- params: { uri: "repo://playwright/feature-x" },
160
- });
161
- expect(result.contents[0].uri).toBe("repo://playwright/feature-x");
162
- expect(JSON.parse(result.contents[0].text)).toEqual({ passed: true });
163
- });
164
131
  it("should throw when resource is unknown", async () => {
165
132
  const handler = requestHandlers.get("resources/read");
166
133
  await expect(handler({
@@ -1512,6 +1512,35 @@ export const GetPackageScriptsInputJsonSchema = {
1512
1512
  "title": "GetPackageScriptsInput",
1513
1513
  "type": "object"
1514
1514
  };
1515
+ export const GetPrCommitsInputSchema = z.object({ "prNumber": z.number().int().describe("The number of the PR to list commits for."), "repo": z.union([z.string().regex(new RegExp("^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$")), z.null()]).describe("The target repository override (e.g. org/repo).").default(null) });
1516
+ export const GetPrCommitsInputJsonSchema = {
1517
+ "properties": {
1518
+ "prNumber": {
1519
+ "description": "The number of the PR to list commits for.",
1520
+ "title": "Prnumber",
1521
+ "type": "integer"
1522
+ },
1523
+ "repo": {
1524
+ "anyOf": [
1525
+ {
1526
+ "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
1527
+ "type": "string"
1528
+ },
1529
+ {
1530
+ "type": "null"
1531
+ }
1532
+ ],
1533
+ "default": null,
1534
+ "description": "The target repository override (e.g. org/repo).",
1535
+ "title": "Repo"
1536
+ }
1537
+ },
1538
+ "required": [
1539
+ "prNumber"
1540
+ ],
1541
+ "title": "GetPrCommitsInput",
1542
+ "type": "object"
1543
+ };
1515
1544
  export const GetPrDiffInputSchema = z.object({ "prNumber": z.number().int().describe("The number of the pull request to get the diff for."), "repo": z.union([z.string().regex(new RegExp("^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$")), z.null()]).describe("The target repository override (e.g. org/repo).").default(null) });
1516
1545
  export const GetPrDiffInputJsonSchema = {
1517
1546
  "properties": {
@@ -1570,6 +1599,35 @@ export const GetPrInputJsonSchema = {
1570
1599
  "title": "GetPrInput",
1571
1600
  "type": "object"
1572
1601
  };
1602
+ export const GetPrReviewsInputSchema = z.object({ "prNumber": z.number().int().describe("The number of the PR to view reviews for."), "repo": z.union([z.string().regex(new RegExp("^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$")), z.null()]).describe("The target repository override (e.g. org/repo).").default(null) });
1603
+ export const GetPrReviewsInputJsonSchema = {
1604
+ "properties": {
1605
+ "prNumber": {
1606
+ "description": "The number of the PR to view reviews for.",
1607
+ "title": "Prnumber",
1608
+ "type": "integer"
1609
+ },
1610
+ "repo": {
1611
+ "anyOf": [
1612
+ {
1613
+ "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
1614
+ "type": "string"
1615
+ },
1616
+ {
1617
+ "type": "null"
1618
+ }
1619
+ ],
1620
+ "default": null,
1621
+ "description": "The target repository override (e.g. org/repo).",
1622
+ "title": "Repo"
1623
+ }
1624
+ },
1625
+ "required": [
1626
+ "prNumber"
1627
+ ],
1628
+ "title": "GetPrReviewsInput",
1629
+ "type": "object"
1630
+ };
1573
1631
  export const GetRouteMapInputSchema = z.object({ "includeStatic": z.union([z.boolean(), z.null()]).describe("Whether to include static assets in the route map.").default(null) });
1574
1632
  export const GetRouteMapInputJsonSchema = {
1575
1633
  "properties": {
@@ -1590,6 +1648,53 @@ export const GetRouteMapInputJsonSchema = {
1590
1648
  "title": "GetRouteMapInput",
1591
1649
  "type": "object"
1592
1650
  };
1651
+ export const GetWorkflowRunsInputSchema = z.object({ "branch": z.union([z.string(), z.null()]).describe("Filter workflow runs by branch name.").default(null), "event": z.union([z.string(), z.null()]).describe("Filter workflow runs by event (e.g. push, pull_request).").default(null), "repo": z.union([z.string().regex(new RegExp("^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$")), z.null()]).describe("The target repository override (e.g. org/repo).").default(null) });
1652
+ export const GetWorkflowRunsInputJsonSchema = {
1653
+ "properties": {
1654
+ "branch": {
1655
+ "anyOf": [
1656
+ {
1657
+ "type": "string"
1658
+ },
1659
+ {
1660
+ "type": "null"
1661
+ }
1662
+ ],
1663
+ "default": null,
1664
+ "description": "Filter workflow runs by branch name.",
1665
+ "title": "Branch"
1666
+ },
1667
+ "event": {
1668
+ "anyOf": [
1669
+ {
1670
+ "type": "string"
1671
+ },
1672
+ {
1673
+ "type": "null"
1674
+ }
1675
+ ],
1676
+ "default": null,
1677
+ "description": "Filter workflow runs by event (e.g. push, pull_request).",
1678
+ "title": "Event"
1679
+ },
1680
+ "repo": {
1681
+ "anyOf": [
1682
+ {
1683
+ "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
1684
+ "type": "string"
1685
+ },
1686
+ {
1687
+ "type": "null"
1688
+ }
1689
+ ],
1690
+ "default": null,
1691
+ "description": "The target repository override (e.g. org/repo).",
1692
+ "title": "Repo"
1693
+ }
1694
+ },
1695
+ "title": "GetWorkflowRunsInput",
1696
+ "type": "object"
1697
+ };
1593
1698
  export const HealthCheckInputSchema = z.object({ "checkDeep": z.boolean().describe("Whether to perform a deep health check including external dependencies.").default(false) });
1594
1699
  export const HealthCheckInputJsonSchema = {
1595
1700
  "properties": {
@@ -2189,7 +2294,7 @@ export const ReadAgentContextInputJsonSchema = {
2189
2294
  "title": "ReadAgentContextInput",
2190
2295
  "type": "object"
2191
2296
  };
2192
- export const ReadCiLogsInputSchema = z.object({ "prNumber": z.number().int().describe("The number of the pull request to read logs for."), "all": z.boolean().describe("Include logs for successful runs (default: false).").default(false) });
2297
+ export const ReadCiLogsInputSchema = z.object({ "prNumber": z.number().int().describe("The number of the pull request to read logs for."), "all": z.boolean().describe("Include logs for successful runs (default: false).").default(false), "repo": z.union([z.string().regex(new RegExp("^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$")), z.null()]).describe("The target repository override (e.g. org/repo).").default(null) });
2193
2298
  export const ReadCiLogsInputJsonSchema = {
2194
2299
  "properties": {
2195
2300
  "prNumber": {
@@ -2202,6 +2307,20 @@ export const ReadCiLogsInputJsonSchema = {
2202
2307
  "description": "Include logs for successful runs (default: false).",
2203
2308
  "title": "All",
2204
2309
  "type": "boolean"
2310
+ },
2311
+ "repo": {
2312
+ "anyOf": [
2313
+ {
2314
+ "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
2315
+ "type": "string"
2316
+ },
2317
+ {
2318
+ "type": "null"
2319
+ }
2320
+ ],
2321
+ "default": null,
2322
+ "description": "The target repository override (e.g. org/repo).",
2323
+ "title": "Repo"
2205
2324
  }
2206
2325
  },
2207
2326
  "required": [
@@ -2373,7 +2492,7 @@ export const ReadPRCommentsResponseJsonSchema = {
2373
2492
  "title": "ReadPRCommentsResponse",
2374
2493
  "type": "object"
2375
2494
  };
2376
- export const RepoLogsInputSchema = z.object({ "prNumber": z.number().int().describe("The number of the pull request to stream logs for."), "grep": z.union([z.string(), z.null()]).describe("Optional pattern to filter log lines.").default(null) });
2495
+ export const RepoLogsInputSchema = z.object({ "prNumber": z.number().int().describe("The number of the pull request to stream logs for."), "grep": z.union([z.string(), z.null()]).describe("Optional pattern to filter log lines.").default(null), "repo": z.union([z.string().regex(new RegExp("^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$")), z.null()]).describe("The target repository override (e.g. org/repo).").default(null) });
2377
2496
  export const RepoLogsInputJsonSchema = {
2378
2497
  "properties": {
2379
2498
  "prNumber": {
@@ -2393,6 +2512,20 @@ export const RepoLogsInputJsonSchema = {
2393
2512
  "default": null,
2394
2513
  "description": "Optional pattern to filter log lines.",
2395
2514
  "title": "Grep"
2515
+ },
2516
+ "repo": {
2517
+ "anyOf": [
2518
+ {
2519
+ "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
2520
+ "type": "string"
2521
+ },
2522
+ {
2523
+ "type": "null"
2524
+ }
2525
+ ],
2526
+ "default": null,
2527
+ "description": "The target repository override (e.g. org/repo).",
2528
+ "title": "Repo"
2396
2529
  }
2397
2530
  },
2398
2531
  "required": [
@@ -1,26 +1,18 @@
1
1
  import { z } from "zod";
2
- import { runCommand } from "../lib/shell.js";
2
+ import { getMergeConflictFiles } from "../lib/git.js";
3
3
  export const GetMergeConflictFilesInputSchema = z.object({
4
- prNumber: z.number(),
4
+ prNumber: z.number().optional(),
5
5
  baseBranch: z.string().optional().default("main"),
6
+ headRef: z.string().optional().default("HEAD"),
6
7
  });
7
8
  export async function getMergeConflictFilesHandler(args) {
8
- const result = await runCommand("td-cli", [
9
- "gh", "merge-conflicts", args.prNumber.toString(),
10
- "--base", args.baseBranch || "main"
11
- ]);
12
- if (result.exitCode !== 0) {
13
- throw new Error(`Failed to get merge conflicts: ${result.stderr}`);
14
- }
15
- const output = JSON.parse(result.stdout);
16
- if (output.status === "error") {
17
- throw new Error(`Failed to get merge conflicts: ${output.message}`);
18
- }
9
+ const baseBranch = args.baseBranch || "main";
10
+ const headRef = args.headRef || "HEAD";
11
+ const conflictFiles = await getMergeConflictFiles(baseBranch, headRef);
19
12
  return {
20
- prNumber: output.prNumber,
21
- baseBranch: output.baseBranch,
22
- headRef: output.headRef,
23
- conflictFiles: output.conflictFiles,
24
- commandLog: output.commandLog
13
+ prNumber: args.prNumber,
14
+ baseBranch,
15
+ headRef,
16
+ conflictFiles,
25
17
  };
26
18
  }
@@ -0,0 +1,21 @@
1
+ import { z } from "zod";
2
+ import { runCommand } from "../lib/shell.js";
3
+ import { resolveRepo } from "../lib/git.js";
4
+ import { sanitizeError } from "../lib/error_utils.js";
5
+ const GetPrCommitsInputSchema = z.object({
6
+ prNumber: z.number().describe("The number of the PR to list commits for."),
7
+ repo: z.string().optional().describe("The target repository override (e.g. org/repo)."),
8
+ });
9
+ export async function getPrCommitsHandler(args) {
10
+ const params = GetPrCommitsInputSchema.parse(args);
11
+ const targetRepo = resolveRepo(params.repo);
12
+ const result = await runCommand("td-cli", ["gh", "list-pr-commits", "--pr-number", params.prNumber.toString(), "--repo", targetRepo]);
13
+ if (result.exitCode !== 0) {
14
+ throw new Error(`Failed to list PR commits: ${sanitizeError(result.stderr)}`);
15
+ }
16
+ const output = JSON.parse(result.stdout);
17
+ if (output.status === "error") {
18
+ throw new Error(`Failed to list PR commits: ${output.message}`);
19
+ }
20
+ return { commits: output.commits };
21
+ }
@@ -0,0 +1,21 @@
1
+ import { z } from "zod";
2
+ import { runCommand } from "../lib/shell.js";
3
+ import { resolveRepo } from "../lib/git.js";
4
+ import { sanitizeError } from "../lib/error_utils.js";
5
+ const GetPrReviewsInputSchema = z.object({
6
+ prNumber: z.number().describe("The number of the PR to view reviews for."),
7
+ repo: z.string().optional().describe("The target repository override (e.g. org/repo)."),
8
+ });
9
+ export async function getPrReviewsHandler(args) {
10
+ const params = GetPrReviewsInputSchema.parse(args);
11
+ const targetRepo = resolveRepo(params.repo);
12
+ const result = await runCommand("td-cli", ["gh", "read-pr-reviews", "--pr-number", params.prNumber.toString(), "--repo", targetRepo]);
13
+ if (result.exitCode !== 0) {
14
+ throw new Error(`Failed to read PR reviews: ${sanitizeError(result.stderr)}`);
15
+ }
16
+ const output = JSON.parse(result.stdout);
17
+ if (output.status === "error") {
18
+ throw new Error(`Failed to read PR reviews: ${output.message}`);
19
+ }
20
+ return { reviews: output.reviews, reviewCount: output.review_count };
21
+ }
@@ -0,0 +1,99 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { getPrReviewsHandler } from "./github.get_pr_reviews.js";
3
+ import { getPrCommitsHandler } from "./github.get_pr_commits.js";
4
+ import { getWorkflowRunsHandler } from "./repo.get_workflow_runs.js";
5
+ import * as shell from "../lib/shell.js";
6
+ vi.mock("../lib/shell.js", () => ({
7
+ runCommand: vi.fn(),
8
+ }));
9
+ describe("MCP GitHub tools", () => {
10
+ beforeEach(() => {
11
+ vi.clearAllMocks();
12
+ });
13
+ describe("getPrReviewsHandler", () => {
14
+ it("should fetch PR reviews via td-cli gh read-pr-reviews", async () => {
15
+ vi.mocked(shell.runCommand).mockResolvedValueOnce({
16
+ stdout: JSON.stringify({
17
+ status: "success",
18
+ reviews: [{ id: 1, state: "APPROVED", body: "LGTM" }],
19
+ review_count: 1,
20
+ }),
21
+ stderr: "",
22
+ exitCode: 0,
23
+ durationMs: 10,
24
+ command: "td-cli gh read-pr-reviews",
25
+ });
26
+ const res = await getPrReviewsHandler({ prNumber: 819, repo: "arii/boomtick" });
27
+ expect(shell.runCommand).toHaveBeenCalledWith("td-cli", [
28
+ "gh",
29
+ "read-pr-reviews",
30
+ "--pr-number",
31
+ "819",
32
+ "--repo",
33
+ "arii/boomtick",
34
+ ]);
35
+ expect(res).toEqual({
36
+ reviews: [{ id: 1, state: "APPROVED", body: "LGTM" }],
37
+ reviewCount: 1,
38
+ });
39
+ });
40
+ });
41
+ describe("getPrCommitsHandler", () => {
42
+ it("should list PR commits via td-cli gh list-pr-commits", async () => {
43
+ vi.mocked(shell.runCommand).mockResolvedValueOnce({
44
+ stdout: JSON.stringify({
45
+ status: "success",
46
+ commits: [{ sha: "abc1234", author: "Dev", message: "feat: test" }],
47
+ }),
48
+ stderr: "",
49
+ exitCode: 0,
50
+ durationMs: 10,
51
+ command: "td-cli gh list-pr-commits",
52
+ });
53
+ const res = await getPrCommitsHandler({ prNumber: 819, repo: "arii/boomtick" });
54
+ expect(shell.runCommand).toHaveBeenCalledWith("td-cli", [
55
+ "gh",
56
+ "list-pr-commits",
57
+ "--pr-number",
58
+ "819",
59
+ "--repo",
60
+ "arii/boomtick",
61
+ ]);
62
+ expect(res).toEqual({
63
+ commits: [{ sha: "abc1234", author: "Dev", message: "feat: test" }],
64
+ });
65
+ });
66
+ });
67
+ describe("getWorkflowRunsHandler", () => {
68
+ it("should fetch workflow runs via td-cli repo workflow-runs", async () => {
69
+ vi.mocked(shell.runCommand).mockResolvedValueOnce({
70
+ stdout: JSON.stringify({
71
+ status: "success",
72
+ workflow_runs: [{ id: 10, name: "CI" }],
73
+ }),
74
+ stderr: "",
75
+ exitCode: 0,
76
+ durationMs: 10,
77
+ command: "td-cli repo workflow-runs",
78
+ });
79
+ const res = await getWorkflowRunsHandler({
80
+ branch: "main",
81
+ event: "push",
82
+ repo: "arii/boomtick",
83
+ });
84
+ expect(shell.runCommand).toHaveBeenCalledWith("td-cli", [
85
+ "repo",
86
+ "workflow-runs",
87
+ "--repo",
88
+ "arii/boomtick",
89
+ "--branch",
90
+ "main",
91
+ "--event",
92
+ "push",
93
+ ]);
94
+ expect(res).toEqual({
95
+ workflowRuns: [{ id: 10, name: "CI" }],
96
+ });
97
+ });
98
+ });
99
+ });
@@ -1,6 +1,5 @@
1
1
  import { z } from "zod";
2
- import { runTDCli } from "../../lib/td-cli.js";
3
- import { parseJulesSession } from "./shared.js";
2
+ import { runTDCli, parseJulesSession } from "./shared.js";
4
3
  export const CreateJulesSessionInputSchema = z.object({
5
4
  task: z.string(),
6
5
  branch: z.string().optional(),
@@ -1,6 +1,5 @@
1
1
  import { z } from "zod";
2
- import { runTDCli } from "../../lib/td-cli.js";
3
- import { parseJulesSession } from "./shared.js";
2
+ import { runTDCli, parseJulesSession } from "./shared.js";
4
3
  export const GetJulesPullRequestInputSchema = z.object({
5
4
  sessionId: z.string(),
6
5
  });
@@ -1,6 +1,5 @@
1
1
  import { z } from "zod";
2
- import { runTDCli } from "../../lib/td-cli.js";
3
- import { parseJulesSession } from "./shared.js";
2
+ import { runTDCli, parseJulesSession } from "./shared.js";
4
3
  export const GetJulesSessionInputSchema = z.object({
5
4
  sessionId: z.string(),
6
5
  });
@@ -1,3 +1,27 @@
1
+ import { runCommand } from "../../lib/shell.js";
2
+ function tryParseJson(text) {
3
+ try {
4
+ return JSON.parse(text);
5
+ }
6
+ catch {
7
+ return null;
8
+ }
9
+ }
10
+ export async function runTDCli(args) {
11
+ const result = await runCommand("td-cli", args);
12
+ const stdout = result.stdout.trim();
13
+ const output = stdout.startsWith("{") ? tryParseJson(stdout) : null;
14
+ if (output?.status === "error") {
15
+ throw new Error(`td-cli returned error: ${output.message ?? "Unknown error"}`);
16
+ }
17
+ if (result.exitCode !== 0) {
18
+ throw new Error(`td-cli command failed (${args.join(" ")}): ${result.stderr || stdout}`);
19
+ }
20
+ if (!output) {
21
+ throw new Error(`td-cli returned non-JSON output with exit code 0: ${stdout}`);
22
+ }
23
+ return output;
24
+ }
1
25
  export function parseJulesSession(data) {
2
26
  const name = data.name || "";
3
27
  const id = name.startsWith("sessions/") ? name.substring(9) : name;
@@ -1,21 +1,10 @@
1
1
  import { z } from "zod";
2
- import { runCommand } from "../lib/shell.js";
2
+ import { getChangedFiles } from "../lib/git.js";
3
3
  export const GetChangedFilesInputSchema = z.object({
4
4
  base: z.string().optional().default("main"),
5
5
  head: z.string().optional().default("HEAD"),
6
6
  });
7
7
  export async function getChangedFilesHandler(args) {
8
- const result = await runCommand("git", [
9
- "diff",
10
- "--name-status",
11
- `${args.base}...${args.head}`
12
- ]);
13
- if (result.exitCode !== 0) {
14
- throw new Error(`Failed to get changed files: ${result.stderr}`);
15
- }
16
- const files = result.stdout.trim().split("\n").filter(line => line.length > 0).map(line => {
17
- const [status, path] = line.split(/\s+/);
18
- return { status, path };
19
- });
8
+ const files = await getChangedFiles(args.base || "main", args.head || "HEAD");
20
9
  return { files };
21
10
  }
@@ -0,0 +1,29 @@
1
+ import { z } from "zod";
2
+ import { runCommand } from "../lib/shell.js";
3
+ import { resolveRepo } from "../lib/git.js";
4
+ import { sanitizeError } from "../lib/error_utils.js";
5
+ const GetWorkflowRunsInputSchema = z.object({
6
+ branch: z.string().optional().describe("Filter workflow runs by branch name."),
7
+ event: z.string().optional().describe("Filter workflow runs by event (e.g. push, pull_request)."),
8
+ repo: z.string().optional().describe("The target repository override (e.g. org/repo)."),
9
+ });
10
+ export async function getWorkflowRunsHandler(args) {
11
+ const params = GetWorkflowRunsInputSchema.parse(args);
12
+ const targetRepo = resolveRepo(params.repo);
13
+ const cliArgs = ["repo", "workflow-runs", "--repo", targetRepo];
14
+ if (params.branch) {
15
+ cliArgs.push("--branch", params.branch);
16
+ }
17
+ if (params.event) {
18
+ cliArgs.push("--event", params.event);
19
+ }
20
+ const result = await runCommand("td-cli", cliArgs);
21
+ if (result.exitCode !== 0) {
22
+ throw new Error(`Failed to fetch workflow runs: ${sanitizeError(result.stderr)}`);
23
+ }
24
+ const output = JSON.parse(result.stdout);
25
+ if (output.status === "error") {
26
+ throw new Error(`Failed to fetch workflow runs: ${output.message}`);
27
+ }
28
+ return { workflowRuns: output.workflow_runs };
29
+ }