@arii/boomtick-mcp 0.4.1 → 0.4.2

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 (111) hide show
  1. package/.mcp/schemas/agent.search_ddgs.json +28 -24
  2. package/.mcp/schemas/agent_search_ddgs.json +31 -0
  3. package/.mcp/schemas/boomtick.health.json +14 -10
  4. package/.mcp/schemas/boomtick_health.json +16 -0
  5. package/.mcp/schemas/github.checkout_branch.json +28 -24
  6. package/.mcp/schemas/github.comment_triage_summary.json +21 -17
  7. package/.mcp/schemas/github.create_issue.json +55 -50
  8. package/.mcp/schemas/github.create_pull_request.json +53 -48
  9. package/.mcp/schemas/github.get_merge_conflict_files.json +21 -17
  10. package/.mcp/schemas/github.get_pr.json +30 -12
  11. package/.mcp/schemas/github.get_pr_diff.json +30 -12
  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_diff.json +32 -0
  24. package/.mcp/schemas/github_issue_comment.json +38 -0
  25. package/.mcp/schemas/github_issue_update.json +123 -0
  26. package/.mcp/schemas/github_issue_view.json +32 -0
  27. package/.mcp/schemas/github_open_replacement_pr.json +67 -0
  28. package/.mcp/schemas/github_search_open_prs.json +65 -0
  29. package/.mcp/schemas/jules.cancel_session.json +16 -12
  30. package/.mcp/schemas/jules.create_session.json +41 -37
  31. package/.mcp/schemas/jules.get_messages.json +16 -12
  32. package/.mcp/schemas/jules.get_pr.json +16 -12
  33. package/.mcp/schemas/jules.get_session.json +16 -12
  34. package/.mcp/schemas/jules.list_sessions.json +33 -29
  35. package/.mcp/schemas/jules.send_message.json +30 -26
  36. package/.mcp/schemas/jules.trigger_feedback.json +16 -12
  37. package/.mcp/schemas/jules_cancel_session.json +18 -0
  38. package/.mcp/schemas/jules_create_session.json +44 -0
  39. package/.mcp/schemas/jules_get_messages.json +18 -0
  40. package/.mcp/schemas/jules_get_pr.json +18 -0
  41. package/.mcp/schemas/jules_get_session.json +18 -0
  42. package/.mcp/schemas/jules_list_sessions.json +36 -0
  43. package/.mcp/schemas/jules_send_message.json +35 -0
  44. package/.mcp/schemas/jules_trigger_feedback.json +18 -0
  45. package/.mcp/schemas/repo.commit_patch.json +34 -30
  46. package/.mcp/schemas/repo.create_branch.json +21 -17
  47. package/.mcp/schemas/repo.create_repair_branch.json +34 -30
  48. package/.mcp/schemas/repo.get_changed_files.json +19 -15
  49. package/.mcp/schemas/repo.get_command_schema.json +16 -12
  50. package/.mcp/schemas/repo.get_package_scripts.json +21 -17
  51. package/.mcp/schemas/repo.get_route_map.json +21 -17
  52. package/.mcp/schemas/repo.logs.json +28 -24
  53. package/.mcp/schemas/repo.read_agent_context.json +7 -3
  54. package/.mcp/schemas/repo.read_ci_logs.json +21 -17
  55. package/.mcp/schemas/repo.run_lighthouse.json +26 -22
  56. package/.mcp/schemas/repo.run_playwright.json +33 -29
  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_route_map.json +23 -0
  65. package/.mcp/schemas/repo_logs.json +31 -0
  66. package/.mcp/schemas/repo_read_agent_context.json +9 -0
  67. package/.mcp/schemas/repo_read_ci_logs.json +24 -0
  68. package/.mcp/schemas/repo_run_lighthouse.json +29 -0
  69. package/.mcp/schemas/repo_run_playwright.json +36 -0
  70. package/.mcp/schemas/repo_run_tests.json +45 -0
  71. package/CHANGELOG.md +9 -0
  72. package/dist/lib/git.js +30 -0
  73. package/dist/lib/param_utils.js +35 -0
  74. package/dist/lib/param_utils.test.js +44 -0
  75. package/dist/mcp/definitions.js +11 -1
  76. package/dist/mcp/schemas.test.js +28 -0
  77. package/dist/mcp/server.js +72 -36
  78. package/dist/tools/contract.js +94 -8
  79. package/dist/tools/github.create_issue.js +3 -2
  80. package/dist/tools/github.create_issue.test.js +10 -0
  81. package/dist/tools/github.create_pull_request.js +4 -4
  82. package/dist/tools/github.get_pr.js +4 -1
  83. package/dist/tools/github.get_pr_diff.js +6 -2
  84. package/dist/tools/github.issue_comment.js +8 -1
  85. package/dist/tools/github.issue_update.js +3 -0
  86. package/dist/tools/github.issue_view.js +4 -1
  87. package/dist/tools/github.search_open_prs.js +4 -0
  88. package/dist/tools/repo.commit_patch.js +30 -10
  89. package/dist/tools/repo.run_playwright.js +10 -5
  90. package/dist/tools/repo.run_tests.js +26 -4
  91. package/package.json +2 -2
  92. package/scripts/sync-mcp-schemas.ts +6 -1
  93. package/src/lib/git.ts +34 -0
  94. package/src/lib/param_utils.test.ts +49 -0
  95. package/src/lib/param_utils.ts +42 -0
  96. package/src/mcp/definitions.ts +12 -1
  97. package/src/mcp/schemas.test.ts +34 -0
  98. package/src/mcp/server.ts +73 -38
  99. package/src/tools/contract.ts +94 -8
  100. package/src/tools/github.create_issue.test.ts +10 -0
  101. package/src/tools/github.create_issue.ts +3 -1
  102. package/src/tools/github.create_pull_request.ts +4 -5
  103. package/src/tools/github.get_pr.ts +4 -1
  104. package/src/tools/github.get_pr_diff.ts +7 -2
  105. package/src/tools/github.issue_comment.ts +8 -1
  106. package/src/tools/github.issue_update.ts +4 -1
  107. package/src/tools/github.issue_view.ts +4 -1
  108. package/src/tools/github.search_open_prs.ts +4 -0
  109. package/src/tools/repo.commit_patch.ts +35 -11
  110. package/src/tools/repo.run_playwright.ts +13 -6
  111. package/src/tools/repo.run_tests.ts +28 -5
