borgmcp 0.5.2 → 0.6.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/dist/claude.d.ts +4 -2
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +24 -2
- package/dist/claude.js.map +1 -1
- package/dist/index.js +32 -53
- package/dist/index.js.map +1 -1
- package/dist/log-audit.js +2 -0
- package/dist/log-audit.js.map +1 -1
- package/dist/log-stream.d.ts +24 -1
- package/dist/log-stream.d.ts.map +1 -1
- package/dist/log-stream.js +26 -3
- package/dist/log-stream.js.map +1 -1
- package/dist/regen.js +2 -0
- package/dist/regen.js.map +1 -1
- package/dist/setup.js +2 -0
- package/dist/setup.js.map +1 -1
- package/dist/spawn.d.ts +116 -0
- package/dist/spawn.d.ts.map +1 -0
- package/dist/spawn.js +273 -0
- package/dist/spawn.js.map +1 -0
- package/dist/stream-status.d.ts +72 -0
- package/dist/stream-status.d.ts.map +1 -0
- package/dist/stream-status.js +148 -0
- package/dist/stream-status.js.map +1 -0
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +61 -1
- package/dist/templates.js.map +1 -1
- package/dist/terminal-title.d.ts +60 -0
- package/dist/terminal-title.d.ts.map +1 -0
- package/dist/terminal-title.js +68 -0
- package/dist/terminal-title.js.map +1 -0
- package/dist/version.d.ts +44 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +70 -0
- package/dist/version.js.map +1 -0
- package/package.json +5 -3
package/dist/spawn.d.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `borg spawn` — create a new git worktree for a sibling drone and
|
|
3
|
+
* (by default) launch a fresh Claude Code session inside it.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists: the cube's multi-drone pattern wants each drone to
|
|
6
|
+
* have its own worktree so parallel branches don't step on each other,
|
|
7
|
+
* but the manual ceremony (`git worktree add ../foo-name`, then
|
|
8
|
+
* `cd ../foo-name && borg`) is enough friction that drones drift back
|
|
9
|
+
* to working in one worktree and serializing. The subcommand collapses
|
|
10
|
+
* that to `borg spawn <name>`.
|
|
11
|
+
*
|
|
12
|
+
* Layout:
|
|
13
|
+
* Given the current worktree's primary at `/path/to/borg-mcp`, a
|
|
14
|
+
* `borg spawn builder` call creates `/path/to/borg-mcp-builder` as
|
|
15
|
+
* a sibling directory and runs `git worktree add` to register it.
|
|
16
|
+
* Default checkout is detached HEAD at the current HEAD so the new
|
|
17
|
+
* drone starts on a clean reference point without competing with
|
|
18
|
+
* the source worktree's branch state.
|
|
19
|
+
*
|
|
20
|
+
* Flags:
|
|
21
|
+
* --no-launch Create the worktree but don't run `borg` inside.
|
|
22
|
+
* Useful for setting up a tree to come back to later.
|
|
23
|
+
* --branch <ref> Check out a specific branch instead of detached
|
|
24
|
+
* HEAD. If <ref> is already checked out in another
|
|
25
|
+
* worktree, git refuses and we surface that error.
|
|
26
|
+
*
|
|
27
|
+
* Edge cases:
|
|
28
|
+
* - Not inside a git repo → friendly error, exit 1
|
|
29
|
+
* - Target path already exists → friendly error, exit 1
|
|
30
|
+
* - Empty name / name with slashes → format-hint error, exit 1
|
|
31
|
+
* - git worktree add fails → surface git's stderr verbatim, exit 1
|
|
32
|
+
*/
|
|
33
|
+
export interface SpawnArgs {
|
|
34
|
+
name: string;
|
|
35
|
+
noLaunch: boolean;
|
|
36
|
+
branch: string | null;
|
|
37
|
+
}
|
|
38
|
+
export type ParseResult = {
|
|
39
|
+
ok: true;
|
|
40
|
+
args: SpawnArgs;
|
|
41
|
+
} | {
|
|
42
|
+
ok: false;
|
|
43
|
+
error: string;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Parse `argv.slice(2)` (i.e. arguments to `borg`, with `spawn` already
|
|
47
|
+
* stripped) into a structured SpawnArgs or a friendly error.
|
|
48
|
+
*
|
|
49
|
+
* Supported shapes:
|
|
50
|
+
* borg spawn <name>
|
|
51
|
+
* borg spawn <name> --no-launch
|
|
52
|
+
* borg spawn <name> --branch <ref>
|
|
53
|
+
* borg spawn <name> --branch <ref> --no-launch
|
|
54
|
+
* (flag order doesn't matter; positional `<name>` may appear before
|
|
55
|
+
* or after the flags)
|
|
56
|
+
*
|
|
57
|
+
* Unknown flags and missing/duplicate positional name are caller-
|
|
58
|
+
* friendly errors with format hints.
|
|
59
|
+
*/
|
|
60
|
+
export declare function parseSpawnArgs(rawArgs: string[]): ParseResult;
|
|
61
|
+
export declare function validateName(name: string): {
|
|
62
|
+
ok: true;
|
|
63
|
+
} | {
|
|
64
|
+
ok: false;
|
|
65
|
+
error: string;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Given the absolute path to the primary worktree and a validated
|
|
69
|
+
* spawn name, return the absolute path of the new sibling worktree.
|
|
70
|
+
*
|
|
71
|
+
* Naming convention: `<sibling-parent>/<primary-basename>-<name>`.
|
|
72
|
+
* Example: primary `/home/x/borg-mcp` + name `builder` →
|
|
73
|
+
* `/home/x/borg-mcp-builder`. Keeps related worktrees alphabetically
|
|
74
|
+
* adjacent in `ls`, which matters when a drone-operator has a dozen
|
|
75
|
+
* sibling trees.
|
|
76
|
+
*
|
|
77
|
+
* Pure. Tested directly.
|
|
78
|
+
*/
|
|
79
|
+
export declare function computeWorktreePath(primaryWorktreeAbs: string, name: string): string;
|
|
80
|
+
export interface SpawnDeps {
|
|
81
|
+
/** Synchronous spawn — used for `git ...` invocations. */
|
|
82
|
+
runSync?: (cmd: string, args: string[], cwd?: string) => SpawnSyncResult;
|
|
83
|
+
/** Path existence — used to bail on collisions. */
|
|
84
|
+
pathExists?: (p: string) => boolean;
|
|
85
|
+
/** Async spawn — used to launch the in-worktree `borg`. */
|
|
86
|
+
runDetached?: (cmd: string, args: string[], cwd: string) => Promise<number>;
|
|
87
|
+
/** Inject for tests; defaults to `process.cwd()`. */
|
|
88
|
+
cwd?: () => string;
|
|
89
|
+
/** stderr sink — overridable for tests. */
|
|
90
|
+
stderr?: (line: string) => void;
|
|
91
|
+
}
|
|
92
|
+
export interface SpawnSyncResult {
|
|
93
|
+
status: number | null;
|
|
94
|
+
stdout: string;
|
|
95
|
+
stderr: string;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Run the `borg spawn` flow against the given args. Returns the
|
|
99
|
+
* desired process exit code. Pure side-effects flow through `deps`
|
|
100
|
+
* so tests can inject mocks for every external interaction.
|
|
101
|
+
*
|
|
102
|
+
* Flow:
|
|
103
|
+
* 1. Validate name
|
|
104
|
+
* 2. Resolve primary worktree path via `git rev-parse --show-toplevel`
|
|
105
|
+
* (run from the operator's cwd — handles nested worktrees by
|
|
106
|
+
* asking git to find the top, which always points at the current
|
|
107
|
+
* worktree's root; for our naming convention we want the
|
|
108
|
+
* `--git-common-dir` parent which IS the primary worktree's
|
|
109
|
+
* filesystem dir, NOT the current worktree's. Resolve below.)
|
|
110
|
+
* 3. Compute target path; abort if exists
|
|
111
|
+
* 4. Run `git worktree add <target> [<ref>|--detach HEAD]`
|
|
112
|
+
* 5. Print stderr nudge
|
|
113
|
+
* 6. If !noLaunch, exec `borg` inside the new worktree
|
|
114
|
+
*/
|
|
115
|
+
export declare function runSpawn(args: SpawnArgs, deps?: SpawnDeps): Promise<number>;
|
|
116
|
+
//# sourceMappingURL=spawn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn.d.ts","sourceRoot":"","sources":["../src/spawn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAWH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC7B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjC;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,WAAW,CA0C7D;AAkBD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAatF;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,kBAAkB,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAIpF;AAMD,MAAM,WAAW,SAAS;IACxB,0DAA0D;IAC1D,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,eAAe,CAAC;IACzE,mDAAmD;IACnD,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;IACpC,2DAA2D;IAC3D,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5E,qDAAqD;IACrD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAsBD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,SAAS,EACf,IAAI,GAAE,SAAc,GACnB,OAAO,CAAC,MAAM,CAAC,CAwGjB"}
|
package/dist/spawn.js
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `borg spawn` — create a new git worktree for a sibling drone and
|
|
3
|
+
* (by default) launch a fresh Claude Code session inside it.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists: the cube's multi-drone pattern wants each drone to
|
|
6
|
+
* have its own worktree so parallel branches don't step on each other,
|
|
7
|
+
* but the manual ceremony (`git worktree add ../foo-name`, then
|
|
8
|
+
* `cd ../foo-name && borg`) is enough friction that drones drift back
|
|
9
|
+
* to working in one worktree and serializing. The subcommand collapses
|
|
10
|
+
* that to `borg spawn <name>`.
|
|
11
|
+
*
|
|
12
|
+
* Layout:
|
|
13
|
+
* Given the current worktree's primary at `/path/to/borg-mcp`, a
|
|
14
|
+
* `borg spawn builder` call creates `/path/to/borg-mcp-builder` as
|
|
15
|
+
* a sibling directory and runs `git worktree add` to register it.
|
|
16
|
+
* Default checkout is detached HEAD at the current HEAD so the new
|
|
17
|
+
* drone starts on a clean reference point without competing with
|
|
18
|
+
* the source worktree's branch state.
|
|
19
|
+
*
|
|
20
|
+
* Flags:
|
|
21
|
+
* --no-launch Create the worktree but don't run `borg` inside.
|
|
22
|
+
* Useful for setting up a tree to come back to later.
|
|
23
|
+
* --branch <ref> Check out a specific branch instead of detached
|
|
24
|
+
* HEAD. If <ref> is already checked out in another
|
|
25
|
+
* worktree, git refuses and we surface that error.
|
|
26
|
+
*
|
|
27
|
+
* Edge cases:
|
|
28
|
+
* - Not inside a git repo → friendly error, exit 1
|
|
29
|
+
* - Target path already exists → friendly error, exit 1
|
|
30
|
+
* - Empty name / name with slashes → format-hint error, exit 1
|
|
31
|
+
* - git worktree add fails → surface git's stderr verbatim, exit 1
|
|
32
|
+
*/
|
|
33
|
+
import { spawnSync, spawn } from 'node:child_process';
|
|
34
|
+
import { existsSync } from 'node:fs';
|
|
35
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
36
|
+
import chalk from 'chalk';
|
|
37
|
+
/**
|
|
38
|
+
* Parse `argv.slice(2)` (i.e. arguments to `borg`, with `spawn` already
|
|
39
|
+
* stripped) into a structured SpawnArgs or a friendly error.
|
|
40
|
+
*
|
|
41
|
+
* Supported shapes:
|
|
42
|
+
* borg spawn <name>
|
|
43
|
+
* borg spawn <name> --no-launch
|
|
44
|
+
* borg spawn <name> --branch <ref>
|
|
45
|
+
* borg spawn <name> --branch <ref> --no-launch
|
|
46
|
+
* (flag order doesn't matter; positional `<name>` may appear before
|
|
47
|
+
* or after the flags)
|
|
48
|
+
*
|
|
49
|
+
* Unknown flags and missing/duplicate positional name are caller-
|
|
50
|
+
* friendly errors with format hints.
|
|
51
|
+
*/
|
|
52
|
+
export function parseSpawnArgs(rawArgs) {
|
|
53
|
+
let name = null;
|
|
54
|
+
let noLaunch = false;
|
|
55
|
+
let branch = null;
|
|
56
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
57
|
+
const arg = rawArgs[i];
|
|
58
|
+
if (arg === '--no-launch') {
|
|
59
|
+
noLaunch = true;
|
|
60
|
+
}
|
|
61
|
+
else if (arg === '--branch') {
|
|
62
|
+
const next = rawArgs[i + 1];
|
|
63
|
+
if (typeof next !== 'string' || next.length === 0) {
|
|
64
|
+
return {
|
|
65
|
+
ok: false,
|
|
66
|
+
error: `--branch requires a ref argument (e.g. \`--branch main\`)`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
branch = next;
|
|
70
|
+
i += 1;
|
|
71
|
+
}
|
|
72
|
+
else if (arg.startsWith('--')) {
|
|
73
|
+
return {
|
|
74
|
+
ok: false,
|
|
75
|
+
error: `unknown flag: ${arg}. Supported: --no-launch, --branch <ref>`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
if (name !== null) {
|
|
80
|
+
return {
|
|
81
|
+
ok: false,
|
|
82
|
+
error: `unexpected extra argument: ${arg} (already have name "${name}")`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
name = arg;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (name === null) {
|
|
89
|
+
return {
|
|
90
|
+
ok: false,
|
|
91
|
+
error: `missing required argument <name>. Usage: borg spawn <name> [--branch <ref>] [--no-launch]`,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return { ok: true, args: { name, noLaunch, branch } };
|
|
95
|
+
}
|
|
96
|
+
// ------------------------------------------------------------------
|
|
97
|
+
// Name validation
|
|
98
|
+
// ------------------------------------------------------------------
|
|
99
|
+
/**
|
|
100
|
+
* Worktree names get tacked onto the parent directory's path. They
|
|
101
|
+
* must therefore be filesystem-safe AND short enough that the
|
|
102
|
+
* resulting path doesn't make `ls` unreadable. Reject anything with
|
|
103
|
+
* slashes, parent-directory tokens, leading dashes (would parse as
|
|
104
|
+
* a flag), or shell-metacharacter punctuation.
|
|
105
|
+
*
|
|
106
|
+
* Allowed: lowercase ASCII letters, digits, hyphens, underscores.
|
|
107
|
+
* Length 1–48. Anchored. No leading hyphen.
|
|
108
|
+
*/
|
|
109
|
+
const NAME_RE = /^[a-z0-9_][a-z0-9_-]{0,47}$/;
|
|
110
|
+
export function validateName(name) {
|
|
111
|
+
if (name.length === 0) {
|
|
112
|
+
return { ok: false, error: `name must not be empty` };
|
|
113
|
+
}
|
|
114
|
+
if (!NAME_RE.test(name)) {
|
|
115
|
+
return {
|
|
116
|
+
ok: false,
|
|
117
|
+
error: `invalid name "${name}". Use lowercase letters, digits, hyphens, or ` +
|
|
118
|
+
`underscores; max 48 chars; must not start with a hyphen.`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return { ok: true };
|
|
122
|
+
}
|
|
123
|
+
// ------------------------------------------------------------------
|
|
124
|
+
// Path computation (pure)
|
|
125
|
+
// ------------------------------------------------------------------
|
|
126
|
+
/**
|
|
127
|
+
* Given the absolute path to the primary worktree and a validated
|
|
128
|
+
* spawn name, return the absolute path of the new sibling worktree.
|
|
129
|
+
*
|
|
130
|
+
* Naming convention: `<sibling-parent>/<primary-basename>-<name>`.
|
|
131
|
+
* Example: primary `/home/x/borg-mcp` + name `builder` →
|
|
132
|
+
* `/home/x/borg-mcp-builder`. Keeps related worktrees alphabetically
|
|
133
|
+
* adjacent in `ls`, which matters when a drone-operator has a dozen
|
|
134
|
+
* sibling trees.
|
|
135
|
+
*
|
|
136
|
+
* Pure. Tested directly.
|
|
137
|
+
*/
|
|
138
|
+
export function computeWorktreePath(primaryWorktreeAbs, name) {
|
|
139
|
+
const parent = dirname(primaryWorktreeAbs);
|
|
140
|
+
const base = basename(primaryWorktreeAbs);
|
|
141
|
+
return join(parent, `${base}-${name}`);
|
|
142
|
+
}
|
|
143
|
+
const defaultDeps = {
|
|
144
|
+
runSync: (cmd, args, cwd) => {
|
|
145
|
+
const r = spawnSync(cmd, args, { cwd, encoding: 'utf-8' });
|
|
146
|
+
return {
|
|
147
|
+
status: r.status,
|
|
148
|
+
stdout: r.stdout ?? '',
|
|
149
|
+
stderr: r.stderr ?? '',
|
|
150
|
+
};
|
|
151
|
+
},
|
|
152
|
+
pathExists: (p) => existsSync(p),
|
|
153
|
+
runDetached: (cmd, args, cwd) => new Promise((resolveExit, rejectExit) => {
|
|
154
|
+
const child = spawn(cmd, args, { cwd, stdio: 'inherit', shell: false });
|
|
155
|
+
child.on('error', rejectExit);
|
|
156
|
+
child.on('exit', (code) => resolveExit(code ?? 0));
|
|
157
|
+
}),
|
|
158
|
+
cwd: () => process.cwd(),
|
|
159
|
+
stderr: (line) => process.stderr.write(line),
|
|
160
|
+
};
|
|
161
|
+
/**
|
|
162
|
+
* Run the `borg spawn` flow against the given args. Returns the
|
|
163
|
+
* desired process exit code. Pure side-effects flow through `deps`
|
|
164
|
+
* so tests can inject mocks for every external interaction.
|
|
165
|
+
*
|
|
166
|
+
* Flow:
|
|
167
|
+
* 1. Validate name
|
|
168
|
+
* 2. Resolve primary worktree path via `git rev-parse --show-toplevel`
|
|
169
|
+
* (run from the operator's cwd — handles nested worktrees by
|
|
170
|
+
* asking git to find the top, which always points at the current
|
|
171
|
+
* worktree's root; for our naming convention we want the
|
|
172
|
+
* `--git-common-dir` parent which IS the primary worktree's
|
|
173
|
+
* filesystem dir, NOT the current worktree's. Resolve below.)
|
|
174
|
+
* 3. Compute target path; abort if exists
|
|
175
|
+
* 4. Run `git worktree add <target> [<ref>|--detach HEAD]`
|
|
176
|
+
* 5. Print stderr nudge
|
|
177
|
+
* 6. If !noLaunch, exec `borg` inside the new worktree
|
|
178
|
+
*/
|
|
179
|
+
export async function runSpawn(args, deps = {}) {
|
|
180
|
+
const { runSync, pathExists, runDetached, cwd, stderr } = {
|
|
181
|
+
...defaultDeps,
|
|
182
|
+
...deps,
|
|
183
|
+
};
|
|
184
|
+
// (1) Name validation
|
|
185
|
+
const nameCheck = validateName(args.name);
|
|
186
|
+
if (!nameCheck.ok) {
|
|
187
|
+
stderr(chalk.red(`◼ borg spawn: ${nameCheck.error}\n`));
|
|
188
|
+
return 1;
|
|
189
|
+
}
|
|
190
|
+
// (2) Primary worktree resolution.
|
|
191
|
+
//
|
|
192
|
+
// `git rev-parse --git-common-dir` returns the path to the SHARED
|
|
193
|
+
// `.git` directory (the primary worktree's `.git`). The PRIMARY
|
|
194
|
+
// WORKTREE itself is that directory's parent — which is what we
|
|
195
|
+
// want to use as the basis for the sibling worktree's path. This
|
|
196
|
+
// resolution works correctly even when `borg spawn` is invoked
|
|
197
|
+
// from inside a non-primary worktree (e.g. the operator already
|
|
198
|
+
// cd'd into a sibling drone tree and wants to spin up another).
|
|
199
|
+
const cwdValue = cwd();
|
|
200
|
+
const gitCommonDir = runSync('git', ['rev-parse', '--git-common-dir'], cwdValue);
|
|
201
|
+
if (gitCommonDir.status !== 0) {
|
|
202
|
+
stderr(chalk.red(`◼ borg spawn: not in a git repository (cwd: ${cwdValue})\n`));
|
|
203
|
+
return 1;
|
|
204
|
+
}
|
|
205
|
+
const commonDirRaw = gitCommonDir.stdout.trim();
|
|
206
|
+
// `--git-common-dir` may print a relative path (e.g. `.git`); resolve
|
|
207
|
+
// against cwd to get an absolute reference.
|
|
208
|
+
const commonDirAbs = resolve(cwdValue, commonDirRaw);
|
|
209
|
+
// Strip the trailing `.git` segment to get the primary worktree root.
|
|
210
|
+
// commonDirAbs ends in `/.git` for a regular checkout, or in
|
|
211
|
+
// `/.git/worktrees/<name>` for a non-primary worktree's perspective
|
|
212
|
+
// (but `--common-dir` always returns the SHARED `.git`, so the suffix
|
|
213
|
+
// is just `/.git`). dirname() peels the `.git` segment off.
|
|
214
|
+
const primaryWorktree = basename(commonDirAbs) === '.git' ? dirname(commonDirAbs) : commonDirAbs;
|
|
215
|
+
// (3) Compute + collision-check target path
|
|
216
|
+
const targetPath = computeWorktreePath(primaryWorktree, args.name);
|
|
217
|
+
if (pathExists(targetPath)) {
|
|
218
|
+
stderr(chalk.red(`◼ borg spawn: target path already exists: ${targetPath}\n`));
|
|
219
|
+
return 1;
|
|
220
|
+
}
|
|
221
|
+
// (4) Create the worktree
|
|
222
|
+
const worktreeArgs = args.branch
|
|
223
|
+
? ['worktree', 'add', targetPath, args.branch]
|
|
224
|
+
: ['worktree', 'add', '--detach', targetPath, 'HEAD'];
|
|
225
|
+
const createResult = runSync('git', worktreeArgs, primaryWorktree);
|
|
226
|
+
if (createResult.status !== 0) {
|
|
227
|
+
const gitErr = createResult.stderr.trim() || createResult.stdout.trim();
|
|
228
|
+
stderr(chalk.red(`◼ borg spawn: git worktree add failed:\n${gitErr}\n`));
|
|
229
|
+
return 1;
|
|
230
|
+
}
|
|
231
|
+
// (5) Stderr nudge.
|
|
232
|
+
//
|
|
233
|
+
// Even though `git worktree add` already prints its own confirmation
|
|
234
|
+
// line, we add a short summary that names the next steps the operator
|
|
235
|
+
// typically wants — particularly the "checkout a branch and pull"
|
|
236
|
+
// reminder for the default detached case, which catches users who
|
|
237
|
+
// would otherwise start work on a stale HEAD without realising.
|
|
238
|
+
stderr(chalk.blue(`◼ Spawned worktree at ${targetPath}\n`));
|
|
239
|
+
if (!args.branch) {
|
|
240
|
+
const shortSha = readShortSha(runSync, targetPath);
|
|
241
|
+
stderr(chalk.gray(`◼ Detached HEAD at ${shortSha}\n`));
|
|
242
|
+
stderr(chalk.gray(`◼ Run \`git checkout main && git pull\` before starting work\n`));
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
stderr(chalk.gray(`◼ Checked out ${args.branch}\n`));
|
|
246
|
+
}
|
|
247
|
+
// (6) Optional drone launch.
|
|
248
|
+
if (args.noLaunch) {
|
|
249
|
+
stderr(chalk.gray(`◼ --no-launch: skipping borg launch\n`));
|
|
250
|
+
stderr(chalk.gray(` cd ${targetPath} && borg\n`));
|
|
251
|
+
return 0;
|
|
252
|
+
}
|
|
253
|
+
stderr(chalk.blue(`◼ Launching Claude Code in ${targetPath}…\n`));
|
|
254
|
+
try {
|
|
255
|
+
const code = await runDetached('borg', [], targetPath);
|
|
256
|
+
return code;
|
|
257
|
+
}
|
|
258
|
+
catch (err) {
|
|
259
|
+
stderr(chalk.red(`◼ borg spawn: failed to launch borg in new worktree: ${err?.message ?? err}\n`));
|
|
260
|
+
return 1;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Best-effort short SHA of the new worktree's HEAD. Used only for the
|
|
265
|
+
* cosmetic "Detached HEAD at <sha>" line; failure here is non-fatal.
|
|
266
|
+
*/
|
|
267
|
+
function readShortSha(runSync, worktreePath) {
|
|
268
|
+
const r = runSync('git', ['rev-parse', '--short', 'HEAD'], worktreePath);
|
|
269
|
+
if (r.status === 0)
|
|
270
|
+
return r.stdout.trim();
|
|
271
|
+
return '(unknown)';
|
|
272
|
+
}
|
|
273
|
+
//# sourceMappingURL=spawn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn.js","sourceRoot":"","sources":["../src/spawn.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renderer + inbox-Monitor liveness probe for `borg:stream-status`.
|
|
3
|
+
*
|
|
4
|
+
* Split out from `index.ts` so the 5-state precedence logic and the
|
|
5
|
+
* `pgrep`-based liveness check can be unit-tested without spinning up
|
|
6
|
+
* the MCP server. drone-4's 18:30:51 UX contract is the spec for the
|
|
7
|
+
* rendered output shape; this module is the implementation surface.
|
|
8
|
+
*
|
|
9
|
+
* Top-line states (drone-4 contract):
|
|
10
|
+
* 1. Stream not started.
|
|
11
|
+
* 2. Stream connected, awaiting first content event.
|
|
12
|
+
* 3. Stream connected, last content <X> ago.
|
|
13
|
+
* 4. Stream disconnected (reconnect attempt N).
|
|
14
|
+
* 5. Stream connected (no inbox-Monitor — wake path broken).
|
|
15
|
+
*
|
|
16
|
+
* Precedence when both `disconnected` and `no inbox-Monitor` apply:
|
|
17
|
+
* prefer (4) — wire-disconnect is the upstream cause and resolves
|
|
18
|
+
* automatically when the wire comes back up; State 5 only matters
|
|
19
|
+
* when the wire is healthy but the file-watch isn't.
|
|
20
|
+
*/
|
|
21
|
+
import type { StreamStatus } from './log-stream.js';
|
|
22
|
+
/**
|
|
23
|
+
* Best-effort check: is a process tailing this inbox file?
|
|
24
|
+
*
|
|
25
|
+
* Returns:
|
|
26
|
+
* - true: at least one process matches `tail.*<inboxPath>` in pgrep
|
|
27
|
+
* - false: pgrep ran cleanly and found no match
|
|
28
|
+
* - null: cannot determine (pgrep unavailable, spawn error, no inbox path)
|
|
29
|
+
*
|
|
30
|
+
* The null case is informative — it means we don't know, so the
|
|
31
|
+
* renderer must NOT fire State 5 (which would be misleading). State 5
|
|
32
|
+
* only fires when we positively know the wake path is broken.
|
|
33
|
+
*
|
|
34
|
+
* Why `pgrep` and not a more elegant check: Claude Code Monitors are
|
|
35
|
+
* tail-based subprocesses spawned by the harness, completely opaque to
|
|
36
|
+
* the MCP server. The MCP server has no IPC channel into the harness's
|
|
37
|
+
* task table. The cheapest reliable signal we can get from inside the
|
|
38
|
+
* MCP server is "is there a tail subprocess open against this path?"
|
|
39
|
+
* — which is what `pgrep -f` answers.
|
|
40
|
+
*
|
|
41
|
+
* macOS + Linux ship `pgrep`. Windows doesn't (borgmcp targets Mac /
|
|
42
|
+
* Linux per package.json `os` field; the null branch handles other
|
|
43
|
+
* platforms gracefully).
|
|
44
|
+
*/
|
|
45
|
+
export declare function checkInboxMonitorHealthy(inboxPath: string | null): boolean | null;
|
|
46
|
+
export interface RenderInputs {
|
|
47
|
+
status: StreamStatus;
|
|
48
|
+
/**
|
|
49
|
+
* Tri-state Monitor liveness: true = healthy, false = wake-path
|
|
50
|
+
* broken, null = cannot determine.
|
|
51
|
+
*/
|
|
52
|
+
inboxMonitorHealthy: boolean | null;
|
|
53
|
+
/**
|
|
54
|
+
* Inbox path for the State-5 self-arm instruction. Pass null when
|
|
55
|
+
* unknown (no active cube); State 5 will then surface the failure
|
|
56
|
+
* mode but omit the exact command.
|
|
57
|
+
*/
|
|
58
|
+
inboxPath: string | null;
|
|
59
|
+
/** Drone label for the Monitor description copy. */
|
|
60
|
+
droneLabel: string | null;
|
|
61
|
+
/** Cube name for the Monitor description copy. */
|
|
62
|
+
cubeName: string | null;
|
|
63
|
+
/** Relative-time formatter (injected so the renderer is pure). */
|
|
64
|
+
humanAgo: (d: Date) => string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Render the `borg:stream-status` markdown body per drone-4's 18:30:51
|
|
68
|
+
* contract. Pure function — no I/O, no clock reads. Caller assembles
|
|
69
|
+
* the inputs.
|
|
70
|
+
*/
|
|
71
|
+
export declare function renderStreamStatus(inputs: RenderInputs): string;
|
|
72
|
+
//# sourceMappingURL=stream-status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-status.d.ts","sourceRoot":"","sources":["../src/stream-status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,OAAO,GAAG,IAAI,CAoBhB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB;;;OAGG;IACH,mBAAmB,EAAE,OAAO,GAAG,IAAI,CAAC;IACpC;;;;OAIG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,oDAAoD;IACpD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,kDAAkD;IAClD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,kEAAkE;IAClE,QAAQ,EAAE,CAAC,CAAC,EAAE,IAAI,KAAK,MAAM,CAAC;CAC/B;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAuG/D"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renderer + inbox-Monitor liveness probe for `borg:stream-status`.
|
|
3
|
+
*
|
|
4
|
+
* Split out from `index.ts` so the 5-state precedence logic and the
|
|
5
|
+
* `pgrep`-based liveness check can be unit-tested without spinning up
|
|
6
|
+
* the MCP server. drone-4's 18:30:51 UX contract is the spec for the
|
|
7
|
+
* rendered output shape; this module is the implementation surface.
|
|
8
|
+
*
|
|
9
|
+
* Top-line states (drone-4 contract):
|
|
10
|
+
* 1. Stream not started.
|
|
11
|
+
* 2. Stream connected, awaiting first content event.
|
|
12
|
+
* 3. Stream connected, last content <X> ago.
|
|
13
|
+
* 4. Stream disconnected (reconnect attempt N).
|
|
14
|
+
* 5. Stream connected (no inbox-Monitor — wake path broken).
|
|
15
|
+
*
|
|
16
|
+
* Precedence when both `disconnected` and `no inbox-Monitor` apply:
|
|
17
|
+
* prefer (4) — wire-disconnect is the upstream cause and resolves
|
|
18
|
+
* automatically when the wire comes back up; State 5 only matters
|
|
19
|
+
* when the wire is healthy but the file-watch isn't.
|
|
20
|
+
*/
|
|
21
|
+
import { spawnSync } from 'node:child_process';
|
|
22
|
+
/**
|
|
23
|
+
* Best-effort check: is a process tailing this inbox file?
|
|
24
|
+
*
|
|
25
|
+
* Returns:
|
|
26
|
+
* - true: at least one process matches `tail.*<inboxPath>` in pgrep
|
|
27
|
+
* - false: pgrep ran cleanly and found no match
|
|
28
|
+
* - null: cannot determine (pgrep unavailable, spawn error, no inbox path)
|
|
29
|
+
*
|
|
30
|
+
* The null case is informative — it means we don't know, so the
|
|
31
|
+
* renderer must NOT fire State 5 (which would be misleading). State 5
|
|
32
|
+
* only fires when we positively know the wake path is broken.
|
|
33
|
+
*
|
|
34
|
+
* Why `pgrep` and not a more elegant check: Claude Code Monitors are
|
|
35
|
+
* tail-based subprocesses spawned by the harness, completely opaque to
|
|
36
|
+
* the MCP server. The MCP server has no IPC channel into the harness's
|
|
37
|
+
* task table. The cheapest reliable signal we can get from inside the
|
|
38
|
+
* MCP server is "is there a tail subprocess open against this path?"
|
|
39
|
+
* — which is what `pgrep -f` answers.
|
|
40
|
+
*
|
|
41
|
+
* macOS + Linux ship `pgrep`. Windows doesn't (borgmcp targets Mac /
|
|
42
|
+
* Linux per package.json `os` field; the null branch handles other
|
|
43
|
+
* platforms gracefully).
|
|
44
|
+
*/
|
|
45
|
+
export function checkInboxMonitorHealthy(inboxPath) {
|
|
46
|
+
if (!inboxPath)
|
|
47
|
+
return null;
|
|
48
|
+
try {
|
|
49
|
+
// `-f` matches against the full command line so we catch the
|
|
50
|
+
// `tail -n 0 -F <inboxPath>` form. `-l` lists matches; we only
|
|
51
|
+
// need the exit code (0 = match, 1 = no match) and a sanity check
|
|
52
|
+
// on stdout (some pgrep variants exit 0 with empty stdout under
|
|
53
|
+
// permission errors — treat empty stdout as "no match" for safety).
|
|
54
|
+
const res = spawnSync('pgrep', ['-f', inboxPath], {
|
|
55
|
+
encoding: 'utf-8',
|
|
56
|
+
timeout: 2_000,
|
|
57
|
+
});
|
|
58
|
+
if (res.error)
|
|
59
|
+
return null;
|
|
60
|
+
if (res.status === 0 && res.stdout.trim().length > 0)
|
|
61
|
+
return true;
|
|
62
|
+
if (res.status === 1)
|
|
63
|
+
return false;
|
|
64
|
+
// pgrep exits 2 for syntax error, 3 for fatal — treat as unknown.
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Render the `borg:stream-status` markdown body per drone-4's 18:30:51
|
|
73
|
+
* contract. Pure function — no I/O, no clock reads. Caller assembles
|
|
74
|
+
* the inputs.
|
|
75
|
+
*/
|
|
76
|
+
export function renderStreamStatus(inputs) {
|
|
77
|
+
const { status, inboxMonitorHealthy, inboxPath, droneLabel, cubeName, humanAgo } = inputs;
|
|
78
|
+
const isNotStarted = status.reconnectAttempts === 0 &&
|
|
79
|
+
status.lastWireActivityAt === null &&
|
|
80
|
+
!status.connected;
|
|
81
|
+
// Top-line verdict — 5 states + override per drone-4 contract.
|
|
82
|
+
// Precedence: disconnected > no-inbox-Monitor (wire-down upstream
|
|
83
|
+
// cause; State 5 only applies when wire is healthy).
|
|
84
|
+
let summary;
|
|
85
|
+
if (isNotStarted) {
|
|
86
|
+
summary = '**Stream not started.**';
|
|
87
|
+
}
|
|
88
|
+
else if (!status.connected) {
|
|
89
|
+
summary = `**Stream disconnected (reconnect attempt ${status.reconnectAttempts}).**`;
|
|
90
|
+
}
|
|
91
|
+
else if (inboxMonitorHealthy === false) {
|
|
92
|
+
summary = '**Stream connected (no inbox-Monitor — wake path broken).**';
|
|
93
|
+
}
|
|
94
|
+
else if (status.lastContentEventAt === null) {
|
|
95
|
+
// State 2: wire works, no content yet. Collapses two underlying
|
|
96
|
+
// conditions per drone-4 contract — fresh connect pre-first-content
|
|
97
|
+
// and quiet cube post-reconnect. The body's heartbeat field
|
|
98
|
+
// distinguishes them (populated vs `_(none)_`).
|
|
99
|
+
summary = '**Stream connected, awaiting first content event.**';
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
summary = `**Stream connected, last content ${humanAgo(new Date(status.lastContentEventAt))}.**`;
|
|
103
|
+
}
|
|
104
|
+
const lines = [];
|
|
105
|
+
lines.push(summary);
|
|
106
|
+
lines.push('');
|
|
107
|
+
lines.push('# Log-stream status');
|
|
108
|
+
lines.push('');
|
|
109
|
+
if (isNotStarted) {
|
|
110
|
+
lines.push('- **state**: _(stream not started)_');
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
lines.push(`- **connected**: ${status.connected}`);
|
|
114
|
+
}
|
|
115
|
+
// Body shape per drone-4 contract: three timestamp lines (content,
|
|
116
|
+
// heartbeat, wire) — looks redundant in the common case where they
|
|
117
|
+
// coincide, but the asymmetric "content quiet, heartbeats alive" case
|
|
118
|
+
// is exactly the diagnostic scenario this surface exists to support.
|
|
119
|
+
lines.push(`- **last content event**: ${status.lastContentEventAt
|
|
120
|
+
? `${status.lastContentEventAt} (${humanAgo(new Date(status.lastContentEventAt))})`
|
|
121
|
+
: '_(none yet)_'}`);
|
|
122
|
+
lines.push(`- **last heartbeat at**: ${status.lastHeartbeatAt
|
|
123
|
+
? `${status.lastHeartbeatAt} (${humanAgo(new Date(status.lastHeartbeatAt))})`
|
|
124
|
+
: '_(none)_'}`);
|
|
125
|
+
lines.push(`- **last wire activity**: ${status.lastWireActivityAt
|
|
126
|
+
? `${status.lastWireActivityAt} (${humanAgo(new Date(status.lastWireActivityAt))})`
|
|
127
|
+
: '_(none)_'}`);
|
|
128
|
+
lines.push(`- **last persisted event id**: ${status.lastPersistedEventId ?? '_(none)_'}`);
|
|
129
|
+
lines.push(`- **reconnect attempts**: ${status.reconnectAttempts}`);
|
|
130
|
+
// State-5 body line + self-arm instruction. Only fires when we
|
|
131
|
+
// POSITIVELY detected wake-path breakage AND the wire is up — the
|
|
132
|
+
// disconnected case takes precedence above and would never reach
|
|
133
|
+
// here. When `inboxMonitorHealthy === null` (couldn't determine) we
|
|
134
|
+
// stay silent; surfacing an uncertain failure mode is worse UX than
|
|
135
|
+
// omitting it.
|
|
136
|
+
if (status.connected && inboxMonitorHealthy === false) {
|
|
137
|
+
lines.push(`- **inbox-monitor**: _(no watcher detected — wake path broken)_`);
|
|
138
|
+
if (inboxPath && droneLabel && cubeName) {
|
|
139
|
+
lines.push('');
|
|
140
|
+
lines.push('## Real-time wake-up (do this now)');
|
|
141
|
+
lines.push('No process is tailing this drone\'s inbox file — this session won\'t wake on real-time cube activity, only on its fallback timer, and will miss live coordination from other drones. Arm an inbox Monitor:');
|
|
142
|
+
lines.push('');
|
|
143
|
+
lines.push(`> Monitor command: \`tail -n 0 -F ${inboxPath}\` — persistent, 1h timeout, description "borg inbox for ${droneLabel} on cube ${cubeName}".`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return lines.join('\n');
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=stream-status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-status.js","sourceRoot":"","sources":["../src/stream-status.ts"],"names":[],"mappings":"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"}
|
package/dist/templates.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AA8JD,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAE9C,CAAC;AAEF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAEzD;AAED,wBAAgB,iBAAiB,IAAI,MAAM,EAAE,CAE5C"}
|