sync-worktrees 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +106 -66
- package/dist/index.js.map +4 -4
- package/dist/mcp-server.js +123 -70
- package/dist/mcp-server.js.map +4 -4
- package/package.json +1 -1
package/dist/mcp-server.js
CHANGED
|
@@ -803,6 +803,43 @@ import * as fs4 from "fs/promises";
|
|
|
803
803
|
import * as path4 from "path";
|
|
804
804
|
import simpleGit3 from "simple-git";
|
|
805
805
|
|
|
806
|
+
// src/errors/index.ts
|
|
807
|
+
var SyncWorktreesError = class extends Error {
|
|
808
|
+
constructor(message, code, cause) {
|
|
809
|
+
super(message);
|
|
810
|
+
this.code = code;
|
|
811
|
+
this.cause = cause;
|
|
812
|
+
this.name = this.constructor.name;
|
|
813
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
814
|
+
if (cause && cause.stack) {
|
|
815
|
+
this.stack = `${this.stack}
|
|
816
|
+
Caused by: ${cause.stack}`;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
var GitError = class extends SyncWorktreesError {
|
|
821
|
+
constructor(message, code, cause) {
|
|
822
|
+
super(message, `GIT_${code}`, cause);
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
var GitOperationError = class extends GitError {
|
|
826
|
+
constructor(operation, details, cause) {
|
|
827
|
+
super(`Git operation '${operation}' failed: ${details}`, "OPERATION_FAILED", cause);
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
var WorktreeError = class extends SyncWorktreesError {
|
|
831
|
+
constructor(message, code, cause) {
|
|
832
|
+
super(message, `WORKTREE_${code}`, cause);
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
var WorktreeNotCleanError = class extends WorktreeError {
|
|
836
|
+
constructor(path10, reasons) {
|
|
837
|
+
super(`Worktree at '${path10}' is not clean: ${reasons.join(", ")}`, "NOT_CLEAN");
|
|
838
|
+
this.path = path10;
|
|
839
|
+
this.reasons = reasons;
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
|
|
806
843
|
// src/utils/git-url.ts
|
|
807
844
|
function extractRepoNameFromUrl(gitUrl) {
|
|
808
845
|
const url = gitUrl.trim();
|
|
@@ -1101,45 +1138,6 @@ var WorktreeMetadataService = class {
|
|
|
1101
1138
|
import * as fs3 from "fs/promises";
|
|
1102
1139
|
import * as path3 from "path";
|
|
1103
1140
|
import simpleGit2 from "simple-git";
|
|
1104
|
-
|
|
1105
|
-
// src/errors/index.ts
|
|
1106
|
-
var SyncWorktreesError = class extends Error {
|
|
1107
|
-
constructor(message, code, cause) {
|
|
1108
|
-
super(message);
|
|
1109
|
-
this.code = code;
|
|
1110
|
-
this.cause = cause;
|
|
1111
|
-
this.name = this.constructor.name;
|
|
1112
|
-
Object.setPrototypeOf(this, new.target.prototype);
|
|
1113
|
-
if (cause && cause.stack) {
|
|
1114
|
-
this.stack = `${this.stack}
|
|
1115
|
-
Caused by: ${cause.stack}`;
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
};
|
|
1119
|
-
var GitError = class extends SyncWorktreesError {
|
|
1120
|
-
constructor(message, code, cause) {
|
|
1121
|
-
super(message, `GIT_${code}`, cause);
|
|
1122
|
-
}
|
|
1123
|
-
};
|
|
1124
|
-
var GitOperationError = class extends GitError {
|
|
1125
|
-
constructor(operation, details, cause) {
|
|
1126
|
-
super(`Git operation '${operation}' failed: ${details}`, "OPERATION_FAILED", cause);
|
|
1127
|
-
}
|
|
1128
|
-
};
|
|
1129
|
-
var WorktreeError = class extends SyncWorktreesError {
|
|
1130
|
-
constructor(message, code, cause) {
|
|
1131
|
-
super(message, `WORKTREE_${code}`, cause);
|
|
1132
|
-
}
|
|
1133
|
-
};
|
|
1134
|
-
var WorktreeNotCleanError = class extends WorktreeError {
|
|
1135
|
-
constructor(path10, reasons) {
|
|
1136
|
-
super(`Worktree at '${path10}' is not clean: ${reasons.join(", ")}`, "NOT_CLEAN");
|
|
1137
|
-
this.path = path10;
|
|
1138
|
-
this.reasons = reasons;
|
|
1139
|
-
}
|
|
1140
|
-
};
|
|
1141
|
-
|
|
1142
|
-
// src/services/worktree-status.service.ts
|
|
1143
1141
|
var OPERATION_FILES = [
|
|
1144
1142
|
{ file: GIT_OPERATIONS.MERGE_HEAD, type: "merge" },
|
|
1145
1143
|
{ file: GIT_OPERATIONS.CHERRY_PICK_HEAD, type: "cherry-pick" },
|
|
@@ -1755,24 +1753,19 @@ var GitService = class {
|
|
|
1755
1753
|
} catch {
|
|
1756
1754
|
}
|
|
1757
1755
|
try {
|
|
1758
|
-
const
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1756
|
+
const { local: localBranchExists, remote: remoteBranchExists } = await this.branchExists(branchName);
|
|
1757
|
+
await this.runWorktreeAddByMatrix(
|
|
1758
|
+
bareGit,
|
|
1759
|
+
branchName,
|
|
1760
|
+
absoluteWorktreePath,
|
|
1761
|
+
localBranchExists,
|
|
1762
|
+
remoteBranchExists
|
|
1763
|
+
);
|
|
1764
|
+
if (localBranchExists && !remoteBranchExists) {
|
|
1765
|
+
this.logger.info(` - Created worktree for '${branchName}' (no remote yet \u2014 push to set upstream)`);
|
|
1764
1766
|
} else {
|
|
1765
|
-
|
|
1766
|
-
"worktree",
|
|
1767
|
-
"add",
|
|
1768
|
-
"--track",
|
|
1769
|
-
"-b",
|
|
1770
|
-
branchName,
|
|
1771
|
-
absoluteWorktreePath,
|
|
1772
|
-
`origin/${branchName}`
|
|
1773
|
-
]);
|
|
1767
|
+
this.logger.info(` - Created worktree for '${branchName}' with tracking to origin/${branchName}`);
|
|
1774
1768
|
}
|
|
1775
|
-
this.logger.info(` - Created worktree for '${branchName}' with tracking to origin/${branchName}`);
|
|
1776
1769
|
if (!this.isLfsSkipEnabled()) {
|
|
1777
1770
|
await this.verifyLfsFilesDownloaded(absoluteWorktreePath, branchName);
|
|
1778
1771
|
}
|
|
@@ -1788,6 +1781,9 @@ var GitService = class {
|
|
|
1788
1781
|
}
|
|
1789
1782
|
} catch (error) {
|
|
1790
1783
|
const errorMessage = getErrorMessage(error);
|
|
1784
|
+
if (error?.isUpstreamSetupFailure) {
|
|
1785
|
+
throw error;
|
|
1786
|
+
}
|
|
1791
1787
|
if (errorMessage.includes("Metadata creation failed")) {
|
|
1792
1788
|
throw error;
|
|
1793
1789
|
}
|
|
@@ -1805,15 +1801,14 @@ var GitService = class {
|
|
|
1805
1801
|
} catch {
|
|
1806
1802
|
}
|
|
1807
1803
|
try {
|
|
1808
|
-
await
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
"--track",
|
|
1812
|
-
"-b",
|
|
1804
|
+
const { local: localBranchExists, remote: remoteBranchExists } = await this.branchExists(branchName);
|
|
1805
|
+
await this.runWorktreeAddByMatrix(
|
|
1806
|
+
bareGit,
|
|
1813
1807
|
branchName,
|
|
1814
1808
|
absoluteWorktreePath,
|
|
1815
|
-
|
|
1816
|
-
|
|
1809
|
+
localBranchExists,
|
|
1810
|
+
remoteBranchExists
|
|
1811
|
+
);
|
|
1817
1812
|
this.logger.info(` - Created worktree for '${branchName}' after pruning`);
|
|
1818
1813
|
if (!this.isLfsSkipEnabled()) {
|
|
1819
1814
|
await this.verifyLfsFilesDownloaded(absoluteWorktreePath, branchName);
|
|
@@ -1882,6 +1877,43 @@ var GitService = class {
|
|
|
1882
1877
|
}
|
|
1883
1878
|
}
|
|
1884
1879
|
}
|
|
1880
|
+
async runWorktreeAddByMatrix(bareGit, branchName, absoluteWorktreePath, localExists, remoteExists) {
|
|
1881
|
+
if (localExists && remoteExists) {
|
|
1882
|
+
await bareGit.raw(["worktree", "add", absoluteWorktreePath, branchName]);
|
|
1883
|
+
try {
|
|
1884
|
+
const worktreeGit = this.getCachedGit(absoluteWorktreePath, this.isLfsSkipEnabled());
|
|
1885
|
+
await worktreeGit.branch(["--set-upstream-to", `origin/${branchName}`, branchName]);
|
|
1886
|
+
} catch (error) {
|
|
1887
|
+
let rollbackFailed = false;
|
|
1888
|
+
try {
|
|
1889
|
+
await bareGit.raw(["worktree", "remove", "--force", absoluteWorktreePath]);
|
|
1890
|
+
} catch (rollbackError) {
|
|
1891
|
+
rollbackFailed = true;
|
|
1892
|
+
this.logger.warn(
|
|
1893
|
+
` - Rollback failed for '${branchName}' at '${absoluteWorktreePath}' after upstream setup error: ${getErrorMessage(rollbackError)}`
|
|
1894
|
+
);
|
|
1895
|
+
}
|
|
1896
|
+
const detail = getErrorMessage(error);
|
|
1897
|
+
const suffix = rollbackFailed ? " (rollback failed; partial worktree may remain)" : "";
|
|
1898
|
+
const wrapped = new Error(`Failed to set upstream for '${branchName}': ${detail}${suffix}`);
|
|
1899
|
+
wrapped.isUpstreamSetupFailure = true;
|
|
1900
|
+
throw wrapped;
|
|
1901
|
+
}
|
|
1902
|
+
return;
|
|
1903
|
+
}
|
|
1904
|
+
if (localExists) {
|
|
1905
|
+
await bareGit.raw(["worktree", "add", absoluteWorktreePath, branchName]);
|
|
1906
|
+
return;
|
|
1907
|
+
}
|
|
1908
|
+
if (remoteExists) {
|
|
1909
|
+
await bareGit.raw(["worktree", "add", "--track", "-b", branchName, absoluteWorktreePath, `origin/${branchName}`]);
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
throw new WorktreeError(
|
|
1913
|
+
`Branch '${branchName}' does not exist locally or on origin; create it first`,
|
|
1914
|
+
"BRANCH_NOT_FOUND"
|
|
1915
|
+
);
|
|
1916
|
+
}
|
|
1885
1917
|
async removeWorktree(worktreePath) {
|
|
1886
1918
|
const bareGit = this.getCachedGit(this.bareRepoPath);
|
|
1887
1919
|
await bareGit.raw(["worktree", "remove", worktreePath, "--force"]);
|
|
@@ -2075,10 +2107,18 @@ var GitService = class {
|
|
|
2075
2107
|
}
|
|
2076
2108
|
async branchExists(branchName) {
|
|
2077
2109
|
const bareGit = this.getCachedGit(this.bareRepoPath);
|
|
2078
|
-
const
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2110
|
+
const checkRef = async (ref) => {
|
|
2111
|
+
try {
|
|
2112
|
+
await bareGit.raw(["show-ref", "--verify", "--quiet", ref]);
|
|
2113
|
+
return true;
|
|
2114
|
+
} catch {
|
|
2115
|
+
return false;
|
|
2116
|
+
}
|
|
2117
|
+
};
|
|
2118
|
+
const [local, remote] = await Promise.all([
|
|
2119
|
+
checkRef(`${GIT_CONSTANTS.REFS.HEADS}${branchName}`),
|
|
2120
|
+
checkRef(`${GIT_CONSTANTS.REFS.REMOTES}/${branchName}`)
|
|
2121
|
+
]);
|
|
2082
2122
|
return { local, remote };
|
|
2083
2123
|
}
|
|
2084
2124
|
async getLocalBranches() {
|
|
@@ -3730,14 +3770,26 @@ function attachProgressReporter(service, extra) {
|
|
|
3730
3770
|
var REPO_NAME_DESCRIBE = "Repository name from loaded config. If omitted, uses the current repository set via set_current_repository or the only loaded repo.";
|
|
3731
3771
|
var PATH_DESCRIBE_SUFFIX = "Absolute path preferred; relative paths resolve from the server's CWD.";
|
|
3732
3772
|
var SERVER_INSTRUCTIONS = "Before running git worktree operations, call `detect_context` to learn the current repo, current branch, sibling repositories under the workspace root, and which capabilities are available. It walks up to auto-discover sync-worktrees.config.{js,mjs,cjs,ts}, lists sibling worktrees, and reports per-capability {available, reason} so you can tell which tool is gated and why.";
|
|
3733
|
-
function
|
|
3773
|
+
function buildInstructions(snapshot) {
|
|
3774
|
+
const d = snapshot?.discovered;
|
|
3775
|
+
if (!d || !d.isWorktree || d.kind !== "managed") return SERVER_INSTRUCTIONS;
|
|
3776
|
+
const lines = ["Connect-time context (call `detect_context` for live state):"];
|
|
3777
|
+
if (d.kind) lines.push(`- kind: ${d.kind}`);
|
|
3778
|
+
if (d.currentWorktreePath) lines.push(`- currentWorktreePath: ${d.currentWorktreePath}`);
|
|
3779
|
+
if (d.currentBranch) lines.push(`- currentBranch: ${d.currentBranch}`);
|
|
3780
|
+
if (d.configPath) lines.push(`- configPath: ${d.configPath}`);
|
|
3781
|
+
return `${SERVER_INSTRUCTIONS}
|
|
3782
|
+
|
|
3783
|
+
${lines.join("\n")}`;
|
|
3784
|
+
}
|
|
3785
|
+
function createServer(context, snapshot) {
|
|
3734
3786
|
const server = new McpServer(
|
|
3735
3787
|
{
|
|
3736
3788
|
name: "sync-worktrees",
|
|
3737
3789
|
version: "1.0.0"
|
|
3738
3790
|
},
|
|
3739
3791
|
{
|
|
3740
|
-
instructions:
|
|
3792
|
+
instructions: buildInstructions(snapshot)
|
|
3741
3793
|
}
|
|
3742
3794
|
);
|
|
3743
3795
|
server.registerResource(
|
|
@@ -3970,8 +4022,9 @@ async function main() {
|
|
|
3970
4022
|
`);
|
|
3971
4023
|
}
|
|
3972
4024
|
}
|
|
4025
|
+
let discovered = null;
|
|
3973
4026
|
try {
|
|
3974
|
-
|
|
4027
|
+
discovered = await context.detectFromPath(process.cwd());
|
|
3975
4028
|
if (discovered.isWorktree) {
|
|
3976
4029
|
process.stderr.write(
|
|
3977
4030
|
`[sync-worktrees-mcp] Auto-detected ${discovered.kind} worktree at ${discovered.currentWorktreePath} (branch: ${discovered.currentBranch})
|
|
@@ -3982,7 +4035,7 @@ async function main() {
|
|
|
3982
4035
|
process.stderr.write(`[sync-worktrees-mcp] Auto-detect failed: ${err.message}
|
|
3983
4036
|
`);
|
|
3984
4037
|
}
|
|
3985
|
-
const server = createServer(context);
|
|
4038
|
+
const server = createServer(context, { discovered });
|
|
3986
4039
|
const transport = new StdioServerTransport();
|
|
3987
4040
|
await server.connect(transport);
|
|
3988
4041
|
}
|