kantban-cli 0.1.24 → 0.1.26

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/index.js CHANGED
@@ -31,10 +31,10 @@ async function main() {
31
31
  }
32
32
  case "pipeline": {
33
33
  if (args[0] === "stop") {
34
- const { stopPipeline } = await import("./pipeline-TEE6GBNZ.js");
34
+ const { stopPipeline } = await import("./pipeline-ETDPJOYI.js");
35
35
  await stopPipeline(args.slice(1));
36
36
  } else {
37
- const { runPipeline } = await import("./pipeline-TEE6GBNZ.js");
37
+ const { runPipeline } = await import("./pipeline-ETDPJOYI.js");
38
38
  await runPipeline(client, args);
39
39
  }
40
40
  break;
@@ -22,9 +22,9 @@ import {
22
22
  } from "./chunk-ZTQJMXJM.js";
23
23
 
24
24
  // src/commands/pipeline.ts
25
- import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3, readFileSync as readFileSync2, unlinkSync as unlinkSync2, existsSync as existsSync2, appendFileSync as appendFileSync2 } from "fs";
26
- import { homedir } from "os";
27
- import { join as join2 } from "path";
25
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readFileSync as readFileSync3, unlinkSync as unlinkSync3, existsSync as existsSync3, appendFileSync as appendFileSync2 } from "fs";
26
+ import { homedir as homedir2 } from "os";
27
+ import { join as join3 } from "path";
28
28
 
29
29
  // src/lib/tool-profiles.ts
30
30
  function resolveToolRestrictions(builtinTools, allowedTools, disallowedTools) {
@@ -3448,15 +3448,451 @@ var CodexProvider = class {
3448
3448
  }
3449
3449
  };
3450
3450
 
