@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/src/lib/git.ts
CHANGED
|
@@ -2,6 +2,7 @@ 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 * as syncFs from "fs";
|
|
5
6
|
import { execSync } from "child_process";
|
|
6
7
|
|
|
7
8
|
const repoCache = new Map<string, string>();
|
|
@@ -37,6 +38,87 @@ export function resolveRepo(explicitRepo?: string | null, cwd?: string): string
|
|
|
37
38
|
return fallback;
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
export interface RepoPathOptions {
|
|
42
|
+
path?: string | null;
|
|
43
|
+
repoPath?: string | null;
|
|
44
|
+
repo?: string | null;
|
|
45
|
+
cwd?: string | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function resolveRepoPath(options: RepoPathOptions = {}): string {
|
|
49
|
+
// 1. Explicit path parameter has highest precedence
|
|
50
|
+
if (options.path && options.path.trim().length > 0) {
|
|
51
|
+
return path.resolve(options.path.trim());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 2. repoPath alias parameter
|
|
55
|
+
if (options.repoPath && options.repoPath.trim().length > 0) {
|
|
56
|
+
return path.resolve(options.repoPath.trim());
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 3. Repository name override (e.g. "arii/tech-dancer" or "tech-dancer")
|
|
60
|
+
if (options.repo && options.repo.trim().length > 0) {
|
|
61
|
+
const rawRepo = options.repo.trim();
|
|
62
|
+
const repoName = rawRepo.includes("/") ? rawRepo.split("/")[1] : rawRepo;
|
|
63
|
+
|
|
64
|
+
// First check project_config.json ecosystem_repos if available
|
|
65
|
+
let rootPath: string = process.cwd();
|
|
66
|
+
try {
|
|
67
|
+
if (options.cwd && options.cwd.trim().length > 0) {
|
|
68
|
+
rootPath = options.cwd.trim();
|
|
69
|
+
} else if (config.repoPath) {
|
|
70
|
+
rootPath = config.repoPath;
|
|
71
|
+
}
|
|
72
|
+
} catch {
|
|
73
|
+
rootPath = process.cwd();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const projectConfigPath = path.join(rootPath, "project_config.json");
|
|
77
|
+
if (syncFs.existsSync(projectConfigPath)) {
|
|
78
|
+
try {
|
|
79
|
+
const parsed = JSON.parse(syncFs.readFileSync(projectConfigPath, "utf-8"));
|
|
80
|
+
if (parsed?.ecosystem_repos && typeof parsed.ecosystem_repos === "object") {
|
|
81
|
+
for (const [key, relPath] of Object.entries(parsed.ecosystem_repos)) {
|
|
82
|
+
const keyRepoName = key.includes("/") ? key.split("/")[1] : key;
|
|
83
|
+
if (key === rawRepo || keyRepoName === repoName) {
|
|
84
|
+
return path.resolve(rootPath, relPath as string);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} catch {
|
|
89
|
+
// Fallback to directory inspection
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check sibling directory relative to cwd or rootPath
|
|
94
|
+
const candidateDirs = [
|
|
95
|
+
path.resolve(process.cwd(), "..", repoName),
|
|
96
|
+
path.resolve(rootPath, "..", repoName),
|
|
97
|
+
];
|
|
98
|
+
for (const cand of candidateDirs) {
|
|
99
|
+
if (syncFs.existsSync(cand)) {
|
|
100
|
+
return cand;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 4. Explicit cwd parameter
|
|
106
|
+
if (options.cwd && options.cwd.trim().length > 0) {
|
|
107
|
+
return path.resolve(options.cwd.trim());
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 5. Configured repoPath or process.cwd()
|
|
111
|
+
try {
|
|
112
|
+
if (config.repoPath && syncFs.existsSync(config.repoPath)) {
|
|
113
|
+
return config.repoPath;
|
|
114
|
+
}
|
|
115
|
+
} catch {
|
|
116
|
+
// Fall back to process.cwd()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return process.cwd();
|
|
120
|
+
}
|
|
121
|
+
|
|
40
122
|
function validatePrNumber(value: unknown): number {
|
|
41
123
|
const prNumber = Number(value);
|
|
42
124
|
|
|
@@ -47,20 +129,25 @@ function validatePrNumber(value: unknown): number {
|
|
|
47
129
|
return prNumber;
|
|
48
130
|
}
|
|
49
131
|
|
|
50
|
-
export async function createWorktree(branch: string, prNumber: number): Promise<string> {
|
|
132
|
+
export async function createWorktree(branch: string, prNumber: number, baseRepoPath?: string): Promise<string> {
|
|
51
133
|
const safePrNumber = validatePrNumber(prNumber);
|
|
52
134
|
|
|
53
135
|
const workspaceRoot = "/tmp/boomtick-worktrees";
|
|
54
136
|
// nosemgrep
|
|
55
137
|
const worktreePath = path.join(workspaceRoot, `boomtick-mcp-rescue-${safePrNumber}`);
|
|
138
|
+
const cwdOption = baseRepoPath ? { cwd: baseRepoPath } : undefined;
|
|
56
139
|
|
|
57
140
|
// Clean up if exists
|
|
58
141
|
try {
|
|
59
142
|
await fs.rm(worktreePath, { recursive: true, force: true });
|
|
60
|
-
await runCommand("git", ["worktree", "prune"]);
|
|
143
|
+
await runCommand("git", ["worktree", "prune"], cwdOption);
|
|
61
144
|
} catch (e) { /* ignore if no previous worktree */ }
|
|
62
145
|
|
|
63
|
-
const result = await runCommand(
|
|
146
|
+
const result = await runCommand(
|
|
147
|
+
"git",
|
|
148
|
+
["worktree", "add", "-b", `repair-pr-${prNumber}-${Date.now()}`, worktreePath, branch],
|
|
149
|
+
cwdOption
|
|
150
|
+
);
|
|
64
151
|
|
|
65
152
|
if (result.exitCode !== 0) {
|
|
66
153
|
throw new Error(`Failed to create worktree: ${result.stderr}`);
|
|
@@ -69,9 +156,10 @@ export async function createWorktree(branch: string, prNumber: number): Promise<
|
|
|
69
156
|
return worktreePath;
|
|
70
157
|
}
|
|
71
158
|
|
|
72
|
-
export async function removeWorktree(worktreePath: string): Promise<void> {
|
|
73
|
-
|
|
74
|
-
await runCommand("git", ["worktree", "
|
|
159
|
+
export async function removeWorktree(worktreePath: string, baseRepoPath?: string): Promise<void> {
|
|
160
|
+
const cwdOption = baseRepoPath ? { cwd: baseRepoPath } : undefined;
|
|
161
|
+
await runCommand("git", ["worktree", "remove", "--force", worktreePath], cwdOption);
|
|
162
|
+
await runCommand("git", ["worktree", "prune"], cwdOption);
|
|
75
163
|
}
|
|
76
164
|
|
|
77
165
|
export interface ChangedFile {
|
package/src/tools/contract.ts
CHANGED
|
@@ -1451,7 +1451,7 @@ export const CommitPatchResponseJsonSchema = {
|
|
|
1451
1451
|
"type": "object"
|
|
1452
1452
|
} as const;
|
|
1453
1453
|
|
|
1454
|
-
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") });
|
|
1454
|
+
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) });
|
|
1455
1455
|
|
|
1456
1456
|
export type CreateBranchInput = z.infer<typeof CreateBranchInputSchema>;
|
|
1457
1457
|
|
|
@@ -1467,6 +1467,45 @@ export const CreateBranchInputJsonSchema = {
|
|
|
1467
1467
|
"description": "Branch to branch off from",
|
|
1468
1468
|
"title": "Basebranch",
|
|
1469
1469
|
"type": "string"
|
|
1470
|
+
},
|
|
1471
|
+
"repo": {
|
|
1472
|
+
"anyOf": [
|
|
1473
|
+
{
|
|
1474
|
+
"type": "string"
|
|
1475
|
+
},
|
|
1476
|
+
{
|
|
1477
|
+
"type": "null"
|
|
1478
|
+
}
|
|
1479
|
+
],
|
|
1480
|
+
"default": null,
|
|
1481
|
+
"description": "Target repository override (e.g. org/repo or repo).",
|
|
1482
|
+
"title": "Repo"
|
|
1483
|
+
},
|
|
1484
|
+
"path": {
|
|
1485
|
+
"anyOf": [
|
|
1486
|
+
{
|
|
1487
|
+
"type": "string"
|
|
1488
|
+
},
|
|
1489
|
+
{
|
|
1490
|
+
"type": "null"
|
|
1491
|
+
}
|
|
1492
|
+
],
|
|
1493
|
+
"default": null,
|
|
1494
|
+
"description": "Local filesystem path to target repository root.",
|
|
1495
|
+
"title": "Path"
|
|
1496
|
+
},
|
|
1497
|
+
"repoPath": {
|
|
1498
|
+
"anyOf": [
|
|
1499
|
+
{
|
|
1500
|
+
"type": "string"
|
|
1501
|
+
},
|
|
1502
|
+
{
|
|
1503
|
+
"type": "null"
|
|
1504
|
+
}
|
|
1505
|
+
],
|
|
1506
|
+
"default": null,
|
|
1507
|
+
"description": "Alias for path to target repository root.",
|
|
1508
|
+
"title": "Repopath"
|
|
1470
1509
|
}
|
|
1471
1510
|
},
|
|
1472
1511
|
"required": [
|
|
@@ -1476,7 +1515,7 @@ export const CreateBranchInputJsonSchema = {
|
|
|
1476
1515
|
"type": "object"
|
|
1477
1516
|
} as const;
|
|
1478
1517
|
|
|
1479
|
-
export const CreateBranchResponseSchema = z.object({ "success": z.boolean().default(true), "branchName": z.string(), "baseBranch": z.union([z.string(), z.null()]).default(null) });
|
|
1518
|
+
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) });
|
|
1480
1519
|
|
|
1481
1520
|
export type CreateBranchResponse = z.infer<typeof CreateBranchResponseSchema>;
|
|
1482
1521
|
|
|
@@ -1502,6 +1541,30 @@ export const CreateBranchResponseJsonSchema = {
|
|
|
1502
1541
|
],
|
|
1503
1542
|
"default": null,
|
|
1504
1543
|
"title": "Basebranch"
|
|
1544
|
+
},
|
|
1545
|
+
"repo": {
|
|
1546
|
+
"anyOf": [
|
|
1547
|
+
{
|
|
1548
|
+
"type": "string"
|
|
1549
|
+
},
|
|
1550
|
+
{
|
|
1551
|
+
"type": "null"
|
|
1552
|
+
}
|
|
1553
|
+
],
|
|
1554
|
+
"default": null,
|
|
1555
|
+
"title": "Repo"
|
|
1556
|
+
},
|
|
1557
|
+
"path": {
|
|
1558
|
+
"anyOf": [
|
|
1559
|
+
{
|
|
1560
|
+
"type": "string"
|
|
1561
|
+
},
|
|
1562
|
+
{
|
|
1563
|
+
"type": "null"
|
|
1564
|
+
}
|
|
1565
|
+
],
|
|
1566
|
+
"default": null,
|
|
1567
|
+
"title": "Path"
|
|
1505
1568
|
}
|
|
1506
1569
|
},
|
|
1507
1570
|
"required": [
|
|
@@ -1788,7 +1851,7 @@ export const CreatePullRequestResponseJsonSchema = {
|
|
|
1788
1851
|
"type": "object"
|
|
1789
1852
|
} as const;
|
|
1790
1853
|
|
|
1791
|
-
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.") });
|
|
1854
|
+
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) });
|
|
1792
1855
|
|
|
1793
1856
|
export type CreateRepairBranchInput = z.infer<typeof CreateRepairBranchInputSchema>;
|
|
1794
1857
|
|
|
@@ -1816,6 +1879,45 @@ export const CreateRepairBranchInputJsonSchema = {
|
|
|
1816
1879
|
"description": "Safety gate: Must be true to perform branch creation and worktree setup.",
|
|
1817
1880
|
"title": "Writemode",
|
|
1818
1881
|
"type": "boolean"
|
|
1882
|
+
},
|
|
1883
|
+
"repo": {
|
|
1884
|
+
"anyOf": [
|
|
1885
|
+
{
|
|
1886
|
+
"type": "string"
|
|
1887
|
+
},
|
|
1888
|
+
{
|
|
1889
|
+
"type": "null"
|
|
1890
|
+
}
|
|
1891
|
+
],
|
|
1892
|
+
"default": null,
|
|
1893
|
+
"description": "Target repository override (e.g. org/repo or repo).",
|
|
1894
|
+
"title": "Repo"
|
|
1895
|
+
},
|
|
1896
|
+
"path": {
|
|
1897
|
+
"anyOf": [
|
|
1898
|
+
{
|
|
1899
|
+
"type": "string"
|
|
1900
|
+
},
|
|
1901
|
+
{
|
|
1902
|
+
"type": "null"
|
|
1903
|
+
}
|
|
1904
|
+
],
|
|
1905
|
+
"default": null,
|
|
1906
|
+
"description": "Local filesystem path to target repository root.",
|
|
1907
|
+
"title": "Path"
|
|
1908
|
+
},
|
|
1909
|
+
"repoPath": {
|
|
1910
|
+
"anyOf": [
|
|
1911
|
+
{
|
|
1912
|
+
"type": "string"
|
|
1913
|
+
},
|
|
1914
|
+
{
|
|
1915
|
+
"type": "null"
|
|
1916
|
+
}
|
|
1917
|
+
],
|
|
1918
|
+
"default": null,
|
|
1919
|
+
"description": "Alias for path to target repository root.",
|
|
1920
|
+
"title": "Repopath"
|
|
1819
1921
|
}
|
|
1820
1922
|
},
|
|
1821
1923
|
"required": [
|
|
@@ -1826,7 +1928,7 @@ export const CreateRepairBranchInputJsonSchema = {
|
|
|
1826
1928
|
"type": "object"
|
|
1827
1929
|
} as const;
|
|
1828
1930
|
|
|
1829
|
-
export const CreateRepairBranchResponseSchema = z.object({ "prNumber": z.number().int(), "originalBranch": z.string(), "repairBranch": z.string(), "baseBranch": z.string(), "worktreePath": z.string() });
|
|
1931
|
+
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) });
|
|
1830
1932
|
|
|
1831
1933
|
export type CreateRepairBranchResponse = z.infer<typeof CreateRepairBranchResponseSchema>;
|
|
1832
1934
|
|
|
@@ -1851,6 +1953,30 @@ export const CreateRepairBranchResponseJsonSchema = {
|
|
|
1851
1953
|
"worktreePath": {
|
|
1852
1954
|
"title": "Worktreepath",
|
|
1853
1955
|
"type": "string"
|
|
1956
|
+
},
|
|
1957
|
+
"repo": {
|
|
1958
|
+
"anyOf": [
|
|
1959
|
+
{
|
|
1960
|
+
"type": "string"
|
|
1961
|
+
},
|
|
1962
|
+
{
|
|
1963
|
+
"type": "null"
|
|
1964
|
+
}
|
|
1965
|
+
],
|
|
1966
|
+
"default": null,
|
|
1967
|
+
"title": "Repo"
|
|
1968
|
+
},
|
|
1969
|
+
"path": {
|
|
1970
|
+
"anyOf": [
|
|
1971
|
+
{
|
|
1972
|
+
"type": "string"
|
|
1973
|
+
},
|
|
1974
|
+
{
|
|
1975
|
+
"type": "null"
|
|
1976
|
+
}
|
|
1977
|
+
],
|
|
1978
|
+
"default": null,
|
|
1979
|
+
"title": "Path"
|
|
1854
1980
|
}
|
|
1855
1981
|
},
|
|
1856
1982
|
"required": [
|
|
@@ -1864,7 +1990,7 @@ export const CreateRepairBranchResponseJsonSchema = {
|
|
|
1864
1990
|
"type": "object"
|
|
1865
1991
|
} as const;
|
|
1866
1992
|
|
|
1867
|
-
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") });
|
|
1993
|
+
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) });
|
|
1868
1994
|
|
|
1869
1995
|
export type GetChangedFilesInput = z.infer<typeof GetChangedFilesInputSchema>;
|
|
1870
1996
|
|
|
@@ -1881,6 +2007,45 @@ export const GetChangedFilesInputJsonSchema = {
|
|
|
1881
2007
|
"description": "The head ref to compare to (default: 'HEAD').",
|
|
1882
2008
|
"title": "Head",
|
|
1883
2009
|
"type": "string"
|
|
2010
|
+
},
|
|
2011
|
+
"repo": {
|
|
2012
|
+
"anyOf": [
|
|
2013
|
+
{
|
|
2014
|
+
"type": "string"
|
|
2015
|
+
},
|
|
2016
|
+
{
|
|
2017
|
+
"type": "null"
|
|
2018
|
+
}
|
|
2019
|
+
],
|
|
2020
|
+
"default": null,
|
|
2021
|
+
"description": "Target GitHub repository override.",
|
|
2022
|
+
"title": "Repo"
|
|
2023
|
+
},
|
|
2024
|
+
"path": {
|
|
2025
|
+
"anyOf": [
|
|
2026
|
+
{
|
|
2027
|
+
"type": "string"
|
|
2028
|
+
},
|
|
2029
|
+
{
|
|
2030
|
+
"type": "null"
|
|
2031
|
+
}
|
|
2032
|
+
],
|
|
2033
|
+
"default": null,
|
|
2034
|
+
"description": "Local filesystem path to target repository root.",
|
|
2035
|
+
"title": "Path"
|
|
2036
|
+
},
|
|
2037
|
+
"repoPath": {
|
|
2038
|
+
"anyOf": [
|
|
2039
|
+
{
|
|
2040
|
+
"type": "string"
|
|
2041
|
+
},
|
|
2042
|
+
{
|
|
2043
|
+
"type": "null"
|
|
2044
|
+
}
|
|
2045
|
+
],
|
|
2046
|
+
"default": null,
|
|
2047
|
+
"description": "Alias for path to target repository root.",
|
|
2048
|
+
"title": "Repopath"
|
|
1884
2049
|
}
|
|
1885
2050
|
},
|
|
1886
2051
|
"title": "GetChangedFilesInput",
|
|
@@ -1960,27 +2125,77 @@ export const GetCommandSchemaResponseJsonSchema = {
|
|
|
1960
2125
|
"type": "object"
|
|
1961
2126
|
} as const;
|
|
1962
2127
|
|
|
1963
|
-
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") });
|
|
2128
|
+
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) });
|
|
1964
2129
|
|
|
1965
2130
|
export type GetMergeConflictFilesInput = z.infer<typeof GetMergeConflictFilesInputSchema>;
|
|
1966
2131
|
|
|
1967
2132
|
export const GetMergeConflictFilesInputJsonSchema = {
|
|
1968
2133
|
"properties": {
|
|
1969
2134
|
"prNumber": {
|
|
2135
|
+
"anyOf": [
|
|
2136
|
+
{
|
|
2137
|
+
"type": "integer"
|
|
2138
|
+
},
|
|
2139
|
+
{
|
|
2140
|
+
"type": "null"
|
|
2141
|
+
}
|
|
2142
|
+
],
|
|
2143
|
+
"default": null,
|
|
1970
2144
|
"description": "The number of the pull request to check for conflicts.",
|
|
1971
|
-
"title": "Prnumber"
|
|
1972
|
-
"type": "integer"
|
|
2145
|
+
"title": "Prnumber"
|
|
1973
2146
|
},
|
|
1974
2147
|
"baseBranch": {
|
|
1975
2148
|
"default": "main",
|
|
1976
2149
|
"description": "The base branch to check against (default: 'main').",
|
|
1977
2150
|
"title": "Basebranch",
|
|
1978
2151
|
"type": "string"
|
|
2152
|
+
},
|
|
2153
|
+
"headRef": {
|
|
2154
|
+
"default": "HEAD",
|
|
2155
|
+
"description": "The head ref or branch to check.",
|
|
2156
|
+
"title": "Headref",
|
|
2157
|
+
"type": "string"
|
|
2158
|
+
},
|
|
2159
|
+
"repo": {
|
|
2160
|
+
"anyOf": [
|
|
2161
|
+
{
|
|
2162
|
+
"type": "string"
|
|
2163
|
+
},
|
|
2164
|
+
{
|
|
2165
|
+
"type": "null"
|
|
2166
|
+
}
|
|
2167
|
+
],
|
|
2168
|
+
"default": null,
|
|
2169
|
+
"description": "Target GitHub repository override.",
|
|
2170
|
+
"title": "Repo"
|
|
2171
|
+
},
|
|
2172
|
+
"path": {
|
|
2173
|
+
"anyOf": [
|
|
2174
|
+
{
|
|
2175
|
+
"type": "string"
|
|
2176
|
+
},
|
|
2177
|
+
{
|
|
2178
|
+
"type": "null"
|
|
2179
|
+
}
|
|
2180
|
+
],
|
|
2181
|
+
"default": null,
|
|
2182
|
+
"description": "Local filesystem path to target repository root.",
|
|
2183
|
+
"title": "Path"
|
|
2184
|
+
},
|
|
2185
|
+
"repoPath": {
|
|
2186
|
+
"anyOf": [
|
|
2187
|
+
{
|
|
2188
|
+
"type": "string"
|
|
2189
|
+
},
|
|
2190
|
+
{
|
|
2191
|
+
"type": "null"
|
|
2192
|
+
}
|
|
2193
|
+
],
|
|
2194
|
+
"default": null,
|
|
2195
|
+
"description": "Alias for path to target repository root.",
|
|
2196
|
+
"title": "Repopath"
|
|
1979
2197
|
}
|
|
1980
2198
|
},
|
|
1981
|
-
"required": [
|
|
1982
|
-
"prNumber"
|
|
1983
|
-
],
|
|
1984
2199
|
"title": "GetMergeConflictFilesInput",
|
|
1985
2200
|
"type": "object"
|
|
1986
2201
|
} as const;
|
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { getMergeConflictFiles } from "../lib/git.js";
|
|
2
|
+
import { getMergeConflictFiles, resolveRepoPath } from "../lib/git.js";
|
|
3
3
|
|
|
4
4
|
export const GetMergeConflictFilesInputSchema = z.object({
|
|
5
5
|
prNumber: z.number().optional(),
|
|
6
6
|
baseBranch: z.string().optional().default("main"),
|
|
7
7
|
headRef: z.string().optional().default("HEAD"),
|
|
8
|
+
repo: z.string().optional().describe("Target GitHub repository override."),
|
|
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
|
|
|
10
13
|
export async function getMergeConflictFilesHandler(args: z.infer<typeof GetMergeConflictFilesInputSchema>) {
|
|
11
14
|
const baseBranch = args.baseBranch || "main";
|
|
12
15
|
const headRef = args.headRef || "HEAD";
|
|
13
|
-
const
|
|
16
|
+
const targetCwd = resolveRepoPath({
|
|
17
|
+
path: args.path,
|
|
18
|
+
repoPath: args.repoPath,
|
|
19
|
+
repo: args.repo,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const conflictFiles = await getMergeConflictFiles(baseBranch, headRef, targetCwd);
|
|
14
23
|
|
|
15
24
|
const hasConflicts = conflictFiles.length > 0;
|
|
16
25
|
|
|
@@ -21,13 +21,22 @@ describe("repo.create_branch", () => {
|
|
|
21
21
|
|
|
22
22
|
const result = await createBranchHandler(args);
|
|
23
23
|
|
|
24
|
-
expect(shell.runCommand).toHaveBeenCalledWith(
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
expect(shell.runCommand).toHaveBeenCalledWith(
|
|
25
|
+
"git",
|
|
26
|
+
["fetch", "origin", "main"],
|
|
27
|
+
{ cwd: expect.any(String) }
|
|
28
|
+
);
|
|
29
|
+
expect(shell.runCommand).toHaveBeenCalledWith(
|
|
30
|
+
"git",
|
|
31
|
+
["checkout", "-b", "new-feature", "origin/main"],
|
|
32
|
+
{ cwd: expect.any(String) }
|
|
33
|
+
);
|
|
34
|
+
expect(result).toMatchObject({
|
|
27
35
|
success: true,
|
|
28
36
|
branchName: "new-feature",
|
|
29
37
|
baseBranch: "main",
|
|
30
38
|
});
|
|
39
|
+
expect(result.path).toBeDefined();
|
|
31
40
|
});
|
|
32
41
|
|
|
33
42
|
it("should use default base branch 'main' if not provided", async () => {
|
|
@@ -39,8 +48,67 @@ describe("repo.create_branch", () => {
|
|
|
39
48
|
|
|
40
49
|
const result = await createBranchHandler(args);
|
|
41
50
|
|
|
42
|
-
expect(shell.runCommand).toHaveBeenCalledWith(
|
|
43
|
-
|
|
51
|
+
expect(shell.runCommand).toHaveBeenCalledWith(
|
|
52
|
+
"git",
|
|
53
|
+
["fetch", "origin", "main"],
|
|
54
|
+
{ cwd: expect.any(String) }
|
|
55
|
+
);
|
|
56
|
+
expect(shell.runCommand).toHaveBeenCalledWith(
|
|
57
|
+
"git",
|
|
58
|
+
["checkout", "-b", "another-feature", "origin/main"],
|
|
59
|
+
{ cwd: expect.any(String) }
|
|
60
|
+
);
|
|
61
|
+
expect(result.success).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should create branch in specified target workspace path", async () => {
|
|
65
|
+
const args: CreateBranchInput = {
|
|
66
|
+
branchName: "custom-path-feature",
|
|
67
|
+
baseBranch: "main",
|
|
68
|
+
path: "/home/ari/tech-dancer",
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
(shell.runCommand as any).mockResolvedValue({ exitCode: 0, stdout: "", stderr: "" });
|
|
72
|
+
|
|
73
|
+
const result = await createBranchHandler(args);
|
|
74
|
+
|
|
75
|
+
expect(shell.runCommand).toHaveBeenCalledWith(
|
|
76
|
+
"git",
|
|
77
|
+
["fetch", "origin", "main"],
|
|
78
|
+
{ cwd: "/home/ari/tech-dancer" }
|
|
79
|
+
);
|
|
80
|
+
expect(shell.runCommand).toHaveBeenCalledWith(
|
|
81
|
+
"git",
|
|
82
|
+
["checkout", "-b", "custom-path-feature", "origin/main"],
|
|
83
|
+
{ cwd: "/home/ari/tech-dancer" }
|
|
84
|
+
);
|
|
85
|
+
expect(result).toEqual({
|
|
86
|
+
success: true,
|
|
87
|
+
branchName: "custom-path-feature",
|
|
88
|
+
baseBranch: "main",
|
|
89
|
+
path: "/home/ari/tech-dancer",
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("should include repo field when repo override is specified", async () => {
|
|
94
|
+
const args: CreateBranchInput = {
|
|
95
|
+
branchName: "repo-feature",
|
|
96
|
+
baseBranch: "main",
|
|
97
|
+
repo: "arii/tech-dancer",
|
|
98
|
+
path: "/home/ari/tech-dancer",
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
(shell.runCommand as any).mockResolvedValue({ exitCode: 0, stdout: "", stderr: "" });
|
|
102
|
+
|
|
103
|
+
const result = await createBranchHandler(args);
|
|
104
|
+
|
|
105
|
+
expect(result).toEqual({
|
|
106
|
+
success: true,
|
|
107
|
+
branchName: "repo-feature",
|
|
108
|
+
baseBranch: "main",
|
|
109
|
+
path: "/home/ari/tech-dancer",
|
|
110
|
+
repo: "arii/tech-dancer",
|
|
111
|
+
});
|
|
44
112
|
});
|
|
45
113
|
|
|
46
114
|
it("should throw error if git command fails", async () => {
|
|
@@ -1,29 +1,55 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { runCommand } from "../lib/shell.js";
|
|
3
|
+
import { resolveRepoPath } from "../lib/git.js";
|
|
3
4
|
|
|
4
5
|
export const CreateBranchInputSchema = z.object({
|
|
5
6
|
branchName: z.string(),
|
|
6
7
|
baseBranch: z.string().optional().default("main"),
|
|
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."),
|
|
7
11
|
});
|
|
8
12
|
|
|
9
13
|
export type CreateBranchInput = z.input<typeof CreateBranchInputSchema>;
|
|
10
14
|
|
|
11
15
|
export async function createBranchHandler(args: CreateBranchInput) {
|
|
12
16
|
const params = CreateBranchInputSchema.parse(args);
|
|
17
|
+
const targetCwd = resolveRepoPath({
|
|
18
|
+
path: params.path,
|
|
19
|
+
repoPath: params.repoPath,
|
|
20
|
+
repo: params.repo,
|
|
21
|
+
});
|
|
13
22
|
|
|
14
23
|
// Ensure latest base branch
|
|
15
|
-
await runCommand("git", ["fetch", "origin", params.baseBranch]);
|
|
24
|
+
await runCommand("git", ["fetch", "origin", params.baseBranch], { cwd: targetCwd });
|
|
16
25
|
|
|
17
26
|
// Create branch from origin base branch
|
|
18
|
-
const result = await runCommand(
|
|
27
|
+
const result = await runCommand(
|
|
28
|
+
"git",
|
|
29
|
+
["checkout", "-b", params.branchName, `origin/${params.baseBranch}`],
|
|
30
|
+
{ cwd: targetCwd }
|
|
31
|
+
);
|
|
19
32
|
|
|
20
33
|
if (result.exitCode !== 0) {
|
|
21
34
|
throw new Error(`Failed to create branch ${params.branchName}: ${result.stderr}`);
|
|
22
35
|
}
|
|
23
36
|
|
|
24
|
-
|
|
37
|
+
const response: {
|
|
38
|
+
success: boolean;
|
|
39
|
+
branchName: string;
|
|
40
|
+
baseBranch: string;
|
|
41
|
+
path: string;
|
|
42
|
+
repo?: string;
|
|
43
|
+
} = {
|
|
25
44
|
success: true,
|
|
26
45
|
branchName: params.branchName,
|
|
27
|
-
baseBranch: params.baseBranch
|
|
46
|
+
baseBranch: params.baseBranch,
|
|
47
|
+
path: targetCwd,
|
|
28
48
|
};
|
|
49
|
+
|
|
50
|
+
if (params.repo) {
|
|
51
|
+
response.repo = params.repo;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return response;
|
|
29
55
|
}
|