@arii/boomtick-mcp 0.4.1 → 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 (163) hide show
  1. package/.mcp/schemas/boomtick.health.json +14 -10
  2. package/.mcp/schemas/boomtick_health.json +16 -0
  3. package/.mcp/schemas/github.checkout_branch.json +28 -24
  4. package/.mcp/schemas/github.comment_triage_summary.json +21 -17
  5. package/.mcp/schemas/github.create_issue.json +55 -50
  6. package/.mcp/schemas/github.create_pull_request.json +53 -48
  7. package/.mcp/schemas/github.get_merge_conflict_files.json +21 -17
  8. package/.mcp/schemas/github.get_pr.json +30 -12
  9. package/.mcp/schemas/github.get_pr_commits.json +32 -0
  10. package/.mcp/schemas/github.get_pr_diff.json +30 -12
  11. package/.mcp/schemas/github.get_pr_reviews.json +32 -0
  12. package/.mcp/schemas/github.issue_comment.json +35 -17
  13. package/.mcp/schemas/github.issue_update.json +114 -96
  14. package/.mcp/schemas/github.issue_view.json +30 -12
  15. package/.mcp/schemas/github.open_replacement_pr.json +64 -60
  16. package/.mcp/schemas/github.search_open_prs.json +61 -43
  17. package/.mcp/schemas/github_checkout_branch.json +31 -0
  18. package/.mcp/schemas/github_comment_triage_summary.json +24 -0
  19. package/.mcp/schemas/github_create_issue.json +58 -0
  20. package/.mcp/schemas/github_create_pull_request.json +56 -0
  21. package/.mcp/schemas/github_get_merge_conflict_files.json +24 -0
  22. package/.mcp/schemas/github_get_pr.json +32 -0
  23. package/.mcp/schemas/github_get_pr_commits.json +32 -0
  24. package/.mcp/schemas/github_get_pr_diff.json +32 -0
  25. package/.mcp/schemas/github_get_pr_reviews.json +32 -0
  26. package/.mcp/schemas/github_issue_comment.json +38 -0
  27. package/.mcp/schemas/github_issue_update.json +123 -0
  28. package/.mcp/schemas/github_issue_view.json +32 -0
  29. package/.mcp/schemas/github_open_replacement_pr.json +67 -0
  30. package/.mcp/schemas/github_search_open_prs.json +65 -0
  31. package/.mcp/schemas/jules.cancel_session.json +16 -12
  32. package/.mcp/schemas/jules.create_session.json +41 -37
  33. package/.mcp/schemas/jules.get_messages.json +16 -12
  34. package/.mcp/schemas/jules.get_pr.json +16 -12
  35. package/.mcp/schemas/jules.get_session.json +16 -12
  36. package/.mcp/schemas/jules.list_sessions.json +33 -29
  37. package/.mcp/schemas/jules.send_message.json +30 -26
  38. package/.mcp/schemas/jules.trigger_feedback.json +16 -12
  39. package/.mcp/schemas/jules_cancel_session.json +18 -0
  40. package/.mcp/schemas/jules_create_session.json +44 -0
  41. package/.mcp/schemas/jules_get_messages.json +18 -0
  42. package/.mcp/schemas/jules_get_pr.json +18 -0
  43. package/.mcp/schemas/jules_get_session.json +18 -0
  44. package/.mcp/schemas/jules_list_sessions.json +36 -0
  45. package/.mcp/schemas/jules_send_message.json +35 -0
  46. package/.mcp/schemas/jules_trigger_feedback.json +18 -0
  47. package/.mcp/schemas/repo.commit_patch.json +34 -30
  48. package/.mcp/schemas/repo.create_branch.json +21 -17
  49. package/.mcp/schemas/repo.create_repair_branch.json +34 -30
  50. package/.mcp/schemas/repo.get_changed_files.json +19 -15
  51. package/.mcp/schemas/repo.get_command_schema.json +16 -12
  52. package/.mcp/schemas/repo.get_package_scripts.json +21 -17
  53. package/.mcp/schemas/repo.get_workflow_runs.json +50 -0
  54. package/.mcp/schemas/repo.logs.json +42 -24
  55. package/.mcp/schemas/repo.read_agent_context.json +7 -3
  56. package/.mcp/schemas/repo.read_ci_logs.json +35 -17
  57. package/.mcp/schemas/repo.run_tests.json +40 -36
  58. package/.mcp/schemas/repo_commit_patch.json +39 -0
  59. package/.mcp/schemas/repo_create_branch.json +24 -0
  60. package/.mcp/schemas/repo_create_repair_branch.json +37 -0
  61. package/.mcp/schemas/repo_get_changed_files.json +22 -0
  62. package/.mcp/schemas/repo_get_command_schema.json +18 -0
  63. package/.mcp/schemas/repo_get_package_scripts.json +23 -0
  64. package/.mcp/schemas/repo_get_workflow_runs.json +50 -0
  65. package/.mcp/schemas/repo_logs.json +45 -0
  66. package/.mcp/schemas/repo_read_agent_context.json +9 -0
  67. package/.mcp/schemas/repo_read_ci_logs.json +38 -0
  68. package/.mcp/schemas/repo_run_tests.json +45 -0
  69. package/CHANGELOG.md +16 -0
  70. package/README.md +0 -1
  71. package/actions/impact-analysis/action.yml +4 -16
  72. package/actions/run-project-gate/action.yml +2 -9
  73. package/actions/test-build/action.yml +1 -19
  74. package/dist/lib/git.js +74 -0
  75. package/dist/lib/git.test.js +82 -0
  76. package/dist/lib/param_utils.js +35 -0
  77. package/dist/lib/param_utils.test.js +44 -0
  78. package/dist/mcp/definitions.js +27 -40
  79. package/dist/mcp/schemas.test.js +28 -0
  80. package/dist/mcp/server.js +64 -74
  81. package/dist/mcp/server.test.js +0 -33
  82. package/dist/tools/contract.js +229 -10
  83. package/dist/tools/github.create_issue.js +3 -2
  84. package/dist/tools/github.create_issue.test.js +10 -0
  85. package/dist/tools/github.create_pull_request.js +4 -4
  86. package/dist/tools/github.get_merge_conflict_files.js +10 -18
  87. package/dist/tools/github.get_pr.js +4 -1
  88. package/dist/tools/github.get_pr_commits.js +21 -0
  89. package/dist/tools/github.get_pr_diff.js +6 -2
  90. package/dist/tools/github.get_pr_reviews.js +21 -0
  91. package/dist/tools/github.get_pr_reviews.test.js +99 -0
  92. package/dist/tools/github.issue_comment.js +8 -1
  93. package/dist/tools/github.issue_update.js +3 -0
  94. package/dist/tools/github.issue_view.js +4 -1
  95. package/dist/tools/github.search_open_prs.js +4 -0
  96. package/dist/tools/jules/create-session.js +1 -2
  97. package/dist/tools/jules/get-pr.js +1 -2
  98. package/dist/tools/jules/get-session.js +1 -2
  99. package/dist/tools/jules/shared.js +24 -0
  100. package/dist/tools/repo.commit_patch.js +30 -10
  101. package/dist/tools/repo.get_changed_files.js +2 -13
  102. package/dist/tools/repo.get_workflow_runs.js +29 -0
  103. package/dist/tools/repo.logs.js +5 -1
  104. package/dist/tools/repo.read_agent_context.js +27 -1
  105. package/dist/tools/repo.read_agent_context.test.js +1 -1
  106. package/dist/tools/repo.read_ci_logs.js +5 -1
  107. package/dist/tools/repo.run_tests.js +26 -4
  108. package/package.json +4 -7
  109. package/scripts/sync-mcp-schemas.ts +6 -1
  110. package/src/agents/verifier-agent.prompt.md +0 -4
  111. package/src/lib/git.test.ts +110 -0
  112. package/src/lib/git.ts +109 -0
  113. package/src/lib/param_utils.test.ts +49 -0
  114. package/src/lib/param_utils.ts +42 -0
  115. package/src/mcp/definitions.ts +30 -43
  116. package/src/mcp/schemas.test.ts +34 -0
  117. package/src/mcp/server.test.ts +0 -42
  118. package/src/mcp/server.ts +65 -75
  119. package/src/tools/contract.ts +241 -10
  120. package/src/tools/github.create_issue.test.ts +10 -0
  121. package/src/tools/github.create_issue.ts +3 -1
  122. package/src/tools/github.create_pull_request.ts +4 -5
  123. package/src/tools/github.get_merge_conflict_files.ts +10 -20
  124. package/src/tools/github.get_pr.ts +4 -1
  125. package/src/tools/github.get_pr_commits.ts +27 -0
  126. package/src/tools/github.get_pr_diff.ts +7 -2
  127. package/src/tools/github.get_pr_reviews.test.ts +110 -0
  128. package/src/tools/github.get_pr_reviews.ts +27 -0
  129. package/src/tools/github.issue_comment.ts +8 -1
  130. package/src/tools/github.issue_update.ts +4 -1
  131. package/src/tools/github.issue_view.ts +4 -1
  132. package/src/tools/github.search_open_prs.ts +4 -0
  133. package/src/tools/jules/create-session.ts +1 -2
  134. package/src/tools/jules/get-pr.ts +1 -2
  135. package/src/tools/jules/get-session.ts +1 -2
  136. package/src/tools/jules/shared.ts +35 -0
  137. package/src/tools/repo.commit_patch.ts +35 -11
  138. package/src/tools/repo.get_changed_files.ts +2 -16
  139. package/src/tools/repo.get_workflow_runs.ts +36 -0
  140. package/src/tools/repo.logs.ts +5 -1
  141. package/src/tools/repo.read_agent_context.test.ts +1 -1
  142. package/src/tools/repo.read_agent_context.ts +35 -1
  143. package/src/tools/repo.read_ci_logs.ts +5 -1
  144. package/src/tools/repo.run_tests.ts +28 -5
  145. package/.mcp/schemas/agent.search_ddgs.json +0 -27
  146. package/.mcp/schemas/repo.get_route_map.json +0 -19
  147. package/.mcp/schemas/repo.run_lighthouse.json +0 -25
  148. package/.mcp/schemas/repo.run_playwright.json +0 -32
  149. package/dist/lib/td-cli.js +0 -54
  150. package/dist/tools/ddgs.search.js +0 -50
  151. package/dist/tools/ddgs.search.test.js +0 -67
  152. package/dist/tools/ddgs_search.py +0 -23
  153. package/dist/tools/repo.get_route_map.js +0 -37
  154. package/dist/tools/repo.run_lighthouse.js +0 -37
  155. package/dist/tools/repo.run_playwright.js +0 -29
  156. package/scripts/export-mcp-schema.ts +0 -7
  157. package/src/lib/td-cli.ts +0 -73
  158. package/src/tools/ddgs.search.test.ts +0 -92
  159. package/src/tools/ddgs.search.ts +0 -58
  160. package/src/tools/ddgs_search.py +0 -23
  161. package/src/tools/repo.get_route_map.ts +0 -41
  162. package/src/tools/repo.run_lighthouse.ts +0 -40
  163. package/src/tools/repo.run_playwright.ts +0 -35
