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.
- package/README.md +5 -3
- package/dist/assimilate-cmd.js +43 -43
- package/dist/assimilate-cmd.js.map +1 -1
- package/dist/assimilate-deps.js +2 -2
- package/dist/assimilate-deps.js.map +1 -1
- package/dist/assimilate-guard.js +4 -4
- package/dist/assimilate-guard.js.map +1 -1
- package/dist/assimilate-welcome.js +2 -2
- package/dist/assimilate-welcome.js.map +1 -1
- package/dist/bare-launch-menu.d.ts +5 -5
- package/dist/bare-launch-menu.d.ts.map +1 -1
- package/dist/bare-launch-menu.js +2 -2
- package/dist/bare-launch-menu.js.map +1 -1
- package/dist/claude.d.ts +0 -2
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +5 -18
- package/dist/claude.js.map +1 -1
- package/dist/cleanup-cmd.d.ts +1 -1
- package/dist/cleanup-cmd.d.ts.map +1 -1
- package/dist/cleanup-cmd.js +3 -3
- package/dist/cleanup-cmd.js.map +1 -1
- package/dist/cli-help.d.ts +1 -2
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +15 -24
- package/dist/cli-help.js.map +1 -1
- package/dist/cli-platform.d.ts +4 -0
- package/dist/cli-platform.d.ts.map +1 -1
- package/dist/cli-platform.js +29 -10
- package/dist/cli-platform.js.map +1 -1
- package/dist/cubes.d.ts.map +1 -1
- package/dist/cubes.js +30 -12
- package/dist/cubes.js.map +1 -1
- package/dist/docs-sections.js +1 -1
- package/dist/docs-sections.js.map +1 -1
- package/dist/drone-lifecycle.d.ts.map +1 -1
- package/dist/drone-lifecycle.js +3 -3
- package/dist/drone-lifecycle.js.map +1 -1
- package/dist/drone-management.js +1 -1
- package/dist/drone-management.js.map +1 -1
- package/dist/evict-drone.js +2 -2
- package/dist/evict-drone.js.map +1 -1
- package/dist/first-run-server.d.ts.map +1 -1
- package/dist/first-run-server.js +4 -1
- package/dist/first-run-server.js.map +1 -1
- package/dist/foreign-path-reminder.js +1 -1
- package/dist/foreign-path-reminder.js.map +1 -1
- package/dist/inbox-monitor.js +2 -2
- package/dist/inbox-monitor.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/launch-all-cmd.js +12 -12
- package/dist/launch-all-cmd.js.map +1 -1
- package/dist/launch-all-discovery.js +1 -1
- package/dist/launch-all-discovery.js.map +1 -1
- package/dist/lifecycle-log-guard.d.ts.map +1 -1
- package/dist/lifecycle-log-guard.js +12 -3
- package/dist/lifecycle-log-guard.js.map +1 -1
- package/dist/opencode-seat-identity.js +5 -5
- package/dist/opencode-seat-identity.js.map +1 -1
- package/dist/regen-format.d.ts.map +1 -1
- package/dist/regen-format.js +2 -1
- package/dist/regen-format.js.map +1 -1
- package/dist/remote-client.d.ts.map +1 -1
- package/dist/remote-client.js +10 -5
- package/dist/remote-client.js.map +1 -1
- package/dist/repository-cube-init.js +1 -1
- package/dist/repository-cube-init.js.map +1 -1
- package/dist/reset-local-seat-cmd.d.ts +2 -2
- package/dist/reset-local-seat-cmd.d.ts.map +1 -1
- package/dist/reset-local-seat-cmd.js +17 -17
- package/dist/reset-local-seat-cmd.js.map +1 -1
- package/dist/seat-reattach-guard.js +1 -1
- package/dist/seat-reattach-guard.js.map +1 -1
- package/dist/seat-store.d.ts +1 -1
- package/dist/seat-store.js +6 -6
- package/dist/seat-store.js.map +1 -1
- package/dist/seats.js +1 -1
- package/dist/seats.js.map +1 -1
- package/dist/server-handshake.js +1 -1
- package/dist/server-handshake.js.map +1 -1
- package/dist/stream-status.js +3 -3
- package/dist/stream-status.js.map +1 -1
- package/dist/tool-manifest.js +1 -1
- package/dist/tool-manifest.js.map +1 -1
- package/dist/unknown-subcommand.d.ts +1 -1
- package/dist/unknown-subcommand.d.ts.map +1 -1
- package/dist/unknown-subcommand.js +1 -2
- package/dist/unknown-subcommand.js.map +1 -1
- package/dist/worktree-lifecycle.d.ts +0 -16
- package/dist/worktree-lifecycle.d.ts.map +1 -1
- package/dist/worktree-lifecycle.js +0 -38
- package/dist/worktree-lifecycle.js.map +1 -1
- package/docs/EXTRACTION_PROVENANCE.md +5 -3
- package/docs/LOCAL_SERVER.md +2 -2
- package/docs/SEAT_LIFECYCLE.md +19 -19
- package/package.json +1 -1
- package/src/assimilate-cmd.ts +43 -43
- package/src/assimilate-deps.ts +2 -2
- package/src/assimilate-guard.ts +4 -4
- package/src/assimilate-welcome.ts +2 -2
- package/src/bare-launch-menu.ts +6 -6
- package/src/claude.ts +8 -21
- package/src/cleanup-cmd.ts +4 -4
- package/src/cli-help.ts +15 -27
- package/src/cli-platform.ts +42 -10
- package/src/cubes.ts +29 -14
- package/src/docs-sections.ts +1 -1
- package/src/drone-lifecycle.ts +3 -3
- package/src/drone-management.ts +1 -1
- package/src/evict-drone.ts +2 -2
- package/src/first-run-server.ts +4 -1
- package/src/foreign-path-reminder.ts +1 -1
- package/src/inbox-monitor.ts +2 -2
- package/src/index.ts +1 -1
- package/src/launch-all-cmd.ts +12 -12
- package/src/launch-all-discovery.ts +1 -1
- package/src/lifecycle-log-guard.ts +15 -3
- package/src/opencode-seat-identity.ts +5 -5
- package/src/regen-format.ts +2 -1
- package/src/remote-client.ts +12 -6
- package/src/repository-cube-init.ts +1 -1
- package/src/reset-local-seat-cmd.ts +17 -17
- package/src/seat-reattach-guard.ts +1 -1
- package/src/seat-store.ts +6 -6
- package/src/seats.ts +1 -1
- package/src/server-handshake.ts +1 -1
- package/src/stream-status.ts +3 -3
- package/src/tool-manifest.ts +1 -1
- package/src/unknown-subcommand.ts +1 -2
- package/src/worktree-lifecycle.ts +0 -45
- package/dist/sync.d.ts +0 -129
- package/dist/sync.d.ts.map +0 -1
- package/dist/sync.js +0 -286
- package/dist/sync.js.map +0 -1
- package/src/sync.ts +0 -363
package/src/remote-client.ts
CHANGED
|
@@ -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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
|
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
|
|
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-
|
|
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
|
|
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
|
|
109
|
-
`nothing was changed. Re-run \`borg reset-local-
|
|
110
|
-
"reset this worktree's
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
170
|
+
`Another saved connection remains for this worktree on ${snapshot.apiUrl}. ` +
|
|
171
171
|
`Re-attach with ${reattachCommand(snapshot.apiUrl)}; Borg will revalidate that ` +
|
|
172
|
-
'
|
|
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
|
|
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
|
|
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-
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
245
|
+
'Borg private store is malformed or has an unsupported version; refusing to read it',
|
|
246
246
|
);
|
|
247
247
|
}
|
|
248
248
|
return parsed;
|
package/src/server-handshake.ts
CHANGED
|
@@ -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:
|
|
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) {
|
package/src/stream-status.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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) {
|
package/src/tool-manifest.ts
CHANGED
|
@@ -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
|
|
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: {
|
|
@@ -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
|
package/dist/sync.d.ts.map
DELETED
|
@@ -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"}
|