kantban-cli 0.1.19 → 0.1.21
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-D4T7ZUWI.js → chunk-KFPZHDJ2.js} +5 -4
- package/dist/chunk-KFPZHDJ2.js.map +1 -0
- package/dist/{cron-722GUOUP.js → cron-Y25NNMOB.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/{pipeline-5LJYH46J.js → pipeline-Q254R5HA.js} +88 -16
- package/dist/pipeline-Q254R5HA.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-D4T7ZUWI.js.map +0 -1
- package/dist/pipeline-5LJYH46J.js.map +0 -1
- /package/dist/{cron-722GUOUP.js.map → cron-Y25NNMOB.js.map} +0 -0
|
@@ -276,9 +276,10 @@ You are working in an isolated git worktree. Integration branch name is invalid
|
|
|
276
276
|
parts.push(`## Git Worktree
|
|
277
277
|
|
|
278
278
|
You are working in an isolated git worktree. Before starting any code changes:
|
|
279
|
-
1.
|
|
280
|
-
2.
|
|
281
|
-
3.
|
|
279
|
+
1. Verify the origin remote exists: \`git remote -v\`. If origin is missing, add it: \`git remote add origin $(git -C "$(git worktree list | head -1 | awk '{print $1}')" remote get-url origin)\`
|
|
280
|
+
2. Merge \`${integrationBranch}\` into your current branch: \`git merge '${integrationBranch}'\`
|
|
281
|
+
3. Resolve any merge conflicts before proceeding with ticket work
|
|
282
|
+
4. Never rebase \u2014 always merge (rebase destroys traceability across the pipeline)
|
|
282
283
|
|
|
283
284
|
After completing your work, commit all changes to your worktree branch.
|
|
284
285
|
`);
|
|
@@ -1160,4 +1161,4 @@ export {
|
|
|
1160
1161
|
cleanupGateProxyConfigs,
|
|
1161
1162
|
ClaudeProvider
|
|
1162
1163
|
};
|
|
1163
|
-
//# sourceMappingURL=chunk-
|
|
1164
|
+
//# sourceMappingURL=chunk-KFPZHDJ2.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.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## 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, add it: \\`git remote add origin $(git -C \"$(git worktree list | head -1 | awk '{print $1}')\" remote get-url origin)\\`\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 /** 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) 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) 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 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';\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: 'npx',\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: 'npx', 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 };\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 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 return new Promise((resolve) => {\n const child: ChildProcess = spawn('claude', args, {\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 });\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 try { child.kill('SIGKILL'); } catch { /* already dead */ }\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":";;;;;AAAA,SAAS,SAAS;;;ACIX,SAAS,uBAAuB,KAAsB;AAE3D,MAAI;AACF,WAAO,KAAK,MAAM,IAAI,KAAK,CAAC;AAAA,EAC9B,QAAQ;AAAA,EAER;AAGA,QAAM,aAAa,IAAI,MAAM,+BAA+B;AAC5D,MAAI,aAAa,CAAC,GAAG;AACnB,UAAM,SAAS,WAAW,CAAC,EAAE,KAAK;AAClC,QAAI;AACF,aAAO,KAAK,MAAM,MAAM;AAAA,IAC1B,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,QAAI,IAAI,CAAC,MAAM,IAAK;AACpB,QAAI,QAAQ;AACZ,QAAI,WAAW;AACf,QAAI,SAAS;AACb,aAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,YAAM,KAAK,IAAI,CAAC;AAChB,UAAI,QAAQ;AACV,iBAAS;AACT;AAAA,MACF;AACA,UAAI,OAAO,QAAQ,UAAU;AAC3B,iBAAS;AACT;AAAA,MACF;AACA,UAAI,OAAO,KAAK;AACd,mBAAW,CAAC;AACZ;AAAA,MACF;AACA,UAAI,SAAU;AACd,UAAI,OAAO,IAAK;AAChB,UAAI,OAAO,IAAK;AAChB,UAAI,UAAU,GAAG;AACf,cAAM,YAAY,IAAI,MAAM,GAAG,IAAI,CAAC;AACpC,YAAI;AACF,iBAAO,KAAK,MAAM,SAAS;AAAA,QAC7B,QAAQ;AACN;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,IAAI,MAAM,+BAA+B,IAAI,MAAM,GAAG,GAAG,CAAC,EAAE;AACpE;;;ADzCO,SAAS,0BAA0B,QAA8B,WAA4B;AAClG,MAAI,CAAC,OAAO,QAAS,QAAO;AAC5B,QAAM,aAAa,OAAO,cAAc;AACxC,QAAM,WAAW,OAAO,YAAY;AACpC,MAAI,YAAY,EAAG,QAAO,cAAc;AACxC,MAAI,YAAY,WAAY,QAAO;AACnC,MAAI,cAAc,WAAY,QAAO;AACrC,UAAQ,YAAY,cAAc,aAAa;AACjD;AAaO,SAAS,4BAA4B,OAAoC;AAC9E,QAAM,eACJ,MAAM,eAAe,SAAS,IAC1B,MAAM,eAAe,IAAI,CAAC,MAAM,MAAM,EAAE,MAAM,KAAK,EAAE,KAAK,MAAM,GAAG,GAAG,CAAC,EAAE,EAAE,KAAK,IAAI,IACpF;AAEN,SAAO;AAAA;AAAA,WAEE,OAAO,MAAM,YAAY,CAAC,KAAK,MAAM,WAAW;AAAA,UACjD,MAAM,UAAU;AAAA,aACb,OAAO,MAAM,SAAS,CAAC,OAAO,OAAO,MAAM,aAAa,CAAC;AAAA;AAAA;AAAA,EAGpE,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASd;AAEA,IAAM,+BAA+B,EAAE,OAAO;AAAA,EAC5C,QAAQ,EAAE,KAAK,CAAC,eAAe,YAAY,SAAS,CAAC;AAAA,EACrD,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC;AAAA,EACnC,UAAU,EAAE,OAAO;AACrB,CAAC;AAKM,SAAS,4BAA4B,KAAmC;AAC7E,MAAI;AACJ,MAAI;AACF,aAAS,uBAAuB,GAAG;AAAA,EACrC,SAAS,KAAK;AACZ,UAAM,IAAI,MAAM,2BAA2B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,EAC/F;AAGA,MAAI,UAAU,OAAO,WAAW,YAAY,gBAAgB,QAAQ;AAClE,UAAM,IAAI;AACV,QAAI,OAAO,EAAE,eAAe,UAAU;AACpC,QAAE,aAAa,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,EAAE,UAAU,CAAC;AAAA,IACtD;AAAA,EACF;AAEA,QAAM,SAAS,6BAA6B,UAAU,MAAM;AAC5D,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI,MAAM,oDAA+C,OAAO,MAAM,OAAO,EAAE;AAAA,EACvF;AAEA,SAAO,OAAO;AAChB;AAMO,SAAS,mBAAmB,WAAyC;AAC1E,MAAI,UAAU,WAAW,GAAG;AAC1B,WAAO,EAAE,QAAQ,eAAe,UAAU,WAAW,YAAY,IAAI;AAAA,EACvE;AAEA,QAAM,SAAS,UAAU,MAAM,EAAE;AACjC,QAAM,SAAS,OAAO,IAAI,CAAC,MAAM,EAAE,mBAAmB;AAGtD,QAAM,aAAa,OAAO,OAAO,CAAC,MAA8C,MAAM,aAAa;AAEnG,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO,EAAE,QAAQ,eAAe,UAAU,uBAAuB,YAAY,IAAI;AAAA,EACnF;AAMA,MAAI,WAAW,SAAS,GAAG;AACzB,WAAO,EAAE,QAAQ,eAAe,UAAU,oCAAoC,YAAY,IAAI;AAAA,EAChG;AAGA,MAAI,WAAW,MAAM,CAAC,MAAM,MAAM,MAAM,GAAG;AACzC,WAAO,EAAE,QAAQ,YAAY,UAAU,0BAA0B,YAAY,EAAI;AAAA,EACnF;AAGA,MAAI,WAAW,MAAM,CAAC,MAAM,MAAM,eAAe,MAAM,MAAM,GAAG;AAC9D,WAAO,EAAE,QAAQ,cAAc,UAAU,0BAA0B,YAAY,EAAI;AAAA,EACrF;AAGA,MAAI,WAAW,KAAK,CAAC,MAAM,MAAM,UAAU,GAAG;AAC5C,UAAM,iBAAiB,WAAW,WAAW,SAAS,CAAC;AACvD,QAAI,mBAAmB,YAAY;AACjC,aAAO,EAAE,QAAQ,eAAe,UAAU,0BAA0B,YAAY,EAAI;AAAA,IACtF;AAAA,EACF;AAGA,MAAI,WAAW,KAAK,CAAC,MAAM,MAAM,UAAU,KAAK,WAAW,KAAK,CAAC,MAAM,MAAM,WAAW,GAAG;AACzF,WAAO,EAAE,QAAQ,YAAY,UAAU,4BAA4B,YAAY,EAAI;AAAA,EACrF;AAGA,MAAI,WAAW,KAAK,CAAC,MAAM,MAAM,UAAU,GAAG;AAC5C,WAAO,EAAE,QAAQ,YAAY,UAAU,+CAA0C,YAAY,IAAI;AAAA,EACnG;AAEA,SAAO,EAAE,QAAQ,YAAY,UAAU,2BAA2B,YAAY,EAAI;AACpF;;;AExFO,IAAM,iBAAiB;AAAA,EAC5B,iBAAiB;AAAA,EACjB,cAAc;AAAA,EACd,eAAe;AAAA;AAAA,EACf,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,kBAAkB;AAAA,EAClB,oBAAoB;AAAA,EACpB,yBAAyB;AAAA,EACzB,kBAAkB;AAAA,EAClB,UAAU;AACZ;AAEO,SAAS,eAAe,MAAsB;AACnD,SAAO,KAAK,KAAK,KAAK,SAAS,CAAC;AAClC;AAEO,SAAS,iBAAiB,MAAc,WAA2B;AACxE,MAAI,cAAc,SAAU,QAAO;AACnC,QAAM,WAAW,YAAY;AAC7B,MAAI,KAAK,UAAU,SAAU,QAAO;AACpC,SAAO,KAAK,MAAM,GAAG,QAAQ,IAAI;AACnC;AAEO,SAAS,eACd,UACA,WACQ;AACR,QAAM,SAAS,SAAS,OAAO,CAAC,MAAM,EAAE,MAAM;AAC9C,QAAM,WAAW,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM;AACjD,QAAM,aAAa,SAAS,MAAM,EAAE;AACpC,QAAM,QAAQ,SAAS,MAAM,GAAG,EAAE;AAElC,QAAM,QAAkB,CAAC;AAGzB,aAAW,KAAK,QAAQ;AACtB,UAAM,KAAK,cAAc,EAAE,MAAM,OAAO,EAAE,UAAU;AAAA,EAAO,EAAE,IAAI;AAAA,CAAI;AAAA,EACvE;AAGA,aAAW,KAAK,YAAY;AAC1B,UAAM,KAAK,KAAK,EAAE,MAAM,OAAO,EAAE,UAAU;AAAA,EAAO,EAAE,IAAI;AAAA,CAAI;AAAA,EAC9D;AAGA,aAAW,KAAK,OAAO;AACrB,UAAM,YAAY,EAAE,KAAK,MAAM,IAAI,EAAE,CAAC,GAAG,MAAM,GAAG,GAAG,KAAK;AAC1D,UAAM,KAAK,KAAK,EAAE,MAAM,KAAK,SAAS,EAAE;AAAA,EAC1C;AAEA,QAAM,SAAS,MAAM,KAAK,IAAI;AAC9B,SAAO,iBAAiB,QAAQ,SAAS;AAC3C;AAIO,SAAS,cACd,eACA,eACA,MACQ;AACR,QAAM,QAAkB,CAAC;AAEzB,MAAI,CAAC,cAAc,iBAAiB,SAAS;AAC3C,UAAM,IAAI;AAAA,MACR,WAAW,cAAc,OAAO,IAAI;AAAA,IAEtC;AAAA,EACF;AAGA,QAAM,wBAAwB,cAAc,aAAa;AAAA,IACvD,CAAC,MAAM,EAAE,cAAc,YAAY,EAAE,cAAc,YAAY,CAAC,EAAE;AAAA,EACpE;AAEA,QAAM,KAAK;AAAA;AAAA,yDAE4C,OAAO,cAAc,OAAO,aAAa,CAAC,MAAM,cAAc,OAAO,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,gDAKnF,cAAc,WAAW;AAAA,iCACxC,cAAc,WAAW;AAAA;AAAA,eAE3C,KAAK,SAAS,OAAO,KAAK,aAAa;AAAA,EACpD,KAAK,aAAa,KAAK,gBAAgB,IAAI,+FAA0F,YAAY,KAAK,gBAAgB,KAAK,SAAS,iEAAiE;AAAA,EACrP,KAAK,cAAc,IAAI;AAAA;AAAA,sCAEa,KAAK,WAAW;AAAA,EACpD,KAAK,kBAAkB,KAAK,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOrC,EAAE;AAAA,8BACwB,cAAc,WAAW;AAAA,MACjD,cAAc,WAAW;AAAA;AAAA,MAEzB,cAAc,WAAW;AAAA;AAAA,MAEzB,cAAc,WAAW;AAAA;AAAA,MAEzB,cAAc,WAAW;AAAA;AAAA,MAEzB,cAAc,WAAW;AAAA,MACzB,cAAc,WAAW;AAAA,MACzB,cAAc,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,8BAKD,cAAc,WAAW;AAAA;AAAA;AAAA;AAAA,wCAIf,cAAc,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2EASU,cAAc,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAMjF,cAAc,WAAW,2BAA2B,cAAc,WAAW;AAAA;AAAA;AAAA;AAAA,EAI9F,wBAAwB,oHAAoH,EAAE;AAAA;AAAA,CAE/I;AAGC,QAAM,iBAAiB,cAAc,cAAc;AACnD,MAAI,gBAAgB,SAAS;AAC3B,UAAM,oBAAoB,eAAe,sBAAsB;AAE/D,QAAI,CAAC,gBAAgB,KAAK,iBAAiB,GAAG;AAC5C,YAAM,KAAK;AAAA;AAAA,sHAAqI;AAAA,IAClJ,OAAO;AACL,YAAM,KAAK;AAAA;AAAA;AAAA;AAAA,aAIJ,iBAAiB,6CAA6C,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA,CAK3F;AAAA,IACG;AAAA,EACF;AAGA,MAAI,cAAc,QAAQ,SAAS,GAAG;AACpC,UAAM,KAAK;AAAA;AAAA,CAA6B;AACxC,eAAW,KAAK,cAAc,SAAS;AACrC,YAAM,KAAK,KAAK,CAAC,EAAE;AAAA,IACrB;AAAA,EACF;AAGA,MAAI,KAAK,eAAe,KAAK,YAAY,SAAS,GAAG;AACnD,UAAM,KAAK;AAAA;AAAA,CAA8B;AACzC,eAAW,KAAK,KAAK,aAAa;AAChC,YAAM,SAAS,EAAE,SAAS,SAAS;AACnC,YAAM,MAAM,EAAE,WAAW,eAAe;AACxC,YAAM,KAAK,OAAO,EAAE,IAAI,MAAM,MAAM,IAAI,GAAG,KAAK,OAAO,EAAE,WAAW,CAAC,KAAK;AAC1E,UAAI,CAAC,EAAE,UAAU,EAAE,QAAQ;AACzB,cAAM,KAAK;AAAA,IAAe,iBAAiB,EAAE,QAAQ,GAAG,CAAC;AAAA,SAAY;AAAA,MACvE;AAAA,IACF;AAAA,EACF;AAIA,MAAI,KAAK,mBAAmB;AAC1B,UAAM,KAAK;AAAA;AAAA,CAA2D;AACtE,UAAM,KAAK,iBAAiB,KAAK,mBAAmB,eAAe,SAAS,CAAC;AAAA,EAC/E,OAAO;AACL,UAAM,mBAAmB,cAAc,SACpC,MAAM,EACN,QAAQ,EACR,KAAK,CAAC,MAAM,EAAE,KAAK,WAAW,eAAe,KAAK,EAAE,KAAK,WAAW,YAAY,CAAC;AACpF,QAAI,kBAAkB;AACpB,YAAM,KAAK;AAAA;AAAA,CAA2D;AACtE,YAAM,KAAK,iBAAiB,iBAAiB,MAAM,eAAe,SAAS,CAAC;AAAA,IAC9E;AAAA,EACF;AAGA,MAAI,cAAc,iBAAiB,SAAS;AAC1C,UAAM,KAAK,cAAc,gBAAgB,OAAO;AAAA,EAClD;AAGA,MAAI,KAAK,mBAAmB,SAAS;AACnC,UAAM,KAAK;AAAA;AAAA,CAAkD;AAC7D,UAAM,KAAK,UAAU,KAAK,kBAAkB,KAAK;AAAA,CAAK;AACtD,UAAM,KAAK,iBAAiB,KAAK,kBAAkB,SAAS,eAAe,SAAS,CAAC;AAAA,EACvF;AAGA,MAAI,KAAK,kBAAkB;AACzB,UAAM,KAAK;AAAA;AAAA,CAAmB;AAC9B,UAAM,KAAK,iBAAiB,KAAK,kBAAkB,eAAe,UAAU,CAAC;AAAA,EAC/E;AAGA,QAAM,cAAwB,CAAC;AAC/B,cAAY,KAAK;AAAA,CAAqB;AACtC,cAAY,KAAK,cAAc,cAAc,OAAO,KAAK,EAAE;AAC3D,cAAY,KAAK,kBAAkB,cAAc,OAAO,EAAE,EAAE;AAC5D,cAAY,KAAK,sBAAsB,OAAO,cAAc,OAAO,aAAa,CAAC,EAAE;AACnF,MAAI,cAAc,OAAO,aAAa;AACpC,gBAAY,KAAK;AAAA,EAAK,cAAc,OAAO,WAAW,EAAE;AAAA,EAC1D;AAEA,MAAI,cAAc,OAAO,UAAU;AACjC,gBAAY,KAAK,iBAAiB,cAAc,OAAO,SAAS,IAAI,EAAE;AAAA,EACxE;AACA,MAAI,cAAc,OAAO,QAAQ;AAC/B,gBAAY,KAAK,uBAAuB,cAAc,OAAO,OAAO,IAAI,KAAK,cAAc,OAAO,OAAO,IAAI,GAAG;AAAA,EAClH;AACA,MAAI,cAAc,OAAO,uBAAuB,GAAG;AACjD,gBAAY,KAAK,6BAA6B,OAAO,cAAc,OAAO,oBAAoB,CAAC,EAAE;AAAA,EACnG;AAEA,MAAI,cAAc,aAAa,SAAS,GAAG;AACzC,gBAAY,KAAK;AAAA;AAAA,CAAqB;AACtC,eAAW,MAAM,cAAc,cAAc;AAC3C,kBAAY,KAAK,OAAO,GAAG,UAAU,OAAO,GAAG,UAAU,OAAO,KAAK,UAAU,GAAG,KAAK,IAAI,WAAW,EAAE;AAAA,IAC1G;AAAA,EACF;AAEA,MAAI,cAAc,QAAQ;AACxB,gBAAY,KAAK;AAAA;AAAA,CAAsB;AACvC,gBAAY,KAAK,MAAM,OAAO,cAAc,OAAO,aAAa,CAAC,KAAK,cAAc,OAAO,KAAK,EAAE;AAAA,EACpG;AACA,MAAI,cAAc,SAAS,SAAS,GAAG;AACrC,gBAAY,KAAK;AAAA;AAAA,CAAsB;AACvC,eAAW,SAAS,cAAc,UAAU;AAC1C,kBAAY,KAAK,MAAM,OAAO,MAAM,aAAa,CAAC,KAAK,MAAM,KAAK,GAAG,MAAM,cAAc,KAAK,MAAM,WAAW,MAAM,EAAE,EAAE;AAAA,IAC3H;AAAA,EACF;AAEA,MAAI,cAAc,YAAY,SAAS,GAAG;AACxC,gBAAY,KAAK;AAAA;AAAA,CAA2B;AAC5C,QAAI,mBAAmB;AACvB,eAAW,KAAK,cAAc,aAAa;AACzC,UAAI,OAAO,KAAK,EAAE,QAAQ,SAAS,WAAM,EAAE,EAAE;AAC7C,UAAI,EAAE,QAAS,SAAQ,cAAc,KAAK,UAAU,EAAE,OAAO,CAAC;AAC9D,YAAM,aAAa,eAAe,IAAI;AACtC,UAAI,mBAAmB,aAAa,eAAe,oBAAoB;AACrE,oBAAY,KAAK,0BAAqB,OAAO,cAAc,YAAY,MAAM,CAAC,qBAAqB;AACnG;AAAA,MACF;AACA,kBAAY,KAAK,IAAI;AACrB,0BAAoB;AAAA,IACtB;AAAA,EACF;AAEA,MAAI,cAAc,aAAa,SAAS,GAAG;AACzC,gBAAY,KAAK;AAAA;AAAA,CAAqB;AACtC,UAAM,WAAW,cAAc,aAAa,OAAO,OAAK,EAAE,cAAc,YAAY,EAAE,cAAc,QAAQ;AAC5G,UAAM,WAAW,cAAc,aAAa,OAAO,OAAK,EAAE,cAAc,aAAa,EAAE,cAAc,QAAQ;AAC7G,UAAM,UAAU,cAAc,aAAa,OAAO,OAAK,EAAE,cAAc,YAAY;AACnF,QAAI,SAAS,SAAS,GAAG;AACvB,kBAAY,KAAK,iBAAiB;AAClC,iBAAW,KAAK,UAAU;AACxB,cAAM,SAAS,EAAE,WAAW,eAAe;AAC3C,oBAAY,KAAK,MAAM,OAAO,EAAE,aAAa,CAAC,KAAK,EAAE,KAAK,KAAK,EAAE,eAAe,SAAS,KAAK,MAAM,EAAE;AAAA,MACxG;AAAA,IACF;AACA,QAAI,SAAS,SAAS,GAAG;AACvB,kBAAY,KAAK,aAAa;AAC9B,iBAAW,KAAK,UAAU;AACxB,oBAAY,KAAK,MAAM,OAAO,EAAE,aAAa,CAAC,KAAK,EAAE,KAAK,KAAK,EAAE,eAAe,SAAS,GAAG;AAAA,MAC9F;AAAA,IACF;AACA,QAAI,QAAQ,SAAS,GAAG;AACtB,kBAAY,KAAK,cAAc;AAC/B,iBAAW,KAAK,SAAS;AACvB,oBAAY,KAAK,MAAM,OAAO,EAAE,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE;AAAA,MAC9D;AAAA,IACF;AAAA,EACF;AAEA,QAAM,KAAK,iBAAiB,YAAY,KAAK,IAAI,GAAG,eAAe,cAAc,CAAC;AAGlF,MAAI,cAAc,SAAS,SAAS,GAAG;AACrC,UAAM,KAAK;AAAA;AAAA,CAAiB;AAC5B,UAAM,KAAK,eAAe,cAAc,UAAU,eAAe,QAAQ,CAAC;AAAA,EAC5E;AAGA,MAAI,cAAc,kBAAkB;AAClC,UAAM,KAAK;AAAA;AAAA,CAAyB;AACpC,UAAM,KAAK,iBAAiB,cAAc,kBAAkB,eAAe,gBAAgB,CAAC;AAAA,EAC9F;AAGA,MAAI,cAAc,2BAA2B,cAAc,4BAA4B,mDAAmD;AACxI,UAAM,KAAK;AAAA;AAAA,CAAwC;AACnD,UAAM,KAAK,iBAAiB,cAAc,yBAAyB,eAAe,uBAAuB,CAAC;AAAA,EAC5G;AAGA,MAAI,cAAc,iBAAiB,SAAS,GAAG;AAC7C,UAAM,KAAK;AAAA;AAAA,CAAyB;AACpC,QAAI,kBAA0B,eAAe;AAC7C,eAAW,OAAO,cAAc,kBAAkB;AAChD,UAAI,IAAI,SAAS;AACf,cAAM,YAAY,OAAO,IAAI,KAAK;AAAA;AAClC,cAAM,eAAe,eAAe,SAAS;AAC7C,cAAM,gBAAgB,eAAe,IAAI,OAAO;AAChD,YAAI,eAAe,iBAAiB,iBAAiB;AACnD,gBAAM,KAAK,SAAS;AACpB,gBAAM,KAAK,IAAI,OAAO;AACtB,6BAAmB,eAAe;AAAA,QACpC,WAAW,kBAAkB,eAAe,IAAI;AAE9C,gBAAM,KAAK,SAAS;AACpB,gBAAM,KAAK,iBAAiB,IAAI,SAAS,kBAAkB,YAAY,CAAC;AACxE,4BAAkB;AAAA,QACpB,OAAO;AACL,gBAAM,KAAK,KAAK,IAAI,KAAK,2CAAsC;AAC/D,4BAAkB;AAAA,QACpB;AAAA,MACF,WAAW,IAAI,WAAW;AACxB,cAAM,KAAK,KAAK,IAAI,KAAK,4DAAuD;AAAA,MAClF;AACA,UAAI,mBAAmB,EAAG;AAAA,IAC5B;AAAA,EACF;AAGA,QAAM,KAAK;AAAA;AAAA,CAA0B;AACrC,QAAM,KAAK,gBAAgB,KAAK,SAAS,MAAM,KAAK,aAAa,EAAE;AACnE,QAAM,KAAK,iBAAiB,KAAK,SAAS,EAAE;AAC5C,QAAM,KAAK,kBAAkB,cAAc,WAAW,EAAE;AACxD,QAAM,KAAK,aAAa,cAAc,OAAO,IAAI,EAAE;AACnD,QAAM,KAAK,WAAW,cAAc,OAAO,QAAQ,aAAa,EAAE;AAElE,SAAO,MAAM,KAAK,IAAI;AACxB;;;AC5VA,IAAM,iBAAiB;AAEvB,SAAS,YAAe,SAAqB,IAAY,OAA2B;AAClF,MAAI;AACJ,SAAO,QAAQ,KAAK;AAAA,IAClB,QAAQ,QAAQ,MAAM,aAAa,KAAK,CAAC;AAAA,IACzC,IAAI,QAAe,CAAC,GAAG,WAAW;AAChC,cAAQ,WAAW,MAAM,OAAO,IAAI,MAAM,GAAG,KAAK,oBAAoB,EAAE,IAAI,CAAC,GAAG,EAAE;AAAA,IACpF,CAAC;AAAA,EACH,CAAC;AACH;AAEO,IAAM,YAAN,MAAgB;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,mBAAmB;AAAA,EAE3B,YAAY,UAAkB,UAAkB,QAAoB,MAAqB;AACvF,SAAK,WAAW;AAChB,SAAK,WAAW;AAChB,SAAK,SAAS;AACd,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,OAAa;AACX,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,IAAI,YAAoB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,aAAa,KAAuB;AAC1C,UAAM,aAAc,KAAiC,cAC/C,KAA6B;AACnC,QAAI,eAAe,IAAK,QAAO;AAC/B,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,WAAO,QAAQ,SAAS,eAAe;AAAA,EACzC;AAAA,EAEA,MAAM,MAA2B;AAC/B,QAAI,cAAc,KAAK,OAAO,oBAAoB;AAClD,QAAI,kBAA4C,KAAK,OAAO,oBAAoB;AAChF,UAAM,MAAM,KAAK,KAAK,QAAQ,MAAM;AAAA,IAAC;AACrC,UAAM,gBAAgB,KAAK,OAAO,SAAS;AAC3C,UAAM,gBAAgC,CAAC;AACvC,QAAI,qBAAqB;AACzB,QAAI,sBAAsB;AAC1B,QAAI,sBAAsB;AAC1B,QAAI,uBAAuB;AAE3B,UAAM,YAAY,CAAC,OAA+B;AAAA,MAChD,GAAG;AAAA,MACH,UAAU;AAAA,MACV,WAAW;AAAA,MACX,eAAe;AAAA,MACf,YAAY;AAAA,IACd;AACA,QAAI,aAAa;AAEjB,UAAM,YAAY,KAAK,OAAO,kBAAkB;AAChD,aAAS,IAAI,WAAW,KAAK,KAAK,OAAO,eAAe,KAAK;AAC3D,UAAI,KAAK,QAAS,QAAO,UAAU,EAAE,QAAQ,WAAW,YAAY,IAAI,GAAG,aAAa,OAAO,cAAc,CAAC;AAC9G,UAAI,KAAK,OAAO,oBAAoB,GAAG;AACrC,YAAI,0CAAqC,IAAI,CAAC,aAAa;AAC3D,eAAO,UAAU,EAAE,QAAQ,UAAU,YAAY,IAAI,GAAG,aAAa,OAAO,cAAc,CAAC;AAAA,MAC7F;AACA,WAAK,mBAAmB;AACxB,UAAI,aAAa,CAAC,IAAI,KAAK,OAAO,aAAa,WAAW;AAG1D,UAAI,CAAC,iBAAiB;AACpB,YAAI;AACF,4BAAkB,MAAM;AAAA,YACtB,KAAK,KAAK,iBAAiB,KAAK,QAAQ;AAAA,YACxC;AAAA,YAAgB;AAAA,UAClB;AAAA,QACF,SAAS,KAAK;AACZ,gBAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,cAAI,sCAAsC,OAAO,EAAE;AACnD,cAAI,KAAK,aAAa,GAAG,GAAG;AAC1B,mBAAO,UAAU,EAAE,QAAQ,WAAW,YAAY,GAAG,aAAa,OAAO,cAAc,CAAC;AAAA,UAC1F;AACA,iBAAO,UAAU,EAAE,QAAQ,SAAS,YAAY,GAAG,aAAa,WAAW,gCAAgC,OAAO,IAAI,OAAO,cAAc,CAAC;AAAA,QAC9I;AAAA,MACF;AAGA,UAAI;AACJ,UAAI;AACJ,UAAI;AACF,SAAC,WAAW,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,UACzC,YAAY,KAAK,KAAK,mBAAmB,KAAK,QAAQ,GAAG,gBAAgB,oBAAoB;AAAA,UAC7F,YAAY,KAAK,KAAK,mBAAmB,KAAK,QAAQ,GAAG,gBAAgB,oBAAoB;AAAA,QAC/F,CAAC;AAAA,MACH,SAAS,KAAK;AACZ,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,YAAI,yBAAyB,OAAO,EAAE;AACtC,YAAI,KAAK,aAAa,GAAG,GAAG;AAC1B,iBAAO,UAAU,EAAE,QAAQ,WAAW,YAAY,GAAG,aAAa,OAAO,cAAc,CAAC;AAAA,QAC1F;AACA,eAAO,UAAU,EAAE,QAAQ,SAAS,YAAY,GAAG,aAAa,WAAW,yBAAyB,OAAO,IAAI,OAAO,cAAc,CAAC;AAAA,MACvI;AAGA,UAAI,UAAU,OAAO,UAAU,UAAU,OAAO,OAAO,OAAO,KAAK,UAAU;AAC3E,YAAI,4BAA4B,UAAU,OAAO,OAAO,IAAI,SAAS,KAAK,QAAQ,0BAAqB;AACvG,eAAO,UAAU,EAAE,QAAQ,SAAS,YAAY,IAAI,GAAG,aAAa,OAAO,cAAc,CAAC;AAAA,MAC5F;AAGA,YAAM,mBAAmB,KAAK,KAAK,wBAC/B,MAAM,KAAK,KAAK,sBAAsB,EAAE,MAAM,MAAM,EAAE,IACtD;AAEJ,YAAM,oBAAoB,KAAK,KAAK,yBAChC,MAAM,KAAK,KAAK,uBAAuB,EAAE,MAAM,MAAM,MAAS,IAC9D;AAGJ,UAAI;AACJ,UAAI;AACF,iBAAS,cAAc,WAAW,WAAW;AAAA,UAC3C,WAAW;AAAA,UACX,eAAe,KAAK,OAAO;AAAA,UAC3B,WAAW,KAAK,KAAK;AAAA,UACrB;AAAA,UACA,iBAAiB,KAAK,OAAO;AAAA,UAC7B,kBAAkB,oBAAoB;AAAA,UACtC;AAAA,QACF,CAAC;AAAA,MACH,SAAS,KAAK;AACZ,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,YAAI,8BAA8B,OAAO,EAAE;AAC3C,eAAO,UAAU,EAAE,QAAQ,SAAS,YAAY,GAAG,aAAa,WAAW,8BAA8B,OAAO,IAAI,OAAO,cAAc,CAAC;AAAA,MAC5I;AAGA,UAAI,KAAK,QAAS,QAAO,UAAU,EAAE,QAAQ,WAAW,YAAY,IAAI,GAAG,aAAa,OAAO,cAAc,CAAC;AAC9G,UAAI,YAAY,KAAK,KAAK,SAAS,WAAW,WAAW,KAAK,OAAO,SAAS,SAAS,GAAG;AAE1F,YAAM,iBAAiB,GAAG,KAAK,OAAO,SAAS,KAAK,QAAQ,SAAS,OAAO,CAAC,CAAC;AAE9E,WAAK,KAAK,iBAAiB;AAAA,QACzB,OAAO;AAAA,QACP,OAAO,KAAK,OAAO,SAAS;AAAA,QAC5B,WAAW;AAAA,MACb,CAAC;AAED,YAAM,gBAAgB,EAAE,OAAO,gBAAgB,UAAU,KAAK,UAAU,UAAU,KAAK,SAAS;AAChG,YAAM,eAA6B;AAAA,QACjC;AAAA,QACA,GAAI,KAAK,OAAO,SAAS,QAAQ,EAAE,OAAO,KAAK,OAAO,MAAM;AAAA,QAC5D,GAAI,KAAK,OAAO,gBAAgB,QAAQ,EAAE,UAAU,KAAK,IAAI,GAAG,KAAK,KAAK,KAAK,OAAO,eAAe,EAAE,CAAC,EAAE;AAAA,QAC1G,GAAI,KAAK,OAAO,gBAAgB,QAAQ,EAAE,kBAAkB,KAAK,OAAO,aAAa;AAAA,QACrF,GAAI,KAAK,KAAK,aAAa,QAAQ,EAAE,WAAW,KAAK,KAAK,UAAU;AAAA,QACpE,GAAI,KAAK,OAAO,oBAAoB,QAAQ;AAAA,UAC1C,kBAAkB,EAAE,GAAG,KAAK,OAAO,kBAAkB,kBAAkB,KAAK,OAAO,iBAAiB,oBAAoB,KAAK;AAAA,QAC/H;AAAA,QACA,eAAe,CAAC,UAAU,KAAK,KAAK,gBAAgB,OAAO,aAAa;AAAA,MAC1E;AAEA,YAAM,EAAE,UAAU,QAAQ,eAAe,OAAO,YAAY,qBAAqB,qBAAqB,IAAI,MAAM,KAAK,KAAK,SAAS,OAAO,YAAY;AACtJ,YAAM,WAAW,MAAM;AACvB,YAAM,YAAY,MAAM;AAGxB,UAAI,MAAM,aAAa,sBAAsB,QAAQ;AACnD,YAAI,YAAY,KAAK,KAAK,SAAS,EAAE,cAAc,qBAAqB,KAAK,IAAI,CAAC,EAAE;AAAA,MACtF;AAEA,4BAAsB;AACtB,6BAAuB;AACvB,6BAAuB;AACvB,8BAAwB;AAExB,WAAK,KAAK,eAAe;AAAA,QACvB,OAAO;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,YAAY;AAAA,MACd,CAAC;AAED,UAAI,aAAa,GAAG;AAClB,cAAM,UAAU,OAAO,MAAM,IAAI;AACjC,YAAI,GAAG,KAAK,KAAK,SAAS,WAAW,qBAAqB,QAAQ,KAAK,OAAO,EAAE;AAChF,eAAO,UAAU,EAAE,QAAQ,SAAS,YAAY,GAAG,aAAa,WAAW,uBAAuB,QAAQ,kBAAkB,OAAO,IAAI,OAAO,cAAc,CAAC;AAAA,MAC/J;AACA,UAAI,GAAG,KAAK,KAAK,SAAS,WAAW,sBAAsB;AAC3D,mBAAa;AAGb,UAAI;AACJ,UAAI;AACF,cAAM,eAAe,KAAK,OAAO,wBAAwB;AACzD,kBAAU,MAAM;AAAA,UACd,KAAK,KAAK,iBAAiB,KAAK,QAAQ;AAAA,UACxC;AAAA,UAAgB;AAAA,QAClB;AAEA,YAAI,QAAQ,cAAc,KAAK,UAAU;AACvC,mBAAS,QAAQ,GAAG,QAAQ,GAAG,SAAS;AACtC,kBAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC;AACpD,gBAAI,KAAK,QAAS;AAClB,gBAAI;AACF,wBAAU,MAAM;AAAA,gBACd,KAAK,KAAK,iBAAiB,KAAK,QAAQ;AAAA,gBACxC;AAAA,gBAAgB;AAAA,cAClB;AAAA,YACF,SAAS,KAAK;AACZ,kBAAI,qBAAqB,QAAQ,CAAC,YAAY,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,+BAA0B;AACxH;AAAA,YACF;AACA,gBAAI,QAAQ,cAAc,KAAK,SAAU;AAAA,UAC3C;AAAA,QACF;AAAA,MACF,SAAS,KAAK;AACZ,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,YAAI,sCAAsC,OAAO,EAAE;AACnD,YAAI,KAAK,aAAa,GAAG,GAAG;AAC1B,iBAAO,UAAU,EAAE,QAAQ,WAAW,YAAY,GAAG,aAAa,OAAO,cAAc,CAAC;AAAA,QAC1F;AACA,eAAO,UAAU,EAAE,QAAQ,SAAS,YAAY,GAAG,aAAa,WAAW,sCAAsC,OAAO,IAAI,OAAO,cAAc,CAAC;AAAA,MACpJ;AAGA,UAAI,QAAQ,cAAc,KAAK,UAAU;AACvC,YAAI,0BAA0B,QAAQ,aAAa,MAAM,EAAE;AAC3D,eAAO,UAAU,EAAE,QAAQ,SAAS,YAAY,GAAG,aAAa,OAAO,eAAe,QAAQ,WAAW,CAAC;AAAA,MAC5G;AAIA,YAAM,oBAAoB;AAG1B,UAAI,KAAK,OAAO,sBAAsB;AACpC,YAAI;AACF,gBAAM,WAAW,MAAM,KAAK,OAAO,qBAAqB,KAAK,UAAU,CAAC;AACxE,wBAAc,KAAK,QAAQ;AAG3B,gBAAM,aAAa,kBAAkB,QAAQ,sBAAsB,gBAAgB,oBAAoB;AAEvG,kBAAQ,SAAS,qBAAqB;AAAA,YACpC,KAAK;AACH,kBAAI,cAAc,EAAG,KAAI,uDAAkD;AAC3E,4BAAc;AACd;AAAA,YACF,KAAK;AACH,kBAAI,CAAC,YAAY;AACf;AACA,oBAAI,4BAA4B,WAAW,IAAI,KAAK,OAAO,eAAe,GAAG;AAAA,cAC/E,OAAO;AACL,oBAAI,mEAA8D;AAAA,cACpE;AACA;AAAA,YACF,KAAK;AACH,6BAAe;AACf,kBAAI,oCAAoC,WAAW,IAAI,KAAK,OAAO,eAAe,GAAG;AACrF;AAAA,YACF,KAAK;AAEH;AAAA,UACJ;AAAA,QACF,SAAS,KAAK;AACZ,gBAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,cAAI,oDAAoD,OAAO,EAAE;AAEjE,cAAI,mBAAmB,kBAAkB,iBAAiB,OAAO,GAAG;AAClE;AACA,gBAAI,yDAAyD,WAAW,IAAI,KAAK,OAAO,eAAe,GAAG;AAAA,UAC5G,OAAO;AACL,gBAAI,cAAc,EAAG,KAAI,+CAA0C;AACnE,0BAAc;AAAA,UAChB;AAAA,QACF;AAAA,MACF,OAAO;AAEL,YAAI,mBAAmB,kBAAkB,iBAAiB,OAAO,GAAG;AAClE;AACA,cAAI,gCAAgC,WAAW,IAAI,KAAK,OAAO,eAAe,GAAG;AAAA,QACnF,OAAO;AACL,cAAI,cAAc,EAAG,KAAI,+CAA0C;AACnE,wBAAc;AAAA,QAChB;AAAA,MACF;AACA,wBAAkB;AAGlB,UAAI,KAAK,OAAO,kBAAkB,0BAA0B,KAAK,OAAO,gBAAgB,CAAC,GAAG;AAC1F,YAAI;AACF,cAAI;AACJ,cAAI;AACJ,cAAI;AAEJ,cAAI,cAAc,SAAS,GAAG;AAE5B,kBAAM,aAAa,mBAAmB,aAAa;AACnD,uBAAW,WAAW,WAAW,eAAe,aAAa,WAAW;AACxE,yBAAa,WAAW;AACxB,2BAAe,WAAW;AAC1B,gBAAI,+BAA+B,QAAQ,gBAAgB,OAAO,YAAY,CAAC,YAAO,UAAU,EAAE;AAAA,UACpG,WAAW,KAAK,OAAO,sBAAsB;AAE3C,kBAAM,UAA+B;AAAA,cACnC,cAAc,UAAU,OAAO;AAAA,cAC/B,aAAa,UAAU,OAAO;AAAA,cAC9B,YAAY,UAAU,OAAO;AAAA,cAC7B,WAAW;AAAA,cACX,eAAe,KAAK,OAAO;AAAA,cAC3B,gBAAgB,UAAU,SAAS,MAAM,EAAE,EAAE,IAAI,CAAC,OAAO;AAAA,gBACvD,QAAQ,EAAE;AAAA,gBACV,MAAM,EAAE;AAAA,cACV,EAAE;AAAA,YACJ;AACA,kBAAM,WAAW,MAAM,KAAK,OAAO,qBAAqB,OAAO;AAC/D,uBAAW,SAAS;AACpB,yBAAa,SAAS;AACtB,2BAAe,SAAS;AACxB,gBAAI,oBAAoB,QAAQ,gBAAgB,OAAO,YAAY,CAAC,YAAO,UAAU,EAAE;AAAA,UACzF,OAAO;AAEL,uBAAW;AACX,yBAAa;AACb,2BAAe;AAAA,UACjB;AAEA,kBAAQ,UAAU;AAAA,YAChB,KAAK;AACH,kBAAI,cAAc,GAAG;AACnB,oBAAI,2DAA2D,OAAO,WAAW,CAAC,GAAG;AACrF,8BAAc;AAAA,cAChB;AACA;AAAA,YACF,KAAK;AAGH,4BAAc,oBAAoB;AAClC,kBAAI,kDAA6C,OAAO,WAAW,CAAC,IAAI,OAAO,KAAK,OAAO,eAAe,CAAC,EAAE;AAC7G;AAAA,YACF,KAAK;AACH,kBAAI,qDAAgD;AACpD,qBAAO,UAAU;AAAA,gBACf,QAAQ;AAAA,gBACR,YAAY;AAAA,gBACZ,aAAa,KAAK,OAAO;AAAA,gBACzB,OAAO;AAAA,gBACP,QAAQ;AAAA,gBACR,GAAI,cAAc,SAAS,KAAK,EAAE,mBAAmB,cAAc,cAAc,SAAS,CAAC,EAAE;AAAA,cAC/F,CAAC;AAAA,UACL;AAAA,QACF,SAAS,KAAK;AACZ,gBAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,cAAI,0CAA0C,OAAO,EAAE;AAAA,QACzD;AAAA,MACF;AAGA,UAAI,eAAe,KAAK,OAAO,iBAAiB;AAC9C,eAAO,UAAU;AAAA,UACf,QAAQ;AAAA,UACR,YAAY;AAAA,UACZ;AAAA,UACA,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,GAAI,cAAc,SAAS,KAAK,EAAE,mBAAmB,cAAc,cAAc,SAAS,CAAC,EAAE;AAAA,QAC/F,CAAC;AAAA,MACH;AAGA,UAAI,KAAK,OAAO,cAAc;AAC5B,YAAI;AACF,gBAAM,KAAK,OAAO,aAAa,KAAK,UAAU;AAAA,YAC5C,QAAQ,KAAK,OAAO,SAAS;AAAA,YAC7B,WAAW,KAAK;AAAA,YAChB,WAAW;AAAA,YACX,cAAc;AAAA,YACd,qBAAqB;AAAA;AAAA,YACrB,YAAY;AAAA,YACZ,kBAAkB;AAAA,YAClB,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,YACnC,eAAe,KAAK,OAAO;AAAA,UAC7B,CAAC;AAAA,QACH,SAAS,KAAK;AAEZ,cAAI,2CAA2C,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,QACnG;AAAA,MACF;AAAA,IACF;AAEA,WAAO,UAAU;AAAA,MACf,QAAQ;AAAA,MACR,YAAY,KAAK,OAAO;AAAA,MACxB;AAAA,MACA,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,GAAI,cAAc,SAAS,KAAK,EAAE,mBAAmB,cAAc,cAAc,SAAS,CAAC,EAAE;AAAA,IAC/F,CAAC;AAAA,EACH;AACF;AAEA,SAAS,kBAAkB,GAAsB,GAA+B;AAK9E,SAAO,EAAE,cAAc,EAAE,aACpB,EAAE,sBAAsB,EAAE,qBAC1B,EAAE,kBAAkB,EAAE;AAC7B;;;ACzeA,SAAS,eAAe,YAAY,WAAW,YAAY,mBAAmB;AAC9E,SAAS,MAAM,eAAe;AAC9B,SAAS,qBAAqB;AAC9B,SAAS,eAAe;AAExB,IAAM,aAAa,cAAc,YAAY,GAAG;AAChD,IAAM,YAAY,QAAQ,UAAU;AAQ7B,SAAS,kBAAkB,QAAgB,UAAkB,SAAyB;AAM7F,QAAM,eAAe,WAAW,KAAK,WAAW,MAAM,MAAM,OAAO,QAAQ,UAAU,CAAC,IAClF,KAAK,WAAW,MAAM,MAAM,OAAO,QAAQ,UAAU,IACrD,KAAK,WAAW,MAAM,MAAM,MAAM,OAAO,QAAQ,UAAU;AAC7D,QAAM,WAAW,WAAW,YAAY;AAExC,QAAM,gBAAgB,WAClB;AAAA,IACE,SAAS;AAAA,IACT,MAAM,CAAC,YAAY;AAAA,IACnB,KAAK;AAAA,MACH,mBAAmB;AAAA,MACnB,iBAAiB;AAAA,IACnB;AAAA,EACF,IACA;AAAA,IACE,SAAS;AAAA,IACT,MAAM,CAAC,MAAM,oBAAoB;AAAA,IACjC,KAAK;AAAA,MACH,mBAAmB;AAAA,MACnB,iBAAiB;AAAA,IACnB;AAAA,EACF;AAEJ,QAAM,SAAS;AAAA,IACb,YAAY;AAAA,MACV,SAAS;AAAA,IACX;AAAA,EACF;AAEA,QAAM,MAAM,KAAK,QAAQ,GAAG,YAAY,aAAa,OAAO;AAC5D,YAAU,KAAK,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAC/C,QAAM,WAAW,KAAK,KAAK,iBAAiB;AAC5C,gBAAc,UAAU,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG,EAAE,MAAM,IAAM,CAAC;AACxE,SAAO;AACT;AAEO,SAAS,iBAAiB,UAAwB;AACvD,MAAI;AACF,QAAI,WAAW,QAAQ,GAAG;AACxB,iBAAW,QAAQ;AAAA,IACrB;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAWO,SAAS,2BACd,QACA,UACA,SACA,gBACA,UACA,YACA,WACA,SACA,UACQ;AAIV,QAAM,eAAe,WAAW,KAAK,WAAW,MAAM,MAAM,OAAO,QAAQ,UAAU,CAAC,IAClF,KAAK,WAAW,MAAM,MAAM,OAAO,QAAQ,UAAU,IACrD,KAAK,WAAW,MAAM,MAAM,MAAM,OAAO,QAAQ,UAAU;AAC7D,QAAM,WAAW,WAAW,YAAY;AAKxC,QAAM,gBAAgB,WAClB,EAAE,SAAS,QAAQ,MAAM,CAAC,YAAY,GAAG,KAAK,EAAE,mBAAmB,UAAU,iBAAiB,QAAQ,sBAAsB,uFAAuF,EAAE,IACrN,EAAE,SAAS,OAAO,MAAM,CAAC,MAAM,oBAAoB,GAAG,KAAK,EAAE,mBAAmB,UAAU,iBAAiB,QAAQ,sBAAsB,uFAAuF,EAAE;AAItO,QAAM,gBAAgB,WAAW,KAAK,WAAW,OAAO,sBAAsB,CAAC,IAC3E,KAAK,WAAW,OAAO,sBAAsB,IAC7C,KAAK,WAAW,sBAAsB;AAC1C,QAAM,eAAuC;AAAA,IAC3C,kBAAkB;AAAA,IAClB,WAAW;AAAA,IACX,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,EACnB;AACA,MAAI,QAAS,cAAa,UAAU,IAAI;AAExC,QAAM,kBAAkB;AAAA,IACtB,SAAS;AAAA,IACT,MAAM,CAAC,aAAa;AAAA,IACpB,KAAK;AAAA,EACP;AAEA,QAAM,SAAS;AAAA,IACb,YAAY;AAAA,MACV,SAAS;AAAA,MACT,iBAAiB;AAAA,IACnB;AAAA,EACF;AAEA,QAAM,MAAM,KAAK,QAAQ,GAAG,YAAY,aAAa,OAAO;AAC5D,YAAU,KAAK,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAG/C,QAAM,SAAS,WAAW,GAAG,QAAQ,IAAI,QAAQ,KAAK;AACtD,QAAM,WAAW,KAAK,KAAK,cAAc,MAAM,OAAO;AACtD,gBAAc,UAAU,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG,EAAE,MAAM,IAAM,CAAC;AACxE,SAAO;AACT;AAMO,SAAS,wBAAwB,aAA2B;AACjE,MAAI;AACF,UAAM,QAAQ,YAAY,WAAW;AACrC,eAAW,KAAK,OAAO;AACrB,UAAI,EAAE,WAAW,aAAa,KAAK,EAAE,SAAS,OAAO,GAAG;AACtD,YAAI;AAAE,qBAAW,KAAK,aAAa,CAAC,CAAC;AAAA,QAAG,QAAQ;AAAA,QAAe;AAAA,MACjE;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAAgC;AAC1C;;;ACxJA,SAAS,aAAgC;AACzC,SAAS,iBAAAA,gBAAe,aAAAC,kBAAiB;AACzC,SAAS,QAAAC,aAAY;AACrB,SAAS,WAAAC,gBAAe;;;ACGjB,IAAM,qBAAN,MAAyB;AAAA,EACtB,SAAS;AAAA,EACT,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,eAAe;AAAA,EACf,aAAa;AAAA,EAErB,UAAkD,MAAM;AAAA,EAAC;AAAA,EACzD,UAAkC,MAAM;AAAA,EAAC;AAAA,EAEzC,KAAK,OAAqB;AACxB,SAAK,UAAU;AACf,UAAM,QAAQ,KAAK,OAAO,MAAM,IAAI;AACpC,SAAK,SAAS,MAAM,IAAI,KAAK;AAE7B,eAAW,QAAQ,OAAO;AACxB,WAAK,UAAU,KAAK,KAAK,CAAC;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,QAAc;AACZ,UAAM,UAAU,KAAK,OAAO,KAAK;AACjC,SAAK,SAAS;AACd,QAAI,QAAS,MAAK,UAAU,OAAO;AAAA,EACrC;AAAA,EAEA,mBAA2B;AACzB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,WAA0D;AACxD,WAAO,EAAE,aAAa,KAAK,aAAa,cAAc,KAAK,aAAa;AAAA,EAC1E;AAAA,EAEA,gBAAwB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,QAAc;AACZ,SAAK,SAAS;AACd,SAAK,gBAAgB;AACrB,SAAK,cAAc;AACnB,SAAK,eAAe;AACpB,SAAK,aAAa;AAAA,EACpB;AAAA,EAEQ,UAAU,MAAoB;AACpC,QAAI,CAAC,KAAM;AACX,QAAI;AACF,YAAM,MAAM,KAAK,MAAM,IAAI;AAC3B,WAAK,eAAe,GAAG;AAAA,IACzB,QAAQ;AACN,WAAK,QAAQ,IAAI,MAAM,qCAAqC,KAAK,MAAM,GAAG,GAAG,CAAC,EAAE,CAAC;AAAA,IACnF;AAAA,EACF;AAAA,EAEQ,eAAe,KAAoC;AAEzD,UAAM,UAAU,IAAI;AACpB,UAAM,UAAW,SAAS,WAAW,IAAI;AACzC,QAAI,IAAI,SAAS,eAAe,MAAM,QAAQ,OAAO,GAAG;AACtD,iBAAW,SAAS,SAAS;AAC3B,YAAI,MAAM,SAAS,UAAU,OAAO,MAAM,SAAS,UAAU;AAC3D,eAAK,QAAQ,EAAE,MAAM,QAAQ,MAAM,MAAM,KAAK,CAAC;AAAA,QACjD,WAAW,MAAM,SAAS,YAAY;AACpC,eAAK;AACL,eAAK,QAAQ;AAAA,YACX,MAAM;AAAA,YACN,MAAO,MAAM,QAAmB;AAAA,YAChC,OAAO,MAAM;AAAA,UACf,CAAC;AAAA,QACH,WAAW,MAAM,SAAS,eAAe;AACvC,eAAK,QAAQ;AAAA,YACX,MAAM;AAAA,YACN,MAAO,MAAM,QAAmB;AAAA,YAChC,QAAQ,MAAM,WAAW,MAAM;AAAA,UACjC,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,WAAW,IAAI,SAAS,UAAU;AAChC,YAAM,QAAQ,IAAI;AAClB,YAAM,QAAQ,OAAO,gBAAgB;AACrC,YAAM,SAAS,OAAO,iBAAiB;AACvC,WAAK,eAAe;AACpB,WAAK,gBAAgB;AACrB,UAAI,SAAS,QAAQ;AACnB,aAAK,QAAQ,EAAE,MAAM,SAAS,aAAa,OAAO,cAAc,OAAO,CAAC;AAAA,MAC1E;AACA,UAAI,OAAO,IAAI,WAAW,UAAU;AAClC,aAAK,aAAa,IAAI;AACtB,aAAK,QAAQ,EAAE,MAAM,QAAQ,QAAQ,IAAI,OAAO,CAAC;AAAA,MACnD;AAAA,IACF;AAAA,EACF;AACF;;;AD3FA,IAAM,oBAAoB,KAAK,KAAK;AAE7B,IAAM,iBAAN,MAA8C;AAAA,EAC1C,KAAK;AAAA,EACL,cAAc;AAAA,EAEvB,eAAqC;AACnC,WAAO;AAAA,MACL,uBAAuB;AAAA,MACvB,sBAAsB;AAAA,MACtB,8BAA8B;AAAA,MAC9B,kBAAkB;AAAA,MAClB,4BAA4B;AAAA,MAC5B,2BAA2B;AAAA,MAC3B,sBAAsB;AAAA,MACtB,sBAAsB;AAAA,MACtB,iBAAiB;AAAA,QACf,EAAE,IAAI,6BAA6B,aAAa,aAAa,MAAM,OAAO;AAAA,QAC1E,EAAE,IAAI,qBAAqB,aAAa,cAAc,MAAM,UAAU;AAAA,QACtE,EAAE,IAAI,mBAAmB,aAAa,YAAY,MAAM,WAAW;AAAA,MACrE;AAAA,MACA,cAAc;AAAA,IAChB;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,SAA6C;AACxD,UAAM,OAAO,KAAK,UAAU,OAAO;AACnC,UAAM,YAAY,KAAK,IAAI;AAE3B,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,YAAM,QAAsB,MAAM,UAAU,MAAM;AAAA,QAChD,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA;AAAA;AAAA,MAGhC,CAAC;AAED,YAAM,SAAS,IAAI,mBAAmB;AACtC,aAAO,UAAU,CAAC,UAAU,QAAQ,gBAAgB,KAAK;AACzD,aAAO,UAAU,CAAC,QAAQ,QAAQ,OAAO,MAAM,mBAAmB,IAAI,OAAO;AAAA,CAAI;AAEjF,UAAI,SAAS;AAEb,YAAM,QAAQ,GAAG,QAAQ,CAAC,UAAkB,OAAO,KAAK,MAAM,SAAS,CAAC,CAAC;AACzE,YAAM,QAAQ,GAAG,QAAQ,CAAC,UAAkB;AAAE,kBAAU,MAAM,SAAS;AAAA,MAAG,CAAC;AAC3E,YAAM,OAAO,IAAI;AAGjB,UAAI,QAAQ,aAAa;AACvB,gBAAQ,YAAY,iBAAiB,SAAS,MAAM;AAClD,cAAI;AAAE,kBAAM,KAAK,SAAS;AAAA,UAAG,QAAQ;AAAA,UAAqB;AAAA,QAC5D,GAAG,EAAE,MAAM,KAAK,CAAC;AAAA,MACnB;AAGA,UAAI;AACJ,YAAM,gBAAgB,WAAW,MAAM;AACrC,YAAI;AAAE,gBAAM,KAAK,SAAS;AAAA,QAAG,QAAQ;AAAA,QAAqB;AAC1D,oBAAY,WAAW,MAAM;AAC3B,cAAI;AAAE,kBAAM,KAAK,SAAS;AAAA,UAAG,QAAQ;AAAA,UAAqB;AAAA,QAC5D,GAAG,GAAI;AAAA,MACT,GAAG,iBAAiB;AAEpB,UAAI,WAAW;AACf,YAAM,SAAS,CAAC,MAAqB,aAAsB;AACzD,YAAI,SAAU;AACd,mBAAW;AACX,qBAAa,aAAa;AAC1B,YAAI,UAAW,cAAa,SAAS;AACrC,eAAO,MAAM;AAEb,cAAM,QAAQ,OAAO,SAAS;AAC9B,gBAAQ;AAAA,UACN,UAAU,QAAQ;AAAA,UAClB,QAAQ,aAAa,OAAO,cAAc,KAAK;AAAA,UAC/C,eAAe,OAAO,iBAAiB;AAAA,UACvC;AAAA,UACA,YAAY,KAAK,IAAI,IAAI;AAAA,QAC3B,CAAC;AAAA,MACH;AAEA,YAAM,GAAG,SAAS,CAAC,SAAS,OAAO,IAAI,CAAC;AACxC,YAAM,GAAG,SAAS,CAAC,QAAQ,OAAO,GAAG,IAAI,OAAO,CAAC;AAAA,IACnD,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,YAAsC;AAC1C,QAAI;AAEF,YAAM,cAAc,MAAM,OAAO,OAAO;AACxC,YAAM,SAAS,YAAY,SAAS,QAAQ,YAAY;AACxD,aAAO,QAAQ;AACf,aAAO,EAAE,WAAW,MAAM,eAAe,KAAK;AAAA,IAChD,QAAQ;AACN,aAAO,EAAE,WAAW,OAAO,eAAe,OAAO,OAAO,kCAAkC;AAAA,IAC5F;AAAA,EACF;AAAA,EAEQ,UAAU,SAAiC;AACjD,UAAM,OAAiB;AAAA,MACrB;AAAA,MAAM,QAAQ;AAAA,MACd;AAAA,MACA;AAAA,MAAmB;AAAA,MACnB;AAAA,IACF;AAGA,QAAI,QAAQ,aAAa,QAAQ,kBAAkB,qBAAqB,OAAO;AAC7E,YAAM,aAAa,KAAK,mBAAmB,QAAQ,SAAS;AAC5D,WAAK,KAAK,gBAAgB,UAAU;AAAA,IACtC;AAEA,QAAI,QAAQ,MAAO,MAAK,KAAK,WAAW,QAAQ,KAAK;AAErD,QAAI,QAAQ,UAAU;AACpB,WAAK,KAAK,eAAe,OAAO,QAAQ,QAAQ,CAAC;AAAA,IACnD;AAEA,QAAI,QAAQ,kBAAkB;AAC5B,WAAK,KAAK,cAAc,QAAQ,gBAAgB;AAAA,IAClD;AAGA,QAAI,QAAQ,kBAAkB;AAC5B,YAAM,KAAK,QAAQ;AACnB,UAAI,GAAG,UAAU,OAAW,MAAK,KAAK,WAAW,GAAG,KAAK;AACzD,UAAI,GAAG,cAAc,OAAQ,MAAK,KAAK,kBAAkB,GAAG,GAAG,YAAY;AAC3E,UAAI,GAAG,iBAAiB,OAAQ,MAAK,KAAK,qBAAqB,GAAG,GAAG,eAAe;AAAA,IACtF;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,mBAAmB,WAA8C;AACvE,QAAI,CAAC,UAAW,QAAO;AACvB,UAAM,SAAS,EAAE,YAAY,UAAU,QAAQ;AAC/C,UAAM,MAAMC,MAAKC,SAAQ,GAAG,YAAY,KAAK;AAC7C,IAAAC,WAAU,KAAK,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAC/C,UAAM,WAAWF,MAAK,KAAK,cAAc,KAAK,IAAI,CAAC,OAAO;AAC1D,IAAAG,eAAc,UAAU,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG,EAAE,MAAM,IAAM,CAAC;AACxE,WAAO;AAAA,EACT;AACF;","names":["writeFileSync","mkdirSync","join","homedir","join","homedir","mkdirSync","writeFileSync"]}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
RalphLoop,
|
|
4
4
|
cleanupMcpConfig,
|
|
5
5
|
generateMcpConfig
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-KFPZHDJ2.js";
|
|
7
7
|
import "./chunk-ZTQJMXJM.js";
|
|
8
8
|
|
|
9
9
|
// src/commands/cron.ts
|
|
@@ -101,4 +101,4 @@ async function runCron(client, args) {
|
|
|
101
101
|
export {
|
|
102
102
|
runCron
|
|
103
103
|
};
|
|
104
|
-
//# sourceMappingURL=cron-
|
|
104
|
+
//# sourceMappingURL=cron-Y25NNMOB.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-Q254R5HA.js");
|
|
35
35
|
await stopPipeline(args.slice(1));
|
|
36
36
|
} else {
|
|
37
|
-
const { runPipeline } = await import("./pipeline-
|
|
37
|
+
const { runPipeline } = await import("./pipeline-Q254R5HA.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-Y25NNMOB.js");
|
|
44
44
|
await runCron(client, args);
|
|
45
45
|
break;
|
|
46
46
|
}
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
generateMcpConfig,
|
|
13
13
|
parseJsonFromLlmOutput,
|
|
14
14
|
parseStuckDetectionResponse
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-KFPZHDJ2.js";
|
|
16
16
|
import {
|
|
17
17
|
LoopCheckpointSchema,
|
|
18
18
|
VerdictSchema,
|
|
@@ -41,11 +41,33 @@ function resolveToolRestrictions(builtinTools, allowedTools, disallowedTools) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
// src/lib/worktree.ts
|
|
44
|
-
import { execFile as defaultExecFile } from "child_process";
|
|
44
|
+
import { execFile as defaultExecFile, execFileSync } from "child_process";
|
|
45
45
|
function generateWorktreeName(ticketNumber, columnName) {
|
|
46
46
|
const slug = columnName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
47
47
|
return `kantban-${ticketNumber}-${slug}`;
|
|
48
48
|
}
|
|
49
|
+
function ensureWorktreeRemote(worktreePath) {
|
|
50
|
+
try {
|
|
51
|
+
const remotes = execFileSync("git", ["-C", worktreePath, "remote"], {
|
|
52
|
+
stdio: "pipe",
|
|
53
|
+
encoding: "utf-8"
|
|
54
|
+
}).trim();
|
|
55
|
+
if (remotes.split("\n").includes("origin")) return;
|
|
56
|
+
const originUrl = execFileSync("git", ["remote", "get-url", "origin"], {
|
|
57
|
+
stdio: "pipe",
|
|
58
|
+
encoding: "utf-8"
|
|
59
|
+
}).trim();
|
|
60
|
+
if (originUrl) {
|
|
61
|
+
execFileSync("git", ["-C", worktreePath, "remote", "add", "origin", originUrl], {
|
|
62
|
+
stdio: "pipe"
|
|
63
|
+
});
|
|
64
|
+
console.error(`[worktree] Added missing origin remote to ${worktreePath}: ${originUrl}`);
|
|
65
|
+
}
|
|
66
|
+
} catch (err) {
|
|
67
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
68
|
+
console.error(`[worktree] Failed to ensure remote for ${worktreePath}: ${msg}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
49
71
|
async function cleanupWorktree(worktreeName, exec = defaultExecFile) {
|
|
50
72
|
try {
|
|
51
73
|
const path = await findWorktreeForBranch(exec, worktreeName);
|
|
@@ -1269,6 +1291,10 @@ var PipelineOrchestrator = class {
|
|
|
1269
1291
|
ticketId,
|
|
1270
1292
|
columnId
|
|
1271
1293
|
});
|
|
1294
|
+
if (response.action === "RELAX_WITH_DEBT" && result.reason === "max_iterations") {
|
|
1295
|
+
console.error(` [advisor] Overriding RELAX_WITH_DEBT \u2192 ESCALATE for ${ticketId} (max_iterations \u2014 work incomplete)`);
|
|
1296
|
+
response = { ...response, action: "ESCALATE", reason: `${response.reason} [overridden: max_iterations cannot auto-advance]` };
|
|
1297
|
+
}
|
|
1272
1298
|
switch (response.action) {
|
|
1273
1299
|
case "RETRY_WITH_FEEDBACK": {
|
|
1274
1300
|
if (response.feedback) {
|
|
@@ -2219,6 +2245,9 @@ var PipelineWsClient = class _PipelineWsClient {
|
|
|
2219
2245
|
client;
|
|
2220
2246
|
options;
|
|
2221
2247
|
pingTimer = null;
|
|
2248
|
+
reconnectTimer = null;
|
|
2249
|
+
reconnectAttempt = 0;
|
|
2250
|
+
lastPong = 0;
|
|
2222
2251
|
stopped = false;
|
|
2223
2252
|
sendBuffer = [];
|
|
2224
2253
|
static CRITICAL_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -2230,6 +2259,9 @@ var PipelineWsClient = class _PipelineWsClient {
|
|
|
2230
2259
|
static BUFFER_MAX = 100;
|
|
2231
2260
|
static BUFFER_TTL_MS = 3e4;
|
|
2232
2261
|
// 30 seconds
|
|
2262
|
+
static PING_INTERVAL_MS = 3e4;
|
|
2263
|
+
static PONG_TIMEOUT_MS = 45e3;
|
|
2264
|
+
// dead if no pong in 45s
|
|
2233
2265
|
constructor(client, options) {
|
|
2234
2266
|
this.client = client;
|
|
2235
2267
|
this.options = options;
|
|
@@ -2247,7 +2279,7 @@ var PipelineWsClient = class _PipelineWsClient {
|
|
|
2247
2279
|
}
|
|
2248
2280
|
this.ws = null;
|
|
2249
2281
|
}
|
|
2250
|
-
this.
|
|
2282
|
+
this.cleanupTimers();
|
|
2251
2283
|
const { ticket } = await this.client.post("/ws-ticket");
|
|
2252
2284
|
const wsUrl = this.client.baseUrl.replace(/^http/, "ws") + `/ws?ticket=${ticket}&clientType=cli`;
|
|
2253
2285
|
return new Promise((resolve, reject) => {
|
|
@@ -2255,11 +2287,23 @@ var PipelineWsClient = class _PipelineWsClient {
|
|
|
2255
2287
|
this.ws = new WebSocket(wsUrl);
|
|
2256
2288
|
let subscribed = false;
|
|
2257
2289
|
this.ws.on("open", () => {
|
|
2258
|
-
this.
|
|
2290
|
+
this.lastPong = Date.now();
|
|
2291
|
+
this.reconnectAttempt = 0;
|
|
2292
|
+
this.pingTimer = setInterval(() => {
|
|
2293
|
+
if (Date.now() - this.lastPong > _PipelineWsClient.PONG_TIMEOUT_MS) {
|
|
2294
|
+
console.warn("[WS] No pong in 45s \u2014 connection dead, forcing reconnect");
|
|
2295
|
+
this.ws?.close();
|
|
2296
|
+
return;
|
|
2297
|
+
}
|
|
2298
|
+
this.send({ type: "ping", payload: {} });
|
|
2299
|
+
}, _PipelineWsClient.PING_INTERVAL_MS);
|
|
2259
2300
|
});
|
|
2260
2301
|
this.ws.on("message", (data) => {
|
|
2261
2302
|
try {
|
|
2262
2303
|
const event = JSON.parse(data.toString());
|
|
2304
|
+
if (event.type === "pong") {
|
|
2305
|
+
this.lastPong = Date.now();
|
|
2306
|
+
}
|
|
2263
2307
|
if (!subscribed) {
|
|
2264
2308
|
subscribed = true;
|
|
2265
2309
|
this.send({ type: "board:subscribe", payload: { boardId: this.options.boardId, projectId: this.options.projectId } });
|
|
@@ -2276,11 +2320,13 @@ var PipelineWsClient = class _PipelineWsClient {
|
|
|
2276
2320
|
}
|
|
2277
2321
|
});
|
|
2278
2322
|
this.ws.on("close", (code) => {
|
|
2279
|
-
this.
|
|
2323
|
+
this.cleanupTimers();
|
|
2280
2324
|
this.options.onDisconnect();
|
|
2281
2325
|
if (!resolved) {
|
|
2282
2326
|
resolved = true;
|
|
2283
2327
|
reject(new Error(`WebSocket closed before handshake (code ${code})`));
|
|
2328
|
+
} else {
|
|
2329
|
+
this.scheduleReconnect();
|
|
2284
2330
|
}
|
|
2285
2331
|
});
|
|
2286
2332
|
this.ws.on("error", (err) => {
|
|
@@ -2307,7 +2353,7 @@ var PipelineWsClient = class _PipelineWsClient {
|
|
|
2307
2353
|
stop() {
|
|
2308
2354
|
this.sendBuffer = [];
|
|
2309
2355
|
this.stopped = true;
|
|
2310
|
-
this.
|
|
2356
|
+
this.cleanupTimers();
|
|
2311
2357
|
if (this.ws) {
|
|
2312
2358
|
try {
|
|
2313
2359
|
this.ws.removeAllListeners();
|
|
@@ -2345,11 +2391,32 @@ var PipelineWsClient = class _PipelineWsClient {
|
|
|
2345
2391
|
}
|
|
2346
2392
|
}
|
|
2347
2393
|
}
|
|
2348
|
-
|
|
2394
|
+
scheduleReconnect() {
|
|
2395
|
+
if (this.stopped || this.reconnectTimer) return;
|
|
2396
|
+
const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempt), 3e4);
|
|
2397
|
+
this.reconnectAttempt++;
|
|
2398
|
+
console.log(`[WS] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempt})`);
|
|
2399
|
+
this.reconnectTimer = setTimeout(() => {
|
|
2400
|
+
this.reconnectTimer = null;
|
|
2401
|
+
if (this.stopped) return;
|
|
2402
|
+
this.connect().then(() => {
|
|
2403
|
+
console.log("[WS] Reconnected successfully");
|
|
2404
|
+
}).catch((err) => {
|
|
2405
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2406
|
+
console.warn(`[WS] Reconnect failed: ${msg}`);
|
|
2407
|
+
this.scheduleReconnect();
|
|
2408
|
+
});
|
|
2409
|
+
}, delay);
|
|
2410
|
+
}
|
|
2411
|
+
cleanupTimers() {
|
|
2349
2412
|
if (this.pingTimer) {
|
|
2350
2413
|
clearInterval(this.pingTimer);
|
|
2351
2414
|
this.pingTimer = null;
|
|
2352
2415
|
}
|
|
2416
|
+
if (this.reconnectTimer) {
|
|
2417
|
+
clearTimeout(this.reconnectTimer);
|
|
2418
|
+
this.reconnectTimer = null;
|
|
2419
|
+
}
|
|
2353
2420
|
}
|
|
2354
2421
|
};
|
|
2355
2422
|
|
|
@@ -2599,7 +2666,9 @@ ${input.failurePatterns}`);
|
|
|
2599
2666
|
| All gates pass but agent didn't move | RETRY_WITH_FEEDBACK ("Gates pass \u2014 call move_ticket") |
|
|
2600
2667
|
| Gates regressing across iterations | SPLIT_TICKET |
|
|
2601
2668
|
| Zero gates pass after max iterations | ESCALATE |
|
|
2602
|
-
| Most gates pass, one stubborn failure | RELAX_WITH_DEBT
|
|
2669
|
+
| Most gates pass, one stubborn failure | RELAX_WITH_DEBT |
|
|
2670
|
+
|
|
2671
|
+
**IMPORTANT**: RELAX_WITH_DEBT is NOT allowed when the exit reason is \`max_iterations\`. If the agent exhausted its iteration limit without moving the ticket, the work is incomplete \u2014 do NOT auto-advance. Use ESCALATE or RETRY instead.`);
|
|
2603
2672
|
const escalationSection = input.escalationModels.length > 0 ? `Available escalation models: ${input.escalationModels.join(", ")}` : `Available escalation models: none \u2014 RETRY_DIFFERENT_MODEL is unavailable`;
|
|
2604
2673
|
const budgetNote = input.remainingBudget <= 1 ? `
|
|
2605
2674
|
This is your final invocation (remaining budget: ${String(input.remainingBudget)}). Prefer RELAX_WITH_DEBT or ESCALATE.` : `Remaining advisor budget: ${String(input.remainingBudget)}`;
|
|
@@ -3052,7 +3121,7 @@ var ProviderRegistry = class {
|
|
|
3052
3121
|
};
|
|
3053
3122
|
|
|
3054
3123
|
// src/providers/codex-provider.ts
|
|
3055
|
-
import { spawn as spawn2, execFileSync } from "child_process";
|
|
3124
|
+
import { spawn as spawn2, execFileSync as execFileSync2 } from "child_process";
|
|
3056
3125
|
import { existsSync } from "fs";
|
|
3057
3126
|
|
|
3058
3127
|
// src/providers/codex-jsonl-parser.ts
|
|
@@ -3192,12 +3261,12 @@ var CodexProvider = class {
|
|
|
3192
3261
|
if (request.workingDirectory) {
|
|
3193
3262
|
if (!existsSync(request.workingDirectory)) {
|
|
3194
3263
|
try {
|
|
3195
|
-
|
|
3264
|
+
execFileSync2("git", ["worktree", "add", "-b", request.workingDirectory, request.workingDirectory, "HEAD"], {
|
|
3196
3265
|
stdio: "pipe"
|
|
3197
3266
|
});
|
|
3198
3267
|
} catch {
|
|
3199
3268
|
try {
|
|
3200
|
-
|
|
3269
|
+
execFileSync2("git", ["worktree", "add", request.workingDirectory, request.workingDirectory], {
|
|
3201
3270
|
stdio: "pipe"
|
|
3202
3271
|
});
|
|
3203
3272
|
} catch {
|
|
@@ -3206,18 +3275,18 @@ var CodexProvider = class {
|
|
|
3206
3275
|
}
|
|
3207
3276
|
} else {
|
|
3208
3277
|
try {
|
|
3209
|
-
|
|
3278
|
+
execFileSync2("git", ["-C", request.workingDirectory, "rev-parse", "--git-dir"], {
|
|
3210
3279
|
stdio: "pipe"
|
|
3211
3280
|
});
|
|
3212
3281
|
} catch {
|
|
3213
3282
|
try {
|
|
3214
|
-
|
|
3215
|
-
|
|
3283
|
+
execFileSync2("rm", ["-rf", request.workingDirectory], { stdio: "pipe" });
|
|
3284
|
+
execFileSync2("git", ["worktree", "add", "-b", request.workingDirectory, request.workingDirectory, "HEAD"], {
|
|
3216
3285
|
stdio: "pipe"
|
|
3217
3286
|
});
|
|
3218
3287
|
} catch {
|
|
3219
3288
|
try {
|
|
3220
|
-
|
|
3289
|
+
execFileSync2("git", ["worktree", "add", request.workingDirectory, request.workingDirectory], {
|
|
3221
3290
|
stdio: "pipe"
|
|
3222
3291
|
});
|
|
3223
3292
|
} catch {
|
|
@@ -3226,6 +3295,9 @@ var CodexProvider = class {
|
|
|
3226
3295
|
}
|
|
3227
3296
|
}
|
|
3228
3297
|
}
|
|
3298
|
+
if (!degraded.includes("worktreeCreation") && existsSync(request.workingDirectory)) {
|
|
3299
|
+
ensureWorktreeRemote(request.workingDirectory);
|
|
3300
|
+
}
|
|
3229
3301
|
}
|
|
3230
3302
|
return new Promise((resolve) => {
|
|
3231
3303
|
const child = spawn2("codex", args, {
|
|
@@ -4288,4 +4360,4 @@ export {
|
|
|
4288
4360
|
runPipeline,
|
|
4289
4361
|
stopPipeline
|
|
4290
4362
|
};
|
|
4291
|
-
//# sourceMappingURL=pipeline-
|
|
4363
|
+
//# sourceMappingURL=pipeline-Q254R5HA.js.map
|