kantban-cli 0.1.49 → 0.1.52

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.
@@ -5,10 +5,155 @@ import {
5
5
  crossSpawnOptions,
6
6
  defaultPath,
7
7
  killProcessTree,
8
+ normalizeEol,
8
9
  npxCommand,
9
10
  resolveCommand
10
11
  } from "./chunk-5ZU2OOES.js";
11
12
 
13
+ // src/lib/worktree.ts
14
+ import { execFile as defaultExecFile, execFileSync } from "child_process";
15
+ import { homedir } from "os";
16
+ import { join } from "path";
17
+ function generateWorktreeName(ticketNumber, columnName) {
18
+ const slug = columnSlug(columnName);
19
+ return `kantban-${ticketNumber}-${slug}`;
20
+ }
21
+ function columnSlug(columnName) {
22
+ return columnName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
23
+ }
24
+ function renderWorktreePath(ticketNumber, columnName, pathPattern, options) {
25
+ const slug = columnSlug(columnName);
26
+ const worktreeName = generateWorktreeName(ticketNumber, columnName);
27
+ if (pathPattern) {
28
+ return pathPattern.replace(/\{ticket_number\}/g, String(ticketNumber)).replace(/\{column_slug\}/g, slug).replace(/\{worktree_name\}/g, worktreeName);
29
+ }
30
+ return join(options.defaultRoot, options.boardId, slug, String(ticketNumber));
31
+ }
32
+ function defaultWorktreeRoot() {
33
+ return join(homedir(), ".kantban", "worktrees");
34
+ }
35
+ function isPlausibleRemoteUrl(url) {
36
+ return url.includes("/") || url.includes("@") || url.includes("://");
37
+ }
38
+ function ensureWorktreeRemote(worktreePath) {
39
+ try {
40
+ const remotes = normalizeEol(execFileSync("git", ["-C", worktreePath, "remote"], {
41
+ stdio: "pipe",
42
+ encoding: "utf-8"
43
+ })).trim();
44
+ if (remotes.split("\n").includes("origin")) {
45
+ const currentUrl = normalizeEol(execFileSync("git", ["-C", worktreePath, "remote", "get-url", "origin"], {
46
+ stdio: "pipe",
47
+ encoding: "utf-8"
48
+ })).trim();
49
+ if (isPlausibleRemoteUrl(currentUrl)) return;
50
+ console.error(`[worktree] Invalid origin URL in ${worktreePath}: "${currentUrl}" \u2014 fixing`);
51
+ execFileSync("git", ["-C", worktreePath, "remote", "remove", "origin"], {
52
+ stdio: "pipe"
53
+ });
54
+ }
55
+ const originUrl = normalizeEol(execFileSync("git", ["remote", "get-url", "origin"], {
56
+ stdio: "pipe",
57
+ encoding: "utf-8"
58
+ })).trim();
59
+ if (originUrl && isPlausibleRemoteUrl(originUrl)) {
60
+ execFileSync("git", ["-C", worktreePath, "remote", "add", "origin", originUrl], {
61
+ stdio: "pipe"
62
+ });
63
+ console.error(`[worktree] Added missing origin remote to ${worktreePath}: ${originUrl}`);
64
+ } else {
65
+ console.error(`[worktree] WARNING: main repo origin URL is also invalid: "${originUrl}"`);
66
+ }
67
+ } catch (err) {
68
+ const msg = err instanceof Error ? err.message : String(err);
69
+ console.error(`[worktree] Failed to ensure remote for ${worktreePath}: ${msg}`);
70
+ }
71
+ }
72
+ async function cleanupWorktree(worktreeName, exec = defaultExecFile) {
73
+ let target = worktreeName;
74
+ try {
75
+ const path = await findWorktreeForBranch(exec, worktreeName);
76
+ if (!path) return true;
77
+ target = path;
78
+ } catch {
79
+ return true;
80
+ }
81
+ return new Promise((resolve) => {
82
+ exec("git", ["worktree", "remove", "--force", target], (err) => {
83
+ if (err) {
84
+ console.error(`[worktree] cleanup failed for ${worktreeName} (${target}): ${err.message}`);
85
+ resolve(false);
86
+ } else {
87
+ resolve(true);
88
+ }
89
+ });
90
+ });
91
+ }
92
+ function execPromise(exec, cmd, args) {
93
+ return new Promise((resolve, reject) => {
94
+ exec(cmd, args, (err, stdout, stderr) => {
95
+ if (err) reject(Object.assign(err, { stdout, stderr }));
96
+ else resolve({ stdout, stderr });
97
+ });
98
+ });
99
+ }
100
+ async function findWorktreeForBranch(exec, branch) {
101
+ try {
102
+ const { stdout } = await execPromise(exec, "git", ["worktree", "list", "--porcelain"]);
103
+ const targetRef = `refs/heads/${branch}`;
104
+ let currentPath = null;
105
+ for (const line of normalizeEol(stdout).split("\n")) {
106
+ if (line.startsWith("worktree ")) currentPath = line.slice("worktree ".length);
107
+ if (line.startsWith("branch ") && line.slice("branch ".length) === targetRef && currentPath) {
108
+ return currentPath;
109
+ }
110
+ }
111
+ return null;
112
+ } catch {
113
+ return null;
114
+ }
115
+ }
116
+ async function mergeWorktreeBranch(worktreeName, integrationBranch, exec = defaultExecFile) {
117
+ try {
118
+ try {
119
+ await execPromise(exec, "git", ["rev-parse", "--verify", worktreeName]);
120
+ } catch {
121
+ return true;
122
+ }
123
+ await execPromise(exec, "git", ["branch", integrationBranch, "HEAD"]).catch(() => {
124
+ });
125
+ const checkedOutPath = await findWorktreeForBranch(exec, integrationBranch);
126
+ if (checkedOutPath) {
127
+ await execPromise(exec, "git", ["-C", checkedOutPath, "merge", "--no-edit", worktreeName]);
128
+ console.error(`[worktree] merged ${worktreeName} \u2192 ${integrationBranch}`);
129
+ return true;
130
+ }
131
+ const { stdout: baseOut } = await execPromise(exec, "git", ["merge-base", integrationBranch, worktreeName]);
132
+ const mergeBase = baseOut.trim();
133
+ const { stdout: integrationSha } = await execPromise(exec, "git", ["rev-parse", integrationBranch]);
134
+ if (integrationSha.trim() === mergeBase) {
135
+ const { stdout: worktreeSha } = await execPromise(exec, "git", ["rev-parse", worktreeName]);
136
+ await execPromise(exec, "git", ["update-ref", `refs/heads/${integrationBranch}`, worktreeSha.trim()]);
137
+ console.error(`[worktree] fast-forward merged ${worktreeName} \u2192 ${integrationBranch}`);
138
+ return true;
139
+ }
140
+ const tmpWorktree = `merge-tmp-${Date.now()}`;
141
+ try {
142
+ await execPromise(exec, "git", ["worktree", "add", tmpWorktree, integrationBranch]);
143
+ await execPromise(exec, "git", ["-C", tmpWorktree, "merge", "--no-edit", worktreeName]);
144
+ console.error(`[worktree] merged ${worktreeName} \u2192 ${integrationBranch}`);
145
+ } finally {
146
+ await execPromise(exec, "git", ["worktree", "remove", "--force", tmpWorktree]).catch(() => {
147
+ });
148
+ }
149
+ return true;
150
+ } catch (err) {
151
+ const msg = err instanceof Error ? err.message : String(err);
152
+ console.error(`[worktree] merge failed for ${worktreeName} \u2192 ${integrationBranch}: ${msg}`);
153
+ return false;
154
+ }
155
+ }
156
+
12
157
  // src/lib/stuck-detector.ts
