kantban-cli 0.1.49 → 0.1.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-QHJZIGEE.js → chunk-3A4B7CUH.js} +265 -72
- package/dist/chunk-3A4B7CUH.js.map +1 -0
- package/dist/{chunk-DENXSVKE.js → chunk-GQQUH3TA.js} +7 -2
- package/dist/chunk-GQQUH3TA.js.map +1 -0
- package/dist/{cron-F6D6475M.js → cron-VZMNM2XT.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/lib/gate-proxy-server.js +1 -1
- package/dist/{pipeline-GZOSDNPF.js → pipeline-NRG2Q2TE.js} +223 -203
- package/dist/pipeline-NRG2Q2TE.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-DENXSVKE.js.map +0 -1
- package/dist/chunk-QHJZIGEE.js.map +0 -1
- package/dist/pipeline-GZOSDNPF.js.map +0 -1
- /package/dist/{cron-F6D6475M.js.map → cron-VZMNM2XT.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/worktree.ts","../src/lib/stuck-detector.ts","../src/lib/parse-utils.ts","../src/lib/git-state.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 { execFile as defaultExecFile, execFileSync } from 'node:child_process';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { normalizeEol } from './platform.js';\n\n/**\n * Generate a deterministic worktree name from ticket context.\n * Claude CLI's --worktree flag accepts a name (not a path).\n */\nexport function generateWorktreeName(ticketNumber: number, columnName: string): string {\n const slug = columnSlug(columnName);\n return `kantban-${ticketNumber}-${slug}`;\n}\n\nexport function columnSlug(columnName: string): string {\n return columnName\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-|-$/g, '');\n}\n\nexport interface RenderWorktreePathOptions {\n boardId: string;\n defaultRoot: string;\n}\n\nexport function renderWorktreePath(\n ticketNumber: number,\n columnName: string,\n pathPattern: string | undefined,\n options: RenderWorktreePathOptions,\n): string {\n const slug = columnSlug(columnName);\n const worktreeName = generateWorktreeName(ticketNumber, columnName);\n if (pathPattern) {\n return pathPattern\n .replace(/\\{ticket_number\\}/g, String(ticketNumber))\n .replace(/\\{column_slug\\}/g, slug)\n .replace(/\\{worktree_name\\}/g, worktreeName);\n }\n return join(options.defaultRoot, options.boardId, slug, String(ticketNumber));\n}\n\nexport function defaultWorktreeRoot(): string {\n return join(homedir(), '.kantban', 'worktrees');\n}\n\n/**\n * Check if a URL looks like a plausible git remote URL.\n * Valid git URLs contain at least one of: `/`, `@`, or `://`.\n * Bare words like \"origin\" or plain hostnames like \"github.com\" are invalid.\n */\nexport function isPlausibleRemoteUrl(url: string): boolean {\n return url.includes('/') || url.includes('@') || url.includes('://');\n}\n\n/**\n * Ensure a git worktree has the origin remote configured with a valid URL.\n * Standard worktrees share .git/config with the main repo so remotes should\n * propagate automatically. If they don't (e.g. EnterWorktree edge case),\n * copy origin from the main repo. Also validates that the existing origin URL\n * is plausible — bare words or hostnames without paths are replaced.\n */\nexport function ensureWorktreeRemote(worktreePath: string): void {\n try {\n const remotes = normalizeEol(execFileSync('git', ['-C', worktreePath, 'remote'], {\n stdio: 'pipe',\n encoding: 'utf-8',\n })).trim();\n\n if (remotes.split('\\n').includes('origin')) {\n // Origin exists — validate its URL\n const currentUrl = normalizeEol(execFileSync('git', ['-C', worktreePath, 'remote', 'get-url', 'origin'], {\n stdio: 'pipe',\n encoding: 'utf-8',\n })).trim();\n\n if (isPlausibleRemoteUrl(currentUrl)) return; // valid URL, nothing to do\n\n // Invalid URL — remove it so we can re-add from main repo\n console.error(`[worktree] Invalid origin URL in ${worktreePath}: \"${currentUrl}\" — fixing`);\n execFileSync('git', ['-C', worktreePath, 'remote', 'remove', 'origin'], {\n stdio: 'pipe',\n });\n }\n\n // Get origin URL from main repo\n const originUrl = normalizeEol(execFileSync('git', ['remote', 'get-url', 'origin'], {\n stdio: 'pipe',\n encoding: 'utf-8',\n })).trim();\n\n if (originUrl && isPlausibleRemoteUrl(originUrl)) {\n execFileSync('git', ['-C', worktreePath, 'remote', 'add', 'origin', originUrl], {\n stdio: 'pipe',\n });\n console.error(`[worktree] Added missing origin remote to ${worktreePath}: ${originUrl}`);\n } else {\n console.error(`[worktree] WARNING: main repo origin URL is also invalid: \"${originUrl}\"`);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[worktree] Failed to ensure remote for ${worktreePath}: ${msg}`);\n }\n}\n\ntype ExecFn = (cmd: string, args: string[], cb: (err: Error | null, stdout: string, stderr: string) => void) => void;\n\n/**\n * Remove a git worktree. Fire-and-forget — never throws.\n */\nexport async function cleanupWorktree(\n worktreeName: string,\n exec: ExecFn = defaultExecFile as unknown as ExecFn,\n): Promise<boolean> {\n // Guard: check if this is actually a registered git worktree before removing.\n // Use the discovered path for removal — the worktree path may differ from\n // the branch name when path_pattern is in play.\n let target: string = worktreeName;\n try {\n const path = await findWorktreeForBranch(exec, worktreeName);\n if (!path) return true; // Not a git worktree — nothing to clean up\n target = path;\n } catch {\n return true;\n }\n return new Promise((resolve) => {\n exec('git', ['worktree', 'remove', '--force', target], (err) => {\n if (err) {\n console.error(`[worktree] cleanup failed for ${worktreeName} (${target}): ${err.message}`);\n resolve(false);\n } else {\n resolve(true);\n }\n });\n });\n}\n\nfunction execPromise(exec: ExecFn, cmd: string, args: string[]): Promise<{ stdout: string; stderr: string }> {\n return new Promise((resolve, reject) => {\n exec(cmd, args, (err, stdout, stderr) => {\n if (err) reject(Object.assign(err, { stdout, stderr }));\n else resolve({ stdout, stderr });\n });\n });\n}\n\n/**\n * Find the worktree path where a branch is currently checked out, or null if not checked out.\n */\nasync function findWorktreeForBranch(exec: ExecFn, branch: string): Promise<string | null> {\n try {\n const { stdout } = await execPromise(exec, 'git', ['worktree', 'list', '--porcelain']);\n const targetRef = `refs/heads/${branch}`;\n let currentPath: string | null = null;\n for (const line of normalizeEol(stdout).split('\\n')) {\n if (line.startsWith('worktree ')) currentPath = line.slice('worktree '.length);\n if (line.startsWith('branch ') && line.slice('branch '.length) === targetRef && currentPath) {\n return currentPath;\n }\n }\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Merge a worktree branch into an integration branch.\n * Creates the integration branch from HEAD if it doesn't exist.\n * Returns true on success, false on failure (e.g. merge conflict).\n */\nexport async function mergeWorktreeBranch(\n worktreeName: string,\n integrationBranch: string,\n exec: ExecFn = defaultExecFile as unknown as ExecFn,\n): Promise<boolean> {\n try {\n // Guard: if the worktree branch doesn't exist (e.g. Codex created a plain dir),\n // skip the merge — there's nothing to merge.\n try {\n await execPromise(exec, 'git', ['rev-parse', '--verify', worktreeName]);\n } catch {\n return true; // No branch to merge — not an error\n }\n\n // Ensure integration branch exists (no-op if it already does)\n await execPromise(exec, 'git', ['branch', integrationBranch, 'HEAD']).catch(() => {\n /* branch already exists — fine */\n });\n\n // If the integration branch is checked out somewhere, merge directly there.\n // This avoids the update-ref desync (stages without committing) and the\n // \"branch already checked out\" error from git worktree add.\n const checkedOutPath = await findWorktreeForBranch(exec, integrationBranch);\n if (checkedOutPath) {\n await execPromise(exec, 'git', ['-C', checkedOutPath, 'merge', '--no-edit', worktreeName]);\n console.error(`[worktree] merged ${worktreeName} → ${integrationBranch}`);\n return true;\n }\n\n // Branch is not checked out anywhere — use detached approaches.\n // Step 1: Find the merge base\n const { stdout: baseOut } = await execPromise(exec, 'git', ['merge-base', integrationBranch, worktreeName]);\n const mergeBase = baseOut.trim();\n\n // Step 2: Check if this is a fast-forward\n const { stdout: integrationSha } = await execPromise(exec, 'git', ['rev-parse', integrationBranch]);\n if (integrationSha.trim() === mergeBase) {\n const { stdout: worktreeSha } = await execPromise(exec, 'git', ['rev-parse', worktreeName]);\n await execPromise(exec, 'git', ['update-ref', `refs/heads/${integrationBranch}`, worktreeSha.trim()]);\n console.error(`[worktree] fast-forward merged ${worktreeName} → ${integrationBranch}`);\n return true;\n }\n\n // Step 3: Non-fast-forward — temp worktree (safe since branch is not checked out)\n const tmpWorktree = `merge-tmp-${Date.now()}`;\n try {\n await execPromise(exec, 'git', ['worktree', 'add', tmpWorktree, integrationBranch]);\n await execPromise(exec, 'git', ['-C', tmpWorktree, 'merge', '--no-edit', worktreeName]);\n console.error(`[worktree] merged ${worktreeName} → ${integrationBranch}`);\n } finally {\n await execPromise(exec, 'git', ['worktree', 'remove', '--force', tmpWorktree]).catch(() => {});\n }\n return true;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[worktree] merge failed for ${worktreeName} → ${integrationBranch}: ${msg}`);\n return false;\n }\n}\n","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 { execFile as execFileCb } from 'node:child_process';\nimport { promisify } from 'node:util';\n\ntype ExecFileOptions = { timeout?: number };\ntype ExecFileResult = { stdout: string; stderr: string };\ntype ExecFileFn = (cmd: string, args: string[], opts?: ExecFileOptions) => Promise<ExecFileResult>;\n\nconst realExecFile = promisify(execFileCb) as ExecFileFn;\nlet _execFile: ExecFileFn = realExecFile;\n\n/** Test-only override. Do not call from production code. */\nexport function __setExecFileForTest(fn: ExecFileFn): void {\n _execFile = fn;\n}\n\n/** Restore the production execFile. Useful for test teardown. */\nexport function __resetExecFileForTest(): void {\n _execFile = realExecFile;\n}\n\nexport type BranchMergeResult =\n | { merged: true; mergeCommitSha: string; mergeCommitTime: Date }\n | { merged: false };\n\n/**\n * Detect whether the worktree's current HEAD has been merged into the integration\n * branch (default 'main') during this iteration. Fetches origin/<targetBranch>,\n * runs merge-base --is-ancestor, and checks that the earliest merge commit on the\n * ancestry path is newer than the iteration start time. Any failure (network, git\n * error, malformed output) returns merged=false — never throws.\n */\nexport async function detectBranchMerged(opts: {\n worktreePath: string;\n iterationStartedAt: Date;\n fetchTimeoutMs?: number;\n targetBranch?: string;\n}): Promise<BranchMergeResult> {\n const { worktreePath, iterationStartedAt } = opts;\n const fetchTimeoutMs = opts.fetchTimeoutMs ?? 15_000;\n const targetBranch = opts.targetBranch ?? 'main';\n try {\n const { stdout: headOut } = await _execFile('git', ['-C', worktreePath, 'rev-parse', 'HEAD']);\n const headSha = headOut.trim();\n if (!headSha) return { merged: false };\n\n try {\n await _execFile('git', ['-C', worktreePath, 'fetch', 'origin', targetBranch], { timeout: fetchTimeoutMs });\n } catch {\n return { merged: false };\n }\n\n try {\n await _execFile('git', ['-C', worktreePath, 'merge-base', '--is-ancestor', headSha, `origin/${targetBranch}`]);\n } catch {\n return { merged: false };\n }\n\n const { stdout: logOut } = await _execFile('git', [\n '-C', worktreePath,\n 'log', `origin/${targetBranch}`,\n '--ancestry-path', `${headSha}..origin/${targetBranch}`,\n '--reverse',\n '--format=%H %cI',\n '-1',\n ]);\n const line = logOut.trim().split('\\n')[0] ?? '';\n const spaceIdx = line.indexOf(' ');\n if (spaceIdx < 0) return { merged: false };\n const mergeCommitSha = line.slice(0, spaceIdx);\n const mergeCommitTime = new Date(line.slice(spaceIdx + 1));\n if (Number.isNaN(mergeCommitTime.getTime())) return { merged: false };\n if (mergeCommitTime.getTime() < iterationStartedAt.getTime()) return { merged: false };\n return { merged: true, mergeCommitSha, mergeCommitTime };\n } catch {\n return { merged: false };\n }\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 branchMergeContext?: { mergeCommitSha: string; mergeCommitTime: Date } | 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\n// Prefixes of harness-authored comments that should be suppressed from\n// re-dispatch context so agents don't self-escalate on their own prior\n// status announcements. Free-text matched against trimmed body start.\nconst HARNESS_COMMENT_PREFIXES = [\n 'ADVISOR:',\n 'ADVISOR FEEDBACK:',\n 'EVALUATOR:',\n 'Escalated:',\n 'Pipeline agent stalled',\n 'Pipeline agent reached iteration limit',\n 'Pipeline agent encountered an error',\n 'Pipeline agent advanced ticket',\n 'Pipeline agent was stopped externally',\n 'Pipeline agent stopped',\n 'Pipeline agent auto-advanced',\n 'Pipeline budget exhausted',\n 'Needs human review',\n] as const;\n\nfunction isHarnessComment(body: string): boolean {\n const trimmed = body.trim();\n return HARNESS_COMMENT_PREFIXES.some((p) => trimmed.startsWith(p));\n}\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 unpinnedAll = comments.filter((c) => !c.pinned);\n // Suppress harness-authored comments from unpinned history so re-dispatched\n // agents don't read their own prior status announcements and self-escalate.\n const unpinned = unpinnedAll.filter((c) => !isHarnessComment(c.body));\n const suppressedHarnessCount = unpinnedAll.length - unpinned.length;\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 (even if harness-authored — explicit pin overrides)\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 if (suppressedHarnessCount > 0) {\n parts.push(`[${suppressedHarnessCount} prior harness status comment(s) suppressed from context]`);\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 // --- 3b. Branch merge detected (re-prompt context from ralph-loop) ---\n // When the worktree's HEAD has landed on origin/main during the last\n // iteration, surface that state clearly: the agent must stop doing code\n // work and call kantban_move_ticket to advance the ticket in kantban.\n if (meta.branchMergeContext) {\n const sha = meta.branchMergeContext.mergeCommitSha;\n const when = meta.branchMergeContext.mergeCommitTime.toISOString();\n parts.push(\n `\\n## Branch Merge Detected\\n\\n` +\n `Your feature branch landed on \\`origin/main\\` at commit \\`${sha}\\` (${when}).\\n\\n` +\n `The ticket has NOT yet been advanced in kantban — the Merge step is incomplete until ` +\n `you call \\`${ticketContext.tool_prefix}move_ticket\\` to move this ticket to the next column. ` +\n `Call that tool now as your first action.`,\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 { detectBranchMerged } from './git-state.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 /**\n * Filesystem path for the worktree. Defaults to `worktreeName` when unset\n * (preserving legacy behavior). Set when a column's agent_config specifies\n * `worktree.path_pattern` and the rendered path differs from the branch name.\n */\n worktreePath?: 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 /** Enable branch-merge re-prompt detection. Requires worktreePath. */\n repromptOnBranchMerged?: boolean;\n /** Cap on consecutive branch-merge re-prompts in one loop run. Default 2. */\n maxRepromptAttempts?: number;\n /** Override fetch timeout for branch-merge detection (ms). */\n branchMergeFetchTimeoutMs?: number;\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 private pendingBranchMergeContext:\n | { mergeCommitSha: string; mergeCommitTime: Date }\n | null = null;\n private branchMergeRepromptCount = 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 const iterStartedAt = new Date();\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 branchMergeContext: this.pendingBranchMergeContext ?? undefined,\n });\n this.pendingBranchMergeContext = null;\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 && {\n workingDirectory: this.config.worktreePath ?? this.config.worktreeName,\n ...(this.config.worktreePath != null\n && this.config.worktreePath !== this.config.worktreeName\n && { branch: this.config.worktreeName }),\n }),\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 // Abort-induced termination is not an error. When the orchestrator\n // aborts this loop (e.g. because the ticket moved out from under us\n // after the agent itself called move_ticket), the provider receives\n // SIGTERM and exits 143. Reporting that as reason=\"error\" files a\n // spurious error comment on every successful column transition,\n // which is the dominant source of IM↔CR \"exit code 143\" noise.\n // Treat abort-induced exits as stopped instead.\n if (this.stopped || this.config.abortSignal?.aborted) {\n log(`${this.deps.provider.displayName} terminated by abort signal (exit ${exitCode}) — treating as stopped`);\n return withCosts({ reason: 'stopped', iterations: i - 1, gutterCount, model: resolvedModel });\n }\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 // Branch-merge re-prompt detection: if the worktree's HEAD has landed on\n // origin/main during this iteration, queue a context snapshot for the\n // next iteration's prompt so the agent knows to reorient.\n const maxReprompts = this.config.maxRepromptAttempts ?? 2;\n if (\n this.config.repromptOnBranchMerged === true &&\n this.config.worktreePath != null &&\n this.branchMergeRepromptCount < maxReprompts\n ) {\n try {\n const mergeResult = await detectBranchMerged({\n worktreePath: this.config.worktreePath,\n iterationStartedAt: iterStartedAt,\n ...(this.config.branchMergeFetchTimeoutMs != null && { fetchTimeoutMs: this.config.branchMergeFetchTimeoutMs }),\n });\n if (mergeResult.merged) {\n this.pendingBranchMergeContext = {\n mergeCommitSha: mergeResult.mergeCommitSha,\n mergeCommitTime: mergeResult.mergeCommitTime,\n };\n this.branchMergeRepromptCount += 1;\n log(`Branch merge detected — queued re-prompt context for iteration ${String(i + 1)} (${mergeResult.mergeCommitSha})`);\n }\n } catch (err) {\n // Defensive — detectBranchMerged already swallows errors, but if the\n // promise pipeline itself fails, silently drop it.\n const msg = err instanceof Error ? err.message : String(err);\n log(`Branch-merge detection threw: ${msg}`);\n }\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, rmdirSync, rmSync } 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 per-PID path (~/.kantban/pipelines/<boardId>/<pid>/mcp-config.json)\n * so that sibling orchestrators each have their own file and cleanup by one\n * orchestrator cannot unlink the file that a different orchestrator is using.\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, String(process.pid));\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 // Remove the per-PID directory if it is now empty (prevents orphan dirs\n // accumulating on clean shutdown). Swallow ENOTEMPTY / ENOENT gracefully.\n try { rmdirSync(dirname(filePath)); } catch { /* ignore */ }\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 per-PID, column-scoped path:\n * ~/.kantban/pipelines/<boardId>/<pid>/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 SHELL: process.env.SHELL ?? '/bin/bash',\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, String(process.pid));\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\n/**\n * Scan `boardDir` for per-PID subdirectories and remove those whose PID is no\n * longer alive. Uses `process.kill(pid, 0)` as a liveness probe:\n * - success → process exists, skip\n * - ESRCH → process gone, remove\n * - EPERM → process exists (owned by someone else), skip (conservative)\n *\n * Non-PID entries (non-numeric directory names) are ignored.\n */\nexport function reapOrphanedMcpConfigDirs(boardDir: string): void {\n let entries: string[];\n try {\n entries = readdirSync(boardDir);\n } catch {\n return; // boardDir doesn't exist or is unreadable — nothing to reap\n }\n for (const entry of entries) {\n const pid = Number(entry);\n if (!Number.isInteger(pid) || pid <= 0) continue;\n let alive = false;\n try {\n process.kill(pid, 0);\n alive = true;\n } catch (err: unknown) {\n const code = (err as NodeJS.ErrnoException)?.code;\n if (code === 'EPERM') alive = true; // exists but not ours\n // otherwise (ESRCH) it's gone\n }\n if (!alive) {\n try {\n rmSync(join(boardDir, entry), { recursive: true, force: true });\n } catch {\n // swallow — best-effort cleanup\n }\n }\n }\n}\n","import { spawn, execFileSync, type ChildProcess } from 'node:child_process';\nimport { writeFileSync, mkdirSync, existsSync, rmSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { crossSpawnOptions, killProcessTree, resolveCommand } from '../lib/platform.js';\nimport { ensureWorktreeRemote } from '../lib/worktree.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: false,\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 // Manage the git worktree ourselves so the agent runs in an isolated\n // checkout at a path the orchestrator controls. The previous design\n // relied on Claude Code's `--worktree` flag, which creates the worktree\n // relative to the spawn cwd — when the spawn cwd was the primary repo\n // (the orchestrator's cwd), Claude wrote into the primary repo and any\n // commits the agent made were attributed to whatever branch was checked\n // out there. The pre-spawn guards in pipeline.ts only validate\n // `worktreePath`; if the provider ignores that path, the guards do\n // nothing. Mirror codex-provider: create or reuse the worktree at the\n // absolute path, then spawn `claude` with `cwd` set to it.\n if (request.workingDirectory) {\n const branch = request.branch ?? request.workingDirectory;\n if (!existsSync(request.workingDirectory)) {\n try {\n execFileSync('git', ['worktree', 'add', '-b', branch, request.workingDirectory, 'HEAD'], {\n stdio: 'pipe',\n });\n } catch {\n try {\n execFileSync('git', ['worktree', 'add', request.workingDirectory, branch], {\n stdio: 'pipe',\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`worktree_creation_failed: ${msg}`);\n }\n }\n } else {\n try {\n execFileSync('git', ['-C', request.workingDirectory, 'rev-parse', '--git-dir'], {\n stdio: 'pipe',\n });\n } catch {\n try {\n rmSync(request.workingDirectory, { recursive: true, force: true });\n execFileSync('git', ['worktree', 'add', '-b', branch, request.workingDirectory, 'HEAD'], {\n stdio: 'pipe',\n });\n } catch {\n try {\n execFileSync('git', ['worktree', 'add', request.workingDirectory, branch], {\n stdio: 'pipe',\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`worktree_creation_failed: ${msg}`);\n }\n }\n }\n }\n if (existsSync(request.workingDirectory)) {\n ensureWorktreeRemote(request.workingDirectory);\n }\n }\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 ...(request.workingDirectory ? { cwd: request.workingDirectory } : {}),\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 // The `--worktree` flag is intentionally NOT passed: this provider\n // creates the worktree itself in invoke() and runs `claude` with `cwd`\n // pointing at the resolved absolute path. Letting Claude Code manage\n // the worktree caused it to land inside the orchestrator's cwd\n // (the primary repo) and contaminate unrelated branches.\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":["join","homedir","join","homedir","execFileSync","writeFileSync","mkdirSync","existsSync","rmSync","join","homedir","existsSync","execFileSync","rmSync","join","homedir","mkdirSync","writeFileSync"]}
|
|
@@ -17,7 +17,12 @@ async function runGate(gate, options = {}) {
|
|
|
17
17
|
let timer;
|
|
18
18
|
const child = execFile(shell, [...shellPrefix, gate.run], {
|
|
19
19
|
cwd: options.cwd,
|
|
20
|
-
env: {
|
|
20
|
+
env: {
|
|
21
|
+
...process.env,
|
|
22
|
+
...options.env,
|
|
23
|
+
PATH: options.env?.PATH ?? process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin",
|
|
24
|
+
SHELL: options.env?.SHELL ?? process.env.SHELL ?? "/bin/bash"
|
|
25
|
+
},
|
|
21
26
|
maxBuffer: options.maxBuffer ?? 1024 * 1024
|
|
22
27
|
// 1MB output cap
|
|
23
28
|
}, (error, stdout, stderr) => {
|
|
@@ -126,4 +131,4 @@ export {
|
|
|
126
131
|
formatGateErrors,
|
|
127
132
|
runGates
|
|
128
133
|
};
|
|
129
|
-
//# sourceMappingURL=chunk-
|
|
134
|
+
//# sourceMappingURL=chunk-GQQUH3TA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/gate-runner.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { parseTimeout } from './gate-config.js';\nimport { shellArgs, normalizeEol } from './platform.js';\nimport type { GateDefinition, GateResult } from '@kantban/types';\n\nexport interface RunOptions {\n timeoutMs?: number;\n totalTimeoutMs?: number;\n cwd?: string;\n env?: Record<string, string>;\n maxBuffer?: number;\n}\n\nexport async function runGate(\n gate: GateDefinition,\n options: RunOptions = {},\n): Promise<GateResult> {\n const timeoutMs = gate.timeout ? parseTimeout(gate.timeout) : (options.timeoutMs ?? 60_000);\n const start = Date.now();\n\n return new Promise<GateResult>((resolve) => {\n // SECURITY: gate.run is an operator-authored shell command from pipeline.gates.yaml.\n // It is executed with full process privileges via `sh -c`. This is intentional —\n // gates are trusted operator code, not user input. If gate config sources are ever\n // extended to accept user-supplied values, sanitization must be added here.\n const [shell, shellPrefix] = shellArgs();\n\n // We deliberately do NOT pass execFile's `timeout` option. Node's built-in\n // timeout sends SIGTERM to the child but keeps the callback pending until\n // stdio pipes close. If the gate forks a grandchild that inherits\n // stdout/stderr (e.g. `npm run dev:pipeline &`, a stray browser, a vite\n // dev server), those pipes never close and the callback never fires. We\n // manage the timeout ourselves and force-destroy the pipes on expiration\n // so the promise always resolves — which is what keeps the gate-proxy\n // MCP message queue from deadlocking behind a hung gate.\n let timedOut = false;\n let timer: NodeJS.Timeout | undefined;\n\n const child = execFile(shell, [...shellPrefix, gate.run], {\n cwd: options.cwd,\n env: {\n ...process.env,\n ...options.env,\n PATH: options.env?.PATH ?? process.env.PATH ?? '/usr/local/bin:/usr/bin:/bin',\n SHELL: options.env?.SHELL ?? process.env.SHELL ?? '/bin/bash',\n },\n maxBuffer: options.maxBuffer ?? 1024 * 1024, // 1MB output cap\n }, (error, stdout, stderr) => {\n if (timer) clearTimeout(timer);\n const duration_ms = Date.now() - start;\n const output = normalizeEol((stdout + stderr).trim());\n const buffer_exceeded = (error as NodeJS.ErrnoException)?.code === 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER';\n\n if (error || timedOut) {\n let annotation = '';\n if (timedOut) annotation = `\\n[TIMED OUT after ${timeoutMs}ms]`;\n else if (buffer_exceeded) annotation = `\\n[OUTPUT TRUNCATED — buffer limit exceeded]`;\n\n const errnoException = error as NodeJS.ErrnoException | null;\n // Derive errno: POSIX string code (e.g. 'ENOENT') when error.code is a\n // string and not a timeout signal; numeric error.errno for spawn-level\n // failures; undefined otherwise. ETIMEDOUT is excluded — the timed_out\n // flag already covers that signal.\n const errno: string | number | undefined =\n typeof errnoException?.code === 'string' && errnoException.code !== 'ETIMEDOUT'\n ? errnoException.code\n : typeof errnoException?.errno === 'number'\n ? errnoException.errno\n : undefined;\n const syscall: string | undefined =\n typeof errnoException?.syscall === 'string' ? errnoException.syscall : undefined;\n\n resolve({\n name: gate.name,\n passed: false,\n required: gate.required ?? true,\n duration_ms,\n output: output + annotation,\n stderr: normalizeEol(stderr.trim()),\n exit_code:\n child.exitCode\n ?? (typeof error?.code === 'number' ? error.code : timedOut ? 124 : 1),\n timed_out: timedOut || buffer_exceeded,\n errno,\n syscall,\n stderrTail: stderr.slice(-4096),\n });\n return;\n }\n\n resolve({\n name: gate.name,\n passed: true,\n required: gate.required ?? true,\n duration_ms,\n output,\n stderr: normalizeEol(stderr.trim()),\n exit_code: 0,\n timed_out: false,\n });\n });\n\n timer = setTimeout(() => {\n timedOut = true;\n try { child.kill('SIGKILL'); } catch { /* already exited */ }\n try { child.stdout?.destroy(); } catch { /* already destroyed */ }\n try { child.stderr?.destroy(); } catch { /* already destroyed */ }\n }, timeoutMs);\n });\n}\n\n/** Format gate failures into a structured error string for agent/CLI display. */\nexport function formatGateErrors(results: GateResult[]): string {\n const failures = results.filter((r) => !r.passed);\n if (failures.length === 0) return 'All gates passed.';\n\n return failures\n .map((r) => {\n const lines = [`Gate \"${r.name}\" (${r.required ? 'required' : 'advisory'}): FAILED`];\n lines.push(` Exit code: ${r.exit_code}`);\n if (r.timed_out) lines.push(' Timed out: yes');\n if (r.output) {\n const stderr = normalizeEol(r.output).split('\\n').slice(-20).join('\\n');\n lines.push(` Output:\\n ${stderr.replace(/\\n/g, '\\n ')}`);\n }\n return lines.join('\\n');\n })\n .join('\\n\\n');\n}\n\nexport async function runGates(\n gates: GateDefinition[],\n options: RunOptions = {},\n): Promise<GateResult[]> {\n const results: GateResult[] = [];\n const totalStart = Date.now();\n\n for (const gate of gates) {\n // Check total timeout\n if (options.totalTimeoutMs) {\n const elapsed = Date.now() - totalStart;\n if (elapsed >= options.totalTimeoutMs) {\n // Remaining gates get timed-out results\n results.push({\n name: gate.name,\n passed: false,\n required: gate.required ?? true,\n duration_ms: 0,\n output: '[SKIPPED — total timeout exceeded]',\n stderr: '',\n exit_code: -1,\n timed_out: true,\n });\n continue;\n }\n // Reduce per-gate timeout by elapsed time\n const remainingMs = options.totalTimeoutMs - elapsed;\n const gateTimeout = gate.timeout ? parseTimeout(gate.timeout) : (options.timeoutMs ?? 60_000);\n const effectiveTimeout = Math.min(gateTimeout, remainingMs);\n results.push(await runGate(gate, { ...options, timeoutMs: effectiveTimeout }));\n } else {\n results.push(await runGate(gate, options));\n }\n }\n\n return results;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
RalphLoop,
|
|
4
4
|
cleanupMcpConfig,
|
|
5
5
|
generateMcpConfig
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-3A4B7CUH.js";
|
|
7
7
|
import "./chunk-4R27WTCJ.js";
|
|
8
8
|
import "./chunk-5ZU2OOES.js";
|
|
9
9
|
|
|
@@ -102,4 +102,4 @@ async function runCron(client, args) {
|
|
|
102
102
|
export {
|
|
103
103
|
runCron
|
|
104
104
|
};
|
|
105
|
-
//# sourceMappingURL=cron-
|
|
105
|
+
//# sourceMappingURL=cron-VZMNM2XT.js.map
|
package/dist/index.js
CHANGED
|
@@ -31,16 +31,16 @@ async function main() {
|
|
|
31
31
|
}
|
|
32
32
|
case "pipeline": {
|
|
33
33
|
if (args[0] === "stop") {
|
|
34
|
-
const { stopPipeline } = await import("./pipeline-
|
|
34
|
+
const { stopPipeline } = await import("./pipeline-NRG2Q2TE.js");
|
|
35
35
|
await stopPipeline(args.slice(1));
|
|
36
36
|
} else {
|
|
37
|
-
const { runPipeline } = await import("./pipeline-
|
|
37
|
+
const { runPipeline } = await import("./pipeline-NRG2Q2TE.js");
|
|
38
38
|
await runPipeline(client, args);
|
|
39
39
|
}
|
|
40
40
|
break;
|
|
41
41
|
}
|
|
42
42
|
case "cron": {
|
|
43
|
-
const { runCron } = await import("./cron-
|
|
43
|
+
const { runCron } = await import("./cron-VZMNM2XT.js");
|
|
44
44
|
await runCron(client, args);
|
|
45
45
|
break;
|
|
46
46
|
}
|