@arii/boomtick-mcp 0.4.0 → 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 (115) 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 +16 -0
  72. package/dist/config.js +43 -17
  73. package/dist/config.test.js +18 -3
  74. package/dist/lib/git.js +30 -0
  75. package/dist/lib/param_utils.js +35 -0
  76. package/dist/lib/param_utils.test.js +44 -0
  77. package/dist/mcp/definitions.js +11 -1
  78. package/dist/mcp/schemas.test.js +28 -0
  79. package/dist/mcp/server.js +72 -36
  80. package/dist/tools/contract.js +94 -8
  81. package/dist/tools/github.create_issue.js +3 -2
  82. package/dist/tools/github.create_issue.test.js +10 -0
  83. package/dist/tools/github.create_pull_request.js +4 -4
  84. package/dist/tools/github.get_pr.js +4 -1
  85. package/dist/tools/github.get_pr_diff.js +6 -2
  86. package/dist/tools/github.issue_comment.js +8 -1
  87. package/dist/tools/github.issue_update.js +3 -0
  88. package/dist/tools/github.issue_view.js +4 -1
  89. package/dist/tools/github.search_open_prs.js +4 -0
  90. package/dist/tools/repo.commit_patch.js +30 -10
  91. package/dist/tools/repo.run_playwright.js +10 -5
  92. package/dist/tools/repo.run_tests.js +26 -4
  93. package/package.json +2 -2
  94. package/scripts/sync-mcp-schemas.ts +6 -1
  95. package/src/config.test.ts +22 -3
  96. package/src/config.ts +47 -17
  97. package/src/lib/git.ts +34 -0
  98. package/src/lib/param_utils.test.ts +49 -0
  99. package/src/lib/param_utils.ts +42 -0
  100. package/src/mcp/definitions.ts +12 -1
  101. package/src/mcp/schemas.test.ts +34 -0
  102. package/src/mcp/server.ts +73 -38
  103. package/src/tools/contract.ts +94 -8
  104. package/src/tools/github.create_issue.test.ts +10 -0
  105. package/src/tools/github.create_issue.ts +3 -1
  106. package/src/tools/github.create_pull_request.ts +4 -5
  107. package/src/tools/github.get_pr.ts +4 -1
  108. package/src/tools/github.get_pr_diff.ts +7 -2
  109. package/src/tools/github.issue_comment.ts +8 -1
  110. package/src/tools/github.issue_update.ts +4 -1
  111. package/src/tools/github.issue_view.ts +4 -1
  112. package/src/tools/github.search_open_prs.ts +4 -0
  113. package/src/tools/repo.commit_patch.ts +35 -11
  114. package/src/tools/repo.run_playwright.ts +13 -6
  115. package/src/tools/repo.run_tests.ts +28 -5
@@ -1228,7 +1228,7 @@ export const CreateBranchInputJsonSchema = {
1228
1228
  "type": "object"
1229
1229
  } as const;
1230
1230
 
1231
- 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) });
1231
+ 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) });
1232
1232
 
1233
1233
  export type CreateIssueInput = z.infer<typeof CreateIssueInputSchema>;
1234
1234
 
@@ -1268,6 +1268,7 @@ export const CreateIssueInputJsonSchema = {
1268
1268
  "repo": {
1269
1269
  "anyOf": [
1270
1270
  {
1271
+ "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
1271
1272
  "type": "string"
1272
1273
  },
1273
1274
  {
@@ -1410,7 +1411,7 @@ export const CreateJulesSessionInputJsonSchema = {
1410
1411
  "type": "object"
1411
1412
  } as const;
1412
1413
 
1413
- 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) });
1414
+ 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) });
1414
1415
 
1415
1416
  export type CreatePullRequestInput = z.infer<typeof CreatePullRequestInputSchema>;
1416
1417
 
@@ -1446,6 +1447,7 @@ export const CreatePullRequestInputJsonSchema = {
1446
1447
  "repo": {
1447
1448
  "anyOf": [
1448
1449
  {
1450
+ "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
1449
1451
  "type": "string"
1450
1452
  },
1451
1453
  {
@@ -1595,7 +1597,7 @@ export const GetPackageScriptsInputJsonSchema = {
1595
1597
  "type": "object"
1596
1598
  } as const;
1597
1599
 
1598
- export const GetPrDiffInputSchema = z.object({ "prNumber": z.number().int().describe("The number of the pull request to get the diff for.") });
1600
+ 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) });
1599
1601
 
1600
1602
  export type GetPrDiffInput = z.infer<typeof GetPrDiffInputSchema>;
1601
1603
 
@@ -1605,6 +1607,20 @@ export const GetPrDiffInputJsonSchema = {
1605
1607
  "description": "The number of the pull request to get the diff for.",
1606
1608
  "title": "Prnumber",
1607
1609
  "type": "integer"
1610
+ },
1611
+ "repo": {
1612
+ "anyOf": [
1613
+ {
1614
+ "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
1615
+ "type": "string"
1616
+ },
1617
+ {
1618
+ "type": "null"
1619
+ }
1620
+ ],
1621
+ "default": null,
1622
+ "description": "The target repository override (e.g. org/repo).",
1623
+ "title": "Repo"
1608
1624
  }
1609
1625
  },