13
158
  import { z } from "zod";
14
159
 
@@ -148,6 +293,53 @@ function classifyTrajectory(snapshots) {
148
293
  return { status: "spinning", evidence: "no improvement detected", confidence: 1 };
149
294
  }
150
295
 
296
+ // src/lib/git-state.ts
297
+ import { execFile as execFileCb } from "child_process";
298
+ import { promisify } from "util";
299
+ var realExecFile = promisify(execFileCb);
300
+ var _execFile = realExecFile;
301
+ async function detectBranchMerged(opts) {
302
+ const { worktreePath, iterationStartedAt } = opts;
303
+ const fetchTimeoutMs = opts.fetchTimeoutMs ?? 15e3;
304
+ const targetBranch = opts.targetBranch ?? "main";
305
+ try {
306
+ const { stdout: headOut } = await _execFile("git", ["-C", worktreePath, "rev-parse", "HEAD"]);
307
+ const headSha = headOut.trim();
308
+ if (!headSha) return { merged: false };
309
+ try {
310
+ await _execFile("git", ["-C", worktreePath, "fetch", "origin", targetBranch], { timeout: fetchTimeoutMs });
311
+ } catch {
312
+ return { merged: false };
313
+ }
314
+ try {
315
+ await _execFile("git", ["-C", worktreePath, "merge-base", "--is-ancestor", headSha, `origin/${targetBranch}`]);
316
+ } catch {
317
+ return { merged: false };
318
+ }
319
+ const { stdout: logOut } = await _execFile("git", [
320
+ "-C",
321
+ worktreePath,
322
+ "log",
323
+ `origin/${targetBranch}`,
324
+ "--ancestry-path",
325
+ `${headSha}..origin/${targetBranch}`,
326
+ "--reverse",
327
+ "--format=%H %cI",
328
+ "-1"
329
+ ]);
330
+ const line = logOut.trim().split("\n")[0] ?? "";
331
+ const spaceIdx = line.indexOf(" ");
332
+ if (spaceIdx < 0) return { merged: false };
333
+ const mergeCommitSha = line.slice(0, spaceIdx);
334
+ const mergeCommitTime = new Date(line.slice(spaceIdx + 1));
335
+ if (Number.isNaN(mergeCommitTime.getTime())) return { merged: false };
336
+ if (mergeCommitTime.getTime() < iterationStartedAt.getTime()) return { merged: false };
337
+ return { merged: true, mergeCommitSha, mergeCommitTime };
338
+ } catch {
339
+ return { merged: false };
340
+ }
341
+ }
342
+
151
343
  // src/lib/prompt-composer.ts