@@ -1179,7 +1179,7 @@ export const CreateBranchInputJsonSchema = {
1179
1179
  "title": "CreateBranchInput",
1180
1180
  "type": "object"
1181
1181
  };
1182
- export const CreateIssueInputSchema = z.object({ "title": z.string().min(1), "body": z.union([z.string().min(1), z.null()]).default(null), "file": z.union([z.string().min(1), z.null()]).default(null), "repo": z.union([z.string(), z.null()]).describe("The target repository override (e.g. org/repo).").default(null) });
1182
+ export const CreateIssueInputSchema = z.object({ "title": z.string().min(1), "body": z.union([z.string().min(1), z.null()]).default(null), "file": z.union([z.string().min(1), z.null()]).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) });
1183
1183
  export const CreateIssueInputJsonSchema = {
1184
1184
  "properties": {
1185
1185
  "title": {
@@ -1216,6 +1216,7 @@ export const CreateIssueInputJsonSchema = {
1216
1216
  "repo": {
1217
1217
  "anyOf": [
1218
1218
  {
1219
+ "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
1219
1220
  "type": "string"
1220
1221
  },
1221
1222
  {
@@ -1349,7 +1350,7 @@ export const CreateJulesSessionInputJsonSchema = {
1349
1350
  "title": "CreateJulesSessionInput",
1350
1351
  "type": "object"
1351
1352
  };
1352
- export const CreatePullRequestInputSchema = z.object({ "title": z.string().describe("PR Title."), "body": z.string().describe("Description of changes."), "head": z.string().describe("The branch containing changes to merge."), "base": z.string().describe("The target branch to merge into.").default("main"), "draft": z.boolean().describe("Whether to create the PR as a draft.").default(false), "repo": z.union([z.string(), z.null()]).describe("The target repository override (e.g. org/repo).").default(null) });
1353
+ export const CreatePullRequestInputSchema = z.object({ "title": z.string().describe("PR Title."), "body": z.string().describe("Description of changes."), "head": z.string().describe("The branch containing changes to merge."), "base": z.string().describe("The target branch to merge into.").default("main"), "draft": z.boolean().describe("Whether to create the PR as a draft.").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) });
1353
1354
  export const CreatePullRequestInputJsonSchema = {
1354
1355
  "properties": {
1355
1356
  "title": {
@@ -1382,6 +1383,7 @@ export const CreatePullRequestInputJsonSchema = {
1382
1383
  "repo": {
1383
1384
  "anyOf": [
1384
1385
  {
1386
+ "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
1385
1387
  "type": "string"
1386
1388
  },
1387
1389
  {
@@ -1510,13 +1512,27 @@ export const GetPackageScriptsInputJsonSchema = {
1510
1512
  "title": "GetPackageScriptsInput",
1511
1513
  "type": "object"
1512
1514
  };
1513
- export const GetPrDiffInputSchema = z.object({ "prNumber": z.number().int().describe("The number of the pull request to get the diff for.") });
1515
+ 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) });
1514
1516
  export const GetPrDiffInputJsonSchema = {
1515
1517
  "properties": {
1516
1518
  "prNumber": {
1517
1519
  "description": "The number of the pull request to get the diff for.",
1518
1520
  "title": "Prnumber",
1519
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"
1520
1536
  }
1521
1537
  },
1522
1538
  "required": [
@@ -1525,13 +1541,27 @@ export const GetPrDiffInputJsonSchema = {
1525
1541
  "title": "GetPrDiffInput",
1526
1542
  "type": "object"
1527
1543
  };
1528
- export const GetPrInputSchema = z.object({ "prNumber": z.number().int().describe("The number of the PR to view.") });
1544
+ export const GetPrInputSchema = z.object({ "prNumber": z.number().int().describe("The number of the PR to view."), "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) });
1529
1545
  export const GetPrInputJsonSchema = {
1530
1546
  "properties": {
1531
1547
  "prNumber": {
1532
1548
  "description": "The number of the PR to view.",
1533
1549
  "title": "Prnumber",
1534
1550
  "type": "integer"
1551
+ },
1552
+ "repo": {
1553
+ "anyOf": [
1554
+ {
1555
+ "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
1556
+ "type": "string"
1557
+ },
1558
+ {
1559
+ "type": "null"
1560
+ }
1561
+ ],
1562
+ "default": null,
1563
+ "description": "The target repository override (e.g. org/repo).",
1564
+ "title": "Repo"
1535
1565
  }
1536
1566
  },
1537
1567
  "required": [
@@ -1573,7 +1603,7 @@ export const HealthCheckInputJsonSchema = {
1573
1603
  "title": "HealthCheckInput",
1574
1604
  "type": "object"
1575
1605
  };
1576
- export const IssueCommentInputSchema = z.object({ "issueNumber": z.number().int().describe("The number of the issue to comment on."), "body": z.string().describe("The content of the comment.") });
1606
+ export const IssueCommentInputSchema = z.object({ "issueNumber": z.number().int().describe("The number of the issue to comment on."), "body": z.string().describe("The content of the comment."), "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) });
1577
1607
  export const IssueCommentInputJsonSchema = {
1578
1608
  "properties": {
1579
1609
  "issueNumber": {
@@ -1585,6 +1615,20 @@ export const IssueCommentInputJsonSchema = {
1585
1615
  "description": "The content of the comment.",
1586
1616
  "title": "Body",
1587
1617
  "type": "string"
1618
+ },
1619
+ "repo": {
1620
+ "anyOf": [
1621
+ {
1622
+ "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
1623
+ "type": "string"
1624
+ },
1625
+ {
1626
+ "type": "null"
1627
+ }
1628
+ ],
1629
+ "default": null,
1630
+ "description": "The target repository override (e.g. org/repo).",
1631
+ "title": "Repo"
1588
1632
  }
1589
1633
  },
1590
1634
  "required": [
@@ -1623,7 +1667,7 @@ export const IssueSummaryJsonSchema = {
1623
1667
  "title": "IssueSummary",
1624
1668
  "type": "object"
1625
1669
  };
1626
- export const IssueUpdateInputSchema = z.object({ "issueNumber": z.number().int().describe("The number of the issue to update."), "body": z.union([z.string(), z.null()]).describe("The new body content for the issue.").default(null), "file": z.union([z.string(), z.null()]).describe("Path to file containing new issue body.").default(null), "labels": z.union([z.array(z.string()), z.null()]).describe("Comma-separated list of labels to set.").default(null), "addLabels": z.union([z.array(z.string()), z.null()]).describe("Comma-separated list of labels to add.").default(null), "removeLabels": z.union([z.array(z.string()), z.null()]).describe("Comma-separated list of labels to remove.").default(null), "state": z.union([z.enum(["open", "closed"]), z.null()]).describe("The state to set the issue to.").default(null) });
1670
+ export const IssueUpdateInputSchema = z.object({ "issueNumber": z.number().int().describe("The number of the issue to update."), "body": z.union([z.string(), z.null()]).describe("The new body content for the issue.").default(null), "file": z.union([z.string(), z.null()]).describe("Path to file containing new issue body.").default(null), "labels": z.union([z.array(z.string()), z.null()]).describe("Comma-separated list of labels to set.").default(null), "addLabels": z.union([z.array(z.string()), z.null()]).describe("Comma-separated list of labels to add.").default(null), "removeLabels": z.union([z.array(z.string()), z.null()]).describe("Comma-separated list of labels to remove.").default(null), "state": z.union([z.enum(["open", "closed"]), z.null()]).describe("The state to set the issue to.").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) });
1627
1671
  export const IssueUpdateInputJsonSchema = {
1628
1672
  "properties": {
1629
1673
  "issueNumber": {
@@ -1721,6 +1765,20 @@ export const IssueUpdateInputJsonSchema = {
1721
1765
  "default": null,
1722
1766
  "description": "The state to set the issue to.",
1723
1767
  "title": "State"
1768
+ },
1769
+ "repo": {
1770
+ "anyOf": [
1771
+ {
1772
+ "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
1773
+ "type": "string"
1774
+ },
1775
+ {
1776
+ "type": "null"
1777
+ }
1778
+ ],
1779
+ "default": null,
1780
+ "description": "The target repository override (e.g. org/repo).",
1781
+ "title": "Repo"
1724
1782
  }
1725
1783
  },
1726
1784
  "required": [
@@ -1804,13 +1862,27 @@ export const IssueUpdateResponseJsonSchema = {
1804
1862
  "title": "IssueUpdateResponse",
1805
1863
  "type": "object"
1806
1864
  };
1807
- export const IssueViewInputSchema = z.object({ "issueNumber": z.number().int().describe("The number of the issue to view.") });
1865
+ export const IssueViewInputSchema = z.object({ "issueNumber": z.number().int().describe("The number of the issue to view."), "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) });
1808
1866
  export const IssueViewInputJsonSchema = {
1809
1867
  "properties": {
1810
1868
  "issueNumber": {
1811
1869
  "description": "The number of the issue to view.",
1812
1870
  "title": "Issuenumber",
1813
1871
  "type": "integer"
1872
+ },
1873
+ "repo": {
1874
+ "anyOf": [
1875
+ {
1876
+ "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
1877
+ "type": "string"
1878
+ },
1879
+ {
1880
+ "type": "null"
1881
+ }
1882
+ ],
1883
+ "default": null,
1884
+ "description": "The target repository override (e.g. org/repo).",
1885
+ "title": "Repo"
1814
1886
  }
1815
1887
  },
1816
1888
  "required": [
@@ -2801,7 +2873,7 @@ export const SearchDdgsInputJsonSchema = {
2801
2873
  "title": "SearchDdgsInput",
2802
2874
  "type": "object"
2803
2875
  };
2804
- export const SearchPRsInputSchema = z.object({ "state": z.enum(["open", "closed", "all"]).describe("The state of the PRs to search for.").default("open"), "limit": z.number().int().gte(1).lte(100).describe("The maximum number of PRs to return (default: 100, range: 1-100).").default(100), "includeDrafts": z.boolean().describe("Whether to include draft PRs in the results.").default(true), "labels": z.union([z.array(z.string()), z.null()]).describe("Filter PRs by labels.").default(null) });
2876
+ export const SearchPRsInputSchema = z.object({ "state": z.enum(["open", "closed", "all"]).describe("The state of the PRs to search for.").default("open"), "limit": z.number().int().gte(1).lte(100).describe("The maximum number of PRs to return (default: 100, range: 1-100).").default(100), "includeDrafts": z.boolean().describe("Whether to include draft PRs in the results.").default(true), "labels": z.union([z.array(z.string()), z.null()]).describe("Filter PRs by labels.").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) });
2805
2877
  export const SearchPRsInputJsonSchema = {
2806
2878
  "properties": {
2807
2879
  "state": {
@@ -2844,6 +2916,20 @@ export const SearchPRsInputJsonSchema = {
2844
2916
  "default": null,
2845
2917
  "description": "Filter PRs by labels.",
2846
2918
  "title": "Labels"
2919
+ },
2920
+ "repo": {
2921
+ "anyOf": [
2922
+ {
2923
+ "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
2924
+ "type": "string"
2925
+ },
2926
+ {
2927
+ "type": "null"
2928
+ }
2929
+ ],
2930
+ "default": null,
2931
+ "description": "The target repository override (e.g. org/repo).",
2932
+ "title": "Repo"
2847
2933
  }
2848
2934
  },
2849
2935
  "title": "SearchPRsInput",
@@ -1,16 +1,17 @@
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 { CreateIssueInputSchema, CreateIssueResponseSchema } from "./contract.js";
4
5
  export { CreateIssueInputSchema };
5
6
  export async function createIssueHandler(args) {
6
7
  const params = CreateIssueInputSchema.parse(args);
8
+ const targetRepo = resolveRepo(params.repo);
7
9
  const cmdArgs = ["gh", "create-issue", "--title", params.title];
8
10
  if (params.body)
9
11
  cmdArgs.push("--body", params.body);
10
12
  if (params.file)
11
13
  cmdArgs.push("--file", params.file);
12
- if (params.repo)
13
- cmdArgs.push("--repo", params.repo);
14
+ cmdArgs.push("--repo", targetRepo);
14
15
  const result = await runCommand("td-cli", cmdArgs);
15
16
  if (result.exitCode !== 0) {
16
17
  throw new Error(`Failed to create issue: ${sanitizeError(result.stderr)}`);
@@ -23,6 +23,16 @@ describe("github.create_issue", () => {
23
23
  command: "td-cli gh create-issue --title 'Test Issue' --body 'Test Body'"
24
24
  });
25
25
  const result = await createIssueHandler({ title: "Test Issue", body: "Test Body", file: null, repo: null });
26
+ expect(shell.runCommand).toHaveBeenCalledWith("td-cli", [
27
+ "gh",
28
+ "create-issue",
29
+ "--title",
30
+ "Test Issue",
31
+ "--body",
32
+ "Test Body",
33
+ "--repo",
34
+ expect.any(String)
35
+ ]);
26
36
  expect(result.status).toBe("success");
27
37
  expect(result.issue?.number).toBe(123);
28
38
  expect(result.issue?.title).toBe("Test Issue");
@@ -1,22 +1,22 @@
1
1
  import { runCommand } from "../lib/shell.js";
2
+ import { resolveRepo } from "../lib/git.js";
2
3
  import { CreatePullRequestInputSchema } from "./contract.js";
3
4
  export { CreatePullRequestInputSchema };
4
5
  export async function createPullRequestHandler(args) {
5
6
  const params = CreatePullRequestInputSchema.parse(args);
7
+ const targetRepo = resolveRepo(params.repo);
6
8
  const tdArgs = [
7
9
  "gh",
8
10
  "create-pr",
9
11
  "--title", params.title,
10
12
  "--body", params.body,
11
13
  "--head", params.head,
12
- "--base", params.base
14
+ "--base", params.base,
15
+ "--repo", targetRepo
13
16
  ];
14
17
  if (params.draft) {
15
18
  tdArgs.push("--draft");
16
19
  }
17
- if (params.repo) {
18
- tdArgs.push("--repo", params.repo);
19
- }
20
20
  const result = await runCommand("td-cli", tdArgs);
21
21
  if (result.exitCode !== 0) {
22
22
  // Attempt to parse stdout as JSON to see if there's a structured error message
@@ -1,12 +1,15 @@
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 GetPrInputSchema = z.object({
5
6
  prNumber: z.number().describe("The number of the PR to view."),
7
+ repo: z.string().optional().describe("The target repository override (e.g. org/repo)."),
6
8
  });
7
9
  export async function getPrHandler(args) {
8
10
  const params = GetPrInputSchema.parse(args);
9
- const result = await runCommand("td-cli", ["gh", "view", params.prNumber.toString()]);
11
+ const targetRepo = resolveRepo(params.repo);
12
+ const result = await runCommand("td-cli", ["gh", "view", params.prNumber.toString(), "--repo", targetRepo]);
10
13
  if (result.exitCode !== 0) {
11
14
  throw new Error(`Failed to get PR details: ${sanitizeError(result.stderr)}`);
12
15
  }
@@ -1,11 +1,15 @@
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 GetPrDiffInputSchema = z.object({
4
- prNumber: z.number(),
5
+ prNumber: z.number().describe("The number of the pull request to get the diff for."),
6
+ repo: z.string().optional().describe("The target repository override (e.g. org/repo)."),
5
7
  });
6
8
  export async function getPrDiffHandler(args) {
9
+ const params = GetPrDiffInputSchema.parse(args);
10
+ const targetRepo = resolveRepo(params.repo);
7
11
  const result = await runCommand("td-cli", [
8
- "gh", "pr-diff", args.prNumber.toString()
12
+ "gh", "pr-diff", params.prNumber.toString(), "--repo", targetRepo
9
13
  ]);
10
14
  if (result.exitCode !== 0) {
11
15
  throw new Error(`Failed to get PR diff: ${result.stderr}`);
@@ -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,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
  }
@@ -3,16 +3,21 @@ import { runCommand } from "../lib/shell.js";
3
3
  export const RunPlaywrightInputSchema = z.object({
4
4
  grep: z.string().optional(),
5
5
  worktreePath: z.string().optional(),
6
+ timeoutSeconds: z.number().optional().default(180),
6
7
  });
7
8
  export async function runPlaywrightHandler(args) {
9
+ const parsed = RunPlaywrightInputSchema.parse(args || {});
8
10
  const tdArgs = ["repo", "run-playwright"];
9
- if (args.grep) {
10
- tdArgs.push("--grep", args.grep);
11
+ if (parsed.grep) {
12
+ tdArgs.push("--grep", parsed.grep);
11
13
  }
12
- if (args.worktreePath) {
13
- tdArgs.push("--worktree", args.worktreePath);
14
+ if (parsed.worktreePath) {
15
+ tdArgs.push("--worktree", parsed.worktreePath);
14
16
  }
15
- const result = await runCommand("td-cli", tdArgs);
17
+ const result = await runCommand("td-cli", tdArgs, {
18
+ cwd: parsed.worktreePath,
19
+ timeout: parsed.timeoutSeconds * 1000,
20
+ });
16
21
  if (result.exitCode !== 0) {
17
22
  // Playwright might exit with non-zero if tests fail, but our td-cli should ideally handle it
18
23
  // and return a JSON with success: false.
@@ -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
+ export 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.4.2",
4
4
  "description": "Boomtick MCP Guide: PR Rescue + Merge Conflict Agent",
5
5
  "repository": {
6
6
  "type": "git",
@@ -37,7 +37,7 @@
37
37
  "build": "tsx scripts/sync-contracts.ts && tsc && shx cp src/tools/ddgs_search.py dist/tools/ && tsx scripts/sync-mcp-schemas.ts",
38
38
  "watch": "tsc -w",
39
39
  "start": "node dist/index.js",
40
- "test": "tsx scripts/sync-contracts.ts && vitest run",
40
+ "test": "tsx scripts/sync-contracts.ts && tsx scripts/sync-mcp-schemas.ts && vitest run",
41
41
  "run-evals": "node dist/evals/run-evals.js",
42
42
  "sync:mcp-schemas": "tsx scripts/sync-mcp-schemas.ts",
43
43
  "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 => {
package/src/lib/git.ts CHANGED
@@ -2,6 +2,40 @@ import { runCommand } from "./shell.js";
2
2
  import { config } from "../config.js";
3
3
  import path from "path";
4
4
  import fs from "fs/promises";
5
+ import { execSync } from "child_process";
6
+
7
+ const repoCache = new Map<string, string>();
8
+
9
+ export function resolveRepo(explicitRepo?: string | null, cwd?: string): string {
10
+ if (explicitRepo && explicitRepo.trim().length > 0) {
11
+ return explicitRepo.trim();
12
+ }
13
+
14
+ const targetCwd = cwd || process.cwd();
15
+ if (repoCache.has(targetCwd)) {
16
+ return repoCache.get(targetCwd)!;
17
+ }
18
+
19
+ try {
20
+ const url = execSync("git remote get-url origin", {
21
+ cwd: targetCwd,
22
+ encoding: "utf-8",
23
+ stdio: ["ignore", "pipe", "ignore"],
24
+ }).trim();
25
+
26
+ const match = url.match(/[:/]([^/]+\/[^/.]+)(\.git)?$/);
27
+ if (match) {
28
+ repoCache.set(targetCwd, match[1]);
29
+ return match[1];
30
+ }
31
+ } catch (e) {
32
+ // Ignore error and fall back to static config
33
+ }
34
+
35
+ const fallback = `${config.githubOwner}/${config.githubRepo}`;
36
+ repoCache.set(targetCwd, fallback);
37
+ return fallback;
38
+ }
5
39
 
6
40
  function validatePrNumber(value: unknown): number {
7
41
  const prNumber = Number(value);