borgmcp 2.16.1 → 3.0.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 (135) hide show
  1. package/README.md +5 -3
  2. package/dist/assimilate-cmd.js +43 -43
  3. package/dist/assimilate-cmd.js.map +1 -1
  4. package/dist/assimilate-deps.js +2 -2
  5. package/dist/assimilate-deps.js.map +1 -1
  6. package/dist/assimilate-guard.js +4 -4
  7. package/dist/assimilate-guard.js.map +1 -1
  8. package/dist/assimilate-welcome.js +2 -2
  9. package/dist/assimilate-welcome.js.map +1 -1
  10. package/dist/bare-launch-menu.d.ts +5 -5
  11. package/dist/bare-launch-menu.d.ts.map +1 -1
  12. package/dist/bare-launch-menu.js +2 -2
  13. package/dist/bare-launch-menu.js.map +1 -1
  14. package/dist/claude.d.ts +0 -2
  15. package/dist/claude.d.ts.map +1 -1
  16. package/dist/claude.js +5 -18
  17. package/dist/claude.js.map +1 -1
  18. package/dist/cleanup-cmd.d.ts +1 -1
  19. package/dist/cleanup-cmd.d.ts.map +1 -1
  20. package/dist/cleanup-cmd.js +3 -3
  21. package/dist/cleanup-cmd.js.map +1 -1
  22. package/dist/cli-help.d.ts +1 -2
  23. package/dist/cli-help.d.ts.map +1 -1
  24. package/dist/cli-help.js +15 -24
  25. package/dist/cli-help.js.map +1 -1
  26. package/dist/cli-platform.d.ts +4 -0
  27. package/dist/cli-platform.d.ts.map +1 -1
  28. package/dist/cli-platform.js +29 -10
  29. package/dist/cli-platform.js.map +1 -1
  30. package/dist/cubes.d.ts.map +1 -1
  31. package/dist/cubes.js +30 -12
  32. package/dist/cubes.js.map +1 -1
  33. package/dist/docs-sections.js +1 -1
  34. package/dist/docs-sections.js.map +1 -1
  35. package/dist/drone-lifecycle.d.ts.map +1 -1
  36. package/dist/drone-lifecycle.js +3 -3
  37. package/dist/drone-lifecycle.js.map +1 -1
  38. package/dist/drone-management.js +1 -1
  39. package/dist/drone-management.js.map +1 -1
  40. package/dist/evict-drone.js +2 -2
  41. package/dist/evict-drone.js.map +1 -1
  42. package/dist/first-run-server.d.ts.map +1 -1
  43. package/dist/first-run-server.js +4 -1
  44. package/dist/first-run-server.js.map +1 -1
  45. package/dist/foreign-path-reminder.js +1 -1
  46. package/dist/foreign-path-reminder.js.map +1 -1
  47. package/dist/inbox-monitor.js +2 -2
  48. package/dist/inbox-monitor.js.map +1 -1
  49. package/dist/index.js +1 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/launch-all-cmd.js +12 -12
  52. package/dist/launch-all-cmd.js.map +1 -1
  53. package/dist/launch-all-discovery.js +1 -1
  54. package/dist/launch-all-discovery.js.map +1 -1
  55. package/dist/lifecycle-log-guard.d.ts.map +1 -1
  56. package/dist/lifecycle-log-guard.js +12 -3
  57. package/dist/lifecycle-log-guard.js.map +1 -1
  58. package/dist/opencode-seat-identity.js +5 -5
  59. package/dist/opencode-seat-identity.js.map +1 -1
  60. package/dist/regen-format.d.ts.map +1 -1
  61. package/dist/regen-format.js +2 -1
  62. package/dist/regen-format.js.map +1 -1
  63. package/dist/remote-client.d.ts.map +1 -1
  64. package/dist/remote-client.js +10 -5
  65. package/dist/remote-client.js.map +1 -1
  66. package/dist/repository-cube-init.js +1 -1
  67. package/dist/repository-cube-init.js.map +1 -1
  68. package/dist/reset-local-seat-cmd.d.ts +2 -2
  69. package/dist/reset-local-seat-cmd.d.ts.map +1 -1
  70. package/dist/reset-local-seat-cmd.js +17 -17
  71. package/dist/reset-local-seat-cmd.js.map +1 -1
  72. package/dist/seat-reattach-guard.js +1 -1
  73. package/dist/seat-reattach-guard.js.map +1 -1
  74. package/dist/seat-store.d.ts +1 -1
  75. package/dist/seat-store.js +6 -6
  76. package/dist/seat-store.js.map +1 -1
  77. package/dist/seats.js +1 -1
  78. package/dist/seats.js.map +1 -1
  79. package/dist/server-handshake.js +1 -1
  80. package/dist/server-handshake.js.map +1 -1
  81. package/dist/stream-status.js +3 -3
  82. package/dist/stream-status.js.map +1 -1
  83. package/dist/tool-manifest.js +1 -1
  84. package/dist/tool-manifest.js.map +1 -1
  85. package/dist/unknown-subcommand.d.ts +1 -1
  86. package/dist/unknown-subcommand.d.ts.map +1 -1
  87. package/dist/unknown-subcommand.js +1 -2
  88. package/dist/unknown-subcommand.js.map +1 -1
  89. package/dist/worktree-lifecycle.d.ts +0 -16
  90. package/dist/worktree-lifecycle.d.ts.map +1 -1
  91. package/dist/worktree-lifecycle.js +0 -38
  92. package/dist/worktree-lifecycle.js.map +1 -1
  93. package/docs/EXTRACTION_PROVENANCE.md +5 -3
  94. package/docs/LOCAL_SERVER.md +2 -2
  95. package/docs/SEAT_LIFECYCLE.md +19 -19
  96. package/package.json +1 -1
  97. package/src/assimilate-cmd.ts +43 -43
  98. package/src/assimilate-deps.ts +2 -2
  99. package/src/assimilate-guard.ts +4 -4
  100. package/src/assimilate-welcome.ts +2 -2
  101. package/src/bare-launch-menu.ts +6 -6
  102. package/src/claude.ts +8 -21
  103. package/src/cleanup-cmd.ts +4 -4
  104. package/src/cli-help.ts +15 -27
  105. package/src/cli-platform.ts +42 -10
  106. package/src/cubes.ts +29 -14
  107. package/src/docs-sections.ts +1 -1
  108. package/src/drone-lifecycle.ts +3 -3
  109. package/src/drone-management.ts +1 -1
  110. package/src/evict-drone.ts +2 -2
  111. package/src/first-run-server.ts +4 -1
  112. package/src/foreign-path-reminder.ts +1 -1
  113. package/src/inbox-monitor.ts +2 -2
  114. package/src/index.ts +1 -1
  115. package/src/launch-all-cmd.ts +12 -12
  116. package/src/launch-all-discovery.ts +1 -1
  117. package/src/lifecycle-log-guard.ts +15 -3
  118. package/src/opencode-seat-identity.ts +5 -5
  119. package/src/regen-format.ts +2 -1
  120. package/src/remote-client.ts +12 -6
  121. package/src/repository-cube-init.ts +1 -1
  122. package/src/reset-local-seat-cmd.ts +17 -17
  123. package/src/seat-reattach-guard.ts +1 -1
  124. package/src/seat-store.ts +6 -6
  125. package/src/seats.ts +1 -1
  126. package/src/server-handshake.ts +1 -1
  127. package/src/stream-status.ts +3 -3
  128. package/src/tool-manifest.ts +1 -1
  129. package/src/unknown-subcommand.ts +1 -2
  130. package/src/worktree-lifecycle.ts +0 -45
  131. package/dist/sync.d.ts +0 -129
  132. package/dist/sync.d.ts.map +0 -1
  133. package/dist/sync.js +0 -286
  134. package/dist/sync.js.map +0 -1
  135. package/src/sync.ts +0 -363
