sync-worktrees 4.1.0 → 4.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.
@@ -6,7 +6,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
6
6
  // src/mcp/context.ts
7
7
  import * as fs10 from "fs/promises";
8
8
  import * as path14 from "path";
9
- import pLimit2 from "p-limit";
9
+ import pLimit3 from "p-limit";
10
10
  import simpleGit6 from "simple-git";
11
11
 
12
12
  // src/constants.ts
@@ -127,6 +127,8 @@ var SyncWorktreesError = class extends Error {
127
127
  Caused by: ${cause.stack}`;
128
128
  }
129
129
  }
130
+ code;
131
+ cause;
130
132
  };
131
133
  var GitError = class extends SyncWorktreesError {
132
134
  constructor(message, code, cause) {
@@ -149,6 +151,8 @@ var WorktreeNotCleanError = class extends WorktreeError {
149
151
  this.path = path16;
150
152
  this.reasons = reasons;
151
153
  }
154
+ path;
155
+ reasons;
152
156
  };
153
157
  var ConfigError = class extends SyncWorktreesError {
154
158
  constructor(message, code, cause) {
@@ -161,12 +165,15 @@ var ConfigValidationError = class extends ConfigError {
161
165
  this.field = field;
162
166
  this.reason = reason;
163
167
  }
168
+ field;
169
+ reason;
164
170
  };
165
171
  var ConfigFileNotFoundError = class extends ConfigError {
166
172
  constructor(configPath) {
167
173
  super(`Config file not found: ${configPath}`, "FILE_NOT_FOUND");
168
174
  this.configPath = configPath;
169
175
  }
176
+ configPath;
170
177
  };
171
178
 
172
179
  // src/utils/branch-filter.ts
@@ -886,6 +893,9 @@ function defaultConsoleOutput(msg, level) {
886
893
  else console.log(msg);
887
894
  }
888
895
 
896
+ // src/services/worktree-sync.service.ts
897
+ import pLimit2 from "p-limit";
898
+
889
899
  // src/utils/lfs-error.ts
890
900
  function getErrorMessage(error) {
891
901
  if (error instanceof Error) {
@@ -1316,6 +1326,7 @@ var SyncOutcomeAccumulator = class {
1316
1326
  constructor(options) {
1317
1327
  this.options = options;
1318
1328
  }
1329
+ options;
1319
1330
  counts = cloneCounts(EMPTY_COUNTS);
1320
1331
  actions = [];
1321
1332
  add(action) {
@@ -1402,6 +1413,9 @@ var CloneSyncService = class {
1402
1413
  this.progressEmitter = options.progressEmitter;
1403
1414
  this.onSkip = options.onSkip;
1404
1415
  }
1416
+ config;
1417
+ gitService;
1418
+ logger;
1405
1419
  initialized = false;
1406
1420
  resolvedBranch = null;
1407
1421
  branchCreatedActions;
@@ -2408,6 +2422,7 @@ var WorktreeStatusService = class {
2408
2422
  this.config = config;
2409
2423
  this.logger = logger ?? Logger.createDefault();
2410
2424
  }
2425
+ config;
2411
2426
  gitInstances = /* @__PURE__ */ new Map();
2412
2427
  logger;
2413
2428
  async checkWorktreeStatus(worktreePath) {
@@ -2766,6 +2781,8 @@ var GitService = class {
2766
2781
  this.statusService = new WorktreeStatusService({ skipLfs: this.config.skipLfs }, this.logger);
2767
2782
  this.sparseCheckoutService = new SparseCheckoutService(this.logger);
2768
2783
  }
2784
+ config;
2785
+ progressEmitter;
2769
2786
  git = null;
2770
2787
  bareRepoPath;
2771
2788
  mainWorktreePath;
@@ -3660,6 +3677,9 @@ var RepoOperationLock = class {
3660
3677
  this.gitService = gitService;
3661
3678
  this.logger = logger;
3662
3679
  }
3680
+ config;
3681
+ gitService;
3682
+ logger;
3663
3683
  updateLogger(logger) {
3664
3684
  this.logger = logger;
3665
3685
  }
@@ -3721,6 +3741,9 @@ var SyncRetryPolicy = class {
3721
3741
  this.gitService = gitService;
3722
3742
  this.logger = logger;
3723
3743
  }
3744
+ config;
3745
+ gitService;
3746
+ logger;
3724
3747
  updateLogger(logger) {
3725
3748
  this.logger = logger;
3726
3749
  }
@@ -3941,6 +3964,10 @@ var WorktreeModeSyncRunner = class {
3941
3964
  this.logger = logger;
3942
3965
  this.progressEmitter = progressEmitter;
3943
3966
  }
3967
+ config;
3968
+ gitService;
3969
+ logger;
3970
+ progressEmitter;
3944
3971
  pathResolution = new PathResolutionService();
3945
3972
  updateLogger(logger) {
3946
3973
  this.logger = logger;
@@ -4639,10 +4666,15 @@ var WorktreeSyncService = class {
4639
4666
  });
4640
4667
  }
4641
4668
  }
4669
+ config;
4642
4670
  gitService;
4643
4671
  cloneSyncService = null;
4644
4672
  logger;
4645
- syncInProgress = false;
4673
+ // In-process FIFO serializer for all bare-repo-mutating operations (sync, init,
4674
+ // interactive create). One per repo. wait:true callers queue behind an in-flight op;
4675
+ // wait:false callers fail fast. The cross-process file lock (RepoOperationLock) is
4676
+ // acquired inside the mutex body for multi-process safety.
4677
+ repoMutex = pLimit2(1);
4646
4678
  progressEmitter = new ProgressEmitter();
4647
4679
  repoOperationLock;
4648
4680
  retryPolicy;
@@ -4694,7 +4726,7 @@ var WorktreeSyncService = class {
4694
4726
  return this.gitService.isInitialized();
4695
4727
  }
4696
4728
  isSyncInProgress() {
4697
- return this.syncInProgress;
4729
+ return this.repoMutex.activeCount + this.repoMutex.pendingCount > 0;
4698
4730
  }
4699
4731
  getGitService() {
4700
4732
  return this.gitService;
@@ -4710,34 +4742,31 @@ var WorktreeSyncService = class {
4710
4742
  onProgress(listener) {
4711
4743
  return this.progressEmitter.onProgress(listener);
4712
4744
  }
4713
- async runExclusiveRepoOperation(operation) {
4714
- if (this.syncInProgress) {
4745
+ async runExclusiveRepoOperation(operation, options = {}) {
4746
+ if (!options.wait && this.repoMutex.activeCount + this.repoMutex.pendingCount > 0) {
4715
4747
  this.logger.warn("\u26A0\uFE0F Another repository operation is already in progress, skipping...");
4716
4748
  return { started: false, reason: "in_progress" };
4717
4749
  }
4718
- this.syncInProgress = true;
4719
- let release;
4720
- try {
4721
- release = await this.repoOperationLock.acquire();
4722
- } catch (error) {
4723
- this.syncInProgress = false;
4724
- throw error;
4725
- }
4726
- if (release === null) {
4727
- this.syncInProgress = false;
4728
- this.logger.warn("\u26A0\uFE0F Another process holds the sync lock for this repo, skipping...");
4729
- return { started: false, reason: "locked" };
4730
- }
4731
- try {
4732
- return { started: true, value: await operation() };
4733
- } finally {
4750
+ return this.repoMutex(async () => {
4751
+ const release = await this.repoOperationLock.acquire();
4752
+ if (release === null) {
4753
+ this.logger.warn("\u26A0\uFE0F Another process holds the sync lock for this repo, skipping...");
4754
+ return { started: false, reason: "locked" };
4755
+ }
4734
4756
  try {
4735
- await release();
4736
- } catch (releaseError) {
4737
- this.logger.warn(`Failed to release sync lock: ${getErrorMessage(releaseError)}`);
4757
+ return { started: true, value: await operation() };
4758
+ } finally {
4759
+ try {
4760
+ await release();
4761
+ } catch (releaseError) {
4762
+ this.logger.warn(`Failed to release sync lock: ${getErrorMessage(releaseError)}`);
4763
+ }
4738
4764
  }
4739
- this.syncInProgress = false;
4740
- }
4765
+ });
4766
+ }
4767
+ // Interactive variant: queues behind any in-flight sync/op instead of failing fast.
4768
+ async runQueuedRepoOperation(operation) {
4769
+ return this.runExclusiveRepoOperation(operation, { wait: true });
4741
4770
  }
4742
4771
  emitProgress(event) {
4743
4772
  this.progressEmitter.emit(event);
@@ -5340,7 +5369,7 @@ var RepositoryContext = class {
5340
5369
  if (!options.detailed) {
5341
5370
  return entries.map(buildLean);
5342
5371
  }
5343
- const limit = pLimit2(DEFAULT_CONFIG.PARALLELISM.MAX_STATUS_CHECKS);
5372
+ const limit = pLimit3(DEFAULT_CONFIG.PARALLELISM.MAX_STATUS_CHECKS);
5344
5373
  return Promise.all(
5345
5374
  entries.map(
5346
5375
  (entry) => limit(async () => {
@@ -5569,7 +5598,7 @@ import { z } from "zod";
5569
5598
 
5570
5599
  // src/mcp/handlers.ts
5571
5600
  import * as path15 from "path";
5572
- import pLimit3 from "p-limit";
5601
+ import pLimit4 from "p-limit";
5573
5602
 
5574
5603
  // src/utils/disk-space.ts
5575
5604
  import fastFolderSize from "fast-folder-size";
@@ -5816,7 +5845,7 @@ async function handleDetectContext(ctx, params, _extra) {
5816
5845
  return formatToolResponse(response);
5817
5846
  }
5818
5847
  const statusService = new WorktreeStatusService();
5819
- const statusLimit = pLimit3(DEFAULT_CONFIG.PARALLELISM.MAX_STATUS_CHECKS);
5848
+ const statusLimit = pLimit4(DEFAULT_CONFIG.PARALLELISM.MAX_STATUS_CHECKS);
5820
5849
  const enriched = await enrichDetectedWorktrees(response.allWorktrees, statusService, statusLimit);
5821
5850
  let allWorktreesByRepo = response.allWorktreesByRepo;
5822
5851
  if (allWorktreesByRepo) {
@@ -5852,8 +5881,8 @@ async function enrichDetectedWorktrees(worktrees, statusService, limit) {
5852
5881
  async function handleListWorktrees(ctx, params, _extra) {
5853
5882
  const configuredRepoNames = params.repoName ? [] : ctx.getConfiguredRepositoryNames();
5854
5883
  if (configuredRepoNames.length > 0) {
5855
- const limit = pLimit3(DEFAULT_CONFIG.PARALLELISM.MAX_REPOSITORIES);
5856
- const statusLimit = pLimit3(DEFAULT_CONFIG.PARALLELISM.MAX_STATUS_CHECKS);
5884
+ const limit = pLimit4(DEFAULT_CONFIG.PARALLELISM.MAX_REPOSITORIES);
5885
+ const statusLimit = pLimit4(DEFAULT_CONFIG.PARALLELISM.MAX_STATUS_CHECKS);
5857
5886
  const repositories = await Promise.all(
5858
5887
  configuredRepoNames.map(
5859
5888
  (repoName) => limit(async () => {
@@ -5881,7 +5910,7 @@ async function handleListWorktrees(ctx, params, _extra) {
5881
5910
  const results = await listWorktreesForRepo(ctx, params.repoName, params.includeSize);
5882
5911
  return formatToolResponse({ worktrees: results });
5883
5912
  }
5884
- async function listWorktreesForRepo(ctx, repoName, includeSize, limit = pLimit3(DEFAULT_CONFIG.PARALLELISM.MAX_STATUS_CHECKS)) {
5913
+ async function listWorktreesForRepo(ctx, repoName, includeSize, limit = pLimit4(DEFAULT_CONFIG.PARALLELISM.MAX_STATUS_CHECKS)) {
5885
5914
  const { discovered, service, git } = await getReadyService(ctx, repoName, {
5886
5915
  capability: "listWorktrees",
5887
5916
  toolName: "list_worktrees"