3451
+ // src/providers/gemini-provider.ts
3452
+ import { spawn as spawn3, execFileSync as execFileSync3 } from "child_process";
3453
+ import { existsSync as existsSync2, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
3454
+ import { join as join2, dirname } from "path";
3455
+ import { homedir } from "os";
3456
+ import { fileURLToPath } from "url";
3457
+ import { readFileSync as readFileSync2 } from "fs";
3458
+
3459
+ // src/providers/gemini-jsonl-parser.ts
3460
+ var GeminiJsonlParser = class {
3461
+ buffer = "";
3462
+ /** Accumulates lines that failed individual JSON parse (multi-line object) */
3463
+ jsonAccum = "";
3464
+ toolCallCount = 0;
3465
+ inputTokens = 0;
3466
+ outputTokens = 0;
3467
+ lastOutput = "";
3468
+ onEvent = () => {
3469
+ };
3470
+ onError = () => {
3471
+ };
3472
+ feed(chunk) {
3473
+ this.buffer += chunk;
3474
+ const lines = this.buffer.split("\n");
3475
+ this.buffer = lines.pop() ?? "";
3476
+ for (const line of lines) {
3477
+ this.parseLine(line.trim());
3478
+ }
3479
+ }
3480
+ flush() {
3481
+ const trimmed = this.buffer.trim();
3482
+ this.buffer = "";
3483
+ if (trimmed) this.parseLine(trimmed);
3484
+ this.tryFlushAccum();
3485
+ }
3486
+ getToolCallCount() {
3487
+ return this.toolCallCount;
3488
+ }
3489
+ getUsage() {
3490
+ return { inputTokens: this.inputTokens, outputTokens: this.outputTokens };
3491
+ }
3492
+ getLastOutput() {
3493
+ return this.lastOutput;
3494
+ }
3495
+ reset() {
3496
+ this.buffer = "";
3497
+ this.toolCallCount = 0;
3498
+ this.inputTokens = 0;
3499
+ this.outputTokens = 0;
3500
+ this.lastOutput = "";
3501
+ }
3502
+ parseLine(line) {
3503
+ if (!line) return;
3504
+ if (this.jsonAccum) {
3505
+ this.jsonAccum += "\n" + line;
3506
+ if (this.tryFlushAccum()) return;
3507
+ return;
3508
+ }
3509
+ try {
3510
+ const raw = JSON.parse(line);
3511
+ this.translateEvent(raw);
3512
+ } catch {
3513
+ if (line.startsWith("{") || line.startsWith("[")) {
3514
+ this.jsonAccum = line;
3515
+ }
3516
+ }
3517
+ }
3518
+ /** Try to parse the accumulated multi-line buffer as JSON. Returns true if successful. */
3519
+ tryFlushAccum() {
3520
+ if (!this.jsonAccum) return false;
3521
+ try {
3522
+ const raw = JSON.parse(this.jsonAccum);
3523
+ this.jsonAccum = "";
3524
+ this.translateEvent(raw);
3525
+ return true;
3526
+ } catch {
3527
+ return false;
3528
+ }
3529
+ }
3530
+ translateEvent(raw) {
3531
+ const eventType = raw.type;
3532
+ if (eventType === "message") {
3533
+ if (raw.role === "assistant" && typeof raw.content === "string") {
3534
+ this.onEvent({ type: "text", text: raw.content });
3535
+ }
3536
+ } else if (eventType === "tool_use") {
3537
+ this.toolCallCount++;
3538
+ this.onEvent({
3539
+ type: "tool_call",
3540
+ tool: raw.name ?? "unknown",
3541
+ input: raw.args ?? raw.input
3542
+ });
3543
+ } else if (eventType === "tool_result") {
3544
+ this.onEvent({
3545
+ type: "tool_result",
3546
+ tool: raw.name ?? "unknown",
3547
+ output: raw.output ?? raw.result
3548
+ });
3549
+ } else if (eventType === "result" || raw.session_id && raw.stats) {
3550
+ const { inTok, outTok } = this.extractTokens(raw);
3551
+ this.inputTokens += inTok;
3552
+ this.outputTokens += outTok;
3553
+ if (inTok || outTok) {
3554
+ this.onEvent({ type: "usage", inputTokens: inTok, outputTokens: outTok });
3555
+ }
3556
+ const tools = raw.stats?.tools;
3557
+ if (tools?.totalCalls) this.toolCallCount = Math.max(this.toolCallCount, tools.totalCalls);
3558
+ if (typeof raw.response === "string") {
3559
+ this.lastOutput = raw.response;
3560
+ this.onEvent({ type: "done", result: raw.response });
3561
+ }
3562
+ } else if (eventType === "error") {
3563
+ const msg = raw.message ?? "unknown error";
3564
+ this.lastOutput = msg;
3565
+ this.onEvent({ type: "error", message: msg });
3566
+ }
3567
+ }
3568
+ /** Extract token counts from Gemini's nested stats structure */
3569
+ extractTokens(raw) {
3570
+ const stats = raw.stats;
3571
+ if (!stats) return { inTok: 0, outTok: 0 };
3572
+ if (stats.promptTokenCount || stats.input_tokens) {
3573
+ return {
3574
+ inTok: stats.promptTokenCount ?? stats.input_tokens ?? 0,
3575
+ outTok: stats.candidatesTokenCount ?? stats.output_tokens ?? 0
3576
+ };
3577
+ }
3578
+ if (stats.models && typeof stats.models === "object") {
3579
+ let inTok = 0;
3580
+ let outTok = 0;
3581
+ for (const model of Object.values(stats.models)) {
3582
+ const tokens = model?.tokens;
3583
+ if (tokens) {
3584
+ inTok += tokens.prompt ?? tokens.input ?? 0;
3585
+ outTok += tokens.candidates ?? tokens.output ?? 0;
3586
+ }
3587
+ }
3588
+ return { inTok, outTok };
3589
+ }
3590
+ return { inTok: 0, outTok: 0 };
3591
+ }
3592
+ };
3593
+
3594
+ // src/providers/gemini-provider.ts
3595
+ var GEMINI_TIMEOUT_MS = 60 * 60 * 1e3;
3596
+ var TOOL_NAME_MAP = {
3597
+ Write: "write_file",
3598
+ Edit: "replace",
3599
+ Read: "read_file",
3600
+ Bash: "run_shell_command",
3601
+ Grep: "grep_search",
3602
+ Glob: "glob",
3603
+ WebFetch: "web_fetch",
3604
+ WebSearch: "google_web_search",
3605
+ LS: "list_directory"
3606
+ };
3607
+ function translateToolNames(tools) {
3608
+ return tools.map((t) => TOOL_NAME_MAP[t] ?? t);
3609
+ }
3610
+ function resolveHookScriptPath() {
3611
+ try {
3612
+ const thisDir = dirname(fileURLToPath(import.meta.url));
3613
+ const candidates = [
3614
+ join2(thisDir, "lib", "gemini-hooks.mjs"),
3615
+ // dist/lib/ (flat bundle)
3616
+ join2(thisDir, "..", "lib", "gemini-hooks.mjs"),
3617
+ // dist/../lib/ (nested)
3618
+ join2(thisDir, "..", "src", "lib", "gemini-hooks.mjs"),
3619
+ // source from dist/
3620
+ join2(thisDir, "..", "..", "src", "lib", "gemini-hooks.mjs")
3621
+ // source from dist/providers/
3622
+ ];
3623
+ for (const p of candidates) {
3624
+ if (existsSync2(p)) return p;
3625
+ }
3626
+ return null;
3627
+ } catch {
3628
+ return null;
3629
+ }
3630
+ }
3631
+ var GeminiProvider = class _GeminiProvider {
3632
+ id = "gemini";
3633
+ displayName = "Gemini CLI";
3634
+ capabilities() {
3635
+ return {
3636
+ supportsToolAllowlist: true,
3637
+ supportsToolDenylist: true,
3638
+ supportsBuiltinToolStripping: true,
3639
+ supportsMaxTurns: true,
3640
+ supportsMcpConfigInjection: false,
3641
+ supportsMcpConfigOverride: true,
3642
+ supportsWorktreeFlag: false,
3643
+ supportsSandboxModes: true,
3644
+ supportedModels: [
3645
+ { id: "gemini-2.5-flash-lite", displayName: "Flash Lite", tier: "fast" },
3646
+ { id: "gemini-2.5-flash", displayName: "Flash", tier: "default" },
3647
+ { id: "gemini-2.5-pro", displayName: "Pro", tier: "thorough" }
3648
+ ],
3649
+ streamFormat: "jsonl"
3650
+ };
3651
+ }
3652
+ async invoke(request) {
3653
+ const degraded = [];
3654
+ const args = this.buildArgs(request);
3655
+ const startTime = Date.now();
3656
+ if (request.workingDirectory) {
3657
+ if (!existsSync2(request.workingDirectory)) {
3658
+ try {
3659
+ execFileSync3("git", ["worktree", "add", "-b", request.workingDirectory, request.workingDirectory, "HEAD"], {
3660
+ stdio: "pipe"
3661
+ });
3662
+ } catch {
3663
+ try {
3664
+ execFileSync3("git", ["worktree", "add", request.workingDirectory, request.workingDirectory], {
3665
+ stdio: "pipe"
3666
+ });
3667
+ } catch {
3668
+ degraded.push("worktreeCreation");
3669
+ }
3670
+ }
3671
+ } else {
3672
+ try {
3673
+ execFileSync3("git", ["-C", request.workingDirectory, "rev-parse", "--git-dir"], {
3674
+ stdio: "pipe"
3675
+ });
3676
+ } catch {
3677
+ try {
3678
+ execFileSync3("rm", ["-rf", request.workingDirectory], { stdio: "pipe" });
3679
+ execFileSync3("git", ["worktree", "add", "-b", request.workingDirectory, request.workingDirectory, "HEAD"], {
3680
+ stdio: "pipe"
3681
+ });
3682
+ } catch {
3683
+ try {
3684
+ execFileSync3("git", ["worktree", "add", request.workingDirectory, request.workingDirectory], {
3685
+ stdio: "pipe"
3686
+ });
3687
+ } catch {
3688
+ degraded.push("worktreeCreation");
3689
+ }
3690
+ }
3691
+ }
3692
+ }
3693
+ if (!degraded.includes("worktreeCreation") && existsSync2(request.workingDirectory)) {
3694
+ ensureWorktreeRemote(request.workingDirectory);
3695
+ }
3696
+ }
3697
+ const settingsDir = this.writeGeminiSettings(request, degraded);
3698
+ const cwd = request.workingDirectory ?? settingsDir;
3699
+ if (request.workingDirectory && settingsDir) {
3700
+ this.copySettingsToDir(settingsDir, request.workingDirectory);
3701
+ }
3702
+ return new Promise((resolve) => {
3703
+ const child = spawn3("gemini", args, {
3704
+ stdio: ["pipe", "pipe", "pipe"],
3705
+ cwd: cwd || void 0
3706
+ });
3707
+ const parser = new GeminiJsonlParser();
3708
+ parser.onEvent = (event) => request.onStreamEvent?.(event);
3709
+ parser.onError = (err) => process.stderr.write(`[gemini-jsonl] ${err.message}
3710
+ `);
3711
+ let stderr = "";
3712
+ child.stdout?.on("data", (chunk) => parser.feed(chunk.toString()));
3713
+ child.stderr?.on("data", (chunk) => {
3714
+ stderr += chunk.toString();
3715
+ });
3716
+ child.stdin?.end();
3717
+ if (request.abortSignal) {
3718
+ request.abortSignal.addEventListener("abort", () => {
3719
+ try {
3720
+ child.kill("SIGTERM");
3721
+ } catch {
3722
+ }
3723
+ }, { once: true });
3724
+ }
3725
+ let killTimer;
3726
+ const timeoutHandle = setTimeout(() => {
3727
+ try {
3728
+ child.kill("SIGTERM");
3729
+ } catch {
3730
+ }
3731
+ killTimer = setTimeout(() => {
3732
+ try {
3733
+ child.kill("SIGKILL");
3734
+ } catch {
3735
+ }
3736
+ }, 5e3);
3737
+ }, GEMINI_TIMEOUT_MS);
3738
+ let resolved = false;
3739
+ const finish = (code, errorMsg) => {
3740
+ if (resolved) return;
3741
+ resolved = true;
3742
+ clearTimeout(timeoutHandle);
3743
+ if (killTimer) clearTimeout(killTimer);
3744
+ parser.flush();
3745
+ const usage = parser.getUsage();
3746
+ const rawOutput = errorMsg ?? (parser.getLastOutput() || _GeminiProvider.stripCliPreamble(stderr));
3747
+ const result = {
3748
+ exitCode: (code === 53 ? 0 : code) ?? 1,
3749
+ output: rawOutput,
3750
+ toolCallCount: parser.getToolCallCount(),
3751
+ usage,
3752
+ durationMs: Date.now() - startTime
3753
+ };
3754
+ if (degraded.length > 0) result.degradedCapabilities = degraded;
3755
+ resolve(result);
3756
+ };
3757
+ child.on("close", (code) => finish(code));
3758
+ child.on("error", (err) => finish(1, err.message));
3759
+ });
3760
+ }
3761
+ async preflight() {
3762
+ try {
3763
+ const whichModule = await import("which");
3764
+ const syncFn = whichModule.default?.sync ?? whichModule.sync;
3765
+ syncFn("gemini");
3766
+ return { available: true, authenticated: true };
3767
+ } catch {
3768
+ return { available: false, authenticated: false, error: "gemini binary not found on PATH" };
3769
+ }
3770
+ }
3771
+ buildArgs(request) {
3772
+ const args = [
3773
+ "-p",
3774
+ request.prompt,
3775
+ "--yolo",
3776
+ "--output-format",
3777
+ "json"
3778
+ ];
3779
+ if (request.model) args.push("--model", request.model);
3780
+ return args;
3781
+ }
3782
+ writeGeminiSettings(request, degraded) {
3783
+ try {
3784
+ const baseDir = join2(homedir(), ".kantban", "pipelines", "gemini-tmp");
3785
+ const dir = join2(baseDir, `session-${Date.now()}`);
3786
+ const geminiDir = join2(dir, ".gemini");
3787
+ mkdirSync2(geminiDir, { recursive: true, mode: 448 });
3788
+ const settings = {};
3789
+ if (request.mcpConfig && Object.keys(request.mcpConfig.servers).length > 0) {
3790
+ settings.mcpServers = {};
3791
+ for (const [name, server] of Object.entries(request.mcpConfig.servers)) {
3792
+ settings.mcpServers[name] = {
3793
+ command: server.command,
3794
+ args: server.args,
3795
+ env: server.env,
3796
+ trust: true
3797
+ };
3798
+ }
3799
+ }
3800
+ const hookPath = resolveHookScriptPath();
3801
+ if (hookPath) {
3802
+ const hooks = {};
3803
+ const hookConfig = {};
3804
+ if (request.toolRestrictions) {
3805
+ const tr = request.toolRestrictions;
3806
+ hookConfig.allowedTools = tr.allowedTools ? translateToolNames(tr.allowedTools) : null;
3807
+ hookConfig.disallowedTools = tr.disallowedTools ? translateToolNames(tr.disallowedTools) : null;
3808
+ if (tr.tools !== void 0) hookConfig.builtinToolsMode = tr.tools;
3809
+ const wrapperPath = join2(dir, ".kantban-hook-before-tool.sh");
3810
+ writeFileSync3(wrapperPath, this.generateHookWrapper(hookPath, "BeforeToolSelection", join2(dir, ".kantban-hook-config.json")), { mode: 493 });
3811
+ hooks.BeforeToolSelection = [{ matcher: "*", hooks: [{ type: "command", command: wrapperPath, timeout: 3e4 }] }];
3812
+ }
3813
+ if (request.maxTurns) {
3814
+ hookConfig.maxTurns = request.maxTurns;
3815
+ hookConfig.turnFile = join2(dir, ".kantban-turn-counter");
3816
+ const wrapperPath = join2(dir, ".kantban-hook-after-agent.sh");
3817
+ writeFileSync3(wrapperPath, this.generateHookWrapper(hookPath, "AfterAgent", join2(dir, ".kantban-hook-config.json")), { mode: 493 });
3818
+ hooks.AfterAgent = [{ matcher: "*", hooks: [{ type: "command", command: wrapperPath, timeout: 3e4 }] }];
3819
+ }
3820
+ if (Object.keys(hookConfig).length > 0) {
3821
+ writeFileSync3(join2(dir, ".kantban-hook-config.json"), JSON.stringify(hookConfig), { mode: 384 });
3822
+ }
3823
+ if (Object.keys(hooks).length > 0) {
3824
+ settings.hooks = hooks;
3825
+ }
3826
+ } else {
3827
+ if (request.maxTurns) degraded.push("maxTurns");
3828
+ if (request.toolRestrictions?.allowedTools?.length) degraded.push("toolAllowlist");
3829
+ if (request.toolRestrictions?.disallowedTools?.length) degraded.push("toolDenylist");
3830
+ if (request.toolRestrictions?.tools !== void 0) degraded.push("builtinToolStripping");
3831
+ process.stderr.write(`[gemini] Hook script not found \u2014 tool scoping and turn limits unavailable
3832
+ `);
3833
+ }
3834
+ writeFileSync3(join2(geminiDir, "settings.json"), JSON.stringify(settings, null, 2), { mode: 384 });
3835
+ return dir;
3836
+ } catch (err) {
3837
+ process.stderr.write(`[gemini] Failed to write settings.json: ${err}
3838
+ `);
3839
+ if (request.maxTurns) degraded.push("maxTurns");
3840
+ if (request.toolRestrictions?.allowedTools?.length) degraded.push("toolAllowlist");
3841
+ if (request.toolRestrictions?.disallowedTools?.length) degraded.push("toolDenylist");
3842
+ if (request.toolRestrictions?.tools !== void 0) degraded.push("builtinToolStripping");
3843
+ return null;
3844
+ }
3845
+ }
3846
+ /** Generate a per-session bash wrapper that invokes the Node hook script.
3847
+ * Gemini CLI ignores `args` in hook definitions, so we embed all arguments. */
3848
+ generateHookWrapper(hookScript, event, configFile) {
3849
+ return `#!/bin/bash
3850
+ # KantBan pipeline hook wrapper \u2014 Gemini CLI does not pass args, so we embed them.
3851
+ STDIN_FILE=$(mktemp)
3852
+ trap 'rm -f "$STDIN_FILE"' EXIT
3853
+ cat > "$STDIN_FILE"
3854
+ node "${hookScript}" "${event}" "${configFile}" < "$STDIN_FILE"
3855
+ `;
3856
+ }
3857
+ /** Strip Gemini CLI startup messages from stderr output.
3858
+ * When the JSONL parser finds no result event, the output falls back to stderr
3859
+ * which contains lines like "YOLO mode is enabled..." and "Loaded cached credentials."
3860
+ * that corrupt JSON parsing in downstream consumers (advisor, light-call). */
3861
+ static stripCliPreamble(text) {
3862
+ const preamblePatterns = [
3863
+ /^YOLO mode is enabled\. All tool calls will be automatically approved\.$/,
3864
+ /^Loaded cached credentials\.$/,
3865
+ /^Sandbox mode: .+$/
3866
+ ];
3867
+ const lines = text.split("\n");
3868
+ const filtered = lines.filter((line) => {
3869
+ const trimmed = line.trim();
3870
+ return trimmed !== "" && !preamblePatterns.some((p) => p.test(trimmed));
3871
+ });
3872
+ return filtered.join("\n");
3873
+ }
3874
+ copySettingsToDir(settingsDir, targetDir) {
3875
+ try {
3876
+ const sourceFile = join2(settingsDir, ".gemini", "settings.json");
3877
+ const targetGeminiDir = join2(targetDir, ".gemini");
3878
+ mkdirSync2(targetGeminiDir, { recursive: true, mode: 448 });
3879
+ const content = readFileSync2(sourceFile, "utf-8");
3880
+ writeFileSync3(join2(targetGeminiDir, "settings.json"), content, { mode: 384 });
3881
+ } catch {
3882
+ }
3883
+ }
3884
+ };
3885
+
3451
3886
  // src/commands/pipeline.ts
3452
3887
  function createProviderRegistry() {
3453
3888
  const registry = new ProviderRegistry();
3454
3889
  registry.register(new ClaudeProvider());
3455
3890
  registry.register(new CodexProvider());
3891
+ registry.register(new GeminiProvider());
3456
3892
  return registry;
3457
3893
  }
3458
3894
  function readMcpConfigAsProviderConfig(filePath) {
3459
- const raw = JSON.parse(readFileSync2(filePath, "utf-8"));
3895
+ const raw = JSON.parse(readFileSync3(filePath, "utf-8"));
3460
3896
  return { servers: raw.mcpServers };
3461
3897
  }
3462
3898
  function parseArgs(args) {
@@ -3544,7 +3980,7 @@ Flags:
3544
3980
  --max-iterations <n> Override max iterations per ticket
3545
3981
  --max-budget <usd> Per-ticket budget cap (USD)
3546
3982
  --model <model> Override model preference
3547
- --provider <id> Override default provider (claude, codex)
3983
+ --provider <id> Override default provider (claude, codex, gemini)
3548
3984
  --concurrency <n> Override concurrency per column
3549
3985
  --log-retention <d> Log retention in days (default: 7)
3550
3986
  --yes, -y Skip safety confirmation`);
@@ -3553,28 +3989,28 @@ Flags:
3553
3989
  return { boardId, once, dryRun, columnFilter, maxIterations, maxBudget, model, provider, concurrency, logRetention, yes };
3554
3990
  }
3555
3991
  function pidDir(boardId) {
3556
- return join2(homedir(), ".kantban", "pipelines", boardId);
3992
+ return join3(homedir2(), ".kantban", "pipelines", boardId);
3557
3993
  }
3558
3994
  function pidFilePath(boardId) {
3559
- return join2(pidDir(boardId), "orchestrator.pid");
3995
+ return join3(pidDir(boardId), "orchestrator.pid");
3560
3996
  }
3561
3997
  function writePidFile(boardId) {
3562
3998
  const dir = pidDir(boardId);
3563
- mkdirSync2(dir, { recursive: true, mode: 448 });
3564
- writeFileSync3(pidFilePath(boardId), String(process.pid), { mode: 384 });
3999
+ mkdirSync3(dir, { recursive: true, mode: 448 });
4000
+ writeFileSync4(pidFilePath(boardId), String(process.pid), { mode: 384 });
3565
4001
  }
3566
4002
  function removePidFile(boardId) {
3567
4003
  try {
3568
- unlinkSync2(pidFilePath(boardId));
4004
+ unlinkSync3(pidFilePath(boardId));
3569
4005
  } catch {
3570
4006
  }
3571
4007
  }
3572
4008
  function childManifestPath(boardId) {
3573
- return join2(pidDir(boardId), "children.pid");
4009
+ return join3(pidDir(boardId), "children.pid");
3574
4010
  }
3575
4011
  function readChildManifest(boardId) {
3576
4012
  try {
3577
- const contents = readFileSync2(childManifestPath(boardId), "utf-8");
4013
+ const contents = readFileSync3(childManifestPath(boardId), "utf-8");
3578
4014
  return contents.split("\n").map((l) => l.trim()).filter((l) => l !== "").map(Number).filter((n) => !isNaN(n) && n > 0);
3579
4015
  } catch {
3580
4016
  return [];
@@ -3582,15 +4018,15 @@ function readChildManifest(boardId) {
3582
4018
  }
3583
4019
  function removeChildManifest(boardId) {
3584
4020
  try {
3585
- unlinkSync2(childManifestPath(boardId));
4021
+ unlinkSync3(childManifestPath(boardId));
3586
4022
  } catch {
3587
4023
  }
3588
4024
  }
3589
4025
  function cleanupOrphanedProcesses(boardId) {
3590
4026
  const pidPath = pidFilePath(boardId);
3591
- if (existsSync2(pidPath)) {
4027
+ if (existsSync3(pidPath)) {
3592
4028
  try {
3593
- const stalePid = parseInt(readFileSync2(pidPath, "utf-8").trim(), 10);
4029
+ const stalePid = parseInt(readFileSync3(pidPath, "utf-8").trim(), 10);
3594
4030
  if (stalePid && stalePid !== process.pid) {
3595
4031
  try {
3596
4032
  process.kill(stalePid, 0);
@@ -3615,10 +4051,10 @@ function cleanupOrphanedProcesses(boardId) {
3615
4051
  console.log(`Killed ${String(manifestPids.length)} orphaned child process(es) from manifest`);
3616
4052
  removeChildManifest(boardId);
3617
4053
  }
3618
- const staleReaperPath = join2(pidDir(boardId), "reaper.pid");
4054
+ const staleReaperPath = join3(pidDir(boardId), "reaper.pid");
3619
4055
  try {
3620
- if (existsSync2(staleReaperPath)) {
3621
- const reaperPid = parseInt(readFileSync2(staleReaperPath, "utf-8").trim(), 10);
4056
+ if (existsSync3(staleReaperPath)) {
4057
+ const reaperPid = parseInt(readFileSync3(staleReaperPath, "utf-8").trim(), 10);
3622
4058
  if (reaperPid && !isNaN(reaperPid)) {
3623
4059
  try {
3624
4060
  process.kill(reaperPid, 0);
@@ -3627,7 +4063,7 @@ function cleanupOrphanedProcesses(boardId) {
3627
4063
  } catch {
3628
4064
  }
3629
4065
  }
3630
- unlinkSync2(staleReaperPath);
4066
+ unlinkSync3(staleReaperPath);
3631
4067
  }
3632
4068
  } catch {
3633
4069
  }
@@ -3674,15 +4110,15 @@ async function runPipeline(client, args) {
3674
4110
  return;
3675
4111
  }
3676
4112
  }
3677
- const gateFilePath = join2(process.cwd(), "pipeline.gates.yaml");
4113
+ const gateFilePath = join3(process.cwd(), "pipeline.gates.yaml");
3678
4114
  let gateConfig;
3679
- if (!existsSync2(gateFilePath)) {
4115
+ if (!existsSync3(gateFilePath)) {
3680
4116
  console.error(`Error: pipeline.gates.yaml not found in ${process.cwd()}`);
3681
4117
  console.error('Run "kantban pipeline init" to generate a starter gate file.');
3682
4118
  process.exit(1);
3683
4119
  }
3684
4120
  try {
3685
- const gateYaml = readFileSync2(gateFilePath, "utf-8");
4121
+ const gateYaml = readFileSync3(gateFilePath, "utf-8");
3686
4122
  gateConfig = parseGateConfig(gateYaml);
3687
4123
  console.log(`Gate config loaded: ${gateConfig.default.length} default gate(s)`);
3688
4124
  } catch (err) {
@@ -3710,7 +4146,7 @@ async function runPipeline(client, args) {
3710
4146
  intelligence_provider: opts.provider ?? void 0
3711
4147
  };
3712
4148
  const intelligenceProvider = registry.resolveForIntelligence(boardProviderConfig);
3713
- const logBaseDir = join2(homedir(), ".kantban", "pipelines");
4149
+ const logBaseDir = join3(homedir2(), ".kantban", "pipelines");
3714
4150
  const logger = new PipelineLogger(logBaseDir, opts.boardId);
3715
4151
  logger.pruneOldLogs(opts.logRetention);
3716
4152
  let runMemory = null;
@@ -3747,7 +4183,7 @@ async function runPipeline(client, args) {
3747
4183
  effectiveConfig.model = registry.resolveModel(columnProvider, effectiveConfig.model);
3748
4184
  }
3749
4185
  const columnGates = resolveGatesForColumn(gateConfig, resolvedColumnName);
3750
- const gateCwd = effectiveConfig.worktreeName ? join2(process.cwd(), effectiveConfig.worktreeName) : void 0;
4186
+ const gateCwd = effectiveConfig.worktreeName ? join3(process.cwd(), effectiveConfig.worktreeName) : void 0;
3751
4187
  const effectiveMcpConfigPath = columnGates.length > 0 ? generateGateProxyMcpConfig(
3752
4188
  client.baseUrl,
3753
4189
  client.token,
@@ -4196,7 +4632,7 @@ Received ${signal}. Shutting down gracefully...`);
4196
4632
  cleanupOrphanedProcesses(opts.boardId);
4197
4633
  writePidFile(opts.boardId);
4198
4634
  logger.orchestrator(`PID file written: ${String(process.pid)}`);
4199
- const reaperPidPath = join2(pidDir(opts.boardId), "reaper.pid");
4635
+ const reaperPidPath = join3(pidDir(opts.boardId), "reaper.pid");
4200
4636
  const reaperProcess = spawnReaper({
4201
4637
  orchestratorPid: process.pid,
4202
4638
  manifestPath: childManifestPath(opts.boardId),
@@ -4359,7 +4795,7 @@ async function stopPipeline(args) {
4359
4795
  const pidFile = pidFilePath(boardId);
4360
4796
  let pid;
4361
4797
  try {
4362
- const content = readFileSync2(pidFile, "utf8").trim();
4798
+ const content = readFileSync3(pidFile, "utf8").trim();
4363
4799
  pid = Number(content);
4364
4800
  if (isNaN(pid) || pid <= 0) {
4365
4801
  throw new Error("Invalid PID");
@@ -4390,4 +4826,4 @@ export {
4390
4826
  runPipeline,
4391
4827
  stopPipeline
4392
4828
  };
4393
- //# sourceMappingURL=pipeline-TEE6GBNZ.js.map
4829
+ //# sourceMappingURL=pipeline-ETDPJOYI.js.map