@@ -95,16 +95,22 @@ export const LOCAL_SERVER_REQUEST_TIMEOUT_MS = 5_000;
95
95
  const LOCAL_SERVER_RESPONSE_LIMIT_MESSAGE =
96
96
  'Local Borg server response exceeded the response limit';
97
97
 
98
+ export const SERVER_ADVISORY_MAX_CHARS = 512;
99
+ // Keep enough source headroom to remove control sequences that cross the
100
+ // visible 512-character boundary without letting regex work scale with the
101
+ // 32 MiB response-body limit.
102
+ const SERVER_MESSAGE_SANITIZE_MAX_CHARS = SERVER_ADVISORY_MAX_CHARS * 8;
103
+
98
104
  function sanitizeServerMessage(message: string): string {
99
- return message
100
- .replace(/\u001b\][^\u0007]*(?:\u0007|\u001b\\)/g, '')
101
- .replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, '')
105
+ const bounded = message.slice(0, SERVER_MESSAGE_SANITIZE_MAX_CHARS);
106
+
107
+ return bounded
108
+ .replace(/\u001b\][^\u0007]*(?:\u0007|\u001b\\|$)/g, '')
109
+ .replace(/\u001b\[[0-?]*[ -/]*(?:[@-~]|$)/g, '')
102
110
  .replace(/[\u0000-\u001f\u007f-\u009f]/g, '')
103
111
  .replace(/\\u(?:000[0-9a-f]|001[0-9a-f]|007f|008[0-9a-f]|009[0-9a-f])/gi, '');
104
112
  }
