@arii/boomtick-mcp 0.7.0 → 0.7.1
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.
- package/.mcp/schemas/github.get_merge_conflict_files.json +55 -5
- package/.mcp/schemas/github_get_merge_conflict_files.json +55 -5
- package/.mcp/schemas/repo.create_branch.json +63 -0
- package/.mcp/schemas/repo.create_repair_branch.json +63 -0
- package/.mcp/schemas/repo.get_changed_files.json +39 -0
- package/.mcp/schemas/repo_create_branch.json +63 -0
- package/.mcp/schemas/repo_create_repair_branch.json +63 -0
- package/.mcp/schemas/repo_get_changed_files.json +39 -0
- package/CHANGELOG.md +8 -0
- package/dist/lib/git.js +78 -6
- package/dist/lib/git.test.js +34 -1
- package/dist/tools/contract.js +226 -11
- package/dist/tools/github.get_merge_conflict_files.js +10 -2
- package/dist/tools/repo.create_branch.js +18 -4
- package/dist/tools/repo.create_branch.test.js +41 -5
- package/dist/tools/repo.create_repair_branch.js +26 -9
- package/dist/tools/repo.get_changed_files.js +10 -2
- package/package.json +1 -1
- package/src/lib/git.test.ts +45 -1
- package/src/lib/git.ts +94 -6
- package/src/tools/contract.ts +226 -11
- package/src/tools/github.get_merge_conflict_files.ts +11 -2
- package/src/tools/repo.create_branch.test.ts +73 -5
- package/src/tools/repo.create_branch.ts +30 -4
- package/src/tools/repo.create_repair_branch.ts +31 -9
- package/src/tools/repo.get_changed_files.ts +10 -2
package/dist/lib/git.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
2
|
-
import { getChangedFiles, getMergeConflictFiles } from "./git.js";
|
|
2
|
+
import { getChangedFiles, getMergeConflictFiles, resolveRepoPath, createWorktree } from "./git.js";
|
|
3
3
|
import * as shell from "./shell.js";
|
|
4
4
|
vi.mock("./shell.js", () => ({
|
|
5
5
|
runCommand: vi.fn(),
|
|
@@ -79,4 +79,37 @@ describe("Git Helpers", () => {
|
|
|
79
79
|
await expect(getMergeConflictFiles("invalid", "feature")).rejects.toThrow("Git merge-tree failed: fatal: not a valid object name");
|
|
80
80
|
});
|
|
81
81
|
});
|
|
82
|
+
describe("resolveRepoPath", () => {
|
|
83
|
+
it("should return explicit path when path is provided", () => {
|
|
84
|
+
const resolved = resolveRepoPath({ path: "/tmp/custom-repo" });
|
|
85
|
+
expect(resolved).toBe("/tmp/custom-repo");
|
|
86
|
+
});
|
|
87
|
+
it("should return explicit repoPath when repoPath is provided", () => {
|
|
88
|
+
const resolved = resolveRepoPath({ repoPath: "/tmp/another-repo" });
|
|
89
|
+
expect(resolved).toBe("/tmp/another-repo");
|
|
90
|
+
});
|
|
91
|
+
it("should resolve repo path via ecosystem_repos when repo is given", () => {
|
|
92
|
+
const resolved = resolveRepoPath({ repo: "arii/tech-dancer" });
|
|
93
|
+
expect(resolved).toContain("tech-dancer");
|
|
94
|
+
});
|
|
95
|
+
it("should fall back to current working directory when empty", () => {
|
|
96
|
+
const resolved = resolveRepoPath({});
|
|
97
|
+
expect(typeof resolved).toBe("string");
|
|
98
|
+
expect(resolved.length).toBeGreaterThan(0);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
describe("createWorktree", () => {
|
|
102
|
+
it("should pass baseRepoPath cwd to runCommand when provided", async () => {
|
|
103
|
+
vi.mocked(shell.runCommand).mockResolvedValue({
|
|
104
|
+
exitCode: 0,
|
|
105
|
+
stdout: "",
|
|
106
|
+
stderr: "",
|
|
107
|
+
durationMs: 5,
|
|
108
|
+
command: "git",
|
|
109
|
+
});
|
|
110
|
+
const worktree = await createWorktree("origin/feature", 123, "/home/ari/tech-dancer");
|
|
111
|
+
expect(worktree).toContain("boomtick-mcp-rescue-123");
|
|
112
|
+
expect(shell.runCommand).toHaveBeenCalledWith("git", expect.arrayContaining(["worktree", "add"]), { cwd: "/home/ari/tech-dancer" });
|
|
113
|
+
});
|
|
114
|
+
});
|
|
82
115
|
});
|
package/dist/tools/contract.js
CHANGED
|
@@ -1390,7 +1390,7 @@ export const CommitPatchResponseJsonSchema = {
|
|
|
1390
1390
|
"title": "CommitPatchResponse",
|
|
1391
1391
|
"type": "object"
|
|
1392
1392
|
};
|
|
1393
|
-
export const CreateBranchInputSchema = z.object({ "branchName": z.string().describe("The name of the new branch"), "baseBranch": z.string().describe("Branch to branch off from").default("main") });
|
|
1393
|
+
export const CreateBranchInputSchema = z.object({ "branchName": z.string().describe("The name of the new branch"), "baseBranch": z.string().describe("Branch to branch off from").default("main"), "repo": z.union([z.string(), z.null()]).describe("Target repository override (e.g. org/repo or repo).").default(null), "path": z.union([z.string(), z.null()]).describe("Local filesystem path to target repository root.").default(null), "repoPath": z.union([z.string(), z.null()]).describe("Alias for path to target repository root.").default(null) });
|
|
1394
1394
|
export const CreateBranchInputJsonSchema = {
|
|
1395
1395
|
"properties": {
|
|
1396
1396
|
"branchName": {
|
|
@@ -1403,6 +1403,45 @@ export const CreateBranchInputJsonSchema = {
|
|
|
1403
1403
|
"description": "Branch to branch off from",
|
|
1404
1404
|
"title": "Basebranch",
|
|
1405
1405
|
"type": "string"
|
|
1406
|
+
},
|
|
1407
|
+
"repo": {
|
|
1408
|
+
"anyOf": [
|
|
1409
|
+
{
|
|
1410
|
+
"type": "string"
|
|
1411
|
+
},
|
|
1412
|
+
{
|
|
1413
|
+
"type": "null"
|
|
1414
|
+
}
|
|
1415
|
+
],
|
|
1416
|
+
"default": null,
|
|
1417
|
+
"description": "Target repository override (e.g. org/repo or repo).",
|
|
1418
|
+
"title": "Repo"
|
|
1419
|
+
},
|
|
1420
|
+
"path": {
|
|
1421
|
+
"anyOf": [
|
|
1422
|
+
{
|
|
1423
|
+
"type": "string"
|
|
1424
|
+
},
|
|
1425
|
+
{
|
|
1426
|
+
"type": "null"
|
|
1427
|
+
}
|
|
1428
|
+
],
|
|
1429
|
+
"default": null,
|
|
1430
|
+
"description": "Local filesystem path to target repository root.",
|
|
1431
|
+
"title": "Path"
|
|
1432
|
+
},
|
|
1433
|
+
"repoPath": {
|
|
1434
|
+
"anyOf": [
|
|
1435
|
+
{
|
|
1436
|
+
"type": "string"
|
|
1437
|
+
},
|
|
1438
|
+
{
|
|
1439
|
+
"type": "null"
|
|
1440
|
+
}
|
|
1441
|
+
],
|
|
1442
|
+
"default": null,
|
|
1443
|
+
"description": "Alias for path to target repository root.",
|
|
1444
|
+
"title": "Repopath"
|
|
1406
1445
|
}
|
|
1407
1446
|
},
|
|
1408
1447
|
"required": [
|
|
@@ -1411,7 +1450,7 @@ export const CreateBranchInputJsonSchema = {
|
|
|
1411
1450
|
"title": "CreateBranchInput",
|
|
1412
1451
|
"type": "object"
|
|
1413
1452
|
};
|
|
1414
|
-
export const CreateBranchResponseSchema = z.object({ "success": z.boolean().default(true), "branchName": z.string(), "baseBranch": z.union([z.string(), z.null()]).default(null) });
|
|
1453
|
+
export const CreateBranchResponseSchema = z.object({ "success": z.boolean().default(true), "branchName": z.string(), "baseBranch": z.union([z.string(), z.null()]).default(null), "repo": z.union([z.string(), z.null()]).default(null), "path": z.union([z.string(), z.null()]).default(null) });
|
|
1415
1454
|
export const CreateBranchResponseJsonSchema = {
|
|
1416
1455
|
"properties": {
|
|
1417
1456
|
"success": {
|
|
@@ -1434,6 +1473,30 @@ export const CreateBranchResponseJsonSchema = {
|
|
|
1434
1473
|
],
|
|
1435
1474
|
"default": null,
|
|
1436
1475
|
"title": "Basebranch"
|
|
1476
|
+
},
|
|
1477
|
+
"repo": {
|
|
1478
|
+
"anyOf": [
|
|
1479
|
+
{
|
|
1480
|
+
"type": "string"
|
|
1481
|
+
},
|
|
1482
|
+
{
|
|
1483
|
+
"type": "null"
|
|
1484
|
+
}
|
|
1485
|
+
],
|
|
1486
|
+
"default": null,
|
|
1487
|
+
"title": "Repo"
|
|
1488
|
+
},
|
|
1489
|
+
"path": {
|
|
1490
|
+
"anyOf": [
|
|
1491
|
+
{
|
|
1492
|
+
"type": "string"
|
|
1493
|
+
},
|
|
1494
|
+
{
|
|
1495
|
+
"type": "null"
|
|
1496
|
+
}
|
|
1497
|
+
],
|
|
1498
|
+
"default": null,
|
|
1499
|
+
"title": "Path"
|
|
1437
1500
|
}
|
|
1438
1501
|
},
|
|
1439
1502
|
"required": [
|
|
@@ -1699,7 +1762,7 @@ export const CreatePullRequestResponseJsonSchema = {
|
|
|
1699
1762
|
"title": "CreatePullRequestResponse",
|
|
1700
1763
|
"type": "object"
|
|
1701
1764
|
};
|
|
1702
|
-
export const CreateRepairBranchInputSchema = z.object({ "prNumber": z.number().int().describe("The original pull request number to repair."), "repairBranchName": z.union([z.string(), z.null()]).describe("Optional custom name for the new repair branch.").default(null), "writeMode": z.boolean().describe("Safety gate: Must be true to perform branch creation and worktree setup.") });
|
|
1765
|
+
export const CreateRepairBranchInputSchema = z.object({ "prNumber": z.number().int().describe("The original pull request number to repair."), "repairBranchName": z.union([z.string(), z.null()]).describe("Optional custom name for the new repair branch.").default(null), "writeMode": z.boolean().describe("Safety gate: Must be true to perform branch creation and worktree setup."), "repo": z.union([z.string(), z.null()]).describe("Target repository override (e.g. org/repo or repo).").default(null), "path": z.union([z.string(), z.null()]).describe("Local filesystem path to target repository root.").default(null), "repoPath": z.union([z.string(), z.null()]).describe("Alias for path to target repository root.").default(null) });
|
|
1703
1766
|
export const CreateRepairBranchInputJsonSchema = {
|
|
1704
1767
|
"properties": {
|
|
1705
1768
|
"prNumber": {
|
|
@@ -1724,6 +1787,45 @@ export const CreateRepairBranchInputJsonSchema = {
|
|
|
1724
1787
|
"description": "Safety gate: Must be true to perform branch creation and worktree setup.",
|
|
1725
1788
|
"title": "Writemode",
|
|
1726
1789
|
"type": "boolean"
|
|
1790
|
+
},
|
|
1791
|
+
"repo": {
|
|
1792
|
+
"anyOf": [
|
|
1793
|
+
{
|
|
1794
|
+
"type": "string"
|
|
1795
|
+
},
|
|
1796
|
+
{
|
|
1797
|
+
"type": "null"
|
|
1798
|
+
}
|
|
1799
|
+
],
|
|
1800
|
+
"default": null,
|
|
1801
|
+
"description": "Target repository override (e.g. org/repo or repo).",
|
|
1802
|
+
"title": "Repo"
|
|
1803
|
+
},
|
|
1804
|
+
"path": {
|
|
1805
|
+
"anyOf": [
|
|
1806
|
+
{
|
|
1807
|
+
"type": "string"
|
|
1808
|
+
},
|
|
1809
|
+
{
|
|
1810
|
+
"type": "null"
|
|
1811
|
+
}
|
|
1812
|
+
],
|
|
1813
|
+
"default": null,
|
|
1814
|
+
"description": "Local filesystem path to target repository root.",
|
|
1815
|
+
"title": "Path"
|
|
1816
|
+
},
|
|
1817
|
+
"repoPath": {
|
|
1818
|
+
"anyOf": [
|
|
1819
|
+
{
|
|
1820
|
+
"type": "string"
|
|
1821
|
+
},
|
|
1822
|
+
{
|
|
1823
|
+
"type": "null"
|
|
1824
|
+
}
|
|
1825
|
+
],
|
|
1826
|
+
"default": null,
|
|
1827
|
+
"description": "Alias for path to target repository root.",
|
|
1828
|
+
"title": "Repopath"
|
|
1727
1829
|
}
|
|
1728
1830
|
},
|
|
1729
1831
|
"required": [
|
|
@@ -1733,7 +1835,7 @@ export const CreateRepairBranchInputJsonSchema = {
|
|
|
1733
1835
|
"title": "CreateRepairBranchInput",
|
|
1734
1836
|
"type": "object"
|
|
1735
1837
|
};
|
|
1736
|
-
export const CreateRepairBranchResponseSchema = z.object({ "prNumber": z.number().int(), "originalBranch": z.string(), "repairBranch": z.string(), "baseBranch": z.string(), "worktreePath": z.string() });
|
|
1838
|
+
export const CreateRepairBranchResponseSchema = z.object({ "prNumber": z.number().int(), "originalBranch": z.string(), "repairBranch": z.string(), "baseBranch": z.string(), "worktreePath": z.string(), "repo": z.union([z.string(), z.null()]).default(null), "path": z.union([z.string(), z.null()]).default(null) });
|
|
1737
1839
|
export const CreateRepairBranchResponseJsonSchema = {
|
|
1738
1840
|
"properties": {
|
|
1739
1841
|
"prNumber": {
|
|
@@ -1755,6 +1857,30 @@ export const CreateRepairBranchResponseJsonSchema = {
|
|
|
1755
1857
|
"worktreePath": {
|
|
1756
1858
|
"title": "Worktreepath",
|
|
1757
1859
|
"type": "string"
|
|
1860
|
+
},
|
|
1861
|
+
"repo": {
|
|
1862
|
+
"anyOf": [
|
|
1863
|
+
{
|
|
1864
|
+
"type": "string"
|
|
1865
|
+
},
|
|
1866
|
+
{
|
|
1867
|
+
"type": "null"
|
|
1868
|
+
}
|
|
1869
|
+
],
|
|
1870
|
+
"default": null,
|
|
1871
|
+
"title": "Repo"
|
|
1872
|
+
},
|
|
1873
|
+
"path": {
|
|
1874
|
+
"anyOf": [
|
|
1875
|
+
{
|
|
1876
|
+
"type": "string"
|
|
1877
|
+
},
|
|
1878
|
+
{
|
|
1879
|
+
"type": "null"
|
|
1880
|
+
}
|
|
1881
|
+
],
|
|
1882
|
+
"default": null,
|
|
1883
|
+
"title": "Path"
|
|
1758
1884
|
}
|
|
1759
1885
|
},
|
|
1760
1886
|
"required": [
|
|
@@ -1767,7 +1893,7 @@ export const CreateRepairBranchResponseJsonSchema = {
|
|
|
1767
1893
|
"title": "CreateRepairBranchResponse",
|
|
1768
1894
|
"type": "object"
|
|
1769
1895
|
};
|
|
1770
|
-
export const GetChangedFilesInputSchema = z.object({ "base": z.string().describe("The base ref to compare from (default: 'main').").default("main"), "head": z.string().describe("The head ref to compare to (default: 'HEAD').").default("HEAD") });
|
|
1896
|
+
export const GetChangedFilesInputSchema = z.object({ "base": z.string().describe("The base ref to compare from (default: 'main').").default("main"), "head": z.string().describe("The head ref to compare to (default: 'HEAD').").default("HEAD"), "repo": z.union([z.string(), z.null()]).describe("Target GitHub repository override.").default(null), "path": z.union([z.string(), z.null()]).describe("Local filesystem path to target repository root.").default(null), "repoPath": z.union([z.string(), z.null()]).describe("Alias for path to target repository root.").default(null) });
|
|
1771
1897
|
export const GetChangedFilesInputJsonSchema = {
|
|
1772
1898
|
"properties": {
|
|
1773
1899
|
"base": {
|
|
@@ -1781,6 +1907,45 @@ export const GetChangedFilesInputJsonSchema = {
|
|
|
1781
1907
|
"description": "The head ref to compare to (default: 'HEAD').",
|
|
1782
1908
|
"title": "Head",
|
|
1783
1909
|
"type": "string"
|
|
1910
|
+
},
|
|
1911
|
+
"repo": {
|
|
1912
|
+
"anyOf": [
|
|
1913
|
+
{
|
|
1914
|
+
"type": "string"
|
|
1915
|
+
},
|
|
1916
|
+
{
|
|
1917
|
+
"type": "null"
|
|
1918
|
+
}
|
|
1919
|
+
],
|
|
1920
|
+
"default": null,
|
|
1921
|
+
"description": "Target GitHub repository override.",
|
|
1922
|
+
"title": "Repo"
|
|
1923
|
+
},
|
|
1924
|
+
"path": {
|
|
1925
|
+
"anyOf": [
|
|
1926
|
+
{
|
|
1927
|
+
"type": "string"
|
|
1928
|
+
},
|
|
1929
|
+
{
|
|
1930
|
+
"type": "null"
|
|
1931
|
+
}
|
|
1932
|
+
],
|
|
1933
|
+
"default": null,
|
|
1934
|
+
"description": "Local filesystem path to target repository root.",
|
|
1935
|
+
"title": "Path"
|
|
1936
|
+
},
|
|
1937
|
+
"repoPath": {
|
|
1938
|
+
"anyOf": [
|
|
1939
|
+
{
|
|
1940
|
+
"type": "string"
|
|
1941
|
+
},
|
|
1942
|
+
{
|
|
1943
|
+
"type": "null"
|
|
1944
|
+
}
|
|
1945
|
+
],
|
|
1946
|
+
"default": null,
|
|
1947
|
+
"description": "Alias for path to target repository root.",
|
|
1948
|
+
"title": "Repopath"
|
|
1784
1949
|
}
|
|
1785
1950
|
},
|
|
1786
1951
|
"title": "GetChangedFilesInput",
|
|
@@ -1847,24 +2012,74 @@ export const GetCommandSchemaResponseJsonSchema = {
|
|
|
1847
2012
|
"title": "GetCommandSchemaResponse",
|
|
1848
2013
|
"type": "object"
|
|
1849
2014
|
};
|
|
1850
|
-
export const GetMergeConflictFilesInputSchema = z.object({ "prNumber": z.number().int().describe("The number of the pull request to check for conflicts."), "baseBranch": z.string().describe("The base branch to check against (default: 'main').").default("main") });
|
|
2015
|
+
export const GetMergeConflictFilesInputSchema = z.object({ "prNumber": z.union([z.number().int(), z.null()]).describe("The number of the pull request to check for conflicts.").default(null), "baseBranch": z.string().describe("The base branch to check against (default: 'main').").default("main"), "headRef": z.string().describe("The head ref or branch to check.").default("HEAD"), "repo": z.union([z.string(), z.null()]).describe("Target GitHub repository override.").default(null), "path": z.union([z.string(), z.null()]).describe("Local filesystem path to target repository root.").default(null), "repoPath": z.union([z.string(), z.null()]).describe("Alias for path to target repository root.").default(null) });
|
|
1851
2016
|
export const GetMergeConflictFilesInputJsonSchema = {
|
|
1852
2017
|
"properties": {
|
|
1853
2018
|
"prNumber": {
|
|
2019
|
+
"anyOf": [
|
|
2020
|
+
{
|
|
2021
|
+
"type": "integer"
|
|
2022
|
+
},
|
|
2023
|
+
{
|
|
2024
|
+
"type": "null"
|
|
2025
|
+
}
|
|
2026
|
+
],
|
|
2027
|
+
"default": null,
|
|
1854
2028
|
"description": "The number of the pull request to check for conflicts.",
|
|
1855
|
-
"title": "Prnumber"
|
|
1856
|
-
"type": "integer"
|
|
2029
|
+
"title": "Prnumber"
|
|
1857
2030
|
},
|
|
1858
2031
|
"baseBranch": {
|
|
1859
2032
|
"default": "main",
|
|
1860
2033
|
"description": "The base branch to check against (default: 'main').",
|
|
1861
2034
|
"title": "Basebranch",
|
|
1862
2035
|
"type": "string"
|
|
2036
|
+
},
|
|
2037
|
+
"headRef": {
|
|
2038
|
+
"default": "HEAD",
|
|
2039
|
+
"description": "The head ref or branch to check.",
|
|
2040
|
+
"title": "Headref",
|
|
2041
|
+
"type": "string"
|
|
2042
|
+
},
|
|
2043
|
+
"repo": {
|
|
2044
|
+
"anyOf": [
|
|
2045
|
+
{
|
|
2046
|
+
"type": "string"
|
|
2047
|
+
},
|
|
2048
|
+
{
|
|
2049
|
+
"type": "null"
|
|
2050
|
+
}
|
|
2051
|
+
],
|
|
2052
|
+
"default": null,
|
|
2053
|
+
"description": "Target GitHub repository override.",
|
|
2054
|
+
"title": "Repo"
|
|
2055
|
+
},
|
|
2056
|
+
"path": {
|
|
2057
|
+
"anyOf": [
|
|
2058
|
+
{
|
|
2059
|
+
"type": "string"
|
|
2060
|
+
},
|
|
2061
|
+
{
|
|
2062
|
+
"type": "null"
|
|
2063
|
+
}
|
|
2064
|
+
],
|
|
2065
|
+
"default": null,
|
|
2066
|
+
"description": "Local filesystem path to target repository root.",
|
|
2067
|
+
"title": "Path"
|
|
2068
|
+
},
|
|
2069
|
+
"repoPath": {
|
|
2070
|
+
"anyOf": [
|
|
2071
|
+
{
|
|
2072
|
+
"type": "string"
|
|
2073
|
+
},
|
|
2074
|
+
{
|
|
2075
|
+
"type": "null"
|
|
2076
|
+
}
|
|
2077
|
+
],
|
|
2078
|
+
"default": null,
|
|
2079
|
+
"description": "Alias for path to target repository root.",
|
|
2080
|
+
"title": "Repopath"
|
|
1863
2081
|
}
|
|
1864
2082
|
},
|
|
1865
|
-
"required": [
|
|
1866
|
-
"prNumber"
|
|
1867
|
-
],
|
|
1868
2083
|
"title": "GetMergeConflictFilesInput",
|
|
1869
2084
|
"type": "object"
|
|
1870
2085
|
};
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { getMergeConflictFiles } from "../lib/git.js";
|
|
2
|
+
import { getMergeConflictFiles, resolveRepoPath } from "../lib/git.js";
|
|
3
3
|
export const GetMergeConflictFilesInputSchema = z.object({
|
|
4
4
|
prNumber: z.number().optional(),
|
|
5
5
|
baseBranch: z.string().optional().default("main"),
|
|
6
6
|
headRef: z.string().optional().default("HEAD"),
|
|
7
|
+
repo: z.string().optional().describe("Target GitHub repository override."),
|
|
8
|
+
path: z.string().optional().describe("Local filesystem path to target repository root."),
|
|
9
|
+
repoPath: z.string().optional().describe("Alias for path to target repository root."),
|
|
7
10
|
});
|
|
8
11
|
export async function getMergeConflictFilesHandler(args) {
|
|
9
12
|
const baseBranch = args.baseBranch || "main";
|
|
10
13
|
const headRef = args.headRef || "HEAD";
|
|
11
|
-
const
|
|
14
|
+
const targetCwd = resolveRepoPath({
|
|
15
|
+
path: args.path,
|
|
16
|
+
repoPath: args.repoPath,
|
|
17
|
+
repo: args.repo,
|
|
18
|
+
});
|
|
19
|
+
const conflictFiles = await getMergeConflictFiles(baseBranch, headRef, targetCwd);
|
|
12
20
|
const hasConflicts = conflictFiles.length > 0;
|
|
13
21
|
return {
|
|
14
22
|
hasConflicts,
|
|
@@ -1,21 +1,35 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { runCommand } from "../lib/shell.js";
|
|
3
|
+
import { resolveRepoPath } from "../lib/git.js";
|
|
3
4
|
export const CreateBranchInputSchema = z.object({
|
|
4
5
|
branchName: z.string(),
|
|
5
6
|
baseBranch: z.string().optional().default("main"),
|
|
7
|
+
repo: z.string().optional().describe("Target repository override (e.g. org/repo or repo)."),
|
|
8
|
+
path: z.string().optional().describe("Local filesystem path to target repository root."),
|
|
9
|
+
repoPath: z.string().optional().describe("Alias for path to target repository root."),
|
|
6
10
|
});
|
|
7
11
|
export async function createBranchHandler(args) {
|
|
8
12
|
const params = CreateBranchInputSchema.parse(args);
|
|
13
|
+
const targetCwd = resolveRepoPath({
|
|
14
|
+
path: params.path,
|
|
15
|
+
repoPath: params.repoPath,
|
|
16
|
+
repo: params.repo,
|
|
17
|
+
});
|
|
9
18
|
// Ensure latest base branch
|
|
10
|
-
await runCommand("git", ["fetch", "origin", params.baseBranch]);
|
|
19
|
+
await runCommand("git", ["fetch", "origin", params.baseBranch], { cwd: targetCwd });
|
|
11
20
|
// Create branch from origin base branch
|
|
12
|
-
const result = await runCommand("git", ["checkout", "-b", params.branchName, `origin/${params.baseBranch}`]);
|
|
21
|
+
const result = await runCommand("git", ["checkout", "-b", params.branchName, `origin/${params.baseBranch}`], { cwd: targetCwd });
|
|
13
22
|
if (result.exitCode !== 0) {
|
|
14
23
|
throw new Error(`Failed to create branch ${params.branchName}: ${result.stderr}`);
|
|
15
24
|
}
|
|
16
|
-
|
|
25
|
+
const response = {
|
|
17
26
|
success: true,
|
|
18
27
|
branchName: params.branchName,
|
|
19
|
-
baseBranch: params.baseBranch
|
|
28
|
+
baseBranch: params.baseBranch,
|
|
29
|
+
path: targetCwd,
|
|
20
30
|
};
|
|
31
|
+
if (params.repo) {
|
|
32
|
+
response.repo = params.repo;
|
|
33
|
+
}
|
|
34
|
+
return response;
|
|
21
35
|
}
|
|
@@ -15,13 +15,14 @@ describe("repo.create_branch", () => {
|
|
|
15
15
|
};
|
|
16
16
|
shell.runCommand.mockResolvedValue({ exitCode: 0, stdout: "", stderr: "" });
|
|
17
17
|
const result = await createBranchHandler(args);
|
|
18
|
-
expect(shell.runCommand).toHaveBeenCalledWith("git", ["fetch", "origin", "main"]);
|
|
19
|
-
expect(shell.runCommand).toHaveBeenCalledWith("git", ["checkout", "-b", "new-feature", "origin/main"]);
|
|
20
|
-
expect(result).
|
|
18
|
+
expect(shell.runCommand).toHaveBeenCalledWith("git", ["fetch", "origin", "main"], { cwd: expect.any(String) });
|
|
19
|
+
expect(shell.runCommand).toHaveBeenCalledWith("git", ["checkout", "-b", "new-feature", "origin/main"], { cwd: expect.any(String) });
|
|
20
|
+
expect(result).toMatchObject({
|
|
21
21
|
success: true,
|
|
22
22
|
branchName: "new-feature",
|
|
23
23
|
baseBranch: "main",
|
|
24
24
|
});
|
|
25
|
+
expect(result.path).toBeDefined();
|
|
25
26
|
});
|
|
26
27
|
it("should use default base branch 'main' if not provided", async () => {
|
|
27
28
|
const args = {
|
|
@@ -29,8 +30,43 @@ describe("repo.create_branch", () => {
|
|
|
29
30
|
};
|
|
30
31
|
shell.runCommand.mockResolvedValue({ exitCode: 0, stdout: "", stderr: "" });
|
|
31
32
|
const result = await createBranchHandler(args);
|
|
32
|
-
expect(shell.runCommand).toHaveBeenCalledWith("git", ["fetch", "origin", "main"]);
|
|
33
|
-
expect(shell.runCommand).toHaveBeenCalledWith("git", ["checkout", "-b", "another-feature", "origin/main"]);
|
|
33
|
+
expect(shell.runCommand).toHaveBeenCalledWith("git", ["fetch", "origin", "main"], { cwd: expect.any(String) });
|
|
34
|
+
expect(shell.runCommand).toHaveBeenCalledWith("git", ["checkout", "-b", "another-feature", "origin/main"], { cwd: expect.any(String) });
|
|
35
|
+
expect(result.success).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
it("should create branch in specified target workspace path", async () => {
|
|
38
|
+
const args = {
|
|
39
|
+
branchName: "custom-path-feature",
|
|
40
|
+
baseBranch: "main",
|
|
41
|
+
path: "/home/ari/tech-dancer",
|
|
42
|
+
};
|
|
43
|
+
shell.runCommand.mockResolvedValue({ exitCode: 0, stdout: "", stderr: "" });
|
|
44
|
+
const result = await createBranchHandler(args);
|
|
45
|
+
expect(shell.runCommand).toHaveBeenCalledWith("git", ["fetch", "origin", "main"], { cwd: "/home/ari/tech-dancer" });
|
|
46
|
+
expect(shell.runCommand).toHaveBeenCalledWith("git", ["checkout", "-b", "custom-path-feature", "origin/main"], { cwd: "/home/ari/tech-dancer" });
|
|
47
|
+
expect(result).toEqual({
|
|
48
|
+
success: true,
|
|
49
|
+
branchName: "custom-path-feature",
|
|
50
|
+
baseBranch: "main",
|
|
51
|
+
path: "/home/ari/tech-dancer",
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
it("should include repo field when repo override is specified", async () => {
|
|
55
|
+
const args = {
|
|
56
|
+
branchName: "repo-feature",
|
|
57
|
+
baseBranch: "main",
|
|
58
|
+
repo: "arii/tech-dancer",
|
|
59
|
+
path: "/home/ari/tech-dancer",
|
|
60
|
+
};
|
|
61
|
+
shell.runCommand.mockResolvedValue({ exitCode: 0, stdout: "", stderr: "" });
|
|
62
|
+
const result = await createBranchHandler(args);
|
|
63
|
+
expect(result).toEqual({
|
|
64
|
+
success: true,
|
|
65
|
+
branchName: "repo-feature",
|
|
66
|
+
baseBranch: "main",
|
|
67
|
+
path: "/home/ari/tech-dancer",
|
|
68
|
+
repo: "arii/tech-dancer",
|
|
69
|
+
});
|
|
34
70
|
});
|
|
35
71
|
it("should throw error if git command fails", async () => {
|
|
36
72
|
const args = {
|
|
@@ -1,38 +1,55 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { runCommand } from "../lib/shell.js";
|
|
3
|
-
import { createWorktree } from "../lib/git.js";
|
|
3
|
+
import { createWorktree, resolveRepoPath } from "../lib/git.js";
|
|
4
4
|
export const CreateRepairBranchInputSchema = z.object({
|
|
5
5
|
prNumber: z.number(),
|
|
6
6
|
repairBranchName: z.string().optional(),
|
|
7
7
|
writeMode: z.boolean().optional().default(false),
|
|
8
|
+
repo: z.string().optional().describe("Target repository override (e.g. org/repo or repo)."),
|
|
9
|
+
path: z.string().optional().describe("Local filesystem path to target repository root."),
|
|
10
|
+
repoPath: z.string().optional().describe("Alias for path to target repository root."),
|
|
8
11
|
});
|
|
9
12
|
export async function createRepairBranchHandler(args) {
|
|
10
13
|
if (!args.writeMode) {
|
|
11
14
|
throw new Error("writeMode must be true to create a repair branch.");
|
|
12
15
|
}
|
|
13
|
-
const
|
|
16
|
+
const targetCwd = resolveRepoPath({
|
|
17
|
+
path: args.path,
|
|
18
|
+
repoPath: args.repoPath,
|
|
19
|
+
repo: args.repo,
|
|
20
|
+
});
|
|
21
|
+
const ghArgs = [
|
|
14
22
|
"pr",
|
|
15
23
|
"view",
|
|
16
24
|
args.prNumber.toString(),
|
|
17
25
|
"--json", "headRefName,baseRefName,title"
|
|
18
|
-
]
|
|
26
|
+
];
|
|
27
|
+
if (args.repo) {
|
|
28
|
+
ghArgs.push("--repo", args.repo);
|
|
29
|
+
}
|
|
30
|
+
const prResult = await runCommand("gh", ghArgs, { cwd: targetCwd });
|
|
19
31
|
if (prResult.exitCode !== 0) {
|
|
20
32
|
throw new Error(`Failed to get PR info: ${prResult.stderr}`);
|
|
21
33
|
}
|
|
22
34
|
const { headRefName, baseRefName, title } = JSON.parse(prResult.stdout);
|
|
23
35
|
const sanitizedTitle = title.toLowerCase().replace(/[^a-z0-9]/g, "-").substring(0, 30);
|
|
24
36
|
const repairBranch = args.repairBranchName || `agent/repair-pr-${args.prNumber}-${sanitizedTitle}`;
|
|
25
|
-
// Ensure latest
|
|
26
|
-
await runCommand("git", ["fetch", "origin", headRefName]);
|
|
27
|
-
await runCommand("git", ["fetch", "origin", baseRefName]);
|
|
28
|
-
const worktreePath = await createWorktree(`origin/${headRefName}`, args.prNumber);
|
|
37
|
+
// Ensure latest in target repo
|
|
38
|
+
await runCommand("git", ["fetch", "origin", headRefName], { cwd: targetCwd });
|
|
39
|
+
await runCommand("git", ["fetch", "origin", baseRefName], { cwd: targetCwd });
|
|
40
|
+
const worktreePath = await createWorktree(`origin/${headRefName}`, args.prNumber, targetCwd);
|
|
29
41
|
// Create the actual repair branch in the worktree
|
|
30
42
|
await runCommand("git", ["checkout", "-b", repairBranch], { cwd: worktreePath });
|
|
31
|
-
|
|
43
|
+
const response = {
|
|
32
44
|
prNumber: args.prNumber,
|
|
33
45
|
originalBranch: headRefName,
|
|
34
46
|
repairBranch,
|
|
35
47
|
baseBranch: baseRefName,
|
|
36
|
-
worktreePath
|
|
48
|
+
worktreePath,
|
|
49
|
+
path: targetCwd,
|
|
37
50
|
};
|
|
51
|
+
if (args.repo) {
|
|
52
|
+
response.repo = args.repo;
|
|
53
|
+
}
|
|
54
|
+
return response;
|
|
38
55
|
}
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { getChangedFiles } from "../lib/git.js";
|
|
2
|
+
import { getChangedFiles, resolveRepoPath } from "../lib/git.js";
|
|
3
3
|
export const GetChangedFilesInputSchema = z.object({
|
|
4
4
|
base: z.string().optional().default("main"),
|
|
5
5
|
head: z.string().optional().default("HEAD"),
|
|
6
|
+
repo: z.string().optional().describe("Target GitHub repository override."),
|
|
7
|
+
path: z.string().optional().describe("Local filesystem path to target repository root."),
|
|
8
|
+
repoPath: z.string().optional().describe("Alias for path to target repository root."),
|
|
6
9
|
});
|
|
7
10
|
export async function getChangedFilesHandler(args) {
|
|
8
|
-
const
|
|
11
|
+
const targetCwd = resolveRepoPath({
|
|
12
|
+
path: args.path,
|
|
13
|
+
repoPath: args.repoPath,
|
|
14
|
+
repo: args.repo,
|
|
15
|
+
});
|
|
16
|
+
const files = await getChangedFiles(args.base || "main", args.head || "HEAD", targetCwd);
|
|
9
17
|
return { files };
|
|
10
18
|
}
|
package/package.json
CHANGED
package/src/lib/git.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
2
|
-
import { getChangedFiles, getMergeConflictFiles } from "./git.js";
|
|
2
|
+
import { getChangedFiles, getMergeConflictFiles, resolveRepoPath, createWorktree } from "./git.js";
|
|
3
3
|
import * as shell from "./shell.js";
|
|
4
4
|
|
|
5
5
|
vi.mock("./shell.js", () => ({
|
|
@@ -107,4 +107,48 @@ describe("Git Helpers", () => {
|
|
|
107
107
|
);
|
|
108
108
|
});
|
|
109
109
|
});
|
|
110
|
+
|
|
111
|
+
describe("resolveRepoPath", () => {
|
|
112
|
+
it("should return explicit path when path is provided", () => {
|
|
113
|
+
const resolved = resolveRepoPath({ path: "/tmp/custom-repo" });
|
|
114
|
+
expect(resolved).toBe("/tmp/custom-repo");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("should return explicit repoPath when repoPath is provided", () => {
|
|
118
|
+
const resolved = resolveRepoPath({ repoPath: "/tmp/another-repo" });
|
|
119
|
+
expect(resolved).toBe("/tmp/another-repo");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should resolve repo path via ecosystem_repos when repo is given", () => {
|
|
123
|
+
const resolved = resolveRepoPath({ repo: "arii/tech-dancer" });
|
|
124
|
+
expect(resolved).toContain("tech-dancer");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("should fall back to current working directory when empty", () => {
|
|
128
|
+
const resolved = resolveRepoPath({});
|
|
129
|
+
expect(typeof resolved).toBe("string");
|
|
130
|
+
expect(resolved.length).toBeGreaterThan(0);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe("createWorktree", () => {
|
|
135
|
+
it("should pass baseRepoPath cwd to runCommand when provided", async () => {
|
|
136
|
+
vi.mocked(shell.runCommand).mockResolvedValue({
|
|
137
|
+
exitCode: 0,
|
|
138
|
+
stdout: "",
|
|
139
|
+
stderr: "",
|
|
140
|
+
durationMs: 5,
|
|
141
|
+
command: "git",
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const worktree = await createWorktree("origin/feature", 123, "/home/ari/tech-dancer");
|
|
145
|
+
|
|
146
|
+
expect(worktree).toContain("boomtick-mcp-rescue-123");
|
|
147
|
+
expect(shell.runCommand).toHaveBeenCalledWith(
|
|
148
|
+
"git",
|
|
149
|
+
expect.arrayContaining(["worktree", "add"]),
|
|
150
|
+
{ cwd: "/home/ari/tech-dancer" }
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
110
154
|
});
|