@@ -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,9 +1,11 @@
1
1
  import { z } from "zod";
2
2
  import { runCommand } from "../lib/shell.js";
3
+ import { resolveRepo } from "../lib/git.js";
3
4
  import { sanitizeError } from "../lib/error_utils.js";
4
5
  export const IssueCommentInputSchema = z.object({
5
6
  issueNumber: z.number().describe("The number of the issue to comment on."),
6
7
  body: z.string().min(1, "Comment body cannot be empty").describe("The content of the comment."),
8
+ repo: z.string().optional().describe("The target repository override (e.g. org/repo)."),
7
9
  });
8
10
  const IssueCommentOutputSchema = z.object({
9
11
  status: z.string(),
@@ -12,7 +14,12 @@ const IssueCommentOutputSchema = z.object({
12
14
  });
13
15
  export async function issueCommentHandler(args) {
14
16
  const params = IssueCommentInputSchema.parse(args);
15
- const result = await runCommand("td-cli", ["gh", "issue-comment", params.issueNumber.toString(), "--body", params.body]);
17
+ const targetRepo = resolveRepo(params.repo);
18
+ const result = await runCommand("td-cli", [
19
+ "gh", "issue-comment", params.issueNumber.toString(),
20
+ "--body", params.body,
21
+ "--repo", targetRepo
22
+ ]);
16
23
  if (result.exitCode !== 0) {
17
24
  throw new Error(`Failed to post comment: ${sanitizeError(result.stderr)}`);
18
25
  }
@@ -1,9 +1,11 @@
1
1
  import { runCommand } from "../lib/shell.js";
2
+ import { resolveRepo } from "../lib/git.js";
2
3
  import { sanitizeError } from "../lib/error_utils.js";
3
4
  import { IssueUpdateInputSchema, IssueUpdateResponseSchema } from "./contract.js";
4
5
  export { IssueUpdateInputSchema };
5
6
  export async function issueUpdateHandler(args) {
6
7
  const params = IssueUpdateInputSchema.parse(args);
8
+ const targetRepo = resolveRepo(params.repo);
7
9
  const cmdArgs = ["gh", "issue-update", params.issueNumber.toString()];
8
10
  if (params.body)
9
11
  cmdArgs.push("--body", params.body);
@@ -17,6 +19,7 @@ export async function issueUpdateHandler(args) {
17
19
  cmdArgs.push("--remove-labels", params.removeLabels.join(","));
18
20
  if (params.state)
19
21
  cmdArgs.push("--state", params.state);
22
+ cmdArgs.push("--repo", targetRepo);
20
23
  const result = await runCommand("td-cli", cmdArgs);
21
24
  if (result.exitCode !== 0) {
22
25
  throw new Error(`Failed to update issue: ${sanitizeError(result.stderr)}`);
@@ -1,8 +1,10 @@
1
1
  import { z } from "zod";
2
2
  import { runCommand } from "../lib/shell.js";
3
+ import { resolveRepo } from "../lib/git.js";
3
4
  import { sanitizeError } from "../lib/error_utils.js";
4
5
  export const IssueViewInputSchema = z.object({
5
6
  issueNumber: z.number().describe("The number of the issue to view."),
7
+ repo: z.string().optional().describe("The target repository override (e.g. org/repo)."),
6
8
  });
7
9
  const IssueViewOutputSchema = z.object({
8
10
  status: z.string(),
@@ -16,7 +18,8 @@ const IssueViewOutputSchema = z.object({
16
18
  });
17
19
  export async function issueViewHandler(args) {
18
20
  const params = IssueViewInputSchema.parse(args);
19
- const result = await runCommand("td-cli", ["gh", "issue-view", params.issueNumber.toString()]);
21
+ const targetRepo = resolveRepo(params.repo);
22
+ const result = await runCommand("td-cli", ["gh", "issue-view", params.issueNumber.toString(), "--repo", targetRepo]);
20
23
  if (result.exitCode !== 0) {
21
24
  throw new Error(`Failed to view issue: ${sanitizeError(result.stderr)}`);
22
25
  }
@@ -1,17 +1,21 @@
1
1
  import { z } from "zod";
2
2
  import { runCommand } from "../lib/shell.js";
3
+ import { resolveRepo } from "../lib/git.js";
3
4
  export const SearchOpenPrsInputSchema = z.object({
4
5
  state: z.enum(["open", "closed", "all"]).optional().default("open"),
5
6
  includeDrafts: z.boolean().optional().default(true),
6
7
  limit: z.number().min(1).max(100).optional().default(100),
7
8
  labels: z.array(z.string()).optional(),
9
+ repo: z.string().optional().describe("The target repository override (e.g. org/repo)."),
8
10
  });
9
11
  export async function searchOpenPrsHandler(args) {
10
12
  const params = SearchOpenPrsInputSchema.parse(args);
13
+ const targetRepo = resolveRepo(params.repo);
11
14
  const tdArgs = [
12
15
  "gh", "search-prs",
13
16
  "--state", params.state,
14
17
  "--limit", params.limit.toString(),
18
+ "--repo", targetRepo,
15
19
  ];
16
20
  if (!params.includeDrafts) {
17
21
  tdArgs.push("--no-include-drafts");
@@ -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,33 +1,53 @@
1
1
  import { z } from "zod";
2
2
  import { runCommand } from "../lib/shell.js";
3
+ import { config } from "../config.js";
3
4
  export const CommitPatchInputSchema = z.object({
4
- worktreePath: z.string(),
5
+ worktreePath: z.string().optional(),
5
6
  message: z.string(),
6
7
  allowedFiles: z.array(z.string()),
7
- writeMode: z.boolean().optional().default(false),
8
+ writeMode: z.boolean().optional().default(true),
8
9
  });
9
10
  export async function commitPatchHandler(args) {
10
- if (!args.writeMode) {
11
+ const parsed = CommitPatchInputSchema.parse(args || {});
12
+ const worktreePath = parsed.worktreePath || config.repoPath;
13
+ if (parsed.writeMode === false) {
11
14
  throw new Error("writeMode must be true to commit changes.");
12
15
  }
13
- const statusResult = await runCommand("git", ["status", "--porcelain"], { cwd: args.worktreePath });
14
- const changedFiles = statusResult.stdout.trim().split("\n").filter(l => l.length > 0).map(l => l.substring(3));
15
- const forbiddenFiles = changedFiles.filter(f => !args.allowedFiles.includes(f));
16
+ const statusResult = await runCommand("git", ["status", "--porcelain"], { cwd: worktreePath });
17
+ const changedFiles = statusResult.stdout
18
+ .split("\n")
19
+ .map((l) => (l.endsWith("\r") ? l.slice(0, -1) : l))
20
+ .filter((l) => l.trim().length > 0)
21
+ .map((l) => {
22
+ const match = l.match(/^..\s+(.*)$/);
23
+ if (!match)
24
+ return l.slice(3).trim();
25
+ let file = match[1].trim();
26
+ if (file.includes(" -> ")) {
27
+ file = file.split(" -> ").pop().trim();
28
+ }
29
+ if (file.startsWith('"') && file.endsWith('"')) {
30
+ file = file.slice(1, -1);
31
+ }
32
+ return file;
33
+ })
34
+ .filter(Boolean);
35
+ const forbiddenFiles = changedFiles.filter((f) => !parsed.allowedFiles.includes(f));
16
36
  if (forbiddenFiles.length > 0) {
17
37
  throw new Error(`The following files are not in the allowed list: ${forbiddenFiles.join(", ")}`);
18
38
  }
19
39
  if (changedFiles.length === 0) {
20
40
  throw new Error("No changes to commit.");
21
41
  }
22
- await runCommand("git", ["add", "."], { cwd: args.worktreePath });
23
- const commitResult = await runCommand("git", ["commit", "-m", args.message], { cwd: args.worktreePath });
42
+ await runCommand("git", ["add", "--", ...changedFiles], { cwd: worktreePath });
43
+ const commitResult = await runCommand("git", ["commit", "-m", parsed.message], { cwd: worktreePath });
24
44
  if (commitResult.exitCode !== 0) {
25
45
  throw new Error(`Failed to commit: ${commitResult.stderr}`);
26
46
  }
27
- const shaResult = await runCommand("git", ["rev-parse", "HEAD"], { cwd: args.worktreePath });
47
+ const shaResult = await runCommand("git", ["rev-parse", "HEAD"], { cwd: worktreePath });
28
48
  return {
29
49
  success: true,
30
50
  commitSha: shaResult.stdout.trim(),
31
- changedFiles
51
+ changedFiles,
32
52
  };
33
53
  }
@@ -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
+ }
@@ -1,14 +1,18 @@
1
1
  import { z } from "zod";
2
- import { runTDCli } from "../lib/td-cli.js";
2
+ import { runTDCli } from "./jules/shared.js";
3
3
  export const RepoLogsInputSchema = z.object({
4
4
  prNumber: z.number(),
5
5
  grep: z.string().optional(),
6
+ repo: z.string().optional(),
6
7
  });
7
8
  export async function repoLogsHandler(args) {
8
9
  const params = ["repo", "logs", args.prNumber.toString()];
9
10
  if (args.grep) {
10
11
  params.push("--grep", args.grep);
11
12
  }
13
+ if (args.repo) {
14
+ params.push("--repo", args.repo);
15
+ }
12
16
  const output = await runTDCli(params);
13
17
  return {
14
18
  logs: output.logs || (output.data ? output.data.logs : undefined)
@@ -1,6 +1,32 @@
1
1
  import { z } from "zod";
2
2
  import { config } from "../config.js";
3
- import { loadStandaloneAgentContext } from "../lib/td-cli.js";
3
+ import { execSync } from "child_process";
4
+ import * as fs from "fs";
5
+ import * as path from "path";
6
+ export const loadStandaloneAgentContext = (repoRoot = process.cwd()) => {
7
+ const configPath = path.join(repoRoot, "project_config.json");
8
+ let projectConfig = {};
9
+ if (fs.existsSync(configPath)) {
10
+ try {
11
+ projectConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
12
+ }
13
+ catch (e) {
14
+ console.warn(`[MCP Warning] Failed to parse ${configPath}:`, e);
15
+ }
16
+ }
17
+ let gitCommit = "unknown";
18
+ try {
19
+ gitCommit = execSync("git rev-parse HEAD", { cwd: repoRoot, encoding: "utf-8" }).trim();
20
+ }
21
+ catch {
22
+ // Fallback if git is unavailable
23
+ }
24
+ return {
25
+ packageName: projectConfig.packageName ?? "boomtick",
26
+ gitCommit,
27
+ config: projectConfig,
28
+ };
29
+ };
4
30
  export const ReadAgentContextInputSchema = z.object({});
5
31
  export async function readAgentContextHandler(_args) {
6
32
  const context = loadStandaloneAgentContext(config.repoPath);
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { loadStandaloneAgentContext } from "../lib/td-cli.js";
2
+ import { loadStandaloneAgentContext } from "./repo.read_agent_context.js";
3
3
  describe("Standalone Agent Context Loader", () => {
4
4
  it("should load agent context without submodule references", () => {
5
5
  const context = loadStandaloneAgentContext(process.cwd());
@@ -1,14 +1,18 @@
1
1
  import { z } from "zod";
2
- import { runTDCli } from "../lib/td-cli.js";
2
+ import { runTDCli } from "./jules/shared.js";
3
3
  export const ReadCiLogsInputSchema = z.object({
4
4
  prNumber: z.number(),
5
5
  all: z.boolean().optional(),
6
+ repo: z.string().optional(),
6
7
  });
7
8
  export async function readCiLogsHandler(args) {
8
9
  const params = ["repo", "ci-logs", args.prNumber.toString()];
9
10
  if (args.all) {
10
11
  params.push("--all");
11
12
  }
13
+ if (args.repo) {
14
+ params.push("--repo", args.repo);
15
+ }
12
16
  const output = await runTDCli(params);
13
17
  return {
14
18
  checks: output.checks,
@@ -5,8 +5,30 @@ export const RunTestsInputSchema = z.object({
5
5
  timeoutSeconds: z.number().optional().default(300),
6
6
  worktreePath: z.string().optional(),
7
7
  });
8
+ function parseCommandArguments(fullCmd) {
9
+ const tokens = [];
10
+ const regex = /"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|(\S+)/g;
11
+ let match;
12
+ while ((match = regex.exec(fullCmd)) !== null) {
13
+ if (match[1] !== undefined) {
14
+ tokens.push(match[1].replace(/\\"/g, '"'));
15
+ }
16
+ else if (match[2] !== undefined) {
17
+ tokens.push(match[2].replace(/\\'/g, "'"));
18
+ }
19
+ else {
20
+ tokens.push(match[3]);
21
+ }
22
+ }
23
+ if (tokens.length === 0) {
24
+ return ["", []];
25
+ }
26
+ const [cmd, ...args] = tokens;
27
+ return [cmd, args];
28
+ }
8
29
  export async function runTestsHandler(args) {
9
- const commands = args.commands || [
30
+ const parsed = RunTestsInputSchema.parse(args || {});
31
+ const commands = parsed.commands || [
10
32
  "pnpm install --frozen-lockfile",
11
33
  "pnpm lint",
12
34
  "pnpm test",
@@ -15,11 +37,11 @@ export async function runTestsHandler(args) {
15
37
  const results = [];
16
38
  let success = true;
17
39
  for (const fullCmd of commands) {
18
- const [cmd, ...cmdArgs] = fullCmd.split(" ");
40
+ const [cmd, cmdArgs] = parseCommandArguments(fullCmd);
19
41
  try {
20
42
  const res = await runCommand(cmd, cmdArgs, {
21
- cwd: args.worktreePath,
22
- timeout: args.timeoutSeconds * 1000
43
+ cwd: parsed.worktreePath,
44
+ timeout: parsed.timeoutSeconds * 1000
23
45
  });
24
46
  results.push(res);
25
47
  if (res.exitCode !== 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arii/boomtick-mcp",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Boomtick MCP Guide: PR Rescue + Merge Conflict Agent",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,7 +17,6 @@
17
17
  },
18
18
  "dependencies": {
19
19
  "@modelcontextprotocol/sdk": "^1.6.0",
20
- "dotenv": "^17.4.2",
21
20
  "zod": "^4.4.3"
22
21
  },
23
22
  "engines": {
@@ -27,17 +26,15 @@
27
26
  "devDependencies": {
28
27
  "@types/node": "^20.17.15",
29
28
  "json-schema-to-zod": "^2.8.1",
30
- "shx": "^0.4.0",
31
29
  "tsx": "^4.22.4",
32
30
  "typescript": "^5.7.3",
33
- "vitest": "^4.1.9",
34
- "yaml": "^2.9.0"
31
+ "vitest": "^4.1.9"
35
32
  },
36
33
  "scripts": {
37
- "build": "tsx scripts/sync-contracts.ts && tsc && shx cp src/tools/ddgs_search.py dist/tools/ && tsx scripts/sync-mcp-schemas.ts",
34
+ "build": "tsx scripts/sync-contracts.ts && tsc && tsx scripts/sync-mcp-schemas.ts",
38
35
  "watch": "tsc -w",
39
36
  "start": "node dist/index.js",
40
- "test": "tsx scripts/sync-contracts.ts && vitest run",
37
+ "test": "tsx scripts/sync-contracts.ts && tsx scripts/sync-mcp-schemas.ts && vitest run",
41
38
  "run-evals": "node dist/evals/run-evals.js",
42
39
  "sync:mcp-schemas": "tsx scripts/sync-mcp-schemas.ts",
43
40
  "release:patch": "pnpm version patch",
@@ -69,7 +69,12 @@ async function syncSchemas() {
69
69
  }
70
70
 
71
71
  mcpTools.forEach((tool) => {
72
- const schemaContent = JSON.stringify(tool.inputSchema, null, 2);
72
+ const exportedSchema = {
73
+ name: tool.name,
74
+ description: tool.description,
75
+ parameters: tool.inputSchema,
76
+ };
77
+ const schemaContent = JSON.stringify(exportedSchema, null, 2);
73
78
  const fileName = `${tool.name}.json`;
74
79
 
75
80
  validTargets.forEach(targetDir => {
@@ -10,10 +10,6 @@ Required:
10
10
  - test
11
11
  - build
12
12
 
13
- Conditional:
14
- - Playwright for UI/routes/browser behavior
15
- - Lighthouse for performance/accessibility/SEO-sensitive changes
16
-
17
13
  Output:
18
14
  - commands run
19
15
  - pass/fail status