105
113
 
106
- export const SERVER_ADVISORY_MAX_CHARS = 512;
107
-
108
114
  export function sanitizeServerAdvisory(value: unknown): string | undefined {
109
115
  if (typeof value !== 'string') return undefined;
110
116
  const sanitized = sanitizeServerMessage(value).trim();
@@ -474,7 +480,7 @@ async function localCubeComposition(active: ActiveCube): Promise<{
474
480
  const drones = dronePayload.drones.map(withValidatedRuntimeMetadata);
475
481
  const drone = drones.find((candidate) => candidate.id === active.droneId);
476
482
  const role = rolePayload.roles.find((candidate) => candidate.id === drone?.role_id);
477
- if (!drone || !role) throw new Error('Local Borg server no longer recognizes this drone seat');
483
+ if (!drone || !role) throw new Error('Local Borg server no longer recognizes this drone');
478
484
  return {
479
485
  cube: cubePayload.cube,
480
486
  roles: rolePayload.roles,
@@ -169,7 +169,7 @@ function renderResult(
169
169
  `Next: borg assimilate --host ${shellEscape(input.serverOrigin)}`,
170
170
  );
171
171
  } else {
172
- lines.push('Continuing with role and seat setup…');
172
+ lines.push('Continuing with role and connection setup…');
173
173
  }
174
174
  resultWriter(`${lines.join('\n')}\n`);
175
175
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `borg reset-local-seat [--host <host>] [--yes]` — the dedicated LOCAL/OFFLINE
2
+ * `borg reset-local-connection [--host <host>] [--yes]` — the dedicated LOCAL/OFFLINE
3
3
  * seat reset from the ratified client-seat-reset-state-model (Option W).
4
4
  *
5
5
  * Attach is PURE DIAGNOSIS on a pin-matched SESSION_REJECTED (it mutates
@@ -95,7 +95,7 @@ export async function runResetLocalSeat(
95
95
  const snapshot = await deps.snapshotLocalSeat();
96
96
  if (!snapshot) {
97
97
  deps.stdout(
98
- `No saved local seat was found for this worktree (${worktree}); nothing to reset.\n`,
98
+ `No saved connection was found for this worktree (${worktree}); nothing to reset.\n`,
99
99
  );
100
100
  return 0;
101
101
  }
@@ -105,9 +105,9 @@ export async function runResetLocalSeat(
105
105
  // operator asked to reset.
106
106
  if (requestedHost !== undefined && requestedHost !== snapshot.apiUrl) {
107
107
  deps.stdout(
108
- `This worktree's saved local seat is on ${snapshot.apiUrl}, not ${requestedHost}; ` +
109
- `nothing was changed. Re-run \`borg reset-local-seat --host ${snapshot.apiUrl}\` to ` +
110
- "reset this worktree's seat.\n",
108
+ `This worktree connects to ${snapshot.apiUrl}, not ${requestedHost}; ` +
109
+ `nothing was changed. Re-run \`borg reset-local-connection --host ${snapshot.apiUrl}\` to ` +
110
+ "reset this worktree's saved connection.\n",
111
111
  );
112
112
  return 0;
113
113
  }
@@ -117,7 +117,7 @@ export async function runResetLocalSeat(
117
117
  ? `a saved local session credential is present (${snapshot.observation.state})`
118
118
  : 'the saved local session credential is already cleared (only the binding remains)';
119
119
  deps.stderr(
120
- `This will clear ONLY this worktree's saved local seat on ${snapshot.apiUrl} ` +
120
+ `This will clear ONLY this worktree's saved connection to ${snapshot.apiUrl} ` +
121
121
  `(worktree ${worktree}) — ${observed}. Server, trust anchor, cube, and sibling ` +
122
122
  'worktrees are left untouched. It makes no network call and revokes nothing server-side.\n',
123
123
  );
@@ -125,17 +125,17 @@ export async function runResetLocalSeat(
125
125
  // ----- S1: consent OUTSIDE any lock -----
126
126
  if (deps.isTTY()) {
127
127
  const answer = await deps.prompt(
128
- `Reset this worktree's saved local seat now? [y/N]: `,
128
+ `Reset this worktree's saved connection now? [y/N]: `,
129
129
  );
130
130
  const normalized = answer.trim().toLowerCase();
131
131
  if (normalized !== 'y' && normalized !== 'yes') {
132
- deps.stderr("audit: no changes made — this worktree's saved seat was left in place.\n");
132
+ deps.stderr("audit: no changes made — this worktree's saved connection was left in place.\n");
133
133
  return 0;
134
134
  }
135
135
  } else if (flags.yes !== true) {
136
136
  deps.stderr(
137
137
  'audit: no changes made — stdin is non-interactive and --yes was not passed. Re-run ' +
138
- '`borg reset-local-seat --yes` to clear this worktree\'s saved seat without a prompt.\n',
138
+ '`borg reset-local-connection --yes` to clear this worktree\'s saved connection without a prompt.\n',
139
139
  );
140
140
  return 1;
141
141
  }
@@ -146,7 +146,7 @@ export async function runResetLocalSeat(
146
146
  outcome = await deps.resetLocalSeatBinding(snapshot);
147
147
  } catch {
148
148
  deps.stderr(
149
- `audit: no changes made — the local seat reset for ${snapshot.apiUrl} (worktree ` +
149
+ `audit: no changes made — the connection reset for ${snapshot.apiUrl} (worktree ` +
150
150
  `${worktree}) could not complete (local credential store error). Retry — it is ` +
151
151
  'safe to re-run.\n',
152
152
  );
@@ -155,7 +155,7 @@ export async function runResetLocalSeat(
155
155
 
156
156
  if (outcome.outcome === 'reset') {
157
157
  deps.stderr(
158
- `audit: this worktree's saved local seat for ${snapshot.apiUrl} (worktree ${worktree}) ` +
158
+ `audit: this worktree's saved connection to ${snapshot.apiUrl} (worktree ${worktree}) ` +
159
159
  'was cleared; server, trust anchor, cube, and sibling worktrees unchanged.\n',
160
160
  );
161
161
  let remaining: Awaited<ReturnType<ResetLocalSeatDeps['findRemainingActiveSeat']>> = null;
@@ -167,9 +167,9 @@ export async function runResetLocalSeat(
167
167
  }
168
168
  if (remaining?.apiUrl === snapshot.apiUrl) {
169
169
  deps.stdout(
170
- `Another saved active seat remains for this worktree on ${snapshot.apiUrl}. ` +
170
+ `Another saved connection remains for this worktree on ${snapshot.apiUrl}. ` +
171
171
  `Re-attach with ${reattachCommand(snapshot.apiUrl)}; Borg will revalidate that ` +
172
- 'seat with the server before launch.\n',
172
+ 'it with the server before launch.\n',
173
173
  );
174
174
  } else {
175
175
  deps.stdout(recoveryGuidance(snapshot.apiUrl));
@@ -179,7 +179,7 @@ export async function runResetLocalSeat(
179
179
 
180
180
  if (outcome.outcome === 'no-binding') {
181
181
  deps.stdout(
182
- `No saved local seat remained for this worktree (${worktree}); nothing to reset.\n`,
182
+ `No saved connection remained for this worktree (${worktree}); nothing to reset.\n`,
183
183
  );
184
184
  return 0;
185
185
  }
@@ -188,7 +188,7 @@ export async function runResetLocalSeat(
188
188
  // (a concurrent re-enroll wrote a fresh bearer, or another process already
189
189
  // reset it). Never clobber a replacement — report the honest no-op.
190
190
  deps.stdout(
191
- `This worktree's saved local seat on ${snapshot.apiUrl} changed since it was read ` +
191
+ `This worktree's saved connection to ${snapshot.apiUrl} changed since it was read ` +
192
192
  "(a concurrent re-enroll or reset); nothing was changed. Re-run to observe the current " +
193
193
  'state.\n',
194
194
  );
@@ -221,7 +221,7 @@ export type ResetLocalSeatParseResult =
221
221
  | { ok: true; flags: ResetLocalSeatFlags }
222
222
  | { ok: false; error: string };
223
223
 
224
- /** Parse args after `borg reset-local-seat`. Supports `--host <h>` / `--host=<h>` / `--yes` / `-y`. */
224
+ /** Parse args after `borg reset-local-connection`. Supports `--host <h>` / `--host=<h>` / `--yes` / `-y`. */
225
225
  export function parseResetLocalSeatArgs(rawArgs: string[]): ResetLocalSeatParseResult {
226
226
  const flags: ResetLocalSeatFlags = {};
227
227
  for (let i = 0; i < rawArgs.length; i += 1) {
@@ -244,7 +244,7 @@ export function parseResetLocalSeatArgs(rawArgs: string[]): ResetLocalSeatParseR
244
244
  } else {
245
245
  return {
246
246
  ok: false,
247
- error: `unexpected argument: ${arg}. Usage: borg reset-local-seat [--host <host>] [--yes]`,
247
+ error: `unexpected argument: ${arg}. Usage: borg reset-local-connection [--host <host>] [--yes]`,
248
248
  };
249
249
  }
250
250
  }
@@ -76,7 +76,7 @@ export function formatSeatReattachRefusal(
76
76
  ? ''
77
77
  : ` Its heartbeat is ${holder.heartbeat}, so the process may be wedged.`;
78
78
  return (
79
- `This worktree's Borg seat already has a live session (inbox monitor pid ${holder.pid}).${heartbeat}\n` +
79
+ `This worktree's Borg drone already has a live session (inbox monitor pid ${holder.pid}).${heartbeat}\n` +
80
80
  'No agent was launched. Stop the existing session or use a fresh worktree with `borg assimilate --worktree <name>`. ' +
81
81
  `If the live monitor is wedged, override once with \`${forcedCommand}\`.\n`
82
82
  );
package/src/seat-store.ts CHANGED
@@ -165,7 +165,7 @@ function staleLockError(lockPath: string, held: LockPayload | null): Error {
165
165
  ? `its recorded owner process (pid ${held.pid}, started ${held.startTime}) is no longer running`
166
166
  : 'its lock file is missing a valid owner identity or is corrupt';
167
167
  return new Error(
168
- `Borg seat store lock file ${lockPath} is stale: ${who}. ` +
168
+ `Borg private store lock file ${lockPath} is stale: ${who}. ` +
169
169
  'Borg will NOT remove it automatically. If no borg process is running on this ' +
170
170
  `machine, delete ${lockPath} and retry; otherwise wait for the other borg process to finish.`,
171
171
  );
@@ -263,7 +263,7 @@ async function assertSecureStorePerms(
263
263
  ): Promise<void> {
264
264
  if ((fileMode & 0o077) !== 0) {
265
265
  throw new Error(
266
- `Borg seat store file ${filePath} has insecure permissions ` +
266
+ `Borg private store file ${filePath} has insecure permissions ` +
267
267
  `(0${(fileMode & 0o777).toString(8)}, expected 0600); refusing to read a credential from it`,
268
268
  );
269
269
  }
@@ -277,7 +277,7 @@ async function assertSecureStorePerms(
277
277
  }
278
278
  if ((dirStat.mode & 0o077) !== 0) {
279
279
  throw new Error(
280
- `Borg seat store directory ${dir} has insecure permissions ` +
280
+ `Borg private store directory ${dir} has insecure permissions ` +
281
281
  `(0${(dirStat.mode & 0o777).toString(8)}, expected 0700); refusing to read a credential under it`,
282
282
  );
283
283
  }
@@ -350,7 +350,7 @@ export async function readStoreFile(
350
350
  * release it on EVERY path (finally) by unlinking OUR OWN lock. Acquire is an atomic
351
351
  * `open(lockPath,'wx',0o600)`. On EEXIST the lock is held:
352
352
  * - holder PID ALIVE → bounded wait/retry (attempts×waitMs), then throw the truthful
353
- * transient 'Borg seat store is busy' error;
353
+ * transient 'Borg private store is busy' error;
354
354
  * - holder PID DEAD, or the payload is missing/unparseable → FAIL CLOSED naming the
355
355
  * exact lockfile path + the recorded dead pid/start-time. Borg NEVER auto-deletes
356
356
  * or steals it (no reclaim, no rename-claim). The operator clears it by hand only
@@ -422,7 +422,7 @@ export async function withStoreLock<T>(
422
422
  });
423
423
  }
424
424
  }
425
- throw new Error('Borg seat store is busy');
425
+ throw new Error('Borg private store is busy');
426
426
  } finally {
427
427
  await unlink(tmp).catch(() => {});
428
428
  }
@@ -469,7 +469,7 @@ export async function withStore<S, T>(
469
469
  }
470
470
  if (loaded === null) {
471
471
  throw new Error(
472
- 'Borg seat store is malformed or has an unsupported version; refusing to overwrite it',
472
+ 'Borg private store is malformed or has an unsupported version; refusing to overwrite it',
473
473
  );
474
474
  }
475
475
  data = loaded;
package/src/seats.ts CHANGED
@@ -242,7 +242,7 @@ async function readStore(): Promise<SeatsFile> {
242
242
  const parsed = parseStore(raw);
243
243
  if (parsed === null) {
244
244
  throw new Error(
245
- 'Borg seat store is malformed or has an unsupported version; refusing to read it',
245
+ 'Borg private store is malformed or has an unsupported version; refusing to read it',
246
246
  );
247
247
  }
248
248
  return parsed;
@@ -338,7 +338,7 @@ export async function sendBorgServerAttach(
338
338
  if (rejectedCode === ErrorCode.SESSION_REJECTED) {
339
339
  throw new BorgServerError(
340
340
  'SESSION_REJECTED',
341
- 'Borg server rejected the session: the seat is already bound to another session',
341
+ 'Borg server rejected the session: this worktree\'s connection is already bound to another session',
342
342
  );
343
343
  }
344
344
  if (rejectedCode === ErrorCode.SESSION_REVOKED) {
@@ -163,7 +163,7 @@ export function renderStreamStatus(inputs: RenderInputs): string {
163
163
  } else if (ownedByOther) {
164
164
  const owner = status.ownership!;
165
165
  summary = owner.droneLabel && owner.worktree
166
- ? `**Stream owned by seat ${owner.droneLabel} in \`${owner.worktree}\`.**`
166
+ ? `**Stream owned by ${owner.droneLabel} in \`${owner.worktree}\`.**`
167
167
  : '**Stream owned by another Borg MCP process.**';
168
168
  } else if (isNotStarted) {
169
169
  summary = '**Stream not started.**';
@@ -248,7 +248,7 @@ export function renderStreamStatus(inputs: RenderInputs): string {
248
248
 
249
249
  if (ownedByOther) {
250
250
  const owner = status.ownership!;
251
- lines.push(`- **stream owner seat**: ${owner.droneLabel ?? '_(unknown)_'}`);
251
+ lines.push(`- **stream owner**: ${owner.droneLabel ?? '_(unknown)_'}`);
252
252
  lines.push(`- **stream owner worktree**: ${owner.worktree ?? '_(unknown)_'}`);
253
253
  lines.push(`- **stream owner pid**: ${owner.pid ?? '_(unknown)_'}`);
254
254
  lines.push(`- **stream owner cwd**: ${owner.cwd ?? '_(unknown)_'}`);
@@ -260,7 +260,7 @@ export function renderStreamStatus(inputs: RenderInputs): string {
260
260
  }`
261
261
  );
262
262
  lines.push('');
263
- lines.push('Continue in the owning seat, or close its duplicate agent session before relaunching from the intended worktree. The live owner releases this lock on exit; a stale lock is reclaimed automatically.');
263
+ lines.push('Continue as the owning drone, or close its duplicate agent session before relaunching from the intended worktree. The live owner releases this lock on exit; a stale lock is reclaimed automatically.');
264
264
  }
265
265
 
266
266
  if (wakePath.agentKind === 'opencode' && wakePath.openCode) {
@@ -506,7 +506,7 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
506
506
  {
507
507
  name: 'borg_evict-drone',
508
508
  description:
509
- 'Remove a drone using the selected local client\'s cube-management grant. The seat credential is revoked, project files remain, and activity history keeps the removed seat\'s attribution. Accepts drone_id for the current cube or label with cube_id.',
509
+ 'Remove a drone using the selected local client\'s cube-management grant. The drone\'s credential is revoked, project files remain, and activity history keeps the removed drone\'s attribution. Accepts drone_id for the current cube or label with cube_id.',
510
510
  inputSchema: {
511
511
  type: 'object',
512
512
  properties: {
@@ -16,10 +16,9 @@ export const KNOWN_SUBCOMMANDS = [
16
16
  'setup',
17
17
  'update',
18
18
  'assimilate',
19
- 'reset-local-seat',
19
+ 'reset-local-connection',
20
20
  'recover-enrollment',
21
21
  'spawn',
22
- 'sync',
23
22
  'cleanup',
24
23
  'launch-all',
25
24
  'server',
@@ -9,7 +9,6 @@
9
9
  * Design spec: docs/superpowers/specs/2026-05-29-worktree-lifecycle-design.md
10
10
  * Q-resolutions baked in (SPEC-APPROVED 3a80412d):
11
11
  * Q1 branch naming — `wt-<suffix>` prefix-stripped, full-basename fallback.
12
- * Q2 idle-sync — ff-only, clean-gated; never merge/rebase; never over dirty.
13
12
  * Q3 post-merge — auto-return to wt-<basename>; ANNOUNCE the prunable
14
13
  * merged branch, prune only when explicitly requested.
15
14
  * Q4 uniform — no primary-worktree carve-out; main is never a working branch.
@@ -119,55 +118,11 @@ export function classifyDirty(runSync: RunSync, cwd: string): DirtyClassificatio
119
118
  return out;
120
119
  }
121
120
 
122
- /** True iff `branch` is an ancestor of `ref` — i.e. a clean fast-forward target. */
123
- export function isFastForward(runSync: RunSync, cwd: string, branch: string, ref: string): boolean {
124
- return runSync('git', ['merge-base', '--is-ancestor', branch, ref], cwd).status === 0;
125
- }
126
-
127
121
  /** True iff `branch`'s tip is an ancestor of `ref` — i.e. fully merged into it. */
128
122
  export function isMerged(runSync: RunSync, cwd: string, branch: string, ref: string): boolean {
129
123
  return runSync('git', ['merge-base', '--is-ancestor', branch, ref], cwd).status === 0;
130
124
  }
131
125
 
132
- export interface SyncResult {
133
- action: 'fast-forwarded' | 'already-current' | 'skipped-dirty' | 'skipped-diverged';
134
- message?: string;
135
- }
136
-
137
- /**
138
- * Idle-sync the current per-worktree branch to `ref` (Q2). NEVER discards
139
- * work: dirty -> skipped-dirty (no mutation). Only fast-forwards (no
140
- * merge/rebase): diverged -> skipped-diverged. The caller fetches first.
141
- *
142
- * `already-current` when the branch tip already equals `ref` (the common
143
- * no-op case on every launch).
144
- */
145
- export function syncWorktree(runSync: RunSync, cwd: string, branch: string, ref: string): SyncResult {
146
- if (!isCleanTree(runSync, cwd)) {
147
- return {
148
- action: 'skipped-dirty',
149
- message: 'uncommitted changes present; sync skipped (nothing discarded)',
150
- };
151
- }
152
- if (!isFastForward(runSync, cwd, branch, ref)) {
153
- return {
154
- action: 'skipped-diverged',
155
- message: `${branch} has diverged from ${ref}; resolve manually (no auto-merge/rebase)`,
156
- };
157
- }
158
- // Already at ref? merge --ff-only is a no-op but we report it distinctly
159
- // so callers can stay quiet on the common case.
160
- const ahead = runSync('git', ['rev-list', '--count', `${branch}..${ref}`], cwd);
161
- if (ahead.status === 0 && ahead.stdout.trim() === '0') {
162
- return { action: 'already-current' };
163
- }
164
- const ff = runSync('git', ['merge', '--ff-only', ref], cwd);
165
- if (ff.status !== 0) {
166
- return { action: 'skipped-diverged', message: 'ff-only merge unexpectedly failed' };
167
- }
168
- return { action: 'fast-forwarded' };
169
- }
170
-
171
126
  export interface AdoptResult {
172
127
  action: 'adopted' | 'blocked-unmerged' | 'blocked-target-unmerged' | 'skipped-dirty';
173
128
  message?: string;
package/dist/sync.d.ts DELETED
@@ -1,129 +0,0 @@
1
- /**
2
- * `borg sync` — worktree lifecycle management subcommand (gh#33).
3
- *
4
- * Reconciled to the per-worktree `wt-<suffix>` branch model (PR-B,
5
- * ruling ea643b33). Replaces the previous main-centric semantics
6
- * ("idle = on main"; "post-merge = checkout main") — under the approved
7
- * gh#33 model `main` is NEVER a working branch in any worktree; it is
8
- * purely the integration target. Every worktree works on a named
9
- * `wt-<suffix>` branch and `borg sync` keeps it current with
10
- * origin/main, returns to it after a feature branch merges, and absorbs
11
- * upstream into an in-progress feature branch — never touching `main`
12
- * as a checkout.
13
- *
14
- * All git mutation/decision logic is delegated to
15
- * `src/worktree-lifecycle.ts` (the seam PR-A added:
16
- * `adoptWorktree`, `syncWorktree`, `cleanupMerged`, `isMerged`,
17
- * `localBranchExists`) so the never-discard guards (dirty / unmerged
18
- * HEAD / unmerged target wt- branch) and the args-array subprocess
19
- * shape are shared, not duplicated.
20
- *
21
- * Lifecycle states:
22
- * 1. dirty — uncommitted changes; refuse (never discard).
23
- * 2. on-wt — on the per-worktree `wt-<suffix>` branch;
24
- * fast-forward it to origin/main (ff-only).
25
- * 3. on-main — on `main`/`master` (or detached at a merged
26
- * point); adopt the `wt-<suffix>` branch
27
- * (Q4: main is never a working branch).
28
- * 4. feature-mid-sprint — on a feature branch not yet merged; absorb
29
- * origin/main into it via `git merge --no-edit`
30
- * (no rebase — cube workflow rule (a)) when
31
- * origin/main advanced; else no-op.
32
- * 5. feature-merged — on a feature branch fully merged into
33
- * origin/main; return to `wt-<suffix>` and
34
- * ANNOUNCE the prunable feature branch (prune
35
- * only with `--prune`, Q3).
36
- *
37
- * Anti-features (intentional, unchanged):
38
- * - No auto-stash / auto-commit / auto-discard on dirty tree.
39
- * - No force-push, no rebase, no --force-with-lease.
40
- * - No remote-branch deletion (Coordinator owns merge actions).
41
- * - Local feature-branch deletion only on explicit `--prune` (Q3).
42
- */
43
- import { type RunSync } from './worktree-lifecycle.js';
44
- export interface SpawnSyncResult {
45
- status: number | null;
46
- stdout: string;
47
- stderr: string;
48
- }
49
- export interface SyncDeps {
50
- /** Synchronous spawn — used for `git ...` invocations. */
51
- runSync?: RunSync;
52
- /** Inject for tests; defaults to `process.cwd()`. */
53
- cwd?: () => string;
54
- /** stderr sink — overridable for tests. */
55
- stderr?: (line: string) => void;
56
- /** stdout sink — overridable for tests. */
57
- stdout?: (line: string) => void;
58
- }
59
- export interface SyncOptions {
60
- /** Q3: prune a merged feature branch instead of only announcing it. */
61
- prune: boolean;
62
- }
63
- /**
64
- * Resolve the per-worktree `wt-` branch for the current checkout,
65
- * DETERMINISTICALLY per worktree.
66
- *
67
- * - current branch is already `wt-*` → that is the branch.
68
- * - otherwise → derive `wt-<suffix>` from THIS worktree's directory
69
- * basename minus the main worktree's (repo) basename prefix — the
70
- * same `perWorktreeBranchName` derivation the spawn path uses, so
71
- * the name matches what `borg assimilate --worktree` created.
72
- *
73
- * This must NOT list `git branch --list wt-*`: in linked worktrees the
74
- * local branch namespace is SHARED across all siblings (gh#33 CR-v2
75
- * blocker 32bc45da), so every drone's `wt-` branch is visible and a
76
- * "single match" heuristic is never satisfied in a real multi-drone
77
- * cube. The directory-derivation is unique per worktree and never
78
- * ambiguous. The first `worktree <path>` line of
79
- * `git worktree list --porcelain` is the main worktree (the repo dir);
80
- * its basename is the prefix to strip. For an independent clone the
81
- * main worktree IS this worktree, so the prefix == basename and the
82
- * result is `wt-<basename>` (no strip) — consistent with PR-A's
83
- * in-place adoption.
84
- */
85
- export declare function resolveWtBranch(runSync: RunSync, cwd: string, currentBranch: string): string;
86
- export type SyncState = {
87
- kind: 'dirty';
88
- files: string[];
89
- } | {
90
- kind: 'on-wt';
91
- branch: string;
92
- wtBranch: string;
93
- } | {
94
- kind: 'on-main';
95
- branch: string;
96
- wtBranch: string;
97
- } | {
98
- kind: 'feature-mid-sprint';
99
- branch: string;
100
- wtBranch: string;
101
- commits: number;
102
- } | {
103
- kind: 'feature-merged';
104
- branch: string;
105
- wtBranch: string;
106
- } | {
107
- kind: 'error';
108
- reason: string;
109
- };
110
- /**
111
- * Detect the worktree's lifecycle state. Read-only except for the
112
- * `git fetch origin --prune` needed to measure against the latest tip.
113
- */
114
- export declare function detectState(deps: Required<SyncDeps>): SyncState;
115
- export declare function runSync(deps?: SyncDeps, opts?: SyncOptions): Promise<number>;
116
- export type ParseResult = {
117
- ok: true;
118
- options: SyncOptions;
119
- } | {
120
- ok: false;
121
- error: string;
122
- };
123
- /**
124
- * Parse args after `borg sync`. Supports `--prune` (Q3: delete a merged
125
- * feature branch after returning to the wt- branch). Rejects anything
126
- * else to keep room for future flags.
127
- */
128
- export declare function parseSyncArgs(rawArgs: string[]): ParseResult;
129
- //# sourceMappingURL=sync.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAKH,OAAO,EAML,KAAK,OAAO,EACb,MAAM,yBAAyB,CAAC;AAMjC,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,QAAQ;IACvB,0DAA0D;IAC1D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qDAAqD;IACrD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,2CAA2C;IAC3C,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,WAAW;IAC1B,uEAAuE;IACvE,KAAK,EAAE,OAAO,CAAC;CAChB;AAsBD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,EACX,aAAa,EAAE,MAAM,GACpB,MAAM,CAaR;AAMD,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,oBAAoB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACjF;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC5D;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtC;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,CAAC,QAAQ,CAAC,GAAG,SAAS,CAgE/D;AAMD,wBAAsB,OAAO,CAAC,IAAI,GAAE,QAAa,EAAE,IAAI,GAAE,WAA8B,GAAG,OAAO,CAAC,MAAM,CAAC,CAiFxG;AAiCD,MAAM,MAAM,WAAW,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,WAAW,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5F;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,WAAW,CAa5D"}