152
344
  var PROMPT_BUDGETS = {
153
345
  system_preamble: 800,
@@ -539,52 +731,6 @@ ${ticketContext.ticket.description}`);
539
731
  return parts.join("\n");
540
732
  }
541
733
 
542
- // src/lib/git-state.ts
543
- import { execFile as execFileCb } from "child_process";
544
- import { promisify } from "util";
545
- var realExecFile = promisify(execFileCb);
546
- var _execFile = realExecFile;
547
- async function detectBranchMerged(opts) {
548
- const { worktreePath, iterationStartedAt } = opts;
549
- const fetchTimeoutMs = opts.fetchTimeoutMs ?? 15e3;
550
- try {
551
- const { stdout: headOut } = await _execFile("git", ["-C", worktreePath, "rev-parse", "HEAD"]);
552
- const headSha = headOut.trim();
553
- if (!headSha) return { merged: false };
554
- try {
555
- await _execFile("git", ["-C", worktreePath, "fetch", "origin", "main"], { timeout: fetchTimeoutMs });
556
- } catch {
557
- return { merged: false };
558
- }
559
- try {
560
- await _execFile("git", ["-C", worktreePath, "merge-base", "--is-ancestor", headSha, "origin/main"]);
561
- } catch {
562
- return { merged: false };
563
- }
564
- const { stdout: logOut } = await _execFile("git", [
565
- "-C",
566
- worktreePath,
567
- "log",
568
- "origin/main",
569
- "--ancestry-path",
570
- `${headSha}..origin/main`,
571
- "--reverse",
572
- "--format=%H %cI",
573
- "-1"
574
- ]);
575
- const line = logOut.trim().split("\n")[0] ?? "";
576
- const spaceIdx = line.indexOf(" ");
577
- if (spaceIdx < 0) return { merged: false };
578
- const mergeCommitSha = line.slice(0, spaceIdx);
579
- const mergeCommitTime = new Date(line.slice(spaceIdx + 1));
580
- if (Number.isNaN(mergeCommitTime.getTime())) return { merged: false };
581
- if (mergeCommitTime.getTime() < iterationStartedAt.getTime()) return { merged: false };
582
- return { merged: true, mergeCommitSha, mergeCommitTime };
583
- } catch {
584
- return { merged: false };
585
- }
586
- }
587
-
588
734
  // src/lib/ralph-loop.ts
589
735
  var API_TIMEOUT_MS = 3e4;
590
736
  function withTimeout(promise, ms, label) {
@@ -970,13 +1116,13 @@ function fingerprintsMatch(a, b) {
970
1116
 
971
1117
  // src/lib/mcp-config.ts
972
1118
  import { writeFileSync, unlinkSync, mkdirSync, existsSync, readdirSync, rmdirSync, rmSync } from "fs";
973
- import { join, dirname } from "path";
1119
+ import { join as join2, dirname } from "path";
974
1120
  import { fileURLToPath } from "url";
975
- import { homedir } from "os";
1121
+ import { homedir as homedir2 } from "os";
976
1122
  var __filename = fileURLToPath(import.meta.url);
977
1123
  var __dirname = dirname(__filename);
978
1124
  function generateMcpConfig(apiUrl, apiToken, boardId) {
979
- const localMcpPath = existsSync(join(__dirname, "..", "..", "mcp", "dist", "index.js")) ? join(__dirname, "..", "..", "mcp", "dist", "index.js") : join(__dirname, "..", "..", "..", "mcp", "dist", "index.js");
1125
+ const localMcpPath = existsSync(join2(__dirname, "..", "..", "mcp", "dist", "index.js")) ? join2(__dirname, "..", "..", "mcp", "dist", "index.js") : join2(__dirname, "..", "..", "..", "mcp", "dist", "index.js");
980
1126
  const useLocal = existsSync(localMcpPath);
981
1127
  const kantbanServer = useLocal ? {
982
1128
  command: "node",
@@ -998,9 +1144,9 @@ function generateMcpConfig(apiUrl, apiToken, boardId) {
998
1144
  kantban: kantbanServer
999
1145
  }
1000
1146
  };
1001
- const dir = join(homedir(), ".kantban", "pipelines", boardId, String(process.pid));
1147
+ const dir = join2(homedir2(), ".kantban", "pipelines", boardId, String(process.pid));
1002
1148
  mkdirSync(dir, { recursive: true, mode: 448 });
1003
- const filePath = join(dir, "mcp-config.json");
1149
+ const filePath = join2(dir, "mcp-config.json");
1004
1150
  writeFileSync(filePath, JSON.stringify(config, null, 2), { mode: 384 });
1005
1151
  return filePath;
1006
1152
  }
@@ -1017,10 +1163,10 @@ function cleanupMcpConfig(filePath) {
1017
1163
  }
1018
1164
  }
1019
1165
  function generateGateProxyMcpConfig(apiUrl, apiToken, boardId, gateConfigPath, columnId, columnName, projectId, gateCwd, ticketId) {
1020
- const localMcpPath = existsSync(join(__dirname, "..", "..", "mcp", "dist", "index.js")) ? join(__dirname, "..", "..", "mcp", "dist", "index.js") : join(__dirname, "..", "..", "..", "mcp", "dist", "index.js");
1166
+ const localMcpPath = existsSync(join2(__dirname, "..", "..", "mcp", "dist", "index.js")) ? join2(__dirname, "..", "..", "mcp", "dist", "index.js") : join2(__dirname, "..", "..", "..", "mcp", "dist", "index.js");
1021
1167
  const useLocal = existsSync(localMcpPath);
1022
1168
  const kantbanServer = useLocal ? { command: "node", args: [localMcpPath], env: { KANTBAN_API_TOKEN: apiToken, KANTBAN_API_URL: apiUrl, KANTBAN_HIDDEN_TOOLS: "kantban_move_ticket,kantban_move_tickets,kantban_complete_task,kantban_move_to_board" } } : { command: npxCommand(), args: ["-y", "kantban-mcp@latest"], env: { KANTBAN_API_TOKEN: apiToken, KANTBAN_API_URL: apiUrl, KANTBAN_HIDDEN_TOOLS: "kantban_move_ticket,kantban_move_tickets,kantban_complete_task,kantban_move_to_board" } };
1023
- const gateProxyPath = existsSync(join(__dirname, "lib", "gate-proxy-server.js")) ? join(__dirname, "lib", "gate-proxy-server.js") : join(__dirname, "gate-proxy-server.js");
1169
+ const gateProxyPath = existsSync(join2(__dirname, "lib", "gate-proxy-server.js")) ? join2(__dirname, "lib", "gate-proxy-server.js") : join2(__dirname, "gate-proxy-server.js");
1024
1170
  const gateProxyEnv = {
1025
1171
  GATE_CONFIG_PATH: gateConfigPath,
1026
1172
  COLUMN_ID: columnId,
@@ -1031,7 +1177,8 @@ function generateGateProxyMcpConfig(apiUrl, apiToken, boardId, gateConfigPath, c
1031
1177
  // Ensure gate commands can find npm/node even if the agent CLI spawns
1032
1178
  // MCP servers with a replacement env instead of merging with process.env.
1033
1179
  // Without this, gate-runner's `sh -c "npm run ..."` fails with ENOENT.
1034
- PATH: process.env.PATH ?? defaultPath()
1180
+ PATH: process.env.PATH ?? defaultPath(),
1181
+ SHELL: process.env.SHELL ?? "/bin/bash"
1035
1182
  };
1036
1183
  if (gateCwd) gateProxyEnv["GATE_CWD"] = gateCwd;
1037
1184
  const gateProxyServer = {
@@ -1045,10 +1192,10 @@ function generateGateProxyMcpConfig(apiUrl, apiToken, boardId, gateConfigPath, c
1045
1192
  "kantban-gates": gateProxyServer
1046
1193
  }
1047
1194
  };
1048
- const dir = join(homedir(), ".kantban", "pipelines", boardId, String(process.pid));
1195
+ const dir = join2(homedir2(), ".kantban", "pipelines", boardId, String(process.pid));
1049
1196
  mkdirSync(dir, { recursive: true, mode: 448 });
1050
1197
  const suffix = ticketId ? `${columnId}-${ticketId}` : columnId;
1051
- const filePath = join(dir, `mcp-config-${suffix}.json`);
1198
+ const filePath = join2(dir, `mcp-config-${suffix}.json`);
1052
1199
  writeFileSync(filePath, JSON.stringify(config, null, 2), { mode: 384 });
1053
1200
  return filePath;
1054
1201
  }
@@ -1058,7 +1205,7 @@ function cleanupGateProxyConfigs(pipelineDir) {
1058
1205
  for (const f of files) {
1059
1206
  if (f.startsWith("mcp-config-") && f.endsWith(".json")) {
1060
1207
  try {
1061
- unlinkSync(join(pipelineDir, f));
1208
+ unlinkSync(join2(pipelineDir, f));
1062
1209
  } catch {
1063
1210
  }
1064
1211
  }
@@ -1086,7 +1233,7 @@ function reapOrphanedMcpConfigDirs(boardDir) {
1086
1233
  }
1087
1234
  if (!alive) {
1088
1235
  try {
1089
- rmSync(join(boardDir, entry), { recursive: true, force: true });
1236
+ rmSync(join2(boardDir, entry), { recursive: true, force: true });
1090
1237
  } catch {
1091
1238
  }
1092
1239
  }
@@ -1094,10 +1241,10 @@ function reapOrphanedMcpConfigDirs(boardDir) {
1094
1241
  }
1095
1242
 
1096
1243
  // src/providers/claude-provider.ts
1097
- import { spawn } from "child_process";
1098
- import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
1099
- import { join as join2 } from "path";
1100
- import { homedir as homedir2 } from "os";
1244
+ import { spawn, execFileSync as execFileSync2 } from "child_process";
1245
+ import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2, rmSync as rmSync2 } from "fs";
1246
+ import { join as join3 } from "path";
1247
+ import { homedir as homedir3 } from "os";
1101
1248
 
1102
1249
  // src/providers/claude-stream-parser.ts
1103
1250
  var ClaudeStreamParser = class {
@@ -1200,7 +1347,7 @@ var ClaudeProvider = class {
1200
1347
  supportsMaxTurns: true,
1201
1348
  supportsMcpConfigInjection: true,
1202
1349
  supportsMcpConfigOverride: false,
1203
- supportsWorktreeFlag: true,
1350
+ supportsWorktreeFlag: false,
1204
1351
  supportsSandboxModes: false,
1205
1352
  supportedModels: [
1206
1353
  { id: "claude-haiku-4-5-20251001", displayName: "Haiku 4.5", tier: "fast" },
@@ -1213,13 +1360,56 @@ var ClaudeProvider = class {
1213
1360
  async invoke(request) {
1214
1361
  const args = this.buildArgs(request);
1215
1362
  const startTime = Date.now();
1363
+ if (request.workingDirectory) {
1364
+ const branch = request.branch ?? request.workingDirectory;
1365
+ if (!existsSync2(request.workingDirectory)) {
1366
+ try {
1367
+ execFileSync2("git", ["worktree", "add", "-b", branch, request.workingDirectory, "HEAD"], {
1368
+ stdio: "pipe"
1369
+ });
1370
+ } catch {
1371
+ try {
1372
+ execFileSync2("git", ["worktree", "add", request.workingDirectory, branch], {
1373
+ stdio: "pipe"
1374
+ });
1375
+ } catch (err) {
1376
+ const msg = err instanceof Error ? err.message : String(err);
1377
+ throw new Error(`worktree_creation_failed: ${msg}`);
1378
+ }
1379
+ }
1380
+ } else {
1381
+ try {
1382
+ execFileSync2("git", ["-C", request.workingDirectory, "rev-parse", "--git-dir"], {
1383
+ stdio: "pipe"
1384
+ });
1385
+ } catch {
1386
+ try {
1387
+ rmSync2(request.workingDirectory, { recursive: true, force: true });
1388
+ execFileSync2("git", ["worktree", "add", "-b", branch, request.workingDirectory, "HEAD"], {
1389
+ stdio: "pipe"
1390
+ });
1391
+ } catch {
1392
+ try {
1393
+ execFileSync2("git", ["worktree", "add", request.workingDirectory, branch], {
1394
+ stdio: "pipe"
1395
+ });
1396
+ } catch (err) {
1397
+ const msg = err instanceof Error ? err.message : String(err);
1398
+ throw new Error(`worktree_creation_failed: ${msg}`);
1399
+ }
1400
+ }
1401
+ }
1402
+ }
1403
+ if (existsSync2(request.workingDirectory)) {
1404
+ ensureWorktreeRemote(request.workingDirectory);
1405
+ }
1406
+ }
1216
1407
  const [cmd, prefixArgs] = resolveCommand("claude");
1217
1408
  const resolvedArgs = [...prefixArgs, ...args];
1218
1409
  return new Promise((resolve) => {
1219
1410
  const child = spawn(cmd, resolvedArgs, {
1220
1411
  stdio: ["pipe", "pipe", "pipe"],
1221
- // Do NOT set cwd to the worktree name — the worktree doesn't exist yet.
1222
- // Claude Code's --worktree flag creates it internally.
1412
+ ...request.workingDirectory ? { cwd: request.workingDirectory } : {},
1223
1413
  // Only use shell:true as fallback when resolveCommand couldn't resolve
1224
1414
  ...prefixArgs.length > 0 ? {} : crossSpawnOptions()
1225
1415
  });
@@ -1298,10 +1488,6 @@ var ClaudeProvider = class {
1298
1488
  if (request.maxTurns) {
1299
1489
  args.push("--max-turns", String(request.maxTurns));
1300
1490
  }
1301
- const worktreeName = request.branch ?? request.workingDirectory;
1302
- if (worktreeName) {
1303
- args.push("--worktree", worktreeName);
1304
- }
1305
1491
  if (request.toolRestrictions) {
1306
1492
  const tr = request.toolRestrictions;
1307
1493
  if (tr.tools !== void 0) args.push("--tools", tr.tools);
@@ -1313,19 +1499,26 @@ var ClaudeProvider = class {
1313
1499
  writeMcpConfigJson(mcpConfig) {
1314
1500
  if (!mcpConfig) return "";
1315
1501
  const config = { mcpServers: mcpConfig.servers };
1316
- const dir = join2(homedir2(), ".kantban", "tmp");
1502
+ const dir = join3(homedir3(), ".kantban", "tmp");
1317
1503
  mkdirSync2(dir, { recursive: true, mode: 448 });
1318
- const filePath = join2(dir, `mcp-config-${Date.now()}.json`);
1504
+ const filePath = join3(dir, `mcp-config-${Date.now()}.json`);
1319
1505
  writeFileSync2(filePath, JSON.stringify(config, null, 2), { mode: 384 });
1320
1506
  return filePath;
1321
1507
  }
1322
1508
  };
1323
1509
 
1324
1510
  export {
1511
+ generateWorktreeName,
1512
+ renderWorktreePath,
1513
+ defaultWorktreeRoot,
1514
+ ensureWorktreeRemote,
1515
+ cleanupWorktree,
1516
+ mergeWorktreeBranch,
1325
1517
  parseJsonFromLlmOutput,
1326
1518
  composeStuckDetectionPrompt,
1327
1519
  parseStuckDetectionResponse,
1328
1520
  classifyTrajectory,
1521
+ detectBranchMerged,
1329
1522
  RalphLoop,
1330
1523
  generateMcpConfig,
1331
1524
  cleanupMcpConfig,
@@ -1334,4 +1527,4 @@ export {
1334
1527
  reapOrphanedMcpConfigDirs,
1335
1528
  ClaudeProvider
1336
1529
  };
1337
- //# sourceMappingURL=chunk-QHJZIGEE.js.map
1530
+ //# sourceMappingURL=chunk-3A4B7CUH.js.map