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.
- package/dist/{chunk-QHJZIGEE.js → chunk-3A4B7CUH.js} +265 -72
- package/dist/chunk-3A4B7CUH.js.map +1 -0
- package/dist/{chunk-DENXSVKE.js → chunk-GQQUH3TA.js} +7 -2
- package/dist/chunk-GQQUH3TA.js.map +1 -0
- package/dist/{cron-F6D6475M.js → cron-VZMNM2XT.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/lib/gate-proxy-server.js +1 -1
- package/dist/{pipeline-GZOSDNPF.js → pipeline-NRG2Q2TE.js} +223 -203
- package/dist/pipeline-NRG2Q2TE.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-DENXSVKE.js.map +0 -1
- package/dist/chunk-QHJZIGEE.js.map +0 -1
- package/dist/pipeline-GZOSDNPF.js.map +0 -1
- /package/dist/{cron-F6D6475M.js.map → cron-VZMNM2XT.js.map} +0 -0
|
@@ -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(
|
|
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 =
|
|
1147
|
+
const dir = join2(homedir2(), ".kantban", "pipelines", boardId, String(process.pid));
|
|
1002
1148
|
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
1003
|
-
const filePath =
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
|
1100
|
-
import { homedir as
|
|
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:
|
|
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
|
-
|
|
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 =
|
|
1502
|
+
const dir = join3(homedir3(), ".kantban", "tmp");
|
|
1317
1503
|
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
1318
|
-
const filePath =
|
|
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-
|
|
1530
|
+
//# sourceMappingURL=chunk-3A4B7CUH.js.map
|