@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
@@ -1,5 +1,6 @@
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
  import { CreateIssueInputSchema, CreateIssueResponseSchema } from "./contract.js";
5
6
 
@@ -7,11 +8,12 @@ export { CreateIssueInputSchema };
7
8
 
8
9
  export async function createIssueHandler(args: z.input<typeof CreateIssueInputSchema>) {
9
10
  const params = CreateIssueInputSchema.parse(args);
11
+ const targetRepo = resolveRepo(params.repo);
10
12
 
11
13
  const cmdArgs = ["gh", "create-issue", "--title", params.title];
12
14
  if (params.body) cmdArgs.push("--body", params.body);
13
15
  if (params.file) cmdArgs.push("--file", params.file);
14
- if (params.repo) cmdArgs.push("--repo", params.repo);
16
+ cmdArgs.push("--repo", targetRepo);
15
17
 
16
18
  const result = await runCommand("td-cli", cmdArgs);
17
19
 
@@ -1,11 +1,13 @@
1
1
  import { z } from "zod";
2
2
  import { runCommand } from "../lib/shell.js";
3
+ import { resolveRepo } from "../lib/git.js";
3
4
  import { CreatePullRequestInputSchema } from "./contract.js";
4
5
 
5
6
  export { CreatePullRequestInputSchema };
6
7
 
7
8
  export async function createPullRequestHandler(args: z.input<typeof CreatePullRequestInputSchema>) {
8
9
  const params = CreatePullRequestInputSchema.parse(args);
10
+ const targetRepo = resolveRepo(params.repo);
9
11
 
10
12
  const tdArgs = [
11
13
  "gh",
@@ -13,17 +15,14 @@ export async function createPullRequestHandler(args: z.input<typeof CreatePullRe
13
15
  "--title", params.title,
14
16
  "--body", params.body,
15
17
  "--head", params.head,
16
- "--base", params.base
18
+ "--base", params.base,
19
+ "--repo", targetRepo
17
20
  ];
18
21
 
19
22
  if (params.draft) {
20
23
  tdArgs.push("--draft");
21
24
  }
22
25
 
23
- if (params.repo) {
24
- tdArgs.push("--repo", params.repo);
25
- }
26
-
27
26
  const result = await runCommand("td-cli", tdArgs);
28
27
 
29
28
  if (result.exitCode !== 0) {
@@ -1,31 +1,21 @@
1
1
  import { z } from "zod";
2
- import { runCommand } from "../lib/shell.js";
2
+ import { getMergeConflictFiles } from "../lib/git.js";
3
3
 
4
4
  export const GetMergeConflictFilesInputSchema = z.object({
5
- prNumber: z.number(),
5
+ prNumber: z.number().optional(),
6
6
  baseBranch: z.string().optional().default("main"),
7
+ headRef: z.string().optional().default("HEAD"),
7
8
  });
8
9
 
9
10
  export async function getMergeConflictFilesHandler(args: z.infer<typeof GetMergeConflictFilesInputSchema>) {
10
- const result = await runCommand("td-cli", [
11
- "gh", "merge-conflicts", args.prNumber.toString(),
12
- "--base", args.baseBranch || "main"
13
- ]);
14
-
15
- if (result.exitCode !== 0) {
16
- throw new Error(`Failed to get merge conflicts: ${result.stderr}`);
17
- }
18
-
19
- const output = JSON.parse(result.stdout);
20
- if (output.status === "error") {
21
- throw new Error(`Failed to get merge conflicts: ${output.message}`);
22
- }
11
+ const baseBranch = args.baseBranch || "main";
12
+ const headRef = args.headRef || "HEAD";
13
+ const conflictFiles = await getMergeConflictFiles(baseBranch, headRef);
23
14
 
24
15
  return {
25
- prNumber: output.prNumber,
26
- baseBranch: output.baseBranch,
27
- headRef: output.headRef,
28
- conflictFiles: output.conflictFiles,
29
- commandLog: output.commandLog
16
+ prNumber: args.prNumber,
17
+ baseBranch,
18
+ headRef,
19
+ conflictFiles,
30
20
  };
31
21
  }
@@ -1,15 +1,18 @@
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
 
5
6
  export const GetPrInputSchema = z.object({
6
7
  prNumber: z.number().describe("The number of the PR to view."),
8
+ repo: z.string().optional().describe("The target repository override (e.g. org/repo)."),
7
9
  });
8
10
 
9
11
  export async function getPrHandler(args: z.infer<typeof GetPrInputSchema>) {
10
12
  const params = GetPrInputSchema.parse(args);
13
+ const targetRepo = resolveRepo(params.repo);
11
14
 
12
- const result = await runCommand("td-cli", ["gh", "view", params.prNumber.toString()]);
15
+ const result = await runCommand("td-cli", ["gh", "view", params.prNumber.toString(), "--repo", targetRepo]);
13
16
 
14
17
  if (result.exitCode !== 0) {
15
18
  throw new Error(`Failed to get PR details: ${sanitizeError(result.stderr)}`);
@@ -0,0 +1,27 @@
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
+
6
+ const GetPrCommitsInputSchema = z.object({
7
+ prNumber: z.number().describe("The number of the PR to list commits for."),
8
+ repo: z.string().optional().describe("The target repository override (e.g. org/repo)."),
9
+ });
10
+
11
+ export async function getPrCommitsHandler(args: z.infer<typeof GetPrCommitsInputSchema>) {
12
+ const params = GetPrCommitsInputSchema.parse(args);
13
+ const targetRepo = resolveRepo(params.repo);
14
+
15
+ const result = await runCommand("td-cli", ["gh", "list-pr-commits", "--pr-number", params.prNumber.toString(), "--repo", targetRepo]);
16
+
17
+ if (result.exitCode !== 0) {
18
+ throw new Error(`Failed to list PR commits: ${sanitizeError(result.stderr)}`);
19
+ }
20
+
21
+ const output = JSON.parse(result.stdout);
22
+ if (output.status === "error") {
23
+ throw new Error(`Failed to list PR commits: ${output.message}`);
24
+ }
25
+
26
+ return { commits: output.commits };
27
+ }
@@ -1,13 +1,18 @@
1
1
  import { z } from "zod";
2
2
  import { runCommand } from "../lib/shell.js";
3
+ import { resolveRepo } from "../lib/git.js";
3
4
 
4
5
  export const GetPrDiffInputSchema = z.object({
5
- prNumber: z.number(),
6
+ prNumber: z.number().describe("The number of the pull request to get the diff for."),
7
+ repo: z.string().optional().describe("The target repository override (e.g. org/repo)."),
6
8
  });
7
9
 
8
10
  export async function getPrDiffHandler(args: z.infer<typeof GetPrDiffInputSchema>) {
11
+ const params = GetPrDiffInputSchema.parse(args);
12
+ const targetRepo = resolveRepo(params.repo);
13
+
9
14
  const result = await runCommand("td-cli", [
10
- "gh", "pr-diff", args.prNumber.toString()
15
+ "gh", "pr-diff", params.prNumber.toString(), "--repo", targetRepo
11
16
  ]);
12
17
 
13
18
  if (result.exitCode !== 0) {
@@ -0,0 +1,110 @@
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
+
7
+ vi.mock("../lib/shell.js", () => ({
8
+ runCommand: vi.fn(),
9
+ }));
10
+
11
+ describe("MCP GitHub tools", () => {
12
+ beforeEach(() => {
13
+ vi.clearAllMocks();
14
+ });
15
+
16
+ describe("getPrReviewsHandler", () => {
17
+ it("should fetch PR reviews via td-cli gh read-pr-reviews", async () => {
18
+ vi.mocked(shell.runCommand).mockResolvedValueOnce({
19
+ stdout: JSON.stringify({
20
+ status: "success",
21
+ reviews: [{ id: 1, state: "APPROVED", body: "LGTM" }],
22
+ review_count: 1,
23
+ }),
24
+ stderr: "",
25
+ exitCode: 0,
26
+ durationMs: 10,
27
+ command: "td-cli gh read-pr-reviews",
28
+ });
29
+
30
+ const res = await getPrReviewsHandler({ prNumber: 819, repo: "arii/boomtick" });
31
+
32
+ expect(shell.runCommand).toHaveBeenCalledWith("td-cli", [
33
+ "gh",
34
+ "read-pr-reviews",
35
+ "--pr-number",
36
+ "819",
37
+ "--repo",
38
+ "arii/boomtick",
39
+ ]);
40
+ expect(res).toEqual({
41
+ reviews: [{ id: 1, state: "APPROVED", body: "LGTM" }],
42
+ reviewCount: 1,
43
+ });
44
+ });
45
+ });
46
+
47
+ describe("getPrCommitsHandler", () => {
48
+ it("should list PR commits via td-cli gh list-pr-commits", async () => {
49
+ vi.mocked(shell.runCommand).mockResolvedValueOnce({
50
+ stdout: JSON.stringify({
51
+ status: "success",
52
+ commits: [{ sha: "abc1234", author: "Dev", message: "feat: test" }],
53
+ }),
54
+ stderr: "",
55
+ exitCode: 0,
56
+ durationMs: 10,
57
+ command: "td-cli gh list-pr-commits",
58
+ });
59
+
60
+ const res = await getPrCommitsHandler({ prNumber: 819, repo: "arii/boomtick" });
61
+
62
+ expect(shell.runCommand).toHaveBeenCalledWith("td-cli", [
63
+ "gh",
64
+ "list-pr-commits",
65
+ "--pr-number",
66
+ "819",
67
+ "--repo",
68
+ "arii/boomtick",
69
+ ]);
70
+ expect(res).toEqual({
71
+ commits: [{ sha: "abc1234", author: "Dev", message: "feat: test" }],
72
+ });
73
+ });
74
+ });
75
+
76
+ describe("getWorkflowRunsHandler", () => {
77
+ it("should fetch workflow runs via td-cli repo workflow-runs", async () => {
78
+ vi.mocked(shell.runCommand).mockResolvedValueOnce({
79
+ stdout: JSON.stringify({
80
+ status: "success",
81
+ workflow_runs: [{ id: 10, name: "CI" }],
82
+ }),
83
+ stderr: "",
84
+ exitCode: 0,
85
+ durationMs: 10,
86
+ command: "td-cli repo workflow-runs",
87
+ });
88
+
89
+ const res = await getWorkflowRunsHandler({
90
+ branch: "main",
91
+ event: "push",
92
+ repo: "arii/boomtick",
93
+ });
94
+
95
+ expect(shell.runCommand).toHaveBeenCalledWith("td-cli", [
96
+ "repo",
97
+ "workflow-runs",
98
+ "--repo",
99
+ "arii/boomtick",
100
+ "--branch",
101
+ "main",
102
+ "--event",
103
+ "push",
104
+ ]);
105
+ expect(res).toEqual({
106
+ workflowRuns: [{ id: 10, name: "CI" }],
107
+ });
108
+ });
109
+ });
110
+ });
@@ -0,0 +1,27 @@
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
+
6
+ const GetPrReviewsInputSchema = z.object({
7
+ prNumber: z.number().describe("The number of the PR to view reviews for."),
8
+ repo: z.string().optional().describe("The target repository override (e.g. org/repo)."),
9
+ });
10
+
11
+ export async function getPrReviewsHandler(args: z.infer<typeof GetPrReviewsInputSchema>) {
12
+ const params = GetPrReviewsInputSchema.parse(args);
13
+ const targetRepo = resolveRepo(params.repo);
14
+
15
+ const result = await runCommand("td-cli", ["gh", "read-pr-reviews", "--pr-number", params.prNumber.toString(), "--repo", targetRepo]);
16
+
17
+ if (result.exitCode !== 0) {
18
+ throw new Error(`Failed to read PR reviews: ${sanitizeError(result.stderr)}`);
19
+ }
20
+
21
+ const output = JSON.parse(result.stdout);
22
+ if (output.status === "error") {
23
+ throw new Error(`Failed to read PR reviews: ${output.message}`);
24
+ }
25
+
26
+ return { reviews: output.reviews, reviewCount: output.review_count };
27
+ }
@@ -1,10 +1,12 @@
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
 
5
6
  export const IssueCommentInputSchema = z.object({
6
7
  issueNumber: z.number().describe("The number of the issue to comment on."),
7
8
  body: z.string().min(1, "Comment body cannot be empty").describe("The content of the comment."),
9
+ repo: z.string().optional().describe("The target repository override (e.g. org/repo)."),
8
10
  });
9
11
 
10
12
  const IssueCommentOutputSchema = z.object({
@@ -15,8 +17,13 @@ const IssueCommentOutputSchema = z.object({
15
17
 
16
18
  export async function issueCommentHandler(args: z.infer<typeof IssueCommentInputSchema>) {
17
19
  const params = IssueCommentInputSchema.parse(args);
20
+ const targetRepo = resolveRepo(params.repo);
18
21
 
19
- const result = await runCommand("td-cli", ["gh", "issue-comment", params.issueNumber.toString(), "--body", params.body]);
22
+ const result = await runCommand("td-cli", [
23
+ "gh", "issue-comment", params.issueNumber.toString(),
24
+ "--body", params.body,
25
+ "--repo", targetRepo
26
+ ]);
20
27
 
21
28
  if (result.exitCode !== 0) {
22
29
  throw new Error(`Failed to post comment: ${sanitizeError(result.stderr)}`);
@@ -1,12 +1,14 @@
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
  import { IssueUpdateInputSchema, IssueUpdateResponseSchema } from "./contract.js";
5
6
 
6
7
  export { IssueUpdateInputSchema };
7
8
 
8
- export async function issueUpdateHandler(args: any) {
9
+ export async function issueUpdateHandler(args: z.infer<typeof IssueUpdateInputSchema>) {
9
10
  const params = IssueUpdateInputSchema.parse(args);
11
+ const targetRepo = resolveRepo(params.repo);
10
12
 
11
13
  const cmdArgs = ["gh", "issue-update", params.issueNumber.toString()];
12
14
  if (params.body) cmdArgs.push("--body", params.body);
@@ -15,6 +17,7 @@ export async function issueUpdateHandler(args: any) {
15
17
  if (params.addLabels && params.addLabels.length > 0) cmdArgs.push("--add-labels", params.addLabels.join(","));
16
18
  if (params.removeLabels && params.removeLabels.length > 0) cmdArgs.push("--remove-labels", params.removeLabels.join(","));
17
19
  if (params.state) cmdArgs.push("--state", params.state);
20
+ cmdArgs.push("--repo", targetRepo);
18
21
 
19
22
  const result = await runCommand("td-cli", cmdArgs);
20
23
 
@@ -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
 
5
6
  export const IssueViewInputSchema = z.object({
6
7
  issueNumber: z.number().describe("The number of the issue to view."),
8
+ repo: z.string().optional().describe("The target repository override (e.g. org/repo)."),
7
9
  });
8
10
 
9
11
  const IssueViewOutputSchema = z.object({
@@ -19,8 +21,9 @@ const IssueViewOutputSchema = z.object({
19
21
 
20
22
  export async function issueViewHandler(args: z.infer<typeof IssueViewInputSchema>) {
21
23
  const params = IssueViewInputSchema.parse(args);
24
+ const targetRepo = resolveRepo(params.repo);
22
25
 
23
- const result = await runCommand("td-cli", ["gh", "issue-view", params.issueNumber.toString()]);
26
+ const result = await runCommand("td-cli", ["gh", "issue-view", params.issueNumber.toString(), "--repo", targetRepo]);
24
27
 
25
28
  if (result.exitCode !== 0) {
26
29
  throw new Error(`Failed to view issue: ${sanitizeError(result.stderr)}`);
@@ -1,20 +1,24 @@
1
1
  import { z } from "zod";
2
2
  import { runCommand } from "../lib/shell.js";
3
+ import { resolveRepo } from "../lib/git.js";
3
4
 
4
5
  export const SearchOpenPrsInputSchema = z.object({
5
6
  state: z.enum(["open", "closed", "all"]).optional().default("open"),
6
7
  includeDrafts: z.boolean().optional().default(true),
7
8
  limit: z.number().min(1).max(100).optional().default(100),
8
9
  labels: z.array(z.string()).optional(),
10
+ repo: z.string().optional().describe("The target repository override (e.g. org/repo)."),
9
11
  });
10
12
 
11
13
  export async function searchOpenPrsHandler(args: z.infer<typeof SearchOpenPrsInputSchema>) {
12
14
  const params = SearchOpenPrsInputSchema.parse(args);
15
+ const targetRepo = resolveRepo(params.repo);
13
16
 
14
17
  const tdArgs = [
15
18
  "gh", "search-prs",
16
19
  "--state", params.state,
17
20
  "--limit", params.limit.toString(),
21
+ "--repo", targetRepo,
18
22
  ];
19
23
 
20
24
  if (!params.includeDrafts) {
@@ -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
 
5
4
  export const CreateJulesSessionInputSchema = z.object({
6
5
  task: z.string(),
@@ -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
 
5
4
  export const GetJulesPullRequestInputSchema = z.object({
6
5
  sessionId: z.string(),
@@ -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
 
5
4
  export const GetJulesSessionInputSchema = z.object({
6
5
  sessionId: z.string(),
@@ -1,4 +1,39 @@
1
1
  import { JulesSession, JulesStatus } from "../types.js";
2
+ import { runCommand } from "../../lib/shell.js";
3
+
4
+ export interface TDCliResponse {
5
+ status?: string;
6
+ message?: string;
7
+ [key: string]: any;
8
+ }
9
+
10
+ function tryParseJson(text: string): TDCliResponse | null {
11
+ try {
12
+ return JSON.parse(text) as TDCliResponse;
13
+ } catch {
14
+ return null;
15
+ }
16
+ }
17
+
18
+ export async function runTDCli(args: string[]): Promise<TDCliResponse> {
19
+ const result = await runCommand("td-cli", args);
20
+ const stdout = result.stdout.trim();
21
+ const output = stdout.startsWith("{") ? tryParseJson(stdout) : null;
22
+
23
+ if (output?.status === "error") {
24
+ throw new Error(`td-cli returned error: ${output.message ?? "Unknown error"}`);
25
+ }
26
+
27
+ if (result.exitCode !== 0) {
28
+ throw new Error(`td-cli command failed (${args.join(" ")}): ${result.stderr || stdout}`);
29
+ }
30
+
31
+ if (!output) {
32
+ throw new Error(`td-cli returned non-JSON output with exit code 0: ${stdout}`);
33
+ }
34
+
35
+ return output;
36
+ }
2
37
 
3
38
  export function parseJulesSession(data: any): JulesSession {
4
39
  const name = data.name || "";
@@ -1,21 +1,45 @@
1
1
  import { z } from "zod";
2
2
  import { runCommand } from "../lib/shell.js";
3
+ import { config } from "../config.js";
3
4
 
4
5
  export const CommitPatchInputSchema = z.object({
5
- worktreePath: z.string(),
6
+ worktreePath: z.string().optional(),
6
7
  message: z.string(),
7
8
  allowedFiles: z.array(z.string()),
8
- writeMode: z.boolean().optional().default(false),
9
+ writeMode: z.boolean().optional().default(true),
9
10
  });
10
11
 
11
- export async function commitPatchHandler(args: z.infer<typeof CommitPatchInputSchema>) {
12
- if (!args.writeMode) {
12
+ export type CommitPatchInput = z.input<typeof CommitPatchInputSchema>;
13
+
14
+ export async function commitPatchHandler(args: CommitPatchInput) {
15
+ const parsed = CommitPatchInputSchema.parse(args || {});
16
+ const worktreePath = parsed.worktreePath || config.repoPath;
17
+
18
+ if (parsed.writeMode === false) {
13
19
  throw new Error("writeMode must be true to commit changes.");
14
20
  }
15
- const statusResult = await runCommand("git", ["status", "--porcelain"], { cwd: args.worktreePath });
16
- const changedFiles = statusResult.stdout.trim().split("\n").filter(l => l.length > 0).map(l => l.substring(3));
17
21
 
18
- const forbiddenFiles = changedFiles.filter(f => !args.allowedFiles.includes(f));
22
+ const statusResult = await runCommand("git", ["status", "--porcelain"], { cwd: worktreePath });
23
+
24
+ const changedFiles = statusResult.stdout
25
+ .split("\n")
26
+ .map((l) => (l.endsWith("\r") ? l.slice(0, -1) : l))
27
+ .filter((l) => l.trim().length > 0)
28
+ .map((l) => {
29
+ const match = l.match(/^..\s+(.*)$/);
30
+ if (!match) return l.slice(3).trim();
31
+ let file = match[1].trim();
32
+ if (file.includes(" -> ")) {
33
+ file = file.split(" -> ").pop()!.trim();
34
+ }
35
+ if (file.startsWith('"') && file.endsWith('"')) {
36
+ file = file.slice(1, -1);
37
+ }
38
+ return file;
39
+ })
40
+ .filter(Boolean);
41
+
42
+ const forbiddenFiles = changedFiles.filter((f) => !parsed.allowedFiles.includes(f));
19
43
  if (forbiddenFiles.length > 0) {
20
44
  throw new Error(`The following files are not in the allowed list: ${forbiddenFiles.join(", ")}`);
21
45
  }
@@ -24,18 +48,18 @@ export async function commitPatchHandler(args: z.infer<typeof CommitPatchInputSc
24
48
  throw new Error("No changes to commit.");
25
49
  }
26
50
 
27
- await runCommand("git", ["add", "."], { cwd: args.worktreePath });
28
- const commitResult = await runCommand("git", ["commit", "-m", args.message], { cwd: args.worktreePath });
51
+ await runCommand("git", ["add", "--", ...changedFiles], { cwd: worktreePath });
52
+ const commitResult = await runCommand("git", ["commit", "-m", parsed.message], { cwd: worktreePath });
29
53
 
30
54
  if (commitResult.exitCode !== 0) {
31
55
  throw new Error(`Failed to commit: ${commitResult.stderr}`);
32
56
  }
33
57
 
34
- const shaResult = await runCommand("git", ["rev-parse", "HEAD"], { cwd: args.worktreePath });
58
+ const shaResult = await runCommand("git", ["rev-parse", "HEAD"], { cwd: worktreePath });
35
59
 
36
60
  return {
37
61
  success: true,
38
62
  commitSha: shaResult.stdout.trim(),
39
- changedFiles
63
+ changedFiles,
40
64
  };
41
65
  }
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { runCommand } from "../lib/shell.js";
2
+ import { getChangedFiles } from "../lib/git.js";
3
3
 
4
4
  export const GetChangedFilesInputSchema = z.object({
5
5
  base: z.string().optional().default("main"),
@@ -7,20 +7,6 @@ export const GetChangedFilesInputSchema = z.object({
7
7
  });
8
8
 
9
9
  export async function getChangedFilesHandler(args: z.infer<typeof GetChangedFilesInputSchema>) {
10
- const result = await runCommand("git", [
11
- "diff",
12
- "--name-status",
13
- `${args.base}...${args.head}`
14
- ]);
15
-
16
- if (result.exitCode !== 0) {
17
- throw new Error(`Failed to get changed files: ${result.stderr}`);
18
- }
19
-
20
- const files = result.stdout.trim().split("\n").filter(line => line.length > 0).map(line => {
21
- const [status, path] = line.split(/\s+/);
22
- return { status, path };
23
- });
24
-
10
+ const files = await getChangedFiles(args.base || "main", args.head || "HEAD");
25
11
  return { files };
26
12
  }
@@ -0,0 +1,36 @@
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
+
6
+ const GetWorkflowRunsInputSchema = z.object({
7
+ branch: z.string().optional().describe("Filter workflow runs by branch name."),
8
+ event: z.string().optional().describe("Filter workflow runs by event (e.g. push, pull_request)."),
9
+ repo: z.string().optional().describe("The target repository override (e.g. org/repo)."),
10
+ });
11
+
12
+ export async function getWorkflowRunsHandler(args: z.infer<typeof GetWorkflowRunsInputSchema>) {
13
+ const params = GetWorkflowRunsInputSchema.parse(args);
14
+ const targetRepo = resolveRepo(params.repo);
15
+
16
+ const cliArgs = ["repo", "workflow-runs", "--repo", targetRepo];
17
+ if (params.branch) {
18
+ cliArgs.push("--branch", params.branch);
19
+ }
20
+ if (params.event) {
21
+ cliArgs.push("--event", params.event);
22
+ }
23
+
24
+ const result = await runCommand("td-cli", cliArgs);
25
+
26
+ if (result.exitCode !== 0) {
27
+ throw new Error(`Failed to fetch workflow runs: ${sanitizeError(result.stderr)}`);
28
+ }
29
+
30
+ const output = JSON.parse(result.stdout);
31
+ if (output.status === "error") {
32
+ throw new Error(`Failed to fetch workflow runs: ${output.message}`);
33
+ }
34
+
35
+ return { workflowRuns: output.workflow_runs };
36
+ }
@@ -1,9 +1,10 @@
1
1
  import { z } from "zod";
2
- import { runTDCli } from "../lib/td-cli.js";
2
+ import { runTDCli } from "./jules/shared.js";
3
3
 
4
4
  export const RepoLogsInputSchema = z.object({
5
5
  prNumber: z.number(),
6
6
  grep: z.string().optional(),
7
+ repo: z.string().optional(),
7
8
  });
8
9
 
9
10
  export async function repoLogsHandler(args: z.infer<typeof RepoLogsInputSchema>) {
@@ -12,6 +13,9 @@ export async function repoLogsHandler(args: z.infer<typeof RepoLogsInputSchema>)
12
13
  if (args.grep) {
13
14
  params.push("--grep", args.grep);
14
15
  }
16
+ if (args.repo) {
17
+ params.push("--repo", args.repo);
18
+ }
15
19
 
16
20
  const output = await runTDCli(params);
17
21
 
@@ -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
 
4
4
  describe("Standalone Agent Context Loader", () => {
5
5
  it("should load agent context without submodule references", () => {