1610
1626
  "required": [
@@ -1614,7 +1630,7 @@ export const GetPrDiffInputJsonSchema = {
1614
1630
  "type": "object"
1615
1631
  } as const;
1616
1632
 
1617
- export const GetPrInputSchema = z.object({ "prNumber": z.number().int().describe("The number of the PR to view.") });
1633
+ 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) });
1618
1634
 
1619
1635
  export type GetPrInput = z.infer<typeof GetPrInputSchema>;
1620
1636
 
@@ -1624,6 +1640,20 @@ export const GetPrInputJsonSchema = {
1624
1640
  "description": "The number of the PR to view.",
1625
1641
  "title": "Prnumber",
1626
1642
  "type": "integer"
1643
+ },
1644
+ "repo": {
1645
+ "anyOf": [
1646
+ {
1647
+ "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
1648
+ "type": "string"
1649
+ },
1650
+ {
1651
+ "type": "null"
1652
+ }
1653
+ ],
1654
+ "default": null,
1655
+ "description": "The target repository override (e.g. org/repo).",
1656
+ "title": "Repo"
1627
1657
  }
1628
1658
  },
1629
1659
  "required": [
@@ -1674,7 +1704,7 @@ export const HealthCheckInputJsonSchema = {
1674
1704
  "type": "object"
1675
1705
  } as const;
1676
1706
 
1677
- 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.") });
1707
+ 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) });
1678
1708
 
1679
1709
  export type IssueCommentInput = z.infer<typeof IssueCommentInputSchema>;
1680
1710
 
@@ -1689,6 +1719,20 @@ export const IssueCommentInputJsonSchema = {
1689
1719
  "description": "The content of the comment.",
1690
1720
  "title": "Body",
1691
1721
  "type": "string"
1722
+ },
1723
+ "repo": {
1724
+ "anyOf": [
1725
+ {
1726
+ "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
1727
+ "type": "string"
1728
+ },
1729
+ {
1730
+ "type": "null"
1731
+ }
1732
+ ],
1733
+ "default": null,
1734
+ "description": "The target repository override (e.g. org/repo).",
1735
+ "title": "Repo"
1692
1736
  }
1693
1737
  },
1694
1738
  "required": [
@@ -1732,7 +1776,7 @@ export const IssueSummaryJsonSchema = {
1732
1776
  "type": "object"
1733
1777
  } as const;
1734
1778
 
1735
- 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) });
1779
+ 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) });
1736
1780
 
1737
1781
  export type IssueUpdateInput = z.infer<typeof IssueUpdateInputSchema>;
1738
1782
 
@@ -1833,6 +1877,20 @@ export const IssueUpdateInputJsonSchema = {
1833
1877
  "default": null,
1834
1878
  "description": "The state to set the issue to.",
1835
1879
  "title": "State"
1880
+ },
1881
+ "repo": {
1882
+ "anyOf": [
1883
+ {
1884
+ "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
1885
+ "type": "string"
1886
+ },
1887
+ {
1888
+ "type": "null"
1889
+ }
1890
+ ],
1891
+ "default": null,
1892
+ "description": "The target repository override (e.g. org/repo).",
1893
+ "title": "Repo"
1836
1894
  }
1837
1895
  },
1838
1896
  "required": [
@@ -1921,7 +1979,7 @@ export const IssueUpdateResponseJsonSchema = {
1921
1979
  "type": "object"
1922
1980
  } as const;
1923
1981
 
1924
- export const IssueViewInputSchema = z.object({ "issueNumber": z.number().int().describe("The number of the issue to view.") });
1982
+ 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) });
1925
1983
 
1926
1984
  export type IssueViewInput = z.infer<typeof IssueViewInputSchema>;
1927
1985
 
@@ -1931,6 +1989,20 @@ export const IssueViewInputJsonSchema = {
1931
1989
  "description": "The number of the issue to view.",
1932
1990
  "title": "Issuenumber",
1933
1991
  "type": "integer"
1992
+ },
1993
+ "repo": {
1994
+ "anyOf": [
1995
+ {
1996
+ "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
1997
+ "type": "string"
1998
+ },
1999
+ {
2000
+ "type": "null"
2001
+ }
2002
+ ],
2003
+ "default": null,
2004
+ "description": "The target repository override (e.g. org/repo).",
2005
+ "title": "Repo"
1934
2006
  }
1935
2007
  },
1936
2008
  "required": [
@@ -2998,7 +3070,7 @@ export const SearchDdgsInputJsonSchema = {
2998
3070
  "type": "object"
2999
3071
  } as const;
3000
3072
 
3001
- 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) });
3073
+ 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) });
3002
3074
 
3003
3075
  export type SearchPRsInput = z.infer<typeof SearchPRsInputSchema>;
3004
3076
 
