kantban-cli 0.1.37 → 0.1.39
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-UNSNCYTR.js → chunk-5ZU2OOES.js} +19 -8
- package/dist/chunk-5ZU2OOES.js.map +1 -0
- package/dist/{chunk-AF72765A.js → chunk-JGAOKTRU.js} +5 -4
- package/dist/chunk-JGAOKTRU.js.map +1 -0
- package/dist/{chunk-MIL7SIPJ.js → chunk-MKKHLFA5.js} +2 -2
- package/dist/{context-IRTNYVN6.js → context-7YDNTI3P.js} +1 -3
- package/dist/{context-IRTNYVN6.js.map → context-7YDNTI3P.js.map} +1 -1
- package/dist/{cron-PS2IEPH2.js → cron-Z66K3HWS.js} +3 -4
- package/dist/{cron-PS2IEPH2.js.map → cron-Z66K3HWS.js.map} +1 -1
- package/dist/index.js +6 -7
- package/dist/index.js.map +1 -1
- package/dist/lib/gate-proxy-server.js +36 -5
- package/dist/lib/gate-proxy-server.js.map +1 -1
- package/dist/{pipeline-76ZTI4IN.js → pipeline-GF4H4IDN.js} +48 -13
- package/dist/pipeline-GF4H4IDN.js.map +1 -0
- package/dist/{pipeline-init-QDHBJGWY.js → pipeline-init-IGZZOOLK.js} +1 -3
- package/dist/{pipeline-init-QDHBJGWY.js.map → pipeline-init-IGZZOOLK.js.map} +1 -1
- package/dist/{status-3B3BSJBJ.js → status-4GFXMVIM.js} +1 -3
- package/dist/{status-3B3BSJBJ.js.map → status-4GFXMVIM.js.map} +1 -1
- package/dist/{work-HZVJJG4A.js → work-45GILGKD.js} +2 -3
- package/dist/{work-HZVJJG4A.js.map → work-45GILGKD.js.map} +1 -1
- package/package.json +1 -1
- package/dist/chunk-AF72765A.js.map +0 -1
- package/dist/chunk-DGUM43GV.js +0 -11
- package/dist/chunk-DGUM43GV.js.map +0 -1
- package/dist/chunk-UNSNCYTR.js.map +0 -1
- package/dist/pipeline-76ZTI4IN.js.map +0 -1
- /package/dist/{chunk-MIL7SIPJ.js.map → chunk-MKKHLFA5.js.map} +0 -0
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
__require
|
|
3
|
-
} from "./chunk-DGUM43GV.js";
|
|
4
|
-
|
|
5
1
|
// src/lib/platform.ts
|
|
6
2
|
import { platform } from "os";
|
|
7
3
|
import { execFileSync } from "child_process";
|
|
8
|
-
import { readFileSync } from "fs";
|
|
9
|
-
import { join, dirname } from "path";
|
|
4
|
+
import { readFileSync, existsSync } from "fs";
|
|
5
|
+
import { join, dirname, delimiter } from "path";
|
|
10
6
|
var IS_WINDOWS = platform() === "win32";
|
|
11
7
|
function normalizeEol(text) {
|
|
12
8
|
return text.replace(/\r\n|\r/g, "\n");
|
|
@@ -50,10 +46,25 @@ function killProcessTree(pid, signal = "SIGTERM") {
|
|
|
50
46
|
function npxCommand() {
|
|
51
47
|
return IS_WINDOWS ? "npx.cmd" : "npx";
|
|
52
48
|
}
|
|
49
|
+
function whichSync(command) {
|
|
50
|
+
const pathEnv = process.env["PATH"] || process.env["Path"] || defaultPath();
|
|
51
|
+
const dirs = pathEnv.split(delimiter);
|
|
52
|
+
const extensions = IS_WINDOWS ? (process.env["PATHEXT"] || ".COM;.EXE;.BAT;.CMD").toLowerCase().split(";") : [""];
|
|
53
|
+
for (const dir of dirs) {
|
|
54
|
+
for (const ext of extensions) {
|
|
55
|
+
const candidate = join(dir, command + ext);
|
|
56
|
+
if (existsSync(candidate)) return candidate;
|
|
57
|
+
if (IS_WINDOWS) {
|
|
58
|
+
const candidateUpper = join(dir, command + ext.toUpperCase());
|
|
59
|
+
if (candidateUpper !== candidate && existsSync(candidateUpper)) return candidateUpper;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
throw new Error(`not found: ${command}`);
|
|
64
|
+
}
|
|
53
65
|
function resolveCommand(command) {
|
|
54
66
|
if (!IS_WINDOWS) return [command, []];
|
|
55
67
|
try {
|
|
56
|
-
const whichSync = __require("which").sync;
|
|
57
68
|
const resolved = whichSync(command);
|
|
58
69
|
if (resolved.toLowerCase().endsWith(".exe")) return [resolved, []];
|
|
59
70
|
if (resolved.toLowerCase().endsWith(".cmd")) {
|
|
@@ -93,4 +104,4 @@ export {
|
|
|
93
104
|
resolveCommand,
|
|
94
105
|
defaultPath
|
|
95
106
|
};
|
|
96
|
-
//# sourceMappingURL=chunk-
|
|
107
|
+
//# sourceMappingURL=chunk-5ZU2OOES.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/platform.ts"],"sourcesContent":["import { platform } from 'node:os';\nimport { execFileSync } from 'node:child_process';\nimport { readFileSync, existsSync } from 'node:fs';\nimport { join, dirname, delimiter } from 'node:path';\n\nexport const IS_WINDOWS = platform() === 'win32';\n\n/** Normalize line endings to Unix-style (\\n). Strips \\r\\n and bare \\r. */\nexport function normalizeEol(text: string): string {\n return text.replace(/\\r\\n|\\r/g, '\\n');\n}\n\n/** Returns [shellExecutable, argsPrefix] for running shell commands.\n * Unix: ['/bin/sh', ['-c']] Windows: [cmd.exe /c] or [pwsh -NoProfile -Command] */\nexport function shellArgs(): [string, string[]] {\n if (IS_WINDOWS) {\n const comspec = process.env.COMSPEC || 'cmd.exe';\n // Detect PowerShell — uses -Command instead of /c\n if (/pwsh|powershell/i.test(comspec)) {\n return [comspec, ['-NoProfile', '-Command']];\n }\n return [comspec, ['/c']];\n }\n return ['/bin/sh', ['-c']];\n}\n\n/** Returns spawn options with shell:true on Windows for .cmd/.bat resolution. */\nexport function crossSpawnOptions<T extends Record<string, unknown>>(opts?: T): T & { shell: boolean } {\n return { ...(opts ?? {} as T), shell: IS_WINDOWS } as T & { shell: boolean };\n}\n\n/** Kill a process and its children. On Unix, tries process group first.\n * On Windows, uses `taskkill /T` for tree kill. Never throws. */\nexport function killProcessTree(pid: number, signal: NodeJS.Signals = 'SIGTERM'): void {\n if (IS_WINDOWS) {\n // taskkill /T kills the process tree; /F forces termination\n try { execFileSync('taskkill', ['/pid', String(pid), '/t', '/f'], { stdio: 'pipe' }); return; } catch { /* fall through */ }\n try { process.kill(pid); } catch { /* already dead */ }\n return;\n }\n // Unix: try process group kill first\n try { process.kill(-pid, signal); return; } catch { /* fall through */ }\n try { process.kill(pid, signal); } catch { /* already dead */ }\n}\n\n/** Returns platform-appropriate npx command ('npx' on Unix, 'npx.cmd' on Windows). */\nexport function npxCommand(): string {\n return IS_WINDOWS ? 'npx.cmd' : 'npx';\n}\n\n/** Synchronously find a command on PATH, similar to `which.sync`.\n * On Windows, checks PATHEXT extensions (.COM, .EXE, .CMD, .BAT, etc.).\n * Returns the first match or throws if not found. */\nfunction whichSync(command: string): string {\n const pathEnv = process.env['PATH'] || process.env['Path'] || defaultPath();\n const dirs = pathEnv.split(delimiter);\n const extensions = IS_WINDOWS\n ? (process.env['PATHEXT'] || '.COM;.EXE;.BAT;.CMD').toLowerCase().split(';')\n : [''];\n\n for (const dir of dirs) {\n for (const ext of extensions) {\n const candidate = join(dir, command + ext);\n if (existsSync(candidate)) return candidate;\n // Also try with original casing on Windows (PATHEXT is case-insensitive)\n if (IS_WINDOWS) {\n const candidateUpper = join(dir, command + ext.toUpperCase());\n if (candidateUpper !== candidate && existsSync(candidateUpper)) return candidateUpper;\n }\n }\n }\n throw new Error(`not found: ${command}`);\n}\n\n/**\n * Resolve a CLI command to [executable, prefixArgs] for direct invocation\n * WITHOUT cmd.exe on Windows. This avoids the 8191-char command line limit\n * that cmd.exe imposes (CreateProcessW supports 32,767 chars).\n *\n * On Windows, npm-installed CLI tools are `.cmd` wrappers that invoke Node.js\n * with a specific script. This function parses the wrapper and returns\n * ['node', ['path/to/script.js', ...extraFlags]] so spawn() can call Node directly.\n *\n * On Unix (or if resolution fails), returns [command, []] unchanged.\n */\nexport function resolveCommand(command: string): [cmd: string, prefixArgs: string[]] {\n if (!IS_WINDOWS) return [command, []];\n\n try {\n // Find the command on PATH — inline implementation to avoid ESM/CJS\n // interop issues with the `which` package in bundled ESM context.\n const resolved = whichSync(command);\n\n // If it's an .exe, use it directly — no shell needed\n if (resolved.toLowerCase().endsWith('.exe')) return [resolved, []];\n\n // If it's a .cmd, parse out the node script invocation\n if (resolved.toLowerCase().endsWith('.cmd')) {\n const content = readFileSync(resolved, 'utf-8');\n // Match: \"%_prog%\" [optional-flags] \"%dp0%\\path\\to\\script.js\" %*\n // The .cmd files generated by npm have this exact pattern on the last non-empty line.\n for (const line of content.split(/\\r?\\n/)) {\n const m = line.match(/\"%_prog%\"\\s+(.*?)\"%dp0%\\\\(.+?)\"\\s+%[*]/);\n if (m) {\n const flags = m[1]!.trim().split(/\\s+/).filter(Boolean);\n const scriptPath = join(dirname(resolved), m[2]!);\n return [process.execPath, [...flags, scriptPath]];\n }\n }\n }\n } catch {\n // which not found, or parsing failed — fall through\n }\n\n // Fallback: use the command as-is (will need shell: true)\n return [command, []];\n}\n\n/** Returns platform-appropriate default PATH when process.env.PATH is undefined. */\nexport function defaultPath(): string {\n if (IS_WINDOWS) {\n return [\n process.env.SystemRoot ? `${process.env.SystemRoot}\\\\System32` : 'C:\\\\Windows\\\\System32',\n process.env.SystemRoot || 'C:\\\\Windows',\n 'C:\\\\Program Files\\\\nodejs',\n process.env.APPDATA ? `${process.env.APPDATA}\\\\npm` : '',\n ].filter(Boolean).join(';');\n }\n return '/usr/local/bin:/usr/bin:/bin';\n}\n"],"mappings":"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","names":[]}
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
killProcessTree,
|
|
8
8
|
npxCommand,
|
|
9
9
|
resolveCommand
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-5ZU2OOES.js";
|
|
11
11
|
|
|
12
12
|
// src/lib/stuck-detector.ts
|
|
13
13
|
import { z } from "zod";
|
|
@@ -560,7 +560,7 @@ var RalphLoop = class {
|
|
|
560
560
|
let lastOutput = "";
|
|
561
561
|
const startIter = this.config.startIteration ?? 1;
|
|
562
562
|
for (let i = startIter; i <= this.config.maxIterations; i++) {
|
|
563
|
-
if (this.stopped) return withCosts({ reason: "stopped", iterations: i - 1, gutterCount, model: resolvedModel });
|
|
563
|
+
if (this.stopped || this.config.abortSignal?.aborted) return withCosts({ reason: "stopped", iterations: i - 1, gutterCount, model: resolvedModel });
|
|
564
564
|
if (this.config.isBudgetExhausted?.()) {
|
|
565
565
|
log(`Budget exhausted \u2014 stopping after ${i - 1} iterations`);
|
|
566
566
|
return withCosts({ reason: "budget", iterations: i - 1, gutterCount, model: resolvedModel });
|
|
@@ -620,7 +620,7 @@ var RalphLoop = class {
|
|
|
620
620
|
log(`Prompt composition failed: ${message}`);
|
|
621
621
|
return withCosts({ reason: "error", iterations: i, gutterCount, lastError: `Prompt composition failed: ${message}`, model: resolvedModel });
|
|
622
622
|
}
|
|
623
|
-
if (this.stopped) return withCosts({ reason: "stopped", iterations: i - 1, gutterCount, model: resolvedModel });
|
|
623
|
+
if (this.stopped || this.config.abortSignal?.aborted) return withCosts({ reason: "stopped", iterations: i - 1, gutterCount, model: resolvedModel });
|
|
624
624
|
log(`Invoking ${this.deps.provider.displayName} (model=${this.config.model ?? "default"})`);
|
|
625
625
|
const iterationRunId = `${this.config.runId ?? this.ticketId}-iter-${String(i)}`;
|
|
626
626
|
this.deps.onSessionStart?.({
|
|
@@ -638,6 +638,7 @@ var RalphLoop = class {
|
|
|
638
638
|
...this.config.toolRestrictions != null && {
|
|
639
639
|
toolRestrictions: { ...this.config.toolRestrictions, includeMcpConfig: this.config.toolRestrictions.includeMcpConfig ?? true }
|
|
640
640
|
},
|
|
641
|
+
...this.config.abortSignal != null && { abortSignal: this.config.abortSignal },
|
|
641
642
|
onStreamEvent: (event) => this.deps.onStreamEvent?.(event, streamContext)
|
|
642
643
|
};
|
|
643
644
|
const { exitCode, output, toolCallCount, usage, durationMs: iterationDurationMs, degradedCapabilities } = await this.deps.provider.invoke(agentRequest);
|
|
@@ -1186,4 +1187,4 @@ export {
|
|
|
1186
1187
|
cleanupGateProxyConfigs,
|
|
1187
1188
|
ClaudeProvider
|
|
1188
1189
|
};
|
|
1189
|
-
//# sourceMappingURL=chunk-
|
|
1190
|
+
//# sourceMappingURL=chunk-JGAOKTRU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/stuck-detector.ts","../src/lib/parse-utils.ts","../src/lib/prompt-composer.ts","../src/lib/ralph-loop.ts","../src/lib/mcp-config.ts","../src/providers/claude-provider.ts","../src/providers/claude-stream-parser.ts"],"sourcesContent":["import { z } from 'zod';\nimport { parseJsonFromLlmOutput } from './parse-utils.js';\nimport type { GateSnapshot, GateDelta } from '@kantban/types';\n\nexport interface StuckPattern {\n status: 'progressing' | 'spinning' | 'regressing' | 'blocked';\n evidence: string;\n confidence: number;\n}\n\n// Legacy exports preserved for backward compatibility during transition\nexport interface StuckDetectionConfig {\n enabled: boolean;\n firstCheck?: number;\n interval?: number;\n}\n\nexport function shouldCheckStuckDetection(config: StuckDetectionConfig, iteration: number): boolean {\n if (!config.enabled) return false;\n const firstCheck = config.firstCheck ?? 3;\n const interval = config.interval ?? 2;\n if (interval <= 0) return iteration === firstCheck;\n if (iteration < firstCheck) return false;\n if (iteration === firstCheck) return true;\n return (iteration - firstCheck) % interval === 0;\n}\n\n// Legacy types — kept for ralph-loop.ts compatibility until Task 12 replaces them\nexport interface StuckDetectionInput {\n ticketNumber: number;\n ticketTitle: string;\n columnName: string;\n iteration: number;\n maxIterations: number;\n recentComments: Array<{ author: string; body: string }>;\n}\n\n// Legacy function — kept for pipeline.ts compatibility until orchestrator layer is updated\nexport function composeStuckDetectionPrompt(input: StuckDetectionInput): string {\n const commentLines =\n input.recentComments.length > 0\n ? input.recentComments.map((c) => ` [${c.author}] ${c.body.slice(0, 200)}`).join('\\n')\n : ' (no comments)';\n\n return `You are a pipeline trajectory classifier. Analyze recent agent activity and classify the trajectory.\n\nTicket: #${String(input.ticketNumber)} \"${input.ticketTitle}\"\nColumn: ${input.columnName}\nIteration: ${String(input.iteration)} of ${String(input.maxIterations)}\n\nRecent iteration comments:\n${commentLines}\n\nClassify as one of:\n- \"progressing\" — each iteration makes distinct forward progress (new code, new tests, new fields set)\n- \"spinning\" — agent is active but repeating similar actions without advancing (same error, same approach retried, output duplicated)\n- \"blocked\" — agent cannot make progress due to external dependency, missing information, or fundamental task issue\n\nRespond with ONLY a JSON object:\n{\"status\": \"progressing|spinning|blocked\", \"confidence\": 0.0-1.0, \"evidence\": \"one sentence\"}`;\n}\n\nconst StuckDetectionResponseSchema = z.object({\n status: z.enum(['progressing', 'spinning', 'blocked']),\n confidence: z.number().min(0).max(1),\n evidence: z.string(),\n});\n\nexport type StuckDetectionResult = z.infer<typeof StuckDetectionResponseSchema>;\n\n// Legacy function — kept for pipeline.ts compatibility until orchestrator layer is updated\nexport function parseStuckDetectionResponse(raw: string): StuckDetectionResult {\n let parsed: unknown;\n try {\n parsed = parseJsonFromLlmOutput(raw);\n } catch (err) {\n throw new Error(`StuckDetectionResponse: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n // Clamp confidence before validation\n if (parsed && typeof parsed === 'object' && 'confidence' in parsed) {\n const p = parsed as Record<string, unknown>;\n if (typeof p.confidence === 'number') {\n p.confidence = Math.min(1, Math.max(0, p.confidence));\n }\n }\n\n const result = StuckDetectionResponseSchema.safeParse(parsed);\n if (!result.success) {\n throw new Error(`StuckDetectionResponse: validation failed — ${result.error.message}`);\n }\n\n return result.data;\n}\n\n/**\n * Deterministic trajectory classification from gate snapshot history.\n * Replaces the Haiku-based comment classifier.\n */\nexport function classifyTrajectory(snapshots: GateSnapshot[]): StuckPattern {\n if (snapshots.length === 0) {\n return { status: 'progressing', evidence: 'no data', confidence: 0.5 };\n }\n\n const recent = snapshots.slice(-3);\n const deltas = recent.map((s) => s.delta_from_previous);\n\n // Filter out first_check — it's not a real signal\n const meaningful = deltas.filter((d): d is Exclude<GateDelta, 'first_check'> => d !== 'first_check');\n\n if (meaningful.length === 0) {\n return { status: 'progressing', evidence: 'only initial checks', confidence: 0.5 };\n }\n\n // Need at least 2 meaningful deltas to identify a trajectory pattern.\n // A single delta is not enough evidence — the agent may be mid-work\n // (e.g. running a long build/test command) without having produced\n // a visible state change yet.\n if (meaningful.length < 2) {\n return { status: 'progressing', evidence: 'insufficient data for trajectory', confidence: 0.3 };\n }\n\n // All same = spinning\n if (meaningful.every((d) => d === 'same')) {\n return { status: 'spinning', evidence: 'identical gate results', confidence: 1.0 };\n }\n\n // Monotonic regression\n if (meaningful.every((d) => d === 'regressed' || d === 'same')) {\n return { status: 'regressing', evidence: 'gate results degrading', confidence: 1.0 };\n }\n\n // Recent improvement takes precedence over oscillation\n if (meaningful.some((d) => d === 'improved')) {\n const lastMeaningful = meaningful[meaningful.length - 1];\n if (lastMeaningful === 'improved') {\n return { status: 'progressing', evidence: 'gate results improving', confidence: 1.0 };\n }\n }\n\n // Oscillation: improved AND regressed, but last delta is NOT improved\n if (meaningful.some((d) => d === 'improved') && meaningful.some((d) => d === 'regressed')) {\n return { status: 'spinning', evidence: 'oscillating gate results', confidence: 1.0 };\n }\n\n // Stale improvement — has improved sometime but no recent progress\n if (meaningful.some((d) => d === 'improved')) {\n return { status: 'spinning', evidence: 'stale improvement — no recent progress', confidence: 0.8 };\n }\n\n return { status: 'spinning', evidence: 'no improvement detected', confidence: 1.0 };\n}\n","/**\n * Strip markdown code fences from LLM output and parse as JSON.\n * Used by advisor, light-call, and stuck-detector response parsers.\n */\nexport function parseJsonFromLlmOutput(raw: string): unknown {\n // First: try parsing the whole string (handles pure JSON input from tests/structured responses)\n try {\n return JSON.parse(raw.trim());\n } catch {\n // Not a bare JSON string — fall through to fence and brace extraction\n }\n\n // Second: try code fence extraction\n const fenceMatch = raw.match(/```(?:json)?\\s*([\\s\\S]*?)```/i);\n if (fenceMatch?.[1]) {\n const fenced = fenceMatch[1].trim();\n try {\n return JSON.parse(fenced);\n } catch {\n // Fence content wasn't valid JSON — fall through to brace matching\n }\n }\n\n // Fallback: find valid JSON objects with string-context-aware brace matching\n for (let i = 0; i < raw.length; i++) {\n if (raw[i] !== '{') continue;\n let depth = 0;\n let inString = false;\n let escape = false;\n for (let j = i; j < raw.length; j++) {\n const ch = raw[j];\n if (escape) {\n escape = false;\n continue;\n }\n if (ch === '\\\\' && inString) {\n escape = true;\n continue;\n }\n if (ch === '\"') {\n inString = !inString;\n continue;\n }\n if (inString) continue;\n if (ch === '{') depth++;\n if (ch === '}') depth--;\n if (depth === 0) {\n const candidate = raw.slice(i, j + 1);\n try {\n return JSON.parse(candidate);\n } catch {\n break; // This opening brace didn't work, try next\n }\n }\n }\n }\n\n throw new Error(`Invalid JSON in LLM output: ${raw.slice(0, 120)}`);\n}\n","import type { AgentConfig, GateResult } from '@kantban/types';\nimport { VALID_BRANCH_RE } from './gate-config.js';\n\ninterface ColumnContext {\n scope: 'column';\n column: { id: string; name: string; goal: string | null };\n prompt_document: { id: string; title: string; content: string } | null;\n agent_config: AgentConfig | null;\n tickets: Array<{ id: string; ticket_number: number; title: string }>;\n transition_rules: string;\n signals: string[];\n field_definitions: Array<{ id: string; name: string; type: string }>;\n tool_prefix: string;\n}\n\ninterface TicketContext {\n scope: 'ticket';\n ticket: {\n id: string;\n ticket_number: number;\n title: string;\n description: string;\n backward_transitions: number;\n assignee: { id: string; name: string } | null;\n column: { id: string; name: string; type: string } | null;\n };\n field_values: Array<{ field_name: string; value: unknown }>;\n transitions: Array<{ from: string | null; to: string; handoff: unknown; timestamp: string }>;\n comments: Array<{ author: string; body: string; created_at: string; pinned?: boolean }>;\n ticket_links: Array<{\n direction: 'outward' | 'inward';\n link_type: 'blocks' | 'relates_to' | 'duplicates';\n ticket_id: string;\n ticket_number: number;\n title: string;\n column_name: string | null;\n resolved: boolean;\n }>;\n parent: { id: string; ticket_number: number; title: string } | null;\n children: Array<{ id: string; ticket_number: number; title: string; column_name: string | null }>;\n signals: string[];\n linked_documents: Array<{ id: string; title: string; content?: string; truncated?: boolean }>;\n transition_rules: string;\n dependency_requirements: string;\n tool_prefix: string;\n}\n\ninterface IterationMeta {\n iteration: number;\n maxIterations: number;\n projectId: string;\n gutterCount: number;\n gutterThreshold: number;\n lookaheadDocument?: { title: string; content: string } | undefined;\n runMemoryContent?: string | undefined;\n gateResults?: GateResult[] | undefined;\n rejectionFindings?: string | undefined;\n}\n\nexport type { ColumnContext, TicketContext };\n\n// --- Token budget system ---\n\nexport const PROMPT_BUDGETS = {\n system_preamble: 800,\n gate_results: 500,\n column_prompt: Infinity, // NEVER truncated\n lookahead: 1000,\n run_memory: 1000,\n rejection: 500,\n ticket_details: 1500,\n comments: 2000,\n transition_rules: 500,\n transition_history: 1000,\n dependency_requirements: 500,\n linked_documents: 2000,\n metadata: 200,\n} as const;\n\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n\nexport function truncateToTokens(text: string, maxTokens: number): string {\n if (maxTokens === Infinity) return text;\n const maxChars = maxTokens * 4;\n if (text.length <= maxChars) return text;\n return text.slice(0, maxChars) + '\\n[...truncated]';\n}\n\nexport function windowComments(\n comments: Array<{ author: string; body: string; created_at: string; pinned?: boolean }>,\n maxTokens: number,\n): string {\n const pinned = comments.filter((c) => c.pinned);\n const unpinned = comments.filter((c) => !c.pinned);\n const recentFull = unpinned.slice(-3);\n const older = unpinned.slice(0, -3);\n\n const parts: string[] = [];\n\n // Pinned always included in full\n for (const c of pinned) {\n parts.push(`[pinned] **${c.author}** (${c.created_at}):\\n${c.body}\\n`);\n }\n\n // Recent 3 in full\n for (const c of recentFull) {\n parts.push(`**${c.author}** (${c.created_at}):\\n${c.body}\\n`);\n }\n\n // Older compressed to one line\n for (const c of older) {\n const firstLine = c.body.replace(/\\r\\n/g, '\\n').split('\\n')[0]?.slice(0, 100) ?? '';\n parts.push(`- ${c.author}: ${firstLine}`);\n }\n\n const joined = parts.join('\\n');\n return truncateToTokens(joined, maxTokens);\n}\n\n// --- Prompt composer ---\n\nexport function composePrompt(\n columnContext: ColumnContext,\n ticketContext: TicketContext,\n meta: IterationMeta,\n): string {\n const parts: string[] = [];\n\n if (!columnContext.prompt_document?.content) {\n throw new Error(\n `Column \"${columnContext.column.name}\" has no prompt document. ` +\n `Configure a prompt document for this column before running the pipeline.`\n );\n }\n\n // --- 1. System preamble (identity, iteration N/M) ---\n const hasUnresolvedBlockers = ticketContext.ticket_links.some(\n (l) => l.direction === 'inward' && l.link_type === 'blocks' && !l.resolved,\n );\n\n parts.push(`# Pipeline Agent Instructions\n\nYou are a pipeline automation agent processing ticket #${String(ticketContext.ticket.ticket_number)}: \"${ticketContext.ticket.title}\".\n\n## Your Goal\nAdvance this ticket through the pipeline. Your success criteria:\n1. Complete the work described in the ticket and column prompt below\n2. Move the ticket to the next column using \\`${ticketContext.tool_prefix}move_ticket\\`\n3. Or mark it complete using \\`${ticketContext.tool_prefix}complete_task\\`\n\n## Iteration ${meta.iteration} of ${meta.maxIterations}\n${meta.iteration >= meta.maxIterations - 1 ? '**FINAL ITERATIONS** — prioritize moving the ticket NOW or it will be marked stalled.' : `You have ${meta.maxIterations - meta.iteration} iterations remaining. Make meaningful progress each iteration.`}\n${meta.gutterCount > 0 ? `\n## Progress Warning\nNo meaningful progress detected for ${meta.gutterCount} consecutive iteration(s).\n${meta.gutterThreshold - meta.gutterCount} iteration(s) remain before this loop is terminated as stalled.\n\nYou MUST change approach. What you've been doing is not working. Consider:\n- Breaking the problem into smaller steps\n- Setting a field value to record partial progress\n- Creating a comment explaining what's blocking you\n- Asking for help via a signal\n` : ''}\n## Available Tools (prefix: ${ticketContext.tool_prefix})\n- **${ticketContext.tool_prefix}check_transition** — ALWAYS call this before moving. Returns allowed/blocked with recovery steps.\n Params: \\`{ projectId, boardId, ticketId, targetColumnId }\\`\n- **${ticketContext.tool_prefix}move_ticket** — Move ticket to a new column. Include handoff data for the next agent.\n Params: \\`{ projectId, ticketId, column_id, handoff: { branch?, commit_sha?, build_status?, notes? } }\\`\n- **${ticketContext.tool_prefix}complete_task** — Move to a done column with handoff data.\n Params: \\`{ projectId, ticketId, handoff: { ... } }\\`\n- **${ticketContext.tool_prefix}set_field_value** — Set a required field before moving (check transition rules).\n Params: \\`{ projectId, ticketId, fieldId, value }\\`\n- **${ticketContext.tool_prefix}create_signal** — Leave knowledge for future agents at any scope.\n- **${ticketContext.tool_prefix}create_comment** — Add progress notes to the ticket.\n- **${ticketContext.tool_prefix}append_run_memory** — Share discoveries with future agents.\n Params: \\`{ section: 'conventions'|'interfaces'|'failures'|'decisions', content: string }\\`\n\n## Additional MCP Tools\nYou also have access to these KantBan MCP tools for reading project data. USE THEM instead of writing scripts or creating files manually:\n- **${ticketContext.tool_prefix}get_document** — Read a document's full content. Params: \\`{ projectId, documentId }\\`\n- **${ticketContext.tool_prefix}get_ticket** — Get ticket details. Params: \\`{ projectId, ticketId }\\`\n- **${ticketContext.tool_prefix}search_documents_chunked** — Search documents by query. Params: \\`{ projectId, query }\\`\n- **${ticketContext.tool_prefix}list_signals** — List signals for context. Params: \\`{ projectId }\\`\n- **${ticketContext.tool_prefix}get_field_values** — Get custom field values. Params: \\`{ projectId, ticketId }\\`\n- **${ticketContext.tool_prefix}list_comments** — Read ticket comments. Params: \\`{ projectId, ticketId }\\`\n- **${ticketContext.tool_prefix}search_tickets** — Search tickets by query. Params: \\`{ projectId, query }\\`\n- **${ticketContext.tool_prefix}get_board_context** — Get full board state. Params: \\`{ projectId, boardId }\\`\n\n**IMPORTANT:** Always prefer MCP tools over file operations for reading project data. Do NOT write helper scripts to fetch data — call the MCP tools directly.\n\n## Knowledge Sharing\nBefore moving the ticket to the next column, you MUST:\n1. **Write a signal** via \\`${ticketContext.tool_prefix}create_signal\\` summarizing the key outcome of your work:\n - What you discovered, built, validated, or decided\n - Scope it to the ticket (\\`ticketId\\`) so future agents on this ticket see it\n - One concise signal is better than none — don't skip this step\n2. **Contribute to run memory** via \\`${ticketContext.tool_prefix}append_run_memory\\` if you discovered:\n - Conventions or patterns others should follow (\\`section: 'conventions'\\`)\n - Interfaces or APIs consumed or created (\\`section: 'interfaces'\\`)\n - Failures or dead-ends to avoid (\\`section: 'failures'\\`)\n - Design decisions and their rationale (\\`section: 'decisions'\\`)\n\nSignals and run memory are how you communicate with future agents. Without them, the next agent starts blind.\n\n## Iteration Summary\nIf you made meaningful progress this iteration, create a comment using \\`${ticketContext.tool_prefix}create_comment\\` summarizing:\n- What you accomplished\n- What remains to be done\n- Any blockers or issues encountered\n\n## Critical Rules\n1. **Always call ${ticketContext.tool_prefix}check_transition before ${ticketContext.tool_prefix}move_ticket** — moves that violate workflow rules will fail.\n2. **If the ticket has UNRESOLVED blockers, do not attempt to move it.** Create a comment explaining you are waiting, then stop.\n3. **Include handoff data** when moving — the next agent needs context (branch name, build status, etc.).\n4. **If you cannot make progress**, create a comment explaining why and stop. Do not burn iterations.\n${hasUnresolvedBlockers ? '\\n**WARNING: This ticket has UNRESOLVED blockers. Do NOT attempt to move it. Document your status and stop.**\\n' : ''}\n---\n`);\n\n // Worktree context — operational instructions when worktree isolation is active\n const worktreeConfig = columnContext.agent_config?.worktree;\n if (worktreeConfig?.enabled) {\n const integrationBranch = worktreeConfig.integration_branch ?? 'main';\n\n if (!VALID_BRANCH_RE.test(integrationBranch)) {\n parts.push(`## Git Worktree\\n\\nYou are working in an isolated git worktree. Integration branch name is invalid — contact the pipeline operator.`);\n } else {\n parts.push(`## Git Worktree\n\nYou are working in an isolated git worktree. Before starting any code changes:\n1. Verify the origin remote exists: \\`git remote -v\\`. If origin is missing, find the main repo path via \\`git worktree list\\` (first entry, first column) and add it: \\`git remote add origin <URL from main repo>\\`\n2. Merge \\`${integrationBranch}\\` into your current branch: \\`git merge ${integrationBranch}\\`\n3. Resolve any merge conflicts before proceeding with ticket work\n4. Never rebase — always merge (rebase destroys traceability across the pipeline)\n\nAfter completing your work, commit all changes to your worktree branch.\n`);\n }\n }\n\n // --- 2. Signals (guardrails — at the top) ---\n if (ticketContext.signals.length > 0) {\n parts.push(`\\n## Signals (Guardrails)\\n`);\n for (const s of ticketContext.signals) {\n parts.push(`- ${s}`);\n }\n }\n\n // --- 3. Previous gate results (what failed) ---\n if (meta.gateResults && meta.gateResults.length > 0) {\n parts.push(`\\n## Previous Gate Results\\n`);\n for (const r of meta.gateResults) {\n const status = r.passed ? 'PASS' : 'FAIL';\n const req = r.required ? '(required)' : '(advisory)';\n parts.push(`- **${r.name}** ${status} ${req} [${String(r.duration_ms)}ms]`);\n if (!r.passed && r.output) {\n parts.push(` \\`\\`\\`\\n ${truncateToTokens(r.output, 200)}\\n \\`\\`\\``);\n }\n }\n }\n\n // --- 4. Rejection elevation ---\n // Prefer meta.rejectionFindings, fall back to comment-based detection for backward compat\n if (meta.rejectionFindings) {\n parts.push(`\\n## Previous Rejection (fix these before resubmitting)\\n`);\n parts.push(truncateToTokens(meta.rejectionFindings, PROMPT_BUDGETS.rejection));\n } else {\n const rejectionComment = ticketContext.comments\n .slice()\n .reverse()\n .find((c) => c.body.startsWith('QA REJECTION:') || c.body.startsWith('REJECTION:'));\n if (rejectionComment) {\n parts.push(`\\n## Previous Rejection (fix these before resubmitting)\\n`);\n parts.push(truncateToTokens(rejectionComment.body, PROMPT_BUDGETS.rejection));\n }\n }\n\n // --- 5. Column prompt document (NEVER truncated) ---\n if (columnContext.prompt_document?.content) {\n parts.push(columnContext.prompt_document.content);\n }\n\n // --- 6. Lookahead — downstream column criteria ---\n if (meta.lookaheadDocument?.content) {\n parts.push(`\\n## Downstream Criteria (build to pass these)\\n`);\n parts.push(`*From: ${meta.lookaheadDocument.title}*\\n`);\n parts.push(truncateToTokens(meta.lookaheadDocument.content, PROMPT_BUDGETS.lookahead));\n }\n\n // --- 7. Run memory — cross-agent knowledge ---\n if (meta.runMemoryContent) {\n parts.push(`\\n## Run Memory\\n`);\n parts.push(truncateToTokens(meta.runMemoryContent, PROMPT_BUDGETS.run_memory));\n }\n\n // --- 8. Ticket details (budgeted as a whole section) ---\n const ticketParts: string[] = [];\n ticketParts.push(`## Current Ticket\\n`);\n ticketParts.push(`**Title:** ${ticketContext.ticket.title}`);\n ticketParts.push(`**Ticket ID:** ${ticketContext.ticket.id}`);\n ticketParts.push(`**Ticket Number:** ${String(ticketContext.ticket.ticket_number)}`);\n if (ticketContext.ticket.description) {\n ticketParts.push(`\\n${ticketContext.ticket.description}`);\n }\n\n if (ticketContext.ticket.assignee) {\n ticketParts.push(`**Assignee:** ${ticketContext.ticket.assignee.name}`);\n }\n if (ticketContext.ticket.column) {\n ticketParts.push(`**Current Column:** ${ticketContext.ticket.column.name} (${ticketContext.ticket.column.type})`);\n }\n if (ticketContext.ticket.backward_transitions > 0) {\n ticketParts.push(`**Backward Transitions:** ${String(ticketContext.ticket.backward_transitions)}`);\n }\n\n if (ticketContext.field_values.length > 0) {\n ticketParts.push(`\\n## Field Values\\n`);\n for (const fv of ticketContext.field_values) {\n ticketParts.push(`- **${fv.field_name}:** ${fv.value !== null ? JSON.stringify(fv.value) : '(not set)'}`);\n }\n }\n\n if (ticketContext.parent) {\n ticketParts.push(`\\n## Parent Ticket\\n`);\n ticketParts.push(`- #${String(ticketContext.parent.ticket_number)}: ${ticketContext.parent.title}`);\n }\n if (ticketContext.children.length > 0) {\n ticketParts.push(`\\n## Child Tickets\\n`);\n for (const child of ticketContext.children) {\n ticketParts.push(`- #${String(child.ticket_number)}: ${child.title}${child.column_name ? ` (${child.column_name})` : ''}`);\n }\n }\n\n if (ticketContext.transitions.length > 0) {\n ticketParts.push(`\\n## Transition History\\n`);\n let transitionTokens = 0;\n for (const t of ticketContext.transitions) {\n let line = `- ${t.from ?? 'Backlog'} → ${t.to}`;\n if (t.handoff) line += ` (handoff: ${JSON.stringify(t.handoff)})`;\n const lineTokens = estimateTokens(line);\n if (transitionTokens + lineTokens > PROMPT_BUDGETS.transition_history) {\n ticketParts.push(`- [...truncated — ${String(ticketContext.transitions.length)} total transitions]`);\n break;\n }\n ticketParts.push(line);\n transitionTokens += lineTokens;\n }\n }\n\n if (ticketContext.ticket_links.length > 0) {\n ticketParts.push(`\\n## Ticket Links\\n`);\n const blockers = ticketContext.ticket_links.filter(l => l.direction === 'inward' && l.link_type === 'blocks');\n const blocking = ticketContext.ticket_links.filter(l => l.direction === 'outward' && l.link_type === 'blocks');\n const related = ticketContext.ticket_links.filter(l => l.link_type === 'relates_to');\n if (blockers.length > 0) {\n ticketParts.push(`**Blocked by:**`);\n for (const l of blockers) {\n const status = l.resolved ? '(resolved)' : 'UNRESOLVED';\n ticketParts.push(`- #${String(l.ticket_number)}: ${l.title} [${l.column_name ?? 'backlog'}] ${status}`);\n }\n }\n if (blocking.length > 0) {\n ticketParts.push(`**Blocks:**`);\n for (const l of blocking) {\n ticketParts.push(`- #${String(l.ticket_number)}: ${l.title} [${l.column_name ?? 'backlog'}]`);\n }\n }\n if (related.length > 0) {\n ticketParts.push(`**Related:**`);\n for (const l of related) {\n ticketParts.push(`- #${String(l.ticket_number)}: ${l.title}`);\n }\n }\n }\n\n parts.push(truncateToTokens(ticketParts.join('\\n'), PROMPT_BUDGETS.ticket_details));\n\n // --- 9. Comments (windowed) ---\n if (ticketContext.comments.length > 0) {\n parts.push(`\\n## Comments\\n`);\n parts.push(windowComments(ticketContext.comments, PROMPT_BUDGETS.comments));\n }\n\n // --- 10. Transition rules ---\n if (ticketContext.transition_rules) {\n parts.push(`\\n## Transition Rules\\n`);\n parts.push(truncateToTokens(ticketContext.transition_rules, PROMPT_BUDGETS.transition_rules));\n }\n\n // Dependency requirements (budgeted)\n if (ticketContext.dependency_requirements && ticketContext.dependency_requirements !== 'No dependency or field requirements configured.') {\n parts.push(`\\n## Dependency & Field Requirements\\n`);\n parts.push(truncateToTokens(ticketContext.dependency_requirements, PROMPT_BUDGETS.dependency_requirements));\n }\n\n // --- 11. Linked documents (truncated to budget) ---\n if (ticketContext.linked_documents.length > 0) {\n parts.push(`\\n## Linked Documents\\n`);\n let remainingTokens: number = PROMPT_BUDGETS.linked_documents;\n for (const doc of ticketContext.linked_documents) {\n if (doc.content) {\n const docHeader = `### ${doc.title}\\n`;\n const headerTokens = estimateTokens(docHeader);\n const contentTokens = estimateTokens(doc.content);\n if (headerTokens + contentTokens <= remainingTokens) {\n parts.push(docHeader);\n parts.push(doc.content);\n remainingTokens -= headerTokens + contentTokens;\n } else if (remainingTokens > headerTokens + 50) {\n // Truncate the document content to fit remaining budget\n parts.push(docHeader);\n parts.push(truncateToTokens(doc.content, remainingTokens - headerTokens));\n remainingTokens = 0;\n } else {\n parts.push(`- ${doc.title} (truncated — token budget exceeded)`);\n remainingTokens = 0;\n }\n } else if (doc.truncated) {\n parts.push(`- ${doc.title} (document too large — read via kantban_get_document)`);\n }\n if (remainingTokens <= 0) break;\n }\n }\n\n // --- 12. Metadata ---\n parts.push(`\\n## Pipeline Metadata\\n`);\n parts.push(`- Iteration: ${meta.iteration} / ${meta.maxIterations}`);\n parts.push(`- Project ID: ${meta.projectId}`);\n parts.push(`- Tool Prefix: ${ticketContext.tool_prefix}`);\n parts.push(`- Column: ${columnContext.column.name}`);\n parts.push(`- Goal: ${columnContext.column.goal ?? 'No goal set'}`);\n\n return parts.join('\\n');\n}\n","import { composePrompt, type ColumnContext, type TicketContext } from './prompt-composer.js';\nimport type { TicketFingerprint, GateSnapshot } from '@kantban/types';\nimport type { LoopCheckpoint } from './checkpoint.js';\nimport { shouldCheckStuckDetection, classifyTrajectory, type StuckDetectionConfig, type StuckDetectionInput, type StuckDetectionResult } from './stuck-detector.js';\nimport type { AgentProvider, AgentRequest, NormalizedStreamEvent, McpConfig } from '../providers/types.js';\n\nexport interface LoopConfig {\n maxIterations: number;\n gutterThreshold: number;\n model?: string;\n maxBudgetUsd?: number | null;\n worktreeName?: string;\n postMoveRetryDelayMs?: number;\n lookaheadColumnId?: string;\n runId?: string;\n startIteration?: number;\n startGutterCount?: number;\n onCheckpoint?: (ticketId: string, checkpoint: LoopCheckpoint) => Promise<void>;\n startFingerprint?: TicketFingerprint;\n stuckDetection?: StuckDetectionConfig;\n invokeStuckDetection?: (input: StuckDetectionInput) => Promise<StuckDetectionResult>;\n /** Run gates after each iteration and record snapshot. Returns GateSnapshot. */\n onPostIterationGates?: (ticketId: string, iteration: number) => Promise<GateSnapshot>;\n /** Check if the pipeline-level budget is exhausted. Checked between iterations. */\n isBudgetExhausted?: () => boolean;\n /** Signal to abort the loop externally (e.g., ticket moved out of column) */\n abortSignal?: AbortSignal;\n /** Resolved tool restrictions from profile */\n toolRestrictions?: {\n tools?: string;\n allowedTools?: string[];\n disallowedTools?: string[];\n includeMcpConfig?: boolean;\n };\n}\n\nexport interface LoopResult {\n reason: 'moved' | 'max_iterations' | 'stalled' | 'stopped' | 'error' | 'deleted' | 'budget';\n iterations: number;\n gutterCount: number;\n lastError?: string;\n model?: string;\n /** Final gate snapshot (if gates enabled) */\n finalGateSnapshot?: GateSnapshot;\n /** Final output from the loop (used by evaluator columns for verdict parsing) */\n output?: string;\n /** Cumulative tokens consumed (input) across all iterations */\n tokensIn?: number;\n /** Cumulative tokens consumed (output) across all iterations */\n tokensOut?: number;\n /** Cumulative tool calls across all iterations */\n toolCallCount?: number;\n /** Total wall-clock duration in ms across all iterations */\n durationMs?: number;\n}\n\nexport interface RalphLoopDeps {\n fetchTicketContext: (ticketId: string) => Promise<TicketContext>;\n fetchColumnContext: (columnId: string) => Promise<ColumnContext>;\n fetchFingerprint: (ticketId: string) => Promise<TicketFingerprint>;\n provider: AgentProvider;\n mcpConfig?: McpConfig;\n projectId: string;\n log?: (message: string) => void;\n /** Fetch run memory content. Returns '' if not available. */\n fetchRunMemoryContent?: (() => Promise<string>) | undefined;\n /** Fetch the lookahead document for QA-forward prompting. */\n fetchLookaheadDocument?: (() => Promise<{ title: string; content: string } | undefined>) | undefined;\n /** Forward each normalized stream event (for live pipeline streaming to browser) */\n onStreamEvent?: ((event: NormalizedStreamEvent, context: { runId: string; ticketId: string; columnId: string }) => void) | undefined;\n /** Emitted before each provider invocation */\n onSessionStart?: ((meta: { runId: string; model: string; iteration: number }) => void) | undefined;\n /** Emitted after each provider invocation completes */\n onSessionEnd?: ((meta: { runId: string; exitCode: number; tokensIn: number; tokensOut: number; toolCallCount: number; durationMs: number }) => void) | undefined;\n}\n\nconst API_TIMEOUT_MS = 30_000; // 30s timeout for API calls (fingerprint, context)\n\nfunction withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {\n let timer: ReturnType<typeof setTimeout>;\n return Promise.race([\n promise.finally(() => clearTimeout(timer)),\n new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);\n }),\n ]);\n}\n\nexport class RalphLoop {\n private ticketId: string;\n private columnId: string;\n private config: LoopConfig;\n private deps: RalphLoopDeps;\n private stopped = false;\n private currentIteration = 0;\n\n constructor(ticketId: string, columnId: string, config: LoopConfig, deps: RalphLoopDeps) {\n this.ticketId = ticketId;\n this.columnId = columnId;\n this.config = config;\n this.deps = deps;\n }\n\n stop(): void {\n this.stopped = true;\n }\n\n get iteration(): number {\n return this.currentIteration;\n }\n\n private looksLike404(err: unknown): boolean {\n const statusCode = (err as { statusCode?: number })?.statusCode\n ?? (err as { status?: number })?.status;\n if (statusCode === 404) return true;\n const message = err instanceof Error ? err.message : String(err);\n return message.includes('API error 404');\n }\n\n async run(): Promise<LoopResult> {\n let gutterCount = this.config.startGutterCount ?? 0;\n let lastFingerprint: TicketFingerprint | null = this.config.startFingerprint ?? null;\n const log = this.deps.log ?? (() => {});\n const resolvedModel = this.config.model ?? 'default';\n const gateSnapshots: GateSnapshot[] = [];\n let cumulativeTokensIn = 0;\n let cumulativeTokensOut = 0;\n let cumulativeToolCalls = 0;\n let cumulativeDurationMs = 0;\n\n const withCosts = (r: LoopResult): LoopResult => ({\n ...r,\n tokensIn: cumulativeTokensIn,\n tokensOut: cumulativeTokensOut,\n toolCallCount: cumulativeToolCalls,\n durationMs: cumulativeDurationMs,\n });\n let lastOutput = '';\n\n const startIter = this.config.startIteration ?? 1;\n for (let i = startIter; i <= this.config.maxIterations; i++) {\n if (this.stopped || this.config.abortSignal?.aborted) return withCosts({ reason: 'stopped', iterations: i - 1, gutterCount, model: resolvedModel });\n if (this.config.isBudgetExhausted?.()) {\n log(`Budget exhausted — stopping after ${i - 1} iterations`);\n return withCosts({ reason: 'budget', iterations: i - 1, gutterCount, model: resolvedModel });\n }\n this.currentIteration = i;\n log(`Iteration ${i}/${this.config.maxIterations} starting`);\n\n // Capture baseline fingerprint on first iteration\n if (!lastFingerprint) {\n try {\n lastFingerprint = await withTimeout(\n this.deps.fetchFingerprint(this.ticketId),\n API_TIMEOUT_MS, 'fetchFingerprint (baseline)',\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n log(`Baseline fingerprint fetch failed: ${message}`);\n if (this.looksLike404(err)) {\n return withCosts({ reason: 'deleted', iterations: i, gutterCount, model: resolvedModel });\n }\n return withCosts({ reason: 'error', iterations: i, gutterCount, lastError: `Baseline fingerprint failed: ${message}`, model: resolvedModel });\n }\n }\n\n // Fetch ticket and column context for this iteration\n let ticketCtx: TicketContext;\n let columnCtx: ColumnContext;\n try {\n [ticketCtx, columnCtx] = await Promise.all([\n withTimeout(this.deps.fetchTicketContext(this.ticketId), API_TIMEOUT_MS, 'fetchTicketContext'),\n withTimeout(this.deps.fetchColumnContext(this.columnId), API_TIMEOUT_MS, 'fetchColumnContext'),\n ]);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n log(`Context fetch failed: ${message}`);\n if (this.looksLike404(err)) {\n return withCosts({ reason: 'deleted', iterations: i, gutterCount, model: resolvedModel });\n }\n return withCosts({ reason: 'error', iterations: i, gutterCount, lastError: `Context fetch failed: ${message}`, model: resolvedModel });\n }\n\n // Pre-flight column check — bail if ticket already moved\n if (ticketCtx.ticket.column && ticketCtx.ticket.column.id !== this.columnId) {\n log(`Ticket already in column ${ticketCtx.ticket.column.name}, not ${this.columnId} — exiting as moved`);\n return withCosts({ reason: 'moved', iterations: i - 1, gutterCount, model: resolvedModel });\n }\n\n // Fetch run memory and lookahead enrichments — non-blocking, failures are silenced\n const runMemoryContent = this.deps.fetchRunMemoryContent\n ? await this.deps.fetchRunMemoryContent().catch(() => '')\n : undefined;\n\n const lookaheadDocument = this.deps.fetchLookaheadDocument\n ? await this.deps.fetchLookaheadDocument().catch(() => undefined)\n : undefined;\n\n // Compose prompt from column context, ticket context, and enrichments\n let prompt: string;\n try {\n prompt = composePrompt(columnCtx, ticketCtx, {\n iteration: i,\n maxIterations: this.config.maxIterations,\n projectId: this.deps.projectId,\n gutterCount,\n gutterThreshold: this.config.gutterThreshold,\n runMemoryContent: runMemoryContent || undefined,\n lookaheadDocument,\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n log(`Prompt composition failed: ${message}`);\n return withCosts({ reason: 'error', iterations: i, gutterCount, lastError: `Prompt composition failed: ${message}`, model: resolvedModel });\n }\n\n // Invoke the agent provider with the composed prompt\n if (this.stopped || this.config.abortSignal?.aborted) return withCosts({ reason: 'stopped', iterations: i - 1, gutterCount, model: resolvedModel });\n log(`Invoking ${this.deps.provider.displayName} (model=${this.config.model ?? 'default'})`);\n\n const iterationRunId = `${this.config.runId ?? this.ticketId}-iter-${String(i)}`;\n\n this.deps.onSessionStart?.({\n runId: iterationRunId,\n model: this.config.model ?? 'default',\n iteration: i,\n });\n\n const streamContext = { runId: iterationRunId, ticketId: this.ticketId, columnId: this.columnId };\n const agentRequest: AgentRequest = {\n prompt,\n ...(this.config.model != null && { model: this.config.model }),\n ...(this.config.maxBudgetUsd != null && { maxTurns: Math.max(1, Math.ceil(this.config.maxBudgetUsd * 10)) }),\n ...(this.config.worktreeName != null && { workingDirectory: this.config.worktreeName }),\n ...(this.deps.mcpConfig != null && { mcpConfig: this.deps.mcpConfig }),\n ...(this.config.toolRestrictions != null && {\n toolRestrictions: { ...this.config.toolRestrictions, includeMcpConfig: this.config.toolRestrictions.includeMcpConfig ?? true },\n }),\n ...(this.config.abortSignal != null && { abortSignal: this.config.abortSignal }),\n onStreamEvent: (event) => this.deps.onStreamEvent?.(event, streamContext),\n };\n\n const { exitCode, output, toolCallCount, usage, durationMs: iterationDurationMs, degradedCapabilities } = await this.deps.provider.invoke(agentRequest);\n const tokensIn = usage.inputTokens;\n const tokensOut = usage.outputTokens;\n\n // Log degraded capabilities on first iteration only\n if (i === startIter && degradedCapabilities?.length) {\n log(`Provider ${this.deps.provider.id} degraded: ${degradedCapabilities.join(', ')}`);\n }\n\n cumulativeTokensIn += tokensIn;\n cumulativeTokensOut += tokensOut;\n cumulativeToolCalls += toolCallCount;\n cumulativeDurationMs += iterationDurationMs;\n\n this.deps.onSessionEnd?.({\n runId: iterationRunId,\n exitCode,\n tokensIn,\n tokensOut,\n toolCallCount,\n durationMs: iterationDurationMs,\n });\n\n if (exitCode !== 0) {\n const snippet = output.slice(-200);\n log(`${this.deps.provider.displayName} exited with code ${exitCode}: ${snippet}`);\n return withCosts({ reason: 'error', iterations: i, gutterCount, lastError: `non-zero exit code: ${exitCode}. Last output: ${snippet}`, model: resolvedModel });\n }\n log(`${this.deps.provider.displayName} exited successfully`);\n lastOutput = output;\n\n // Post-iteration fingerprint check — detect if ticket moved out of column\n let afterFp: TicketFingerprint;\n try {\n const retryDelayMs = this.config.postMoveRetryDelayMs ?? 1500;\n afterFp = await withTimeout(\n this.deps.fetchFingerprint(this.ticketId),\n API_TIMEOUT_MS, 'fetchFingerprint (post-iteration)',\n );\n\n if (afterFp.column_id === this.columnId) {\n for (let retry = 0; retry < 2; retry++) {\n await new Promise((r) => setTimeout(r, retryDelayMs));\n if (this.stopped) break; // Honor stop during retry delay\n try {\n afterFp = await withTimeout(\n this.deps.fetchFingerprint(this.ticketId),\n API_TIMEOUT_MS, 'fetchFingerprint (retry)',\n );\n } catch (err) {\n log(`Fingerprint retry ${retry + 1} failed: ${err instanceof Error ? err.message : String(err)} — using last good value`);\n break; // retry fingerprint failed — use last good value\n }\n if (afterFp.column_id !== this.columnId) break;\n }\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n log(`Post-iteration fingerprint failed: ${message}`);\n if (this.looksLike404(err)) {\n return withCosts({ reason: 'deleted', iterations: i, gutterCount, model: resolvedModel });\n }\n return withCosts({ reason: 'error', iterations: i, gutterCount, lastError: `Post-iteration fingerprint failed: ${message}`, model: resolvedModel });\n }\n\n // Did ticket move?\n if (afterFp.column_id !== this.columnId) {\n log(`Ticket moved to column ${afterFp.column_id ?? 'null'}`);\n return withCosts({ reason: 'moved', iterations: i, gutterCount, model: resolvedModel, output: lastOutput });\n }\n\n // Save gutter count before gate delta adjustment so stuck detection\n // can replace (not compound on) the per-iteration gate delta signal.\n const gutterBeforeGates = gutterCount;\n\n // Gate-based gutter detection (preferred) or fingerprint fallback\n if (this.config.onPostIterationGates) {\n try {\n const snapshot = await this.config.onPostIterationGates(this.ticketId, i);\n gateSnapshots.push(snapshot);\n\n // Also check if any field values changed (complementary signal)\n const fieldDelta = lastFingerprint ? afterFp.field_value_count !== lastFingerprint.field_value_count : false;\n\n switch (snapshot.delta_from_previous) {\n case 'improved':\n if (gutterCount > 0) log(`Gate improvement detected — gutter counter reset`);\n gutterCount = 0;\n break;\n case 'same':\n if (!fieldDelta) {\n gutterCount++;\n log(`No gate progress (gutter ${gutterCount}/${this.config.gutterThreshold})`);\n } else {\n log(`Gates unchanged but fields changed — not incrementing gutter`);\n }\n break;\n case 'regressed':\n gutterCount += 2;\n log(`Gate regression detected (gutter ${gutterCount}/${this.config.gutterThreshold})`);\n break;\n case 'first_check':\n // First check — no delta info yet, don't adjust gutter\n break;\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n log(`Post-iteration gate check failed (non-blocking): ${message}`);\n // Fall back to fingerprint gutter on gate failure\n if (lastFingerprint && fingerprintsMatch(lastFingerprint, afterFp)) {\n gutterCount++;\n log(`No progress detected via fingerprint fallback (gutter ${gutterCount}/${this.config.gutterThreshold})`);\n } else {\n if (gutterCount > 0) log(`Progress detected — gutter counter reset`);\n gutterCount = 0;\n }\n }\n } else {\n // Original fingerprint-based gutter detection\n if (lastFingerprint && fingerprintsMatch(lastFingerprint, afterFp)) {\n gutterCount++;\n log(`No progress detected (gutter ${gutterCount}/${this.config.gutterThreshold})`);\n } else {\n if (gutterCount > 0) log(`Progress detected — gutter counter reset`);\n gutterCount = 0;\n }\n }\n lastFingerprint = afterFp;\n\n // Pattern-based stuck detection — classifies trajectory as progressing/spinning/blocked\n if (this.config.stuckDetection && shouldCheckStuckDetection(this.config.stuckDetection, i)) {\n try {\n let sdStatus: 'progressing' | 'spinning' | 'blocked';\n let sdEvidence: string;\n let sdConfidence: number;\n\n if (gateSnapshots.length > 0) {\n // Deterministic classification from gate data\n const trajectory = classifyTrajectory(gateSnapshots);\n sdStatus = trajectory.status === 'regressing' ? 'spinning' : trajectory.status;\n sdEvidence = trajectory.evidence;\n sdConfidence = trajectory.confidence;\n log(`Gate-based stuck detection: ${sdStatus} (confidence=${String(sdConfidence)}) — ${sdEvidence}`);\n } else if (this.config.invokeStuckDetection) {\n // Legacy LLM-based classification\n const sdInput: StuckDetectionInput = {\n ticketNumber: ticketCtx.ticket.ticket_number,\n ticketTitle: ticketCtx.ticket.title,\n columnName: columnCtx.column.name,\n iteration: i,\n maxIterations: this.config.maxIterations,\n recentComments: ticketCtx.comments.slice(-3).map((c) => ({\n author: c.author,\n body: c.body,\n })),\n };\n const sdResult = await this.config.invokeStuckDetection(sdInput);\n sdStatus = sdResult.status;\n sdEvidence = sdResult.evidence;\n sdConfidence = sdResult.confidence;\n log(`Stuck detection: ${sdStatus} (confidence=${String(sdConfidence)}) — ${sdEvidence}`);\n } else {\n // No detection method available — skip\n sdStatus = 'progressing';\n sdEvidence = 'no detection method';\n sdConfidence = 0;\n }\n\n switch (sdStatus) {\n case 'progressing':\n if (gutterCount > 0) {\n log(`Stuck detection override: resetting gutter counter (was ${String(gutterCount)})`);\n gutterCount = 0;\n }\n break;\n case 'spinning':\n // Use pre-gate-delta base to avoid double-counting: the gate delta\n // already incremented gutter from the same underlying signal.\n gutterCount = gutterBeforeGates + 2;\n log(`Stuck detection: spinning — gutter set to ${String(gutterCount)}/${String(this.config.gutterThreshold)}`);\n break;\n case 'blocked':\n log(`Stuck detection: blocked — exiting immediately`);\n return withCosts({\n reason: 'stalled',\n iterations: i,\n gutterCount: this.config.gutterThreshold,\n model: resolvedModel,\n output: lastOutput,\n ...(gateSnapshots.length > 0 && { finalGateSnapshot: gateSnapshots[gateSnapshots.length - 1] }),\n });\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n log(`Stuck detection failed (non-blocking): ${message}`);\n }\n }\n\n // Gutter threshold exit (after stuck detection may have adjusted gutterCount)\n if (gutterCount >= this.config.gutterThreshold) {\n return withCosts({\n reason: 'stalled',\n iterations: i,\n gutterCount,\n model: resolvedModel,\n output: lastOutput,\n ...(gateSnapshots.length > 0 && { finalGateSnapshot: gateSnapshots[gateSnapshots.length - 1] }),\n });\n }\n\n // Write checkpoint after iteration to allow resume on restart\n if (this.config.onCheckpoint) {\n try {\n await this.config.onCheckpoint(this.ticketId, {\n run_id: this.config.runId ?? '00000000-0000-0000-0000-000000000000',\n column_id: this.columnId,\n iteration: i,\n gutter_count: gutterCount,\n advisor_invocations: 0, // tracked by orchestrator, not loop\n model_tier: resolvedModel,\n last_fingerprint: afterFp,\n updated_at: new Date().toISOString(),\n worktree_name: this.config.worktreeName,\n });\n } catch (err) {\n // Checkpoint write must never block the loop\n log(`Checkpoint write failed (non-blocking): ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n }\n\n return withCosts({\n reason: 'max_iterations',\n iterations: this.config.maxIterations,\n gutterCount,\n model: resolvedModel,\n output: lastOutput,\n ...(gateSnapshots.length > 0 && { finalGateSnapshot: gateSnapshots[gateSnapshots.length - 1] }),\n });\n }\n}\n\nfunction fingerprintsMatch(a: TicketFingerprint, b: TicketFingerprint): boolean {\n // column_id change is detected separately (exits as 'moved').\n // signal_count is excluded because dependency-blocked agents inflate it.\n // comment_count IS included because agents are instructed to create\n // iteration summary comments — genuine new comments indicate progress.\n return a.column_id === b.column_id\n && a.field_value_count === b.field_value_count\n && a.comment_count === b.comment_count;\n}\n","import { writeFileSync, unlinkSync, mkdirSync, existsSync, readdirSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { homedir } from 'node:os';\nimport { npxCommand, defaultPath } from './platform.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n/**\n * Generate (or overwrite) a stable MCP config file for the given board.\n * Uses a deterministic path (~/.kantban/pipelines/<boardId>/mcp-config.json)\n * so that child claude -p processes can always find it, even if the\n * orchestrator restarts between iterations.\n */\nexport function generateMcpConfig(apiUrl: string, apiToken: string, boardId: string): string {\n // Use local MCP server if running from the monorepo (dev mode),\n // otherwise fall back to the published npm package.\n // From src/lib/ the MCP package is at ../../../mcp/dist/index.js.\n// From dist/ (after tsup bundles) it's at ../../mcp/dist/index.js.\n// Try both to handle dev and built modes.\nconst localMcpPath = existsSync(join(__dirname, '..', '..', 'mcp', 'dist', 'index.js'))\n ? join(__dirname, '..', '..', 'mcp', 'dist', 'index.js')\n : join(__dirname, '..', '..', '..', 'mcp', 'dist', 'index.js');\n const useLocal = existsSync(localMcpPath);\n\n const kantbanServer = useLocal\n ? {\n command: 'node',\n args: [localMcpPath],\n env: {\n KANTBAN_API_TOKEN: apiToken,\n KANTBAN_API_URL: apiUrl,\n },\n }\n : {\n command: npxCommand(),\n args: ['-y', 'kantban-mcp@latest'],\n env: {\n KANTBAN_API_TOKEN: apiToken,\n KANTBAN_API_URL: apiUrl,\n },\n };\n\n const config = {\n mcpServers: {\n kantban: kantbanServer,\n },\n };\n\n const dir = join(homedir(), '.kantban', 'pipelines', boardId);\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n const filePath = join(dir, 'mcp-config.json');\n writeFileSync(filePath, JSON.stringify(config, null, 2), { mode: 0o600 });\n return filePath;\n}\n\nexport function cleanupMcpConfig(filePath: string): void {\n try {\n if (existsSync(filePath)) {\n unlinkSync(filePath);\n }\n } catch {\n // Non-critical — file may already be gone\n }\n}\n\n/**\n * Generate an MCP config file that includes BOTH the kantban server AND the\n * gate proxy server for a specific column. The gate proxy runs as a local\n * Node process bundled with the CLI and enforces column-level gates during\n * pipeline execution.\n *\n * The file is written to a deterministic, column-scoped path:\n * ~/.kantban/pipelines/<boardId>/mcp-config-<columnId>.json\n */\nexport function generateGateProxyMcpConfig(\n apiUrl: string,\n apiToken: string,\n boardId: string,\n gateConfigPath: string,\n columnId: string,\n columnName: string,\n projectId: string,\n gateCwd?: string,\n ticketId?: string,\n): string {\n // From src/lib/ the MCP package is at ../../../mcp/dist/index.js.\n// From dist/ (after tsup bundles) it's at ../../mcp/dist/index.js.\n// Try both to handle dev and built modes.\nconst localMcpPath = existsSync(join(__dirname, '..', '..', 'mcp', 'dist', 'index.js'))\n ? join(__dirname, '..', '..', 'mcp', 'dist', 'index.js')\n : join(__dirname, '..', '..', '..', 'mcp', 'dist', 'index.js');\n const useLocal = existsSync(localMcpPath);\n\n // Hide move_ticket/complete_task from the kantban server so agents use the\n // gate proxy versions instead. Without this, agents call the kantban server's\n // move_ticket directly, bypassing gate enforcement.\n const kantbanServer = useLocal\n ? { 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' } }\n : { 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' } };\n\n // Gate proxy runs as a local Node script bundled with the CLI.\n // From dist/ the file is at lib/gate-proxy-server.js; from src/lib/ it's sibling.\n const gateProxyPath = existsSync(join(__dirname, 'lib', 'gate-proxy-server.js'))\n ? join(__dirname, 'lib', 'gate-proxy-server.js')\n : join(__dirname, 'gate-proxy-server.js');\n const gateProxyEnv: Record<string, string> = {\n GATE_CONFIG_PATH: gateConfigPath,\n COLUMN_ID: columnId,\n COLUMN_NAME: columnName,\n PROJECT_ID: projectId,\n KANTBAN_API_TOKEN: apiToken,\n KANTBAN_API_URL: apiUrl,\n // Ensure gate commands can find npm/node even if the agent CLI spawns\n // MCP servers with a replacement env instead of merging with process.env.\n // Without this, gate-runner's `sh -c \"npm run ...\"` fails with ENOENT.\n PATH: process.env.PATH ?? defaultPath(),\n };\n if (gateCwd) gateProxyEnv['GATE_CWD'] = gateCwd;\n\n const gateProxyServer = {\n command: 'node',\n args: [gateProxyPath],\n env: gateProxyEnv,\n };\n\n const config = {\n mcpServers: {\n kantban: kantbanServer,\n 'kantban-gates': gateProxyServer,\n },\n };\n\n const dir = join(homedir(), '.kantban', 'pipelines', boardId);\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n // Per-ticket config avoids race conditions when multiple tickets in the same\n // column are processed concurrently (each has a different worktree path).\n const suffix = ticketId ? `${columnId}-${ticketId}` : columnId;\n const filePath = join(dir, `mcp-config-${suffix}.json`);\n writeFileSync(filePath, JSON.stringify(config, null, 2), { mode: 0o600 });\n return filePath;\n}\n\n/**\n * Remove all gate-proxy MCP config files for a given pipeline directory.\n * Called on shutdown to ensure credentials don't persist on disk.\n */\nexport function cleanupGateProxyConfigs(pipelineDir: string): void {\n try {\n const files = readdirSync(pipelineDir);\n for (const f of files) {\n if (f.startsWith('mcp-config-') && f.endsWith('.json')) {\n try { unlinkSync(join(pipelineDir, f)); } catch { /* ignore */ }\n }\n }\n } catch { /* directory may not exist */ }\n}\n","import { spawn, type ChildProcess } from 'node:child_process';\nimport { writeFileSync, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { crossSpawnOptions, killProcessTree, resolveCommand } from '../lib/platform.js';\nimport type {\n AgentProvider, AgentRequest, AgentResult, PreflightResult, ProviderCapabilities,\n} from './types.js';\nimport { ClaudeStreamParser } from './claude-stream-parser.js';\n\nconst CLAUDE_TIMEOUT_MS = 60 * 60 * 1000; // 1 hour\n\nexport class ClaudeProvider implements AgentProvider {\n readonly id = 'claude';\n readonly displayName = 'Claude Code';\n\n capabilities(): ProviderCapabilities {\n return {\n supportsToolAllowlist: true,\n supportsToolDenylist: true,\n supportsBuiltinToolStripping: true,\n supportsMaxTurns: true,\n supportsMcpConfigInjection: true,\n supportsMcpConfigOverride: false,\n supportsWorktreeFlag: true,\n supportsSandboxModes: false,\n supportedModels: [\n { id: 'claude-haiku-4-5-20251001', displayName: 'Haiku 4.5', tier: 'fast' },\n { id: 'claude-sonnet-4-6', displayName: 'Sonnet 4.6', tier: 'default' },\n { id: 'claude-opus-4-6', displayName: 'Opus 4.6', tier: 'thorough' },\n ],\n streamFormat: 'stream-json',\n };\n }\n\n async invoke(request: AgentRequest): Promise<AgentResult> {\n const args = this.buildArgs(request);\n const startTime = Date.now();\n\n // Resolve command to avoid cmd.exe 8191-char limit on Windows.\n // resolveCommand('claude') returns ['C:/.../claude.exe', []] or\n // [process.execPath, ['path/to/script.js']] — both bypass cmd.exe.\n const [cmd, prefixArgs] = resolveCommand('claude');\n const resolvedArgs = [...prefixArgs, ...args];\n\n return new Promise((resolve) => {\n const child: ChildProcess = spawn(cmd, resolvedArgs, {\n stdio: ['pipe', 'pipe', 'pipe'],\n // Do NOT set cwd to the worktree name — the worktree doesn't exist yet.\n // Claude Code's --worktree flag creates it internally.\n // Only use shell:true as fallback when resolveCommand couldn't resolve\n ...(prefixArgs.length > 0 ? {} : crossSpawnOptions()),\n });\n\n const parser = new ClaudeStreamParser();\n parser.onEvent = (event) => request.onStreamEvent?.(event);\n parser.onError = (err) => process.stderr.write(`[claude-stream] ${err.message}\\n`);\n\n let stderr = '';\n\n child.stdout?.on('data', (chunk: Buffer) => parser.feed(chunk.toString()));\n child.stderr?.on('data', (chunk: Buffer) => { stderr += chunk.toString(); });\n child.stdin?.end();\n\n // Abort signal support\n if (request.abortSignal) {\n request.abortSignal.addEventListener('abort', () => {\n try { child.kill('SIGTERM'); } catch { /* already dead */ }\n }, { once: true });\n }\n\n // Timeout\n let killTimer: ReturnType<typeof setTimeout> | undefined;\n const timeoutHandle = setTimeout(() => {\n try { child.kill('SIGTERM'); } catch { /* already dead */ }\n killTimer = setTimeout(() => {\n if (child.pid) killProcessTree(child.pid, 'SIGKILL');\n }, 5000);\n }, CLAUDE_TIMEOUT_MS);\n\n let resolved = false;\n const finish = (code: number | null, errorMsg?: string) => {\n if (resolved) return;\n resolved = true;\n clearTimeout(timeoutHandle);\n if (killTimer) clearTimeout(killTimer);\n parser.flush();\n\n const usage = parser.getUsage();\n resolve({\n exitCode: code ?? 1,\n output: errorMsg ?? (parser.getLastOutput() || stderr),\n toolCallCount: parser.getToolCallCount(),\n usage,\n durationMs: Date.now() - startTime,\n });\n };\n\n child.on('close', (code) => finish(code));\n child.on('error', (err) => finish(1, err.message));\n });\n }\n\n async preflight(): Promise<PreflightResult> {\n try {\n // Dynamic import for CJS interop — which@2 uses module.exports = fn\n const whichModule = await import('which');\n const syncFn = whichModule.default?.sync ?? whichModule.sync;\n syncFn('claude');\n return { available: true, authenticated: true };\n } catch {\n return { available: false, authenticated: false, error: 'claude binary not found on PATH' };\n }\n }\n\n private buildArgs(request: AgentRequest): string[] {\n const args: string[] = [\n '-p', request.prompt,\n '--dangerously-skip-permissions',\n '--output-format', 'stream-json',\n '--verbose',\n ];\n\n // MCP config — write JSON file from McpConfig if provided\n if (request.mcpConfig && request.toolRestrictions?.includeMcpConfig !== false) {\n const configPath = this.writeMcpConfigJson(request.mcpConfig);\n args.push('--mcp-config', configPath);\n }\n\n if (request.model) args.push('--model', request.model);\n\n if (request.maxTurns) {\n args.push('--max-turns', String(request.maxTurns));\n }\n\n if (request.workingDirectory) {\n args.push('--worktree', request.workingDirectory);\n }\n\n // Tool scoping\n if (request.toolRestrictions) {\n const tr = request.toolRestrictions;\n if (tr.tools !== undefined) args.push('--tools', tr.tools);\n if (tr.allowedTools?.length) args.push('--allowedTools', ...tr.allowedTools);\n if (tr.disallowedTools?.length) args.push('--disallowedTools', ...tr.disallowedTools);\n }\n\n return args;\n }\n\n private writeMcpConfigJson(mcpConfig: AgentRequest['mcpConfig']): string {\n if (!mcpConfig) return '';\n const config = { mcpServers: mcpConfig.servers };\n const dir = join(homedir(), '.kantban', 'tmp');\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n const filePath = join(dir, `mcp-config-${Date.now()}.json`);\n writeFileSync(filePath, JSON.stringify(config, null, 2), { mode: 0o600 });\n return filePath;\n }\n}\n","import type { NormalizedStreamEvent } from './types.js';\n\n/**\n * Parses Claude Code's --output-format stream-json (newline-delimited JSON)\n * and emits NormalizedStreamEvents.\n */\nexport class ClaudeStreamParser {\n private buffer = '';\n private toolCallCount = 0;\n private inputTokens = 0;\n private outputTokens = 0;\n private lastOutput = '';\n\n onEvent: (event: NormalizedStreamEvent) => void = () => {};\n onError: (error: Error) => void = () => {};\n\n feed(chunk: string): void {\n this.buffer += chunk;\n const lines = this.buffer.split('\\n');\n this.buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n this.parseLine(line.trim());\n }\n }\n\n flush(): void {\n const trimmed = this.buffer.trim();\n this.buffer = '';\n if (trimmed) this.parseLine(trimmed);\n }\n\n getToolCallCount(): number {\n return this.toolCallCount;\n }\n\n getUsage(): { inputTokens: number; outputTokens: number } {\n return { inputTokens: this.inputTokens, outputTokens: this.outputTokens };\n }\n\n getLastOutput(): string {\n return this.lastOutput;\n }\n\n reset(): void {\n this.buffer = '';\n this.toolCallCount = 0;\n this.inputTokens = 0;\n this.outputTokens = 0;\n this.lastOutput = '';\n }\n\n private parseLine(line: string): void {\n if (!line) return;\n try {\n const raw = JSON.parse(line) as Record<string, unknown>;\n this.translateEvent(raw);\n } catch {\n this.onError(new Error(`Failed to parse stream-json line: ${line.slice(0, 100)}`));\n }\n }\n\n private translateEvent(raw: Record<string, unknown>): void {\n // Content lives at raw.message.content (stream-json format) or raw.content (legacy)\n const message = raw.message as Record<string, unknown> | undefined;\n const content = (message?.content ?? raw.content) as Array<Record<string, unknown>> | undefined;\n if (raw.type === 'assistant' && Array.isArray(content)) {\n for (const block of content) {\n if (block.type === 'text' && typeof block.text === 'string') {\n this.onEvent({ type: 'text', text: block.text });\n } else if (block.type === 'tool_use') {\n this.toolCallCount++;\n this.onEvent({\n type: 'tool_call',\n tool: (block.name as string) ?? 'unknown',\n input: block.input,\n });\n } else if (block.type === 'tool_result') {\n this.onEvent({\n type: 'tool_result',\n tool: (block.name as string) ?? 'unknown',\n output: block.content ?? block.output,\n });\n }\n }\n } else if (raw.type === 'result') {\n const usage = raw.usage as { input_tokens?: number; output_tokens?: number } | undefined;\n const inTok = usage?.input_tokens ?? 0;\n const outTok = usage?.output_tokens ?? 0;\n this.inputTokens += inTok;\n this.outputTokens += outTok;\n if (inTok || outTok) {\n this.onEvent({ type: 'usage', inputTokens: inTok, outputTokens: outTok });\n }\n if (typeof raw.result === 'string') {\n this.lastOutput = raw.result;\n this.onEvent({ type: 'done', result: raw.result });\n }\n }\n }\n}\n"],"mappings":"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","names":["writeFileSync","mkdirSync","join","homedir","join","homedir","mkdirSync","writeFileSync"]}
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
normalizeEol,
|
|
6
6
|
shellArgs
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-5ZU2OOES.js";
|
|
8
8
|
|
|
9
9
|
// src/lib/gate-runner.ts
|
|
10
10
|
import { execFile } from "child_process";
|
|
@@ -104,4 +104,4 @@ export {
|
|
|
104
104
|
formatGateErrors,
|
|
105
105
|
runGates
|
|
106
106
|
};
|
|
107
|
-
//# sourceMappingURL=chunk-
|
|
107
|
+
//# sourceMappingURL=chunk-MKKHLFA5.js.map
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import "./chunk-DGUM43GV.js";
|
|
2
|
-
|
|
3
1
|
// src/commands/context.ts
|
|
4
2
|
async function runContext(client, args) {
|
|
5
3
|
const [scopeType, scopeId] = args;
|
|
@@ -29,4 +27,4 @@ async function runContext(client, args) {
|
|
|
29
27
|
export {
|
|
30
28
|
runContext
|
|
31
29
|
};
|
|
32
|
-
//# sourceMappingURL=context-
|
|
30
|
+
//# sourceMappingURL=context-7YDNTI3P.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/context.ts"],"sourcesContent":["import type { KantBanCLIClient } from '../client.js';\n\nexport async function runContext(client: KantBanCLIClient, args: string[]): Promise<void> {\n const [scopeType, scopeId] = args;\n if (!scopeType || !scopeId) {\n console.error('Usage: kantban context <board|column|ticket> <id>');\n process.exit(1);\n }\n\n const projectId = process.env['KANTBAN_PROJECT_ID'];\n if (!projectId) {\n console.error('Error: KANTBAN_PROJECT_ID environment variable required for context command');\n process.exit(1);\n }\n\n const params: Record<string, string> = {};\n if (scopeType === 'board') params['boardId'] = scopeId;\n else if (scopeType === 'column') params['columnId'] = scopeId;\n else if (scopeType === 'ticket') params['ticketId'] = scopeId;\n else {\n console.error(`Unknown scope type: ${scopeType}. Use board, column, or ticket.`);\n process.exit(1);\n }\n\n const data = await client.get<Record<string, unknown>>(\n `/projects/${projectId}/pipeline-context`,\n params,\n );\n\n // Output as structured JSON to stdout for piping\n console.log(JSON.stringify(data, null, 2));\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/commands/context.ts"],"sourcesContent":["import type { KantBanCLIClient } from '../client.js';\n\nexport async function runContext(client: KantBanCLIClient, args: string[]): Promise<void> {\n const [scopeType, scopeId] = args;\n if (!scopeType || !scopeId) {\n console.error('Usage: kantban context <board|column|ticket> <id>');\n process.exit(1);\n }\n\n const projectId = process.env['KANTBAN_PROJECT_ID'];\n if (!projectId) {\n console.error('Error: KANTBAN_PROJECT_ID environment variable required for context command');\n process.exit(1);\n }\n\n const params: Record<string, string> = {};\n if (scopeType === 'board') params['boardId'] = scopeId;\n else if (scopeType === 'column') params['columnId'] = scopeId;\n else if (scopeType === 'ticket') params['ticketId'] = scopeId;\n else {\n console.error(`Unknown scope type: ${scopeType}. Use board, column, or ticket.`);\n process.exit(1);\n }\n\n const data = await client.get<Record<string, unknown>>(\n `/projects/${projectId}/pipeline-context`,\n params,\n );\n\n // Output as structured JSON to stdout for piping\n console.log(JSON.stringify(data, null, 2));\n}\n"],"mappings":";AAEA,eAAsB,WAAW,QAA0B,MAA+B;AACxF,QAAM,CAAC,WAAW,OAAO,IAAI;AAC7B,MAAI,CAAC,aAAa,CAAC,SAAS;AAC1B,YAAQ,MAAM,mDAAmD;AACjE,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,YAAY,QAAQ,IAAI,oBAAoB;AAClD,MAAI,CAAC,WAAW;AACd,YAAQ,MAAM,6EAA6E;AAC3F,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,SAAiC,CAAC;AACxC,MAAI,cAAc,QAAS,QAAO,SAAS,IAAI;AAAA,WACtC,cAAc,SAAU,QAAO,UAAU,IAAI;AAAA,WAC7C,cAAc,SAAU,QAAO,UAAU,IAAI;AAAA,OACjD;AACH,YAAQ,MAAM,uBAAuB,SAAS,iCAAiC;AAC/E,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,OAAO,MAAM,OAAO;AAAA,IACxB,aAAa,SAAS;AAAA,IACtB;AAAA,EACF;AAGA,UAAQ,IAAI,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAC3C;","names":[]}
|
|
@@ -3,10 +3,9 @@ import {
|
|
|
3
3
|
RalphLoop,
|
|
4
4
|
cleanupMcpConfig,
|
|
5
5
|
generateMcpConfig
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-JGAOKTRU.js";
|
|
7
7
|
import "./chunk-DAFLEMLK.js";
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-DGUM43GV.js";
|
|
8
|
+
import "./chunk-5ZU2OOES.js";
|
|
10
9
|
|
|
11
10
|
// src/commands/cron.ts
|
|
12
11
|
import { readFileSync } from "fs";
|
|
@@ -103,4 +102,4 @@ async function runCron(client, args) {
|
|
|
103
102
|
export {
|
|
104
103
|
runCron
|
|
105
104
|
};
|
|
106
|
-
//# sourceMappingURL=cron-
|
|
105
|
+
//# sourceMappingURL=cron-Z66K3HWS.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/cron.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport type { KantBanCLIClient } from '../client.js';\nimport { generateMcpConfig, cleanupMcpConfig } from '../lib/mcp-config.js';\nimport { RalphLoop, type RalphLoopDeps, type LoopConfig } from '../lib/ralph-loop.js';\nimport type { ColumnContext, TicketContext } from '../lib/prompt-composer.js';\nimport type { TicketFingerprint } from '@kantban/types';\nimport { ClaudeProvider } from '../providers/claude-provider.js';\nimport type { McpConfig } from '../providers/types.js';\n\nfunction parseDuration(input: string): number {\n const match = input.match(/^(\\d+)(s|m|h)$/);\n if (!match) throw new Error(`Invalid duration: ${input}. Use format like 5m, 30s, 1h`);\n const value = Number(match[1]);\n const unit = match[2];\n switch (unit) {\n case 's': return value * 1000;\n case 'm': return value * 60 * 1000;\n case 'h': return value * 60 * 60 * 1000;\n default: throw new Error(`Unknown unit: ${unit}`);\n }\n}\n\nexport async function runCron(client: KantBanCLIClient, args: string[]): Promise<void> {\n const columnId = args[0];\n if (!columnId) {\n console.error('Usage: kantban cron <column-id> [--interval 5m]');\n process.exit(1);\n }\n\n // Parse interval\n let intervalMs = 5 * 60 * 1000; // default 5m\n const intervalIdx = args.indexOf('--interval');\n const intervalArg = intervalIdx !== -1 ? args[intervalIdx + 1] : undefined;\n if (intervalArg) {\n intervalMs = parseDuration(intervalArg);\n }\n\n // Resolve project ID\n const projectId = process.env['KANTBAN_PROJECT_ID'];\n if (!projectId) {\n console.error('Error: KANTBAN_PROJECT_ID required');\n process.exit(1);\n }\n\n // Generate MCP config (stable path keyed by column)\n const mcpConfigPath = generateMcpConfig(client.baseUrl, client.token, `cron-${columnId}`);\n\n // Create provider and read MCP config\n const provider = new ClaudeProvider();\n const raw = JSON.parse(readFileSync(mcpConfigPath, 'utf-8')) as { mcpServers: McpConfig['servers'] };\n const mcpConfig: McpConfig = { servers: raw.mcpServers };\n\n let stopped = false;\n\n // Graceful shutdown\n const shutdown = () => {\n stopped = true;\n cleanupMcpConfig(mcpConfigPath);\n console.log('\\nCron stopped.');\n process.exit(0);\n };\n process.on('SIGTERM', shutdown);\n process.on('SIGINT', shutdown);\n\n console.log(`Cron: column ${columnId}, interval ${String(intervalMs / 1000)}s`);\n console.log('Press Ctrl+C to stop.\\n');\n\n // Main cron tick\n const tick = async () => {\n if (stopped) return;\n\n try {\n // Fetch column scope\n const columnScope = await client.get<ColumnContext>(\n `/projects/${projectId}/pipeline-context`,\n { columnId },\n );\n\n const tickets = columnScope.tickets ?? [];\n if (tickets.length === 0) {\n console.log(`[${new Date().toLocaleTimeString('en-US', { hour12: false })}] No tickets in column. Sleeping...`);\n return;\n }\n\n console.log(`[${new Date().toLocaleTimeString('en-US', { hour12: false })}] Processing ${String(tickets.length)} ticket(s)...`);\n\n // Process each ticket sequentially (one iteration each for cron)\n for (const ticket of tickets) {\n if (stopped) break;\n\n const deps: RalphLoopDeps = {\n fetchTicketContext: (tid) => client.get<TicketContext>(\n `/projects/${projectId}/pipeline-context`, { ticketId: tid },\n ),\n fetchColumnContext: (cid) => client.get<ColumnContext>(\n `/projects/${projectId}/pipeline-context`, { columnId: cid },\n ),\n fetchFingerprint: (tid) => client.getFingerprint(projectId, tid) as Promise<TicketFingerprint>,\n provider,\n mcpConfig,\n projectId,\n };\n\n const config: LoopConfig = { maxIterations: 1, gutterThreshold: 1 };\n\n const loop = new RalphLoop(ticket.id, columnId, config, deps);\n const result = await loop.run();\n console.log(` ${String(ticket.ticket_number)}: ${result.reason} (${String(result.iterations)} iter)`);\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(`Cron tick error: ${message}`);\n }\n };\n\n // Run immediately, then on interval\n await tick();\n setInterval(() => void tick(), intervalMs);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/commands/cron.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport type { KantBanCLIClient } from '../client.js';\nimport { generateMcpConfig, cleanupMcpConfig } from '../lib/mcp-config.js';\nimport { RalphLoop, type RalphLoopDeps, type LoopConfig } from '../lib/ralph-loop.js';\nimport type { ColumnContext, TicketContext } from '../lib/prompt-composer.js';\nimport type { TicketFingerprint } from '@kantban/types';\nimport { ClaudeProvider } from '../providers/claude-provider.js';\nimport type { McpConfig } from '../providers/types.js';\n\nfunction parseDuration(input: string): number {\n const match = input.match(/^(\\d+)(s|m|h)$/);\n if (!match) throw new Error(`Invalid duration: ${input}. Use format like 5m, 30s, 1h`);\n const value = Number(match[1]);\n const unit = match[2];\n switch (unit) {\n case 's': return value * 1000;\n case 'm': return value * 60 * 1000;\n case 'h': return value * 60 * 60 * 1000;\n default: throw new Error(`Unknown unit: ${unit}`);\n }\n}\n\nexport async function runCron(client: KantBanCLIClient, args: string[]): Promise<void> {\n const columnId = args[0];\n if (!columnId) {\n console.error('Usage: kantban cron <column-id> [--interval 5m]');\n process.exit(1);\n }\n\n // Parse interval\n let intervalMs = 5 * 60 * 1000; // default 5m\n const intervalIdx = args.indexOf('--interval');\n const intervalArg = intervalIdx !== -1 ? args[intervalIdx + 1] : undefined;\n if (intervalArg) {\n intervalMs = parseDuration(intervalArg);\n }\n\n // Resolve project ID\n const projectId = process.env['KANTBAN_PROJECT_ID'];\n if (!projectId) {\n console.error('Error: KANTBAN_PROJECT_ID required');\n process.exit(1);\n }\n\n // Generate MCP config (stable path keyed by column)\n const mcpConfigPath = generateMcpConfig(client.baseUrl, client.token, `cron-${columnId}`);\n\n // Create provider and read MCP config\n const provider = new ClaudeProvider();\n const raw = JSON.parse(readFileSync(mcpConfigPath, 'utf-8')) as { mcpServers: McpConfig['servers'] };\n const mcpConfig: McpConfig = { servers: raw.mcpServers };\n\n let stopped = false;\n\n // Graceful shutdown\n const shutdown = () => {\n stopped = true;\n cleanupMcpConfig(mcpConfigPath);\n console.log('\\nCron stopped.');\n process.exit(0);\n };\n process.on('SIGTERM', shutdown);\n process.on('SIGINT', shutdown);\n\n console.log(`Cron: column ${columnId}, interval ${String(intervalMs / 1000)}s`);\n console.log('Press Ctrl+C to stop.\\n');\n\n // Main cron tick\n const tick = async () => {\n if (stopped) return;\n\n try {\n // Fetch column scope\n const columnScope = await client.get<ColumnContext>(\n `/projects/${projectId}/pipeline-context`,\n { columnId },\n );\n\n const tickets = columnScope.tickets ?? [];\n if (tickets.length === 0) {\n console.log(`[${new Date().toLocaleTimeString('en-US', { hour12: false })}] No tickets in column. Sleeping...`);\n return;\n }\n\n console.log(`[${new Date().toLocaleTimeString('en-US', { hour12: false })}] Processing ${String(tickets.length)} ticket(s)...`);\n\n // Process each ticket sequentially (one iteration each for cron)\n for (const ticket of tickets) {\n if (stopped) break;\n\n const deps: RalphLoopDeps = {\n fetchTicketContext: (tid) => client.get<TicketContext>(\n `/projects/${projectId}/pipeline-context`, { ticketId: tid },\n ),\n fetchColumnContext: (cid) => client.get<ColumnContext>(\n `/projects/${projectId}/pipeline-context`, { columnId: cid },\n ),\n fetchFingerprint: (tid) => client.getFingerprint(projectId, tid) as Promise<TicketFingerprint>,\n provider,\n mcpConfig,\n projectId,\n };\n\n const config: LoopConfig = { maxIterations: 1, gutterThreshold: 1 };\n\n const loop = new RalphLoop(ticket.id, columnId, config, deps);\n const result = await loop.run();\n console.log(` ${String(ticket.ticket_number)}: ${result.reason} (${String(result.iterations)} iter)`);\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(`Cron tick error: ${message}`);\n }\n };\n\n // Run immediately, then on interval\n await tick();\n setInterval(() => void tick(), intervalMs);\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import {
|
|
3
3
|
KantBanCLIClient
|
|
4
4
|
} from "./chunk-CQP4B53A.js";
|
|
5
|
-
import "./chunk-DGUM43GV.js";
|
|
6
5
|
|
|
7
6
|
// src/index.ts
|
|
8
7
|
var apiToken = process.env["KANTBAN_API_TOKEN"];
|
|
@@ -16,32 +15,32 @@ var [command, ...args] = process.argv.slice(2);
|
|
|
16
15
|
async function main() {
|
|
17
16
|
switch (command) {
|
|
18
17
|
case "context": {
|
|
19
|
-
const { runContext } = await import("./context-
|
|
18
|
+
const { runContext } = await import("./context-7YDNTI3P.js");
|
|
20
19
|
await runContext(client, args);
|
|
21
20
|
break;
|
|
22
21
|
}
|
|
23
22
|
case "status": {
|
|
24
|
-
const { runStatus } = await import("./status-
|
|
23
|
+
const { runStatus } = await import("./status-4GFXMVIM.js");
|
|
25
24
|
await runStatus(client, args);
|
|
26
25
|
break;
|
|
27
26
|
}
|
|
28
27
|
case "work": {
|
|
29
|
-
const { runWork } = await import("./work-
|
|
28
|
+
const { runWork } = await import("./work-45GILGKD.js");
|
|
30
29
|
await runWork(client, args);
|
|
31
30
|
break;
|
|
32
31
|
}
|
|
33
32
|
case "pipeline": {
|
|
34
33
|
if (args[0] === "stop") {
|
|
35
|
-
const { stopPipeline } = await import("./pipeline-
|
|
34
|
+
const { stopPipeline } = await import("./pipeline-GF4H4IDN.js");
|
|
36
35
|
await stopPipeline(args.slice(1));
|
|
37
36
|
} else {
|
|
38
|
-
const { runPipeline } = await import("./pipeline-
|
|
37
|
+
const { runPipeline } = await import("./pipeline-GF4H4IDN.js");
|
|
39
38
|
await runPipeline(client, args);
|
|
40
39
|
}
|
|
41
40
|
break;
|
|
42
41
|
}
|
|
43
42
|
case "cron": {
|
|
44
|
-
const { runCron } = await import("./cron-
|
|
43
|
+
const { runCron } = await import("./cron-Z66K3HWS.js");
|
|
45
44
|
await runCron(client, args);
|
|
46
45
|
break;
|
|
47
46
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { KantBanCLIClient } from './client.js';\n\nconst apiToken = process.env['KANTBAN_API_TOKEN'];\nconst apiUrl = process.env['KANTBAN_API_URL'];\n\nif (!apiToken || !apiUrl) {\n console.error('Error: KANTBAN_API_TOKEN and KANTBAN_API_URL environment variables are required');\n process.exit(1);\n}\n\nconst client = new KantBanCLIClient(apiUrl, apiToken);\nconst [command, ...args] = process.argv.slice(2);\n\nasync function main() {\n switch (command) {\n case 'context': {\n const { runContext } = await import('./commands/context.js');\n await runContext(client, args);\n break;\n }\n case 'status': {\n const { runStatus } = await import('./commands/status.js');\n await runStatus(client, args);\n break;\n }\n case 'work': {\n const { runWork } = await import('./commands/work.js');\n await runWork(client, args);\n break;\n }\n case 'pipeline': {\n if (args[0] === 'stop') {\n const { stopPipeline } = await import('./commands/pipeline.js');\n await stopPipeline(args.slice(1));\n } else {\n const { runPipeline } = await import('./commands/pipeline.js');\n await runPipeline(client, args);\n }\n break;\n }\n case 'cron': {\n const { runCron } = await import('./commands/cron.js');\n await runCron(client, args);\n break;\n }\n default:\n console.log(`kantban CLI — Pipeline orchestration for KantBan\n\nUsage:\n kantban context <scope-type> <scope-id> Output scoped pipeline context to stdout\n kantban status <board-id> Pipeline health at a glance\n kantban work <ticket-id> Start a Claude session for a ticket\n kantban pipeline <board-id> Persistent pipeline orchestrator\n kantban pipeline stop <board-id> Stop running pipeline\n kantban cron <column-id> [--interval 5m] Run single column on a timer\n\nEnvironment:\n KANTBAN_API_TOKEN API token (required)\n KANTBAN_API_URL API URL (required)\n KANTBAN_PROJECT_ID Default project ID (optional)`);\n }\n}\n\nmain().catch((err: Error) => {\n console.error('Error:', err.message);\n process.exit(1);\n});\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { KantBanCLIClient } from './client.js';\n\nconst apiToken = process.env['KANTBAN_API_TOKEN'];\nconst apiUrl = process.env['KANTBAN_API_URL'];\n\nif (!apiToken || !apiUrl) {\n console.error('Error: KANTBAN_API_TOKEN and KANTBAN_API_URL environment variables are required');\n process.exit(1);\n}\n\nconst client = new KantBanCLIClient(apiUrl, apiToken);\nconst [command, ...args] = process.argv.slice(2);\n\nasync function main() {\n switch (command) {\n case 'context': {\n const { runContext } = await import('./commands/context.js');\n await runContext(client, args);\n break;\n }\n case 'status': {\n const { runStatus } = await import('./commands/status.js');\n await runStatus(client, args);\n break;\n }\n case 'work': {\n const { runWork } = await import('./commands/work.js');\n await runWork(client, args);\n break;\n }\n case 'pipeline': {\n if (args[0] === 'stop') {\n const { stopPipeline } = await import('./commands/pipeline.js');\n await stopPipeline(args.slice(1));\n } else {\n const { runPipeline } = await import('./commands/pipeline.js');\n await runPipeline(client, args);\n }\n break;\n }\n case 'cron': {\n const { runCron } = await import('./commands/cron.js');\n await runCron(client, args);\n break;\n }\n default:\n console.log(`kantban CLI — Pipeline orchestration for KantBan\n\nUsage:\n kantban context <scope-type> <scope-id> Output scoped pipeline context to stdout\n kantban status <board-id> Pipeline health at a glance\n kantban work <ticket-id> Start a Claude session for a ticket\n kantban pipeline <board-id> Persistent pipeline orchestrator\n kantban pipeline stop <board-id> Stop running pipeline\n kantban cron <column-id> [--interval 5m] Run single column on a timer\n\nEnvironment:\n KANTBAN_API_TOKEN API token (required)\n KANTBAN_API_URL API URL (required)\n KANTBAN_PROJECT_ID Default project ID (optional)`);\n }\n}\n\nmain().catch((err: Error) => {\n console.error('Error:', err.message);\n process.exit(1);\n});\n"],"mappings":";;;;;;AAGA,IAAM,WAAW,QAAQ,IAAI,mBAAmB;AAChD,IAAM,SAAS,QAAQ,IAAI,iBAAiB;AAE5C,IAAI,CAAC,YAAY,CAAC,QAAQ;AACxB,UAAQ,MAAM,iFAAiF;AAC/F,UAAQ,KAAK,CAAC;AAChB;AAEA,IAAM,SAAS,IAAI,iBAAiB,QAAQ,QAAQ;AACpD,IAAM,CAAC,SAAS,GAAG,IAAI,IAAI,QAAQ,KAAK,MAAM,CAAC;AAE/C,eAAe,OAAO;AACpB,UAAQ,SAAS;AAAA,IACf,KAAK,WAAW;AACd,YAAM,EAAE,WAAW,IAAI,MAAM,OAAO,uBAAuB;AAC3D,YAAM,WAAW,QAAQ,IAAI;AAC7B;AAAA,IACF;AAAA,IACA,KAAK,UAAU;AACb,YAAM,EAAE,UAAU,IAAI,MAAM,OAAO,sBAAsB;AACzD,YAAM,UAAU,QAAQ,IAAI;AAC5B;AAAA,IACF;AAAA,IACA,KAAK,QAAQ;AACX,YAAM,EAAE,QAAQ,IAAI,MAAM,OAAO,oBAAoB;AACrD,YAAM,QAAQ,QAAQ,IAAI;AAC1B;AAAA,IACF;AAAA,IACA,KAAK,YAAY;AACf,UAAI,KAAK,CAAC,MAAM,QAAQ;AACtB,cAAM,EAAE,aAAa,IAAI,MAAM,OAAO,wBAAwB;AAC9D,cAAM,aAAa,KAAK,MAAM,CAAC,CAAC;AAAA,MAClC,OAAO;AACL,cAAM,EAAE,YAAY,IAAI,MAAM,OAAO,wBAAwB;AAC7D,cAAM,YAAY,QAAQ,IAAI;AAAA,MAChC;AACA;AAAA,IACF;AAAA,IACA,KAAK,QAAQ;AACX,YAAM,EAAE,QAAQ,IAAI,MAAM,OAAO,oBAAoB;AACrD,YAAM,QAAQ,QAAQ,IAAI;AAC1B;AAAA,IACF;AAAA,IACA;AACE,cAAQ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qDAamC;AAAA,EACnD;AACF;AAEA,KAAK,EAAE,MAAM,CAAC,QAAe;AAC3B,UAAQ,MAAM,UAAU,IAAI,OAAO;AACnC,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
|
|
@@ -5,14 +5,13 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
formatGateErrors,
|
|
7
7
|
runGates
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-MKKHLFA5.js";
|
|
9
9
|
import {
|
|
10
10
|
parseGateConfig,
|
|
11
11
|
parseTimeout,
|
|
12
12
|
resolveGatesForColumn
|
|
13
13
|
} from "../chunk-DAFLEMLK.js";
|
|
14
|
-
import "../chunk-
|
|
15
|
-
import "../chunk-DGUM43GV.js";
|
|
14
|
+
import "../chunk-5ZU2OOES.js";
|
|
16
15
|
|
|
17
16
|
// src/lib/gate-proxy-server.ts
|
|
18
17
|
import { readFileSync } from "fs";
|
|
@@ -26,6 +25,16 @@ var GateProxy = class {
|
|
|
26
25
|
this.config = config;
|
|
27
26
|
this.deps = deps2;
|
|
28
27
|
}
|
|
28
|
+
async buildTicketEnv(ticketId, projectId2, columnName2) {
|
|
29
|
+
const details = await this.deps.getTicketDetails(ticketId);
|
|
30
|
+
return {
|
|
31
|
+
KANTBAN_TICKET_ID: ticketId,
|
|
32
|
+
KANTBAN_TICKET_TITLE: details.title,
|
|
33
|
+
KANTBAN_TICKET_DESCRIPTION: details.description,
|
|
34
|
+
KANTBAN_TICKET_COLUMN: columnName2,
|
|
35
|
+
KANTBAN_PROJECT_ID: projectId2
|
|
36
|
+
};
|
|
37
|
+
}
|
|
29
38
|
buildRunOptions() {
|
|
30
39
|
const opts = {};
|
|
31
40
|
if (this.config.settings?.cwd !== void 0) opts.cwd = this.config.settings.cwd;
|
|
@@ -56,7 +65,10 @@ var GateProxy = class {
|
|
|
56
65
|
const gates = allGates.filter((g) => !waiverSet.has(g.name));
|
|
57
66
|
let results;
|
|
58
67
|
try {
|
|
59
|
-
|
|
68
|
+
const opts = this.buildRunOptions();
|
|
69
|
+
const ticketEnv = await this.buildTicketEnv(move.ticketId, move.projectId, move.currentColumnName);
|
|
70
|
+
opts.env = { ...opts.env, ...ticketEnv };
|
|
71
|
+
results = await this.deps.runGates(gates, opts);
|
|
60
72
|
} catch (err) {
|
|
61
73
|
return {
|
|
62
74
|
error: "GATE_FAILURE",
|
|
@@ -89,7 +101,10 @@ var GateProxy = class {
|
|
|
89
101
|
const gates = allGates.filter((g) => !waiverSet.has(g.name));
|
|
90
102
|
let results;
|
|
91
103
|
try {
|
|
92
|
-
|
|
104
|
+
const opts = this.buildRunOptions();
|
|
105
|
+
const ticketEnv = await this.buildTicketEnv(complete.ticketId, complete.projectId, complete.currentColumnName);
|
|
106
|
+
opts.env = { ...opts.env, ...ticketEnv };
|
|
107
|
+
results = await this.deps.runGates(gates, opts);
|
|
93
108
|
} catch (err) {
|
|
94
109
|
return {
|
|
95
110
|
error: "GATE_FAILURE",
|
|
@@ -239,6 +254,22 @@ var deps = {
|
|
|
239
254
|
`);
|
|
240
255
|
return [];
|
|
241
256
|
}
|
|
257
|
+
},
|
|
258
|
+
async getTicketDetails(ticketId) {
|
|
259
|
+
try {
|
|
260
|
+
validateUuid(ticketId, "ticketId");
|
|
261
|
+
const data = await apiGet(`/projects/${projectId}/tickets/${ticketId}`);
|
|
262
|
+
return {
|
|
263
|
+
title: data.title ?? "",
|
|
264
|
+
description: data.description ?? ""
|
|
265
|
+
};
|
|
266
|
+
} catch (err) {
|
|
267
|
+
process.stderr.write(
|
|
268
|
+
`gate-proxy-server: ticket details fetch failed: ${err instanceof Error ? err.message : String(err)}
|
|
269
|
+
`
|
|
270
|
+
);
|
|
271
|
+
return { title: "", description: "" };
|
|
272
|
+
}
|
|
242
273
|
}
|
|
243
274
|
};
|
|
244
275
|
var proxy = new GateProxy(gateConfig, deps);
|