@@ -3044,6 +3116,20 @@ export const SearchPRsInputJsonSchema = {
3044
3116
  "default": null,
3045
3117
  "description": "Filter PRs by labels.",
3046
3118
  "title": "Labels"
3119
+ },
3120
+ "repo": {
3121
+ "anyOf": [
3122
+ {
3123
+ "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
3124
+ "type": "string"
3125
+ },
3126
+ {
3127
+ "type": "null"
3128
+ }
3129
+ ],
3130
+ "default": null,
3131
+ "description": "The target repository override (e.g. org/repo).",
3132
+ "title": "Repo"
3047
3133
  }
3048
3134
  },
3049
3135
  "title": "SearchPRsInput",
@@ -27,6 +27,16 @@ describe("github.create_issue", () => {
27
27
  });
28
28
 
29
29
  const result = await createIssueHandler({ title: "Test Issue", body: "Test Body", file: null, repo: null });
30
+ expect(shell.runCommand).toHaveBeenCalledWith("td-cli", [
31
+ "gh",
32
+ "create-issue",
33
+ "--title",
34
+ "Test Issue",
35
+ "--body",
36
+ "Test Body",
37
+ "--repo",
38
+ expect.any(String)
39
+ ]);
30
40
  expect(result.status).toBe("success");
31
41
  expect(result.issue?.number).toBe(123);
32
42
  expect(result.issue?.title).toBe("Test Issue");
@@ -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,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)}`);
@@ -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) {
@@ -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,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
  }
@@ -4,18 +4,25 @@ import { runCommand } from "../lib/shell.js";
4
4
  export const RunPlaywrightInputSchema = z.object({
5
5
  grep: z.string().optional(),
6
6
  worktreePath: z.string().optional(),
7
+ timeoutSeconds: z.number().optional().default(180),
7
8
  });
8
9
 
9
- export async function runPlaywrightHandler(args: z.infer<typeof RunPlaywrightInputSchema>) {
10
+ export type RunPlaywrightInput = z.input<typeof RunPlaywrightInputSchema>;
11
+
12
+ export async function runPlaywrightHandler(args: RunPlaywrightInput) {
13
+ const parsed = RunPlaywrightInputSchema.parse(args || {});
10
14
  const tdArgs = ["repo", "run-playwright"];
11
- if (args.grep) {
12
- tdArgs.push("--grep", args.grep);
15
+ if (parsed.grep) {
16
+ tdArgs.push("--grep", parsed.grep);
13
17
  }
14
- if (args.worktreePath) {
15
- tdArgs.push("--worktree", args.worktreePath);
18
+ if (parsed.worktreePath) {
19
+ tdArgs.push("--worktree", parsed.worktreePath);
16
20
  }
17
21
 
18
- const result = await runCommand("td-cli", tdArgs);
22
+ const result = await runCommand("td-cli", tdArgs, {
23
+ cwd: parsed.worktreePath,
24
+ timeout: parsed.timeoutSeconds * 1000,
25
+ });
19
26
 
20
27
  if (result.exitCode !== 0) {
21
28
  // Playwright might exit with non-zero if tests fail, but our td-cli should ideally handle it
@@ -7,8 +7,31 @@ export const RunTestsInputSchema = z.object({
7
7
  worktreePath: z.string().optional(),
8
8
  });
9
9
 
10
- export async function runTestsHandler(args: z.infer<typeof RunTestsInputSchema>) {
11
- const commands = args.commands || [
10
+ export type RunTestsInput = z.input<typeof RunTestsInputSchema>;
11
+
12
+ export function parseCommandArguments(fullCmd: string): [string, string[]] {
13
+ const tokens: string[] = [];
14
+ const regex = /"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|(\S+)/g;
15
+ let match: RegExpExecArray | null;
16
+ while ((match = regex.exec(fullCmd)) !== null) {
17
+ if (match[1] !== undefined) {
18
+ tokens.push(match[1].replace(/\\"/g, '"'));
19
+ } else if (match[2] !== undefined) {
20
+ tokens.push(match[2].replace(/\\'/g, "'"));
21
+ } else {
22
+ tokens.push(match[3]);
23
+ }
24
+ }
25
+ if (tokens.length === 0) {
26
+ return ["", []];
27
+ }
28
+ const [cmd, ...args] = tokens;
29
+ return [cmd, args];
30
+ }
31
+
32
+ export async function runTestsHandler(args: RunTestsInput) {
33
+ const parsed = RunTestsInputSchema.parse(args || {});
34
+ const commands = parsed.commands || [
12
35
  "pnpm install --frozen-lockfile",
13
36
  "pnpm lint",
14
37
  "pnpm test",
@@ -19,11 +42,11 @@ export async function runTestsHandler(args: z.infer<typeof RunTestsInputSchema>)
19
42
  let success = true;
20
43
 
21
44
  for (const fullCmd of commands) {
22
- const [cmd, ...cmdArgs] = fullCmd.split(" ");
45
+ const [cmd, cmdArgs] = parseCommandArguments(fullCmd);
23
46
  try {
24
47
  const res = await runCommand(cmd, cmdArgs, {
25
- cwd: args.worktreePath,
26
- timeout: args.timeoutSeconds * 1000
48
+ cwd: parsed.worktreePath,
49
+ timeout: parsed.timeoutSeconds * 1000
27
50
  });
28
51
  results.push(res);
29
52
  if (res.exitCode !== 0) {