@promptprojectmanager/mcp-server 4.6.3 → 4.6.4

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.
@@ -137,19 +137,20 @@ async function claimTicket(client, config2, ticketSlug) {
137
137
  projectSlug: config2.projectSlug,
138
138
  ticketSlug
139
139
  });
140
- if (!result.success || !result.ticket) {
140
+ if (!result) {
141
141
  return {
142
142
  success: false,
143
- error: result.error ?? "Failed to claim ticket"
143
+ error: "Ticket not found or not claimable"
144
144
  };
145
145
  }
146
146
  return {
147
147
  success: true,
148
148
  ticket: {
149
- slug: result.ticket.slug,
150
- ticketNumber: result.ticket.ticketNumber,
151
- content: result.ticket.content,
152
- flattenedContent: result.ticket.flattenedContent
149
+ slug: result.slug,
150
+ ticketNumber: result.ticketNumber,
151
+ content: result.content,
152
+ flattenedContent: result.content
153
+ // workMcpTicket returns flattenedContent as 'content'
153
154
  }
154
155
  };
155
156
  } catch (error) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/watcher/watcher_daemon.ts","../../src/watcher/watcher_spawn.ts","../../src/watcher/watcher_poll.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * Watcher Daemon - Background Process for YOLO Ticket Execution\n *\n * This is the standalone daemon entry point that runs as a detached\n * background process. It polls for YOLO tickets and spawns headless\n * Claude CLI processes to execute them.\n *\n * Usage (spawned by controller):\n * node watcher_daemon.js --config <base64-encoded-config>\n *\n * State files in .ppm/yolo/:\n * - watcher.pid: Daemon's process ID\n * - status.json: Current status (for MCP status tool)\n * - watcher.log: Daemon activity log\n * - logs/{ticket}.log: Individual ticket execution logs\n */\n\nimport { ConvexHttpClient } from \"convex/browser\";\nimport { appendFileSync, readFileSync } from \"fs\";\nimport type {\n WatcherConfig,\n WatcherStatus,\n WatcherTicket,\n TrackedChildProcess,\n CompletedTicket,\n} from \"./watcher_types.js\";\nimport {\n writePid,\n writeStatus,\n readStatus,\n getLogFile,\n addCompletedTicket,\n readTicketLog,\n} from \"./watcher_state.js\";\nimport { spawnClaudeWithHandle, extractSummaryFromLog } from \"./watcher_spawn.js\";\nimport { executePollCycle } from \"./watcher_poll.js\";\n\n// ============================================================================\n// Global State\n// ============================================================================\n\n/** Tracked child processes by PID */\nconst childProcesses = new Map<number, TrackedChildProcess>();\n\n/** Polling interval handle */\nlet pollInterval: ReturnType<typeof setInterval> | null = null;\n\n/** Daemon configuration */\nlet config: WatcherConfig;\n\n/** Convex client */\nlet convexClient: ConvexHttpClient;\n\n/** Path to daemon log file */\nlet daemonLogFile: string;\n\n// ============================================================================\n// Logging\n// ============================================================================\n\n/**\n * Log a message to the daemon log file and console.\n */\nfunction log(message: string): void {\n const timestamp = new Date().toISOString();\n const line = `[${timestamp}] ${message}\\n`;\n\n // Write to log file\n try {\n appendFileSync(daemonLogFile, line);\n } catch {\n // Ignore log write errors\n }\n\n // Also write to console (will go to /dev/null when detached)\n console.log(message);\n}\n\n// ============================================================================\n// Child Process Management\n// ============================================================================\n\n/**\n * Spawn a Claude CLI process to execute a ticket.\n *\n * @param ticket - Ticket to execute\n */\nfunction spawnTicketExecution(ticket: WatcherTicket): void {\n log(`Spawning execution for ticket: ${ticket.slug}`);\n\n const spawnResult = spawnClaudeWithHandle({\n ticket,\n workingDirectory: config.workingDirectory,\n projectSlug: config.projectSlug,\n timeout: config.ticketTimeout,\n });\n\n if (!spawnResult.success || !spawnResult.child) {\n log(`Failed to spawn for ${ticket.slug}: ${spawnResult.error}`);\n return;\n }\n\n const child = spawnResult.child;\n const pid = child.pid!;\n const startedAt = new Date().toISOString();\n\n // Track the child process\n const tracked: TrackedChildProcess = {\n pid,\n ticketSlug: ticket.slug,\n ticketNumber: ticket.ticketNumber,\n startedAt,\n logFile: spawnResult.logFile!,\n };\n\n // Set timeout for the execution\n if (config.ticketTimeout > 0) {\n tracked.timeout = setTimeout(() => {\n handleTimeout(pid);\n }, config.ticketTimeout);\n }\n\n childProcesses.set(pid, tracked);\n\n // Update status file\n updateExecutingStatus();\n\n // Handle child process events\n child.on(\"exit\", (code, signal) => {\n handleChildExit(pid, code, signal);\n });\n\n child.on(\"error\", (error) => {\n log(`Process error for ${ticket.slug}: ${error.message}`);\n // Will also trigger 'exit' event\n });\n\n log(`Spawned PID ${pid} for ${ticket.slug}`);\n\n // Send macOS notification if enabled\n if (config.enableNotifications) {\n sendNotification(`YOLO: Executing ${ticket.slug}`, `Started ticket execution`);\n }\n}\n\n/**\n * Handle child process exit.\n *\n * @param pid - Process ID\n * @param code - Exit code (null if killed)\n * @param signal - Signal that killed process (if any)\n */\nfunction handleChildExit(\n pid: number,\n code: number | null,\n signal: NodeJS.Signals | null\n): void {\n const tracked = childProcesses.get(pid);\n if (!tracked) {\n log(`Unknown child exited: PID ${pid}`);\n return;\n }\n\n // Clear timeout if set\n if (tracked.timeout) {\n clearTimeout(tracked.timeout);\n }\n\n // Remove from tracking\n childProcesses.delete(pid);\n\n // Calculate duration\n const startTime = new Date(tracked.startedAt).getTime();\n const durationMs = Date.now() - startTime;\n\n // Read log file and extract summary\n const logContent = readTicketLog(config.workingDirectory, tracked.ticketSlug);\n const summary = logContent ? extractSummaryFromLog(logContent) : undefined;\n\n // Determine success (exit code 0 is success)\n const success = code === 0;\n\n log(\n `Child exited: ${tracked.ticketSlug} (PID ${pid}) - ` +\n `code=${code}, signal=${signal}, duration=${Math.round(durationMs / 1000)}s`\n );\n\n // Record completion\n const completed: CompletedTicket = {\n ticketSlug: tracked.ticketSlug,\n ticketNumber: tracked.ticketNumber,\n startedAt: tracked.startedAt,\n completedAt: new Date().toISOString(),\n durationMs,\n exitCode: code,\n success,\n summary,\n logFile: tracked.logFile,\n };\n\n addCompletedTicket(config.workingDirectory, completed);\n\n // Update status\n updateExecutingStatus();\n\n // Send notification\n if (config.enableNotifications) {\n const emoji = success ? \"✅\" : \"❌\";\n sendNotification(\n `YOLO: ${emoji} ${tracked.ticketSlug}`,\n success ? \"Ticket completed\" : `Exited with code ${code}`\n );\n }\n}\n\n/**\n * Handle execution timeout.\n *\n * @param pid - Process ID to kill\n */\nfunction handleTimeout(pid: number): void {\n const tracked = childProcesses.get(pid);\n if (!tracked) return;\n\n log(`Timeout for ${tracked.ticketSlug} (PID ${pid}) - killing process`);\n\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n // Process may have already exited\n }\n\n // Note: handleChildExit will be called when process actually exits\n}\n\n/**\n * Update the currently executing list in status file.\n */\nfunction updateExecutingStatus(): void {\n const status = readStatus(config.workingDirectory);\n if (!status) return;\n\n const currentlyExecuting = Array.from(childProcesses.values()).map((p) => ({\n ticketSlug: p.ticketSlug,\n ticketNumber: p.ticketNumber,\n startedAt: p.startedAt,\n pid: p.pid,\n logFile: p.logFile,\n }));\n\n writeStatus(config.workingDirectory, {\n ...status,\n currentlyExecuting,\n lastPollAt: new Date().toISOString(),\n });\n}\n\n// ============================================================================\n// Notifications\n// ============================================================================\n\n/**\n * Send a macOS notification using osascript.\n * Silently fails on non-macOS platforms.\n *\n * @param title - Notification title\n * @param message - Notification message\n */\nfunction sendNotification(title: string, message: string): void {\n if (process.platform !== \"darwin\") return;\n\n try {\n const { execSync } = require(\"child_process\");\n const script = `display notification \"${message}\" with title \"${title}\"`;\n execSync(`osascript -e '${script}'`, { stdio: \"ignore\" });\n } catch {\n // Ignore notification errors\n }\n}\n\n// ============================================================================\n// Polling Loop\n// ============================================================================\n\n/**\n * Execute a single poll cycle.\n */\nasync function poll(): Promise<void> {\n try {\n await executePollCycle({\n client: convexClient,\n config,\n onSpawnTicket: spawnTicketExecution,\n onLog: log,\n });\n } catch (error) {\n const msg = error instanceof Error ? error.message : \"Unknown error\";\n log(`Poll error: ${msg}`);\n }\n}\n\n/**\n * Start the polling loop.\n */\nfunction startPolling(): void {\n log(`Starting poll loop (interval: ${config.pollIntervalMs}ms)`);\n\n // Run first poll immediately\n poll();\n\n // Then poll at interval\n pollInterval = setInterval(() => {\n poll();\n }, config.pollIntervalMs);\n}\n\n/**\n * Stop the polling loop.\n */\nfunction stopPolling(): void {\n if (pollInterval) {\n clearInterval(pollInterval);\n pollInterval = null;\n log(\"Stopped polling\");\n }\n}\n\n// ============================================================================\n// Shutdown\n// ============================================================================\n\n/**\n * Graceful shutdown handler.\n */\nfunction shutdown(signal: string): void {\n log(`Received ${signal} - shutting down`);\n\n // Stop polling\n stopPolling();\n\n // Kill any running child processes\n for (const [pid, tracked] of childProcesses) {\n log(`Killing child process: ${tracked.ticketSlug} (PID ${pid})`);\n if (tracked.timeout) clearTimeout(tracked.timeout);\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n // Already exited\n }\n }\n\n // Update status to stopped\n const status = readStatus(config.workingDirectory);\n if (status) {\n writeStatus(config.workingDirectory, {\n ...status,\n state: \"stopped\",\n currentlyExecuting: [],\n });\n }\n\n log(\"Shutdown complete\");\n process.exit(0);\n}\n\n// ============================================================================\n// Main Entry Point\n// ============================================================================\n\n/**\n * Parse command line arguments and extract config.\n */\nfunction parseArgs(): WatcherConfig {\n const args = process.argv.slice(2);\n\n // Find --config argument\n const configIndex = args.indexOf(\"--config\");\n if (configIndex === -1 || !args[configIndex + 1]) {\n console.error(\"Usage: watcher_daemon --config <base64-encoded-config>\");\n process.exit(1);\n }\n\n const configB64 = args[configIndex + 1];\n\n try {\n const configJson = Buffer.from(configB64, \"base64\").toString(\"utf-8\");\n return JSON.parse(configJson) as WatcherConfig;\n } catch (error) {\n console.error(\"Failed to parse config:\", error);\n process.exit(1);\n }\n}\n\n/**\n * Main daemon entry point.\n */\nasync function main(): Promise<void> {\n // Parse configuration\n config = parseArgs();\n\n // Initialize log file\n daemonLogFile = getLogFile(config.workingDirectory);\n\n log(\"=\".repeat(50));\n log(\"YOLO Watcher Daemon Starting\");\n log(`Project: ${config.projectSlug}`);\n log(`Working Directory: ${config.workingDirectory}`);\n log(`Poll Interval: ${config.pollIntervalMs}ms`);\n log(`Max Parallel: ${config.maxParallel}`);\n log(`Ticket Timeout: ${config.ticketTimeout}ms`);\n log(\"=\".repeat(50));\n\n // Write PID file\n writePid(config.workingDirectory, process.pid);\n log(`PID: ${process.pid}`);\n\n // Initialize Convex client\n convexClient = new ConvexHttpClient(config.convexUrl);\n\n // Write initial status\n const initialStatus: WatcherStatus = {\n state: \"running\",\n pid: process.pid,\n projectSlug: config.projectSlug,\n startedAt: new Date().toISOString(),\n ticketsProcessed: 0,\n currentlyExecuting: [],\n config: {\n projectSlug: config.projectSlug,\n convexUrl: config.convexUrl,\n pollIntervalMs: config.pollIntervalMs,\n maxParallel: config.maxParallel,\n ticketTimeout: config.ticketTimeout,\n enableNotifications: config.enableNotifications,\n workingDirectory: config.workingDirectory,\n },\n };\n writeStatus(config.workingDirectory, initialStatus);\n\n // Set up signal handlers\n process.on(\"SIGTERM\", () => shutdown(\"SIGTERM\"));\n process.on(\"SIGINT\", () => shutdown(\"SIGINT\"));\n\n // Send startup notification\n if (config.enableNotifications) {\n sendNotification(\"YOLO Watcher Started\", `Watching ${config.projectSlug}`);\n }\n\n // Start polling\n startPolling();\n}\n\n// Run main\nmain().catch((error) => {\n console.error(\"Daemon error:\", error);\n process.exit(1);\n});\n","/**\n * Watcher Spawn - Headless Claude CLI Spawner\n *\n * Spawns `claude` CLI processes in headless mode for ticket execution.\n * Output is captured to log files for later review.\n *\n * Key features:\n * - Cross-platform (no Terminal.app/AppleScript dependency)\n * - Output captured to .ppm/yolo/logs/{ticket}.log\n * - Child processes tracked by daemon for completion handling\n */\n\nimport { spawn, type ChildProcess } from \"child_process\";\nimport { openSync, closeSync, appendFileSync } from \"fs\";\nimport type { SpawnResult, WatcherTicket } from \"./watcher_types.js\";\nimport { getTicketLogFile } from \"./watcher_state.js\";\n\n// ============================================================================\n// Claude CLI Spawning\n// ============================================================================\n\n/**\n * Options for spawning a Claude CLI process\n */\nexport interface SpawnClaudeOptions {\n /** Ticket being executed */\n ticket: WatcherTicket;\n\n /** Working directory for Claude session */\n workingDirectory: string;\n\n /** Project slug for context */\n projectSlug: string;\n\n /** Optional timeout in ms (default: 30 minutes) */\n timeout?: number;\n}\n\n/**\n * Spawn a headless Claude CLI process to execute a ticket.\n *\n * The process runs with --dangerously-skip-permissions for autonomous execution.\n * All output (stdout + stderr) is captured to a log file.\n *\n * @param options - Spawn configuration\n * @returns Spawn result with PID and log file path, or error\n */\nexport function spawnClaudeCli(options: SpawnClaudeOptions): SpawnResult {\n const { ticket, workingDirectory, projectSlug } = options;\n\n // Build the prompt for Claude\n const prompt = buildTicketPrompt(ticket, projectSlug);\n\n // Get log file path\n const logFile = getTicketLogFile(workingDirectory, ticket.slug);\n\n try {\n // Open log file for writing (creates if doesn't exist)\n const logFd = openSync(logFile, \"w\");\n\n // Write header to log file\n const header = `=== YOLO Ticket Execution ===\nTicket: ${ticket.slug}${ticket.ticketNumber ? ` (#${ticket.ticketNumber})` : \"\"}\nProject: ${projectSlug}\nStarted: ${new Date().toISOString()}\nWorking Directory: ${workingDirectory}\n${\"=\".repeat(50)}\n\n`;\n appendFileSync(logFile, header);\n\n // Spawn claude CLI in headless mode\n // Note: We use 'claude' command assuming it's in PATH\n const child = spawn(\"claude\", [\"-p\", prompt, \"--dangerously-skip-permissions\"], {\n cwd: workingDirectory,\n stdio: [\"ignore\", logFd, logFd], // stdin: ignore, stdout+stderr: log file\n detached: false, // NOT detached - daemon tracks it\n env: {\n ...process.env,\n // Ensure Claude doesn't try to use a TTY\n TERM: \"dumb\",\n // Disable color output for cleaner logs\n NO_COLOR: \"1\",\n FORCE_COLOR: \"0\",\n },\n });\n\n // Close the file descriptor in the parent process\n // The child process has its own reference to the file\n closeSync(logFd);\n\n if (!child.pid) {\n return {\n success: false,\n error: \"Failed to spawn claude process - no PID returned\",\n };\n }\n\n return {\n success: true,\n pid: child.pid,\n logFile,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n return {\n success: false,\n error: `Failed to spawn claude: ${errorMessage}`,\n };\n }\n}\n\n/**\n * Spawn claude and return the ChildProcess for event handling.\n *\n * This version returns the child process directly so the daemon can\n * attach event handlers for exit, error, etc.\n *\n * @param options - Spawn configuration\n * @returns Object with child process and log file, or error\n */\nexport function spawnClaudeWithHandle(options: SpawnClaudeOptions): {\n success: boolean;\n child?: ChildProcess;\n logFile?: string;\n error?: string;\n} {\n const { ticket, workingDirectory, projectSlug } = options;\n\n // Build the prompt for Claude\n const prompt = buildTicketPrompt(ticket, projectSlug);\n\n // Get log file path\n const logFile = getTicketLogFile(workingDirectory, ticket.slug);\n\n try {\n // Open log file for writing\n const logFd = openSync(logFile, \"w\");\n\n // Write header\n const header = `=== YOLO Ticket Execution ===\nTicket: ${ticket.slug}${ticket.ticketNumber ? ` (#${ticket.ticketNumber})` : \"\"}\nProject: ${projectSlug}\nStarted: ${new Date().toISOString()}\nWorking Directory: ${workingDirectory}\n${\"=\".repeat(50)}\n\n`;\n appendFileSync(logFile, header);\n\n // Spawn claude CLI\n const child = spawn(\"claude\", [\"-p\", prompt, \"--dangerously-skip-permissions\"], {\n cwd: workingDirectory,\n stdio: [\"ignore\", logFd, logFd],\n detached: false,\n env: {\n ...process.env,\n TERM: \"dumb\",\n NO_COLOR: \"1\",\n FORCE_COLOR: \"0\",\n },\n });\n\n closeSync(logFd);\n\n if (!child.pid) {\n return {\n success: false,\n error: \"Failed to spawn claude process - no PID returned\",\n };\n }\n\n return {\n success: true,\n child,\n logFile,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n return {\n success: false,\n error: `Failed to spawn claude: ${errorMessage}`,\n };\n }\n}\n\n// ============================================================================\n// Prompt Building\n// ============================================================================\n\n/**\n * Build the prompt that Claude will execute for the ticket.\n *\n * @param ticket - Ticket to execute\n * @param projectSlug - Project context\n * @returns Formatted prompt string\n */\nfunction buildTicketPrompt(ticket: WatcherTicket, projectSlug: string): string {\n // Use flattened content if available, otherwise raw content\n const content = ticket.flattenedContent || ticket.content;\n\n return `You are executing a YOLO ticket autonomously in project \"${projectSlug}\".\n\n## Ticket: ${ticket.slug}${ticket.ticketNumber ? ` (#${ticket.ticketNumber})` : \"\"}\n\n${content}\n\n---\n\n## Your Instructions\n\n1. Execute the task described above completely\n2. When ALL tasks are done, close the ticket using: tickets_close with ticketSlug: \"${ticket.slug}\"\n3. If you encounter blocking issues, update the ticket using: tickets_update with your findings\n4. Be thorough but efficient - this is autonomous execution\n\nMode: YOLO (autonomous execution - you have full permission to proceed)`;\n}\n\n// ============================================================================\n// Output Parsing\n// ============================================================================\n\n/**\n * Extract a brief summary from Claude's log output.\n *\n * Looks for common patterns that indicate what was accomplished.\n *\n * @param logContent - Full log file contents\n * @returns Brief summary or undefined\n */\nexport function extractSummaryFromLog(logContent: string): string | undefined {\n // Look for ticket close message\n const closeMatch = logContent.match(/tickets_close.*ticketSlug.*[\"']([^\"']+)[\"']/i);\n if (closeMatch) {\n // Try to find what was done before the close\n const lines = logContent.split(\"\\n\");\n const relevantLines = lines\n .filter((line) => {\n // Skip noise\n if (line.includes(\"===\")) return false;\n if (line.includes(\"YOLO\")) return false;\n if (line.trim().length < 10) return false;\n return true;\n })\n .slice(-5); // Last 5 meaningful lines\n\n if (relevantLines.length > 0) {\n return relevantLines[0].slice(0, 100);\n }\n }\n\n // Fallback: first non-header line\n const lines = logContent.split(\"\\n\");\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed && !trimmed.startsWith(\"===\") && trimmed.length > 20) {\n return trimmed.slice(0, 100);\n }\n }\n\n return undefined;\n}\n","/**\n * Watcher Poll - Polling Logic for YOLO Tickets\n *\n * Handles fetching YOLO-flagged tickets from Convex and claiming them\n * for execution. Used by the daemon's polling loop.\n *\n * Flow:\n * 1. Query Convex for open tickets with yolo: true\n * 2. Check capacity (currentlyExecuting.length < maxParallel)\n * 3. Claim ticket via workMcpTicket mutation\n * 4. Return ticket data for spawning\n */\n\nimport { ConvexHttpClient } from \"convex/browser\";\nimport type { WatcherConfig, WatcherTicket } from \"./watcher_types.js\";\nimport { readStatus } from \"./watcher_state.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Result from a poll operation\n */\nexport interface PollResult {\n /** Whether the poll was successful */\n success: boolean;\n\n /** Tickets available for execution */\n availableTickets: WatcherTicket[];\n\n /** How many we can actually pick up (based on capacity) */\n canPickUp: number;\n\n /** Error message if poll failed */\n error?: string;\n}\n\n/**\n * Result from claiming a ticket\n */\nexport interface ClaimResult {\n /** Whether claim was successful */\n success: boolean;\n\n /** The claimed ticket data */\n ticket?: WatcherTicket;\n\n /** Error message if claim failed */\n error?: string;\n}\n\n// ============================================================================\n// Convex Queries\n// ============================================================================\n\n/**\n * Fetch YOLO tickets from Convex.\n *\n * Queries for tickets with status=\"open\" AND yolo=true.\n *\n * @param client - Convex HTTP client\n * @param config - Watcher configuration\n * @returns Poll result with available tickets\n */\nexport async function fetchYoloTickets(\n client: ConvexHttpClient,\n config: WatcherConfig\n): Promise<PollResult> {\n try {\n // Query for YOLO tickets\n const tickets = await client.query<WatcherTicket[]>(\"mcp_tickets:listYoloTickets\", {\n projectToken: config.projectToken,\n projectSlug: config.projectSlug,\n });\n\n // Check current capacity\n const status = readStatus(config.workingDirectory);\n const currentlyExecuting = status?.currentlyExecuting?.length ?? 0;\n const canPickUp = Math.max(0, config.maxParallel - currentlyExecuting);\n\n return {\n success: true,\n availableTickets: tickets,\n canPickUp,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n return {\n success: false,\n availableTickets: [],\n canPickUp: 0,\n error: `Failed to fetch YOLO tickets: ${errorMessage}`,\n };\n }\n}\n\n/**\n * Claim a ticket for execution.\n *\n * Calls workMcpTicket mutation to transition ticket from open to working.\n * This also returns the full ticket content for execution.\n *\n * @param client - Convex HTTP client\n * @param config - Watcher configuration\n * @param ticketSlug - Ticket to claim\n * @returns Claim result with ticket data\n */\nexport async function claimTicket(\n client: ConvexHttpClient,\n config: WatcherConfig,\n ticketSlug: string\n): Promise<ClaimResult> {\n try {\n // Call workMcpTicket to claim the ticket\n const result = await client.mutation<{\n success: boolean;\n ticket?: {\n slug: string;\n ticketNumber?: number;\n content: string;\n flattenedContent: string;\n };\n error?: string;\n }>(\"mcp_tickets:workMcpTicket\", {\n projectToken: config.projectToken,\n projectSlug: config.projectSlug,\n ticketSlug,\n });\n\n if (!result.success || !result.ticket) {\n return {\n success: false,\n error: result.error ?? \"Failed to claim ticket\",\n };\n }\n\n return {\n success: true,\n ticket: {\n slug: result.ticket.slug,\n ticketNumber: result.ticket.ticketNumber,\n content: result.ticket.content,\n flattenedContent: result.ticket.flattenedContent,\n },\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n return {\n success: false,\n error: `Failed to claim ticket: ${errorMessage}`,\n };\n }\n}\n\n// ============================================================================\n// Poll and Execute Coordinator\n// ============================================================================\n\n/**\n * Options for a single poll-and-execute cycle\n */\nexport interface PollCycleOptions {\n /** Convex HTTP client */\n client: ConvexHttpClient;\n\n /** Watcher configuration */\n config: WatcherConfig;\n\n /** Callback when a ticket should be spawned */\n onSpawnTicket: (ticket: WatcherTicket) => void;\n\n /** Callback for logging */\n onLog?: (message: string) => void;\n}\n\n/**\n * Execute a single poll cycle.\n *\n * Fetches available YOLO tickets, claims as many as capacity allows,\n * and triggers spawn callback for each.\n *\n * @param options - Poll cycle options\n * @returns Number of tickets spawned\n */\nexport async function executePollCycle(options: PollCycleOptions): Promise<number> {\n const { client, config, onSpawnTicket, onLog } = options;\n const log = onLog ?? (() => {});\n\n // 1. Fetch available tickets\n log(\"[Poll] Checking for YOLO tickets...\");\n const pollResult = await fetchYoloTickets(client, config);\n\n if (!pollResult.success) {\n log(`[Poll] Error: ${pollResult.error}`);\n return 0;\n }\n\n if (pollResult.availableTickets.length === 0) {\n log(\"[Poll] No YOLO tickets pending\");\n return 0;\n }\n\n log(`[Poll] Found ${pollResult.availableTickets.length} ticket(s), capacity: ${pollResult.canPickUp}`);\n\n if (pollResult.canPickUp === 0) {\n log(\"[Poll] At capacity - waiting for current executions to complete\");\n return 0;\n }\n\n // 2. Claim and spawn tickets up to capacity\n let spawned = 0;\n const ticketsToProcess = pollResult.availableTickets.slice(0, pollResult.canPickUp);\n\n for (const pendingTicket of ticketsToProcess) {\n log(`[Poll] Claiming ticket: ${pendingTicket.slug}`);\n\n const claimResult = await claimTicket(client, config, pendingTicket.slug);\n\n if (!claimResult.success || !claimResult.ticket) {\n log(`[Poll] Failed to claim ${pendingTicket.slug}: ${claimResult.error}`);\n continue;\n }\n\n log(`[Poll] Claimed ${pendingTicket.slug} - spawning execution`);\n onSpawnTicket(claimResult.ticket);\n spawned++;\n }\n\n return spawned;\n}\n"],"mappings":"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","names":["appendFileSync","lines","config","log","appendFileSync"]}
1
+ {"version":3,"sources":["../../src/watcher/watcher_daemon.ts","../../src/watcher/watcher_spawn.ts","../../src/watcher/watcher_poll.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * Watcher Daemon - Background Process for YOLO Ticket Execution\n *\n * This is the standalone daemon entry point that runs as a detached\n * background process. It polls for YOLO tickets and spawns headless\n * Claude CLI processes to execute them.\n *\n * Usage (spawned by controller):\n * node watcher_daemon.js --config <base64-encoded-config>\n *\n * State files in .ppm/yolo/:\n * - watcher.pid: Daemon's process ID\n * - status.json: Current status (for MCP status tool)\n * - watcher.log: Daemon activity log\n * - logs/{ticket}.log: Individual ticket execution logs\n */\n\nimport { ConvexHttpClient } from \"convex/browser\";\nimport { appendFileSync, readFileSync } from \"fs\";\nimport type {\n WatcherConfig,\n WatcherStatus,\n WatcherTicket,\n TrackedChildProcess,\n CompletedTicket,\n} from \"./watcher_types.js\";\nimport {\n writePid,\n writeStatus,\n readStatus,\n getLogFile,\n addCompletedTicket,\n readTicketLog,\n} from \"./watcher_state.js\";\nimport { spawnClaudeWithHandle, extractSummaryFromLog } from \"./watcher_spawn.js\";\nimport { executePollCycle } from \"./watcher_poll.js\";\n\n// ============================================================================\n// Global State\n// ============================================================================\n\n/** Tracked child processes by PID */\nconst childProcesses = new Map<number, TrackedChildProcess>();\n\n/** Polling interval handle */\nlet pollInterval: ReturnType<typeof setInterval> | null = null;\n\n/** Daemon configuration */\nlet config: WatcherConfig;\n\n/** Convex client */\nlet convexClient: ConvexHttpClient;\n\n/** Path to daemon log file */\nlet daemonLogFile: string;\n\n// ============================================================================\n// Logging\n// ============================================================================\n\n/**\n * Log a message to the daemon log file and console.\n */\nfunction log(message: string): void {\n const timestamp = new Date().toISOString();\n const line = `[${timestamp}] ${message}\\n`;\n\n // Write to log file\n try {\n appendFileSync(daemonLogFile, line);\n } catch {\n // Ignore log write errors\n }\n\n // Also write to console (will go to /dev/null when detached)\n console.log(message);\n}\n\n// ============================================================================\n// Child Process Management\n// ============================================================================\n\n/**\n * Spawn a Claude CLI process to execute a ticket.\n *\n * @param ticket - Ticket to execute\n */\nfunction spawnTicketExecution(ticket: WatcherTicket): void {\n log(`Spawning execution for ticket: ${ticket.slug}`);\n\n const spawnResult = spawnClaudeWithHandle({\n ticket,\n workingDirectory: config.workingDirectory,\n projectSlug: config.projectSlug,\n timeout: config.ticketTimeout,\n });\n\n if (!spawnResult.success || !spawnResult.child) {\n log(`Failed to spawn for ${ticket.slug}: ${spawnResult.error}`);\n return;\n }\n\n const child = spawnResult.child;\n const pid = child.pid!;\n const startedAt = new Date().toISOString();\n\n // Track the child process\n const tracked: TrackedChildProcess = {\n pid,\n ticketSlug: ticket.slug,\n ticketNumber: ticket.ticketNumber,\n startedAt,\n logFile: spawnResult.logFile!,\n };\n\n // Set timeout for the execution\n if (config.ticketTimeout > 0) {\n tracked.timeout = setTimeout(() => {\n handleTimeout(pid);\n }, config.ticketTimeout);\n }\n\n childProcesses.set(pid, tracked);\n\n // Update status file\n updateExecutingStatus();\n\n // Handle child process events\n child.on(\"exit\", (code, signal) => {\n handleChildExit(pid, code, signal);\n });\n\n child.on(\"error\", (error) => {\n log(`Process error for ${ticket.slug}: ${error.message}`);\n // Will also trigger 'exit' event\n });\n\n log(`Spawned PID ${pid} for ${ticket.slug}`);\n\n // Send macOS notification if enabled\n if (config.enableNotifications) {\n sendNotification(`YOLO: Executing ${ticket.slug}`, `Started ticket execution`);\n }\n}\n\n/**\n * Handle child process exit.\n *\n * @param pid - Process ID\n * @param code - Exit code (null if killed)\n * @param signal - Signal that killed process (if any)\n */\nfunction handleChildExit(\n pid: number,\n code: number | null,\n signal: NodeJS.Signals | null\n): void {\n const tracked = childProcesses.get(pid);\n if (!tracked) {\n log(`Unknown child exited: PID ${pid}`);\n return;\n }\n\n // Clear timeout if set\n if (tracked.timeout) {\n clearTimeout(tracked.timeout);\n }\n\n // Remove from tracking\n childProcesses.delete(pid);\n\n // Calculate duration\n const startTime = new Date(tracked.startedAt).getTime();\n const durationMs = Date.now() - startTime;\n\n // Read log file and extract summary\n const logContent = readTicketLog(config.workingDirectory, tracked.ticketSlug);\n const summary = logContent ? extractSummaryFromLog(logContent) : undefined;\n\n // Determine success (exit code 0 is success)\n const success = code === 0;\n\n log(\n `Child exited: ${tracked.ticketSlug} (PID ${pid}) - ` +\n `code=${code}, signal=${signal}, duration=${Math.round(durationMs / 1000)}s`\n );\n\n // Record completion\n const completed: CompletedTicket = {\n ticketSlug: tracked.ticketSlug,\n ticketNumber: tracked.ticketNumber,\n startedAt: tracked.startedAt,\n completedAt: new Date().toISOString(),\n durationMs,\n exitCode: code,\n success,\n summary,\n logFile: tracked.logFile,\n };\n\n addCompletedTicket(config.workingDirectory, completed);\n\n // Update status\n updateExecutingStatus();\n\n // Send notification\n if (config.enableNotifications) {\n const emoji = success ? \"✅\" : \"❌\";\n sendNotification(\n `YOLO: ${emoji} ${tracked.ticketSlug}`,\n success ? \"Ticket completed\" : `Exited with code ${code}`\n );\n }\n}\n\n/**\n * Handle execution timeout.\n *\n * @param pid - Process ID to kill\n */\nfunction handleTimeout(pid: number): void {\n const tracked = childProcesses.get(pid);\n if (!tracked) return;\n\n log(`Timeout for ${tracked.ticketSlug} (PID ${pid}) - killing process`);\n\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n // Process may have already exited\n }\n\n // Note: handleChildExit will be called when process actually exits\n}\n\n/**\n * Update the currently executing list in status file.\n */\nfunction updateExecutingStatus(): void {\n const status = readStatus(config.workingDirectory);\n if (!status) return;\n\n const currentlyExecuting = Array.from(childProcesses.values()).map((p) => ({\n ticketSlug: p.ticketSlug,\n ticketNumber: p.ticketNumber,\n startedAt: p.startedAt,\n pid: p.pid,\n logFile: p.logFile,\n }));\n\n writeStatus(config.workingDirectory, {\n ...status,\n currentlyExecuting,\n lastPollAt: new Date().toISOString(),\n });\n}\n\n// ============================================================================\n// Notifications\n// ============================================================================\n\n/**\n * Send a macOS notification using osascript.\n * Silently fails on non-macOS platforms.\n *\n * @param title - Notification title\n * @param message - Notification message\n */\nfunction sendNotification(title: string, message: string): void {\n if (process.platform !== \"darwin\") return;\n\n try {\n const { execSync } = require(\"child_process\");\n const script = `display notification \"${message}\" with title \"${title}\"`;\n execSync(`osascript -e '${script}'`, { stdio: \"ignore\" });\n } catch {\n // Ignore notification errors\n }\n}\n\n// ============================================================================\n// Polling Loop\n// ============================================================================\n\n/**\n * Execute a single poll cycle.\n */\nasync function poll(): Promise<void> {\n try {\n await executePollCycle({\n client: convexClient,\n config,\n onSpawnTicket: spawnTicketExecution,\n onLog: log,\n });\n } catch (error) {\n const msg = error instanceof Error ? error.message : \"Unknown error\";\n log(`Poll error: ${msg}`);\n }\n}\n\n/**\n * Start the polling loop.\n */\nfunction startPolling(): void {\n log(`Starting poll loop (interval: ${config.pollIntervalMs}ms)`);\n\n // Run first poll immediately\n poll();\n\n // Then poll at interval\n pollInterval = setInterval(() => {\n poll();\n }, config.pollIntervalMs);\n}\n\n/**\n * Stop the polling loop.\n */\nfunction stopPolling(): void {\n if (pollInterval) {\n clearInterval(pollInterval);\n pollInterval = null;\n log(\"Stopped polling\");\n }\n}\n\n// ============================================================================\n// Shutdown\n// ============================================================================\n\n/**\n * Graceful shutdown handler.\n */\nfunction shutdown(signal: string): void {\n log(`Received ${signal} - shutting down`);\n\n // Stop polling\n stopPolling();\n\n // Kill any running child processes\n for (const [pid, tracked] of childProcesses) {\n log(`Killing child process: ${tracked.ticketSlug} (PID ${pid})`);\n if (tracked.timeout) clearTimeout(tracked.timeout);\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n // Already exited\n }\n }\n\n // Update status to stopped\n const status = readStatus(config.workingDirectory);\n if (status) {\n writeStatus(config.workingDirectory, {\n ...status,\n state: \"stopped\",\n currentlyExecuting: [],\n });\n }\n\n log(\"Shutdown complete\");\n process.exit(0);\n}\n\n// ============================================================================\n// Main Entry Point\n// ============================================================================\n\n/**\n * Parse command line arguments and extract config.\n */\nfunction parseArgs(): WatcherConfig {\n const args = process.argv.slice(2);\n\n // Find --config argument\n const configIndex = args.indexOf(\"--config\");\n if (configIndex === -1 || !args[configIndex + 1]) {\n console.error(\"Usage: watcher_daemon --config <base64-encoded-config>\");\n process.exit(1);\n }\n\n const configB64 = args[configIndex + 1];\n\n try {\n const configJson = Buffer.from(configB64, \"base64\").toString(\"utf-8\");\n return JSON.parse(configJson) as WatcherConfig;\n } catch (error) {\n console.error(\"Failed to parse config:\", error);\n process.exit(1);\n }\n}\n\n/**\n * Main daemon entry point.\n */\nasync function main(): Promise<void> {\n // Parse configuration\n config = parseArgs();\n\n // Initialize log file\n daemonLogFile = getLogFile(config.workingDirectory);\n\n log(\"=\".repeat(50));\n log(\"YOLO Watcher Daemon Starting\");\n log(`Project: ${config.projectSlug}`);\n log(`Working Directory: ${config.workingDirectory}`);\n log(`Poll Interval: ${config.pollIntervalMs}ms`);\n log(`Max Parallel: ${config.maxParallel}`);\n log(`Ticket Timeout: ${config.ticketTimeout}ms`);\n log(\"=\".repeat(50));\n\n // Write PID file\n writePid(config.workingDirectory, process.pid);\n log(`PID: ${process.pid}`);\n\n // Initialize Convex client\n convexClient = new ConvexHttpClient(config.convexUrl);\n\n // Write initial status\n const initialStatus: WatcherStatus = {\n state: \"running\",\n pid: process.pid,\n projectSlug: config.projectSlug,\n startedAt: new Date().toISOString(),\n ticketsProcessed: 0,\n currentlyExecuting: [],\n config: {\n projectSlug: config.projectSlug,\n convexUrl: config.convexUrl,\n pollIntervalMs: config.pollIntervalMs,\n maxParallel: config.maxParallel,\n ticketTimeout: config.ticketTimeout,\n enableNotifications: config.enableNotifications,\n workingDirectory: config.workingDirectory,\n },\n };\n writeStatus(config.workingDirectory, initialStatus);\n\n // Set up signal handlers\n process.on(\"SIGTERM\", () => shutdown(\"SIGTERM\"));\n process.on(\"SIGINT\", () => shutdown(\"SIGINT\"));\n\n // Send startup notification\n if (config.enableNotifications) {\n sendNotification(\"YOLO Watcher Started\", `Watching ${config.projectSlug}`);\n }\n\n // Start polling\n startPolling();\n}\n\n// Run main\nmain().catch((error) => {\n console.error(\"Daemon error:\", error);\n process.exit(1);\n});\n","/**\n * Watcher Spawn - Headless Claude CLI Spawner\n *\n * Spawns `claude` CLI processes in headless mode for ticket execution.\n * Output is captured to log files for later review.\n *\n * Key features:\n * - Cross-platform (no Terminal.app/AppleScript dependency)\n * - Output captured to .ppm/yolo/logs/{ticket}.log\n * - Child processes tracked by daemon for completion handling\n */\n\nimport { spawn, type ChildProcess } from \"child_process\";\nimport { openSync, closeSync, appendFileSync } from \"fs\";\nimport type { SpawnResult, WatcherTicket } from \"./watcher_types.js\";\nimport { getTicketLogFile } from \"./watcher_state.js\";\n\n// ============================================================================\n// Claude CLI Spawning\n// ============================================================================\n\n/**\n * Options for spawning a Claude CLI process\n */\nexport interface SpawnClaudeOptions {\n /** Ticket being executed */\n ticket: WatcherTicket;\n\n /** Working directory for Claude session */\n workingDirectory: string;\n\n /** Project slug for context */\n projectSlug: string;\n\n /** Optional timeout in ms (default: 30 minutes) */\n timeout?: number;\n}\n\n/**\n * Spawn a headless Claude CLI process to execute a ticket.\n *\n * The process runs with --dangerously-skip-permissions for autonomous execution.\n * All output (stdout + stderr) is captured to a log file.\n *\n * @param options - Spawn configuration\n * @returns Spawn result with PID and log file path, or error\n */\nexport function spawnClaudeCli(options: SpawnClaudeOptions): SpawnResult {\n const { ticket, workingDirectory, projectSlug } = options;\n\n // Build the prompt for Claude\n const prompt = buildTicketPrompt(ticket, projectSlug);\n\n // Get log file path\n const logFile = getTicketLogFile(workingDirectory, ticket.slug);\n\n try {\n // Open log file for writing (creates if doesn't exist)\n const logFd = openSync(logFile, \"w\");\n\n // Write header to log file\n const header = `=== YOLO Ticket Execution ===\nTicket: ${ticket.slug}${ticket.ticketNumber ? ` (#${ticket.ticketNumber})` : \"\"}\nProject: ${projectSlug}\nStarted: ${new Date().toISOString()}\nWorking Directory: ${workingDirectory}\n${\"=\".repeat(50)}\n\n`;\n appendFileSync(logFile, header);\n\n // Spawn claude CLI in headless mode\n // Note: We use 'claude' command assuming it's in PATH\n const child = spawn(\"claude\", [\"-p\", prompt, \"--dangerously-skip-permissions\"], {\n cwd: workingDirectory,\n stdio: [\"ignore\", logFd, logFd], // stdin: ignore, stdout+stderr: log file\n detached: false, // NOT detached - daemon tracks it\n env: {\n ...process.env,\n // Ensure Claude doesn't try to use a TTY\n TERM: \"dumb\",\n // Disable color output for cleaner logs\n NO_COLOR: \"1\",\n FORCE_COLOR: \"0\",\n },\n });\n\n // Close the file descriptor in the parent process\n // The child process has its own reference to the file\n closeSync(logFd);\n\n if (!child.pid) {\n return {\n success: false,\n error: \"Failed to spawn claude process - no PID returned\",\n };\n }\n\n return {\n success: true,\n pid: child.pid,\n logFile,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n return {\n success: false,\n error: `Failed to spawn claude: ${errorMessage}`,\n };\n }\n}\n\n/**\n * Spawn claude and return the ChildProcess for event handling.\n *\n * This version returns the child process directly so the daemon can\n * attach event handlers for exit, error, etc.\n *\n * @param options - Spawn configuration\n * @returns Object with child process and log file, or error\n */\nexport function spawnClaudeWithHandle(options: SpawnClaudeOptions): {\n success: boolean;\n child?: ChildProcess;\n logFile?: string;\n error?: string;\n} {\n const { ticket, workingDirectory, projectSlug } = options;\n\n // Build the prompt for Claude\n const prompt = buildTicketPrompt(ticket, projectSlug);\n\n // Get log file path\n const logFile = getTicketLogFile(workingDirectory, ticket.slug);\n\n try {\n // Open log file for writing\n const logFd = openSync(logFile, \"w\");\n\n // Write header\n const header = `=== YOLO Ticket Execution ===\nTicket: ${ticket.slug}${ticket.ticketNumber ? ` (#${ticket.ticketNumber})` : \"\"}\nProject: ${projectSlug}\nStarted: ${new Date().toISOString()}\nWorking Directory: ${workingDirectory}\n${\"=\".repeat(50)}\n\n`;\n appendFileSync(logFile, header);\n\n // Spawn claude CLI\n const child = spawn(\"claude\", [\"-p\", prompt, \"--dangerously-skip-permissions\"], {\n cwd: workingDirectory,\n stdio: [\"ignore\", logFd, logFd],\n detached: false,\n env: {\n ...process.env,\n TERM: \"dumb\",\n NO_COLOR: \"1\",\n FORCE_COLOR: \"0\",\n },\n });\n\n closeSync(logFd);\n\n if (!child.pid) {\n return {\n success: false,\n error: \"Failed to spawn claude process - no PID returned\",\n };\n }\n\n return {\n success: true,\n child,\n logFile,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n return {\n success: false,\n error: `Failed to spawn claude: ${errorMessage}`,\n };\n }\n}\n\n// ============================================================================\n// Prompt Building\n// ============================================================================\n\n/**\n * Build the prompt that Claude will execute for the ticket.\n *\n * @param ticket - Ticket to execute\n * @param projectSlug - Project context\n * @returns Formatted prompt string\n */\nfunction buildTicketPrompt(ticket: WatcherTicket, projectSlug: string): string {\n // Use flattened content if available, otherwise raw content\n const content = ticket.flattenedContent || ticket.content;\n\n return `You are executing a YOLO ticket autonomously in project \"${projectSlug}\".\n\n## Ticket: ${ticket.slug}${ticket.ticketNumber ? ` (#${ticket.ticketNumber})` : \"\"}\n\n${content}\n\n---\n\n## Your Instructions\n\n1. Execute the task described above completely\n2. When ALL tasks are done, close the ticket using: tickets_close with ticketSlug: \"${ticket.slug}\"\n3. If you encounter blocking issues, update the ticket using: tickets_update with your findings\n4. Be thorough but efficient - this is autonomous execution\n\nMode: YOLO (autonomous execution - you have full permission to proceed)`;\n}\n\n// ============================================================================\n// Output Parsing\n// ============================================================================\n\n/**\n * Extract a brief summary from Claude's log output.\n *\n * Looks for common patterns that indicate what was accomplished.\n *\n * @param logContent - Full log file contents\n * @returns Brief summary or undefined\n */\nexport function extractSummaryFromLog(logContent: string): string | undefined {\n // Look for ticket close message\n const closeMatch = logContent.match(/tickets_close.*ticketSlug.*[\"']([^\"']+)[\"']/i);\n if (closeMatch) {\n // Try to find what was done before the close\n const lines = logContent.split(\"\\n\");\n const relevantLines = lines\n .filter((line) => {\n // Skip noise\n if (line.includes(\"===\")) return false;\n if (line.includes(\"YOLO\")) return false;\n if (line.trim().length < 10) return false;\n return true;\n })\n .slice(-5); // Last 5 meaningful lines\n\n if (relevantLines.length > 0) {\n return relevantLines[0].slice(0, 100);\n }\n }\n\n // Fallback: first non-header line\n const lines = logContent.split(\"\\n\");\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed && !trimmed.startsWith(\"===\") && trimmed.length > 20) {\n return trimmed.slice(0, 100);\n }\n }\n\n return undefined;\n}\n","/**\n * Watcher Poll - Polling Logic for YOLO Tickets\n *\n * Handles fetching YOLO-flagged tickets from Convex and claiming them\n * for execution. Used by the daemon's polling loop.\n *\n * Flow:\n * 1. Query Convex for open tickets with yolo: true\n * 2. Check capacity (currentlyExecuting.length < maxParallel)\n * 3. Claim ticket via workMcpTicket mutation\n * 4. Return ticket data for spawning\n */\n\nimport { ConvexHttpClient } from \"convex/browser\";\nimport type { WatcherConfig, WatcherTicket } from \"./watcher_types.js\";\nimport { readStatus } from \"./watcher_state.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Result from a poll operation\n */\nexport interface PollResult {\n /** Whether the poll was successful */\n success: boolean;\n\n /** Tickets available for execution */\n availableTickets: WatcherTicket[];\n\n /** How many we can actually pick up (based on capacity) */\n canPickUp: number;\n\n /** Error message if poll failed */\n error?: string;\n}\n\n/**\n * Result from claiming a ticket\n */\nexport interface ClaimResult {\n /** Whether claim was successful */\n success: boolean;\n\n /** The claimed ticket data */\n ticket?: WatcherTicket;\n\n /** Error message if claim failed */\n error?: string;\n}\n\n// ============================================================================\n// Convex Queries\n// ============================================================================\n\n/**\n * Fetch YOLO tickets from Convex.\n *\n * Queries for tickets with status=\"open\" AND yolo=true.\n *\n * @param client - Convex HTTP client\n * @param config - Watcher configuration\n * @returns Poll result with available tickets\n */\nexport async function fetchYoloTickets(\n client: ConvexHttpClient,\n config: WatcherConfig\n): Promise<PollResult> {\n try {\n // Query for YOLO tickets\n const tickets = await client.query<WatcherTicket[]>(\"mcp_tickets:listYoloTickets\", {\n projectToken: config.projectToken,\n projectSlug: config.projectSlug,\n });\n\n // Check current capacity\n const status = readStatus(config.workingDirectory);\n const currentlyExecuting = status?.currentlyExecuting?.length ?? 0;\n const canPickUp = Math.max(0, config.maxParallel - currentlyExecuting);\n\n return {\n success: true,\n availableTickets: tickets,\n canPickUp,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n return {\n success: false,\n availableTickets: [],\n canPickUp: 0,\n error: `Failed to fetch YOLO tickets: ${errorMessage}`,\n };\n }\n}\n\n/**\n * Claim a ticket for execution.\n *\n * Calls workMcpTicket mutation to transition ticket from open to working.\n * This also returns the full ticket content for execution.\n *\n * @param client - Convex HTTP client\n * @param config - Watcher configuration\n * @param ticketSlug - Ticket to claim\n * @returns Claim result with ticket data\n */\nexport async function claimTicket(\n client: ConvexHttpClient,\n config: WatcherConfig,\n ticketSlug: string\n): Promise<ClaimResult> {\n try {\n // Call workMcpTicket to claim the ticket\n // Returns the ticket object directly, or null if not found/claimable\n const result = await client.mutation<{\n slug: string;\n ticketNumber?: number;\n status: string;\n content: string; // This is flattenedContent\n startedAt?: number;\n remainingTickets: number;\n wasOpened: boolean;\n } | null>(\"mcp_tickets:workMcpTicket\", {\n projectToken: config.projectToken,\n projectSlug: config.projectSlug,\n ticketSlug,\n });\n\n if (!result) {\n return {\n success: false,\n error: \"Ticket not found or not claimable\",\n };\n }\n\n return {\n success: true,\n ticket: {\n slug: result.slug,\n ticketNumber: result.ticketNumber,\n content: result.content,\n flattenedContent: result.content, // workMcpTicket returns flattenedContent as 'content'\n },\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n return {\n success: false,\n error: `Failed to claim ticket: ${errorMessage}`,\n };\n }\n}\n\n// ============================================================================\n// Poll and Execute Coordinator\n// ============================================================================\n\n/**\n * Options for a single poll-and-execute cycle\n */\nexport interface PollCycleOptions {\n /** Convex HTTP client */\n client: ConvexHttpClient;\n\n /** Watcher configuration */\n config: WatcherConfig;\n\n /** Callback when a ticket should be spawned */\n onSpawnTicket: (ticket: WatcherTicket) => void;\n\n /** Callback for logging */\n onLog?: (message: string) => void;\n}\n\n/**\n * Execute a single poll cycle.\n *\n * Fetches available YOLO tickets, claims as many as capacity allows,\n * and triggers spawn callback for each.\n *\n * @param options - Poll cycle options\n * @returns Number of tickets spawned\n */\nexport async function executePollCycle(options: PollCycleOptions): Promise<number> {\n const { client, config, onSpawnTicket, onLog } = options;\n const log = onLog ?? (() => {});\n\n // 1. Fetch available tickets\n log(\"[Poll] Checking for YOLO tickets...\");\n const pollResult = await fetchYoloTickets(client, config);\n\n if (!pollResult.success) {\n log(`[Poll] Error: ${pollResult.error}`);\n return 0;\n }\n\n if (pollResult.availableTickets.length === 0) {\n log(\"[Poll] No YOLO tickets pending\");\n return 0;\n }\n\n log(`[Poll] Found ${pollResult.availableTickets.length} ticket(s), capacity: ${pollResult.canPickUp}`);\n\n if (pollResult.canPickUp === 0) {\n log(\"[Poll] At capacity - waiting for current executions to complete\");\n return 0;\n }\n\n // 2. Claim and spawn tickets up to capacity\n let spawned = 0;\n const ticketsToProcess = pollResult.availableTickets.slice(0, pollResult.canPickUp);\n\n for (const pendingTicket of ticketsToProcess) {\n log(`[Poll] Claiming ticket: ${pendingTicket.slug}`);\n\n const claimResult = await claimTicket(client, config, pendingTicket.slug);\n\n if (!claimResult.success || !claimResult.ticket) {\n log(`[Poll] Failed to claim ${pendingTicket.slug}: ${claimResult.error}`);\n continue;\n }\n\n log(`[Poll] Claimed ${pendingTicket.slug} - spawning execution`);\n onSpawnTicket(claimResult.ticket);\n spawned++;\n }\n\n return spawned;\n}\n"],"mappings":";;;;;;;;;;;;;;AAkBA,SAAS,wBAAwB;AACjC,SAAS,kBAAAA,uBAAoC;;;ACP7C,SAAS,aAAgC;AACzC,SAAS,UAAU,WAAW,sBAAsB;AA4G7C,SAAS,sBAAsB,SAKpC;AACA,QAAM,EAAE,QAAQ,kBAAkB,YAAY,IAAI;AAGlD,QAAM,SAAS,kBAAkB,QAAQ,WAAW;AAGpD,QAAM,UAAU,iBAAiB,kBAAkB,OAAO,IAAI;AAE9D,MAAI;AAEF,UAAM,QAAQ,SAAS,SAAS,GAAG;AAGnC,UAAM,SAAS;AAAA,UACT,OAAO,IAAI,GAAG,OAAO,eAAe,MAAM,OAAO,YAAY,MAAM,EAAE;AAAA,WACpE,WAAW;AAAA,YACX,oBAAI,KAAK,GAAE,YAAY,CAAC;AAAA,qBACd,gBAAgB;AAAA,EACnC,IAAI,OAAO,EAAE,CAAC;AAAA;AAAA;AAGZ,mBAAe,SAAS,MAAM;AAG9B,UAAM,QAAQ,MAAM,UAAU,CAAC,MAAM,QAAQ,gCAAgC,GAAG;AAAA,MAC9E,KAAK;AAAA,MACL,OAAO,CAAC,UAAU,OAAO,KAAK;AAAA,MAC9B,UAAU;AAAA,MACV,KAAK;AAAA,QACH,GAAG,QAAQ;AAAA,QACX,MAAM;AAAA,QACN,UAAU;AAAA,QACV,aAAa;AAAA,MACf;AAAA,IACF,CAAC;AAED,cAAU,KAAK;AAEf,QAAI,CAAC,MAAM,KAAK;AACd,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,UAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU;AAC9D,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,2BAA2B,YAAY;AAAA,IAChD;AAAA,EACF;AACF;AAaA,SAAS,kBAAkB,QAAuB,aAA6B;AAE7E,QAAM,UAAU,OAAO,oBAAoB,OAAO;AAElD,SAAO,4DAA4D,WAAW;AAAA;AAAA,aAEnE,OAAO,IAAI,GAAG,OAAO,eAAe,MAAM,OAAO,YAAY,MAAM,EAAE;AAAA;AAAA,EAEhF,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sFAO6E,OAAO,IAAI;AAAA;AAAA;AAAA;AAAA;AAKjG;AAcO,SAAS,sBAAsB,YAAwC;AAE5E,QAAM,aAAa,WAAW,MAAM,8CAA8C;AAClF,MAAI,YAAY;AAEd,UAAMC,SAAQ,WAAW,MAAM,IAAI;AACnC,UAAM,gBAAgBA,OACnB,OAAO,CAAC,SAAS;AAEhB,UAAI,KAAK,SAAS,KAAK,EAAG,QAAO;AACjC,UAAI,KAAK,SAAS,MAAM,EAAG,QAAO;AAClC,UAAI,KAAK,KAAK,EAAE,SAAS,GAAI,QAAO;AACpC,aAAO;AAAA,IACT,CAAC,EACA,MAAM,EAAE;AAEX,QAAI,cAAc,SAAS,GAAG;AAC5B,aAAO,cAAc,CAAC,EAAE,MAAM,GAAG,GAAG;AAAA,IACtC;AAAA,EACF;AAGA,QAAM,QAAQ,WAAW,MAAM,IAAI;AACnC,aAAW,QAAQ,OAAO;AACxB,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,WAAW,CAAC,QAAQ,WAAW,KAAK,KAAK,QAAQ,SAAS,IAAI;AAChE,aAAO,QAAQ,MAAM,GAAG,GAAG;AAAA,IAC7B;AAAA,EACF;AAEA,SAAO;AACT;;;ACrMA,eAAsB,iBACpB,QACAC,SACqB;AACrB,MAAI;AAEF,UAAM,UAAU,MAAM,OAAO,MAAuB,+BAA+B;AAAA,MACjF,cAAcA,QAAO;AAAA,MACrB,aAAaA,QAAO;AAAA,IACtB,CAAC;AAGD,UAAM,SAAS,WAAWA,QAAO,gBAAgB;AACjD,UAAM,qBAAqB,QAAQ,oBAAoB,UAAU;AACjE,UAAM,YAAY,KAAK,IAAI,GAAGA,QAAO,cAAc,kBAAkB;AAErE,WAAO;AAAA,MACL,SAAS;AAAA,MACT,kBAAkB;AAAA,MAClB;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,UAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU;AAC9D,WAAO;AAAA,MACL,SAAS;AAAA,MACT,kBAAkB,CAAC;AAAA,MACnB,WAAW;AAAA,MACX,OAAO,iCAAiC,YAAY;AAAA,IACtD;AAAA,EACF;AACF;AAaA,eAAsB,YACpB,QACAA,SACA,YACsB;AACtB,MAAI;AAGF,UAAM,SAAS,MAAM,OAAO,SAQlB,6BAA6B;AAAA,MACrC,cAAcA,QAAO;AAAA,MACrB,aAAaA,QAAO;AAAA,MACpB;AAAA,IACF,CAAC;AAED,QAAI,CAAC,QAAQ;AACX,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,QAAQ;AAAA,QACN,MAAM,OAAO;AAAA,QACb,cAAc,OAAO;AAAA,QACrB,SAAS,OAAO;AAAA,QAChB,kBAAkB,OAAO;AAAA;AAAA,MAC3B;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,UAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU;AAC9D,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,2BAA2B,YAAY;AAAA,IAChD;AAAA,EACF;AACF;AAgCA,eAAsB,iBAAiB,SAA4C;AACjF,QAAM,EAAE,QAAQ,QAAAA,SAAQ,eAAe,MAAM,IAAI;AACjD,QAAMC,OAAM,UAAU,MAAM;AAAA,EAAC;AAG7B,EAAAA,KAAI,qCAAqC;AACzC,QAAM,aAAa,MAAM,iBAAiB,QAAQD,OAAM;AAExD,MAAI,CAAC,WAAW,SAAS;AACvB,IAAAC,KAAI,iBAAiB,WAAW,KAAK,EAAE;AACvC,WAAO;AAAA,EACT;AAEA,MAAI,WAAW,iBAAiB,WAAW,GAAG;AAC5C,IAAAA,KAAI,gCAAgC;AACpC,WAAO;AAAA,EACT;AAEA,EAAAA,KAAI,gBAAgB,WAAW,iBAAiB,MAAM,yBAAyB,WAAW,SAAS,EAAE;AAErG,MAAI,WAAW,cAAc,GAAG;AAC9B,IAAAA,KAAI,iEAAiE;AACrE,WAAO;AAAA,EACT;AAGA,MAAI,UAAU;AACd,QAAM,mBAAmB,WAAW,iBAAiB,MAAM,GAAG,WAAW,SAAS;AAElF,aAAW,iBAAiB,kBAAkB;AAC5C,IAAAA,KAAI,2BAA2B,cAAc,IAAI,EAAE;AAEnD,UAAM,cAAc,MAAM,YAAY,QAAQD,SAAQ,cAAc,IAAI;AAExE,QAAI,CAAC,YAAY,WAAW,CAAC,YAAY,QAAQ;AAC/C,MAAAC,KAAI,0BAA0B,cAAc,IAAI,KAAK,YAAY,KAAK,EAAE;AACxE;AAAA,IACF;AAEA,IAAAA,KAAI,kBAAkB,cAAc,IAAI,uBAAuB;AAC/D,kBAAc,YAAY,MAAM;AAChC;AAAA,EACF;AAEA,SAAO;AACT;;;AF3LA,IAAM,iBAAiB,oBAAI,IAAiC;AAG5D,IAAI,eAAsD;AAG1D,IAAI;AAGJ,IAAI;AAGJ,IAAI;AASJ,SAAS,IAAI,SAAuB;AAClC,QAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AACzC,QAAM,OAAO,IAAI,SAAS,KAAK,OAAO;AAAA;AAGtC,MAAI;AACF,IAAAC,gBAAe,eAAe,IAAI;AAAA,EACpC,QAAQ;AAAA,EAER;AAGA,UAAQ,IAAI,OAAO;AACrB;AAWA,SAAS,qBAAqB,QAA6B;AACzD,MAAI,kCAAkC,OAAO,IAAI,EAAE;AAEnD,QAAM,cAAc,sBAAsB;AAAA,IACxC;AAAA,IACA,kBAAkB,OAAO;AAAA,IACzB,aAAa,OAAO;AAAA,IACpB,SAAS,OAAO;AAAA,EAClB,CAAC;AAED,MAAI,CAAC,YAAY,WAAW,CAAC,YAAY,OAAO;AAC9C,QAAI,uBAAuB,OAAO,IAAI,KAAK,YAAY,KAAK,EAAE;AAC9D;AAAA,EACF;AAEA,QAAM,QAAQ,YAAY;AAC1B,QAAM,MAAM,MAAM;AAClB,QAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AAGzC,QAAM,UAA+B;AAAA,IACnC;AAAA,IACA,YAAY,OAAO;AAAA,IACnB,cAAc,OAAO;AAAA,IACrB;AAAA,IACA,SAAS,YAAY;AAAA,EACvB;AAGA,MAAI,OAAO,gBAAgB,GAAG;AAC5B,YAAQ,UAAU,WAAW,MAAM;AACjC,oBAAc,GAAG;AAAA,IACnB,GAAG,OAAO,aAAa;AAAA,EACzB;AAEA,iBAAe,IAAI,KAAK,OAAO;AAG/B,wBAAsB;AAGtB,QAAM,GAAG,QAAQ,CAAC,MAAM,WAAW;AACjC,oBAAgB,KAAK,MAAM,MAAM;AAAA,EACnC,CAAC;AAED,QAAM,GAAG,SAAS,CAAC,UAAU;AAC3B,QAAI,qBAAqB,OAAO,IAAI,KAAK,MAAM,OAAO,EAAE;AAAA,EAE1D,CAAC;AAED,MAAI,eAAe,GAAG,QAAQ,OAAO,IAAI,EAAE;AAG3C,MAAI,OAAO,qBAAqB;AAC9B,qBAAiB,mBAAmB,OAAO,IAAI,IAAI,0BAA0B;AAAA,EAC/E;AACF;AASA,SAAS,gBACP,KACA,MACA,QACM;AACN,QAAM,UAAU,eAAe,IAAI,GAAG;AACtC,MAAI,CAAC,SAAS;AACZ,QAAI,6BAA6B,GAAG,EAAE;AACtC;AAAA,EACF;AAGA,MAAI,QAAQ,SAAS;AACnB,iBAAa,QAAQ,OAAO;AAAA,EAC9B;AAGA,iBAAe,OAAO,GAAG;AAGzB,QAAM,YAAY,IAAI,KAAK,QAAQ,SAAS,EAAE,QAAQ;AACtD,QAAM,aAAa,KAAK,IAAI,IAAI;AAGhC,QAAM,aAAa,cAAc,OAAO,kBAAkB,QAAQ,UAAU;AAC5E,QAAM,UAAU,aAAa,sBAAsB,UAAU,IAAI;AAGjE,QAAM,UAAU,SAAS;AAEzB;AAAA,IACE,iBAAiB,QAAQ,UAAU,SAAS,GAAG,YACrC,IAAI,YAAY,MAAM,cAAc,KAAK,MAAM,aAAa,GAAI,CAAC;AAAA,EAC7E;AAGA,QAAM,YAA6B;AAAA,IACjC,YAAY,QAAQ;AAAA,IACpB,cAAc,QAAQ;AAAA,IACtB,WAAW,QAAQ;AAAA,IACnB,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,SAAS,QAAQ;AAAA,EACnB;AAEA,qBAAmB,OAAO,kBAAkB,SAAS;AAGrD,wBAAsB;AAGtB,MAAI,OAAO,qBAAqB;AAC9B,UAAM,QAAQ,UAAU,WAAM;AAC9B;AAAA,MACE,SAAS,KAAK,IAAI,QAAQ,UAAU;AAAA,MACpC,UAAU,qBAAqB,oBAAoB,IAAI;AAAA,IACzD;AAAA,EACF;AACF;AAOA,SAAS,cAAc,KAAmB;AACxC,QAAM,UAAU,eAAe,IAAI,GAAG;AACtC,MAAI,CAAC,QAAS;AAEd,MAAI,eAAe,QAAQ,UAAU,SAAS,GAAG,qBAAqB;AAEtE,MAAI;AACF,YAAQ,KAAK,KAAK,SAAS;AAAA,EAC7B,QAAQ;AAAA,EAER;AAGF;AAKA,SAAS,wBAA8B;AACrC,QAAM,SAAS,WAAW,OAAO,gBAAgB;AACjD,MAAI,CAAC,OAAQ;AAEb,QAAM,qBAAqB,MAAM,KAAK,eAAe,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO;AAAA,IACzE,YAAY,EAAE;AAAA,IACd,cAAc,EAAE;AAAA,IAChB,WAAW,EAAE;AAAA,IACb,KAAK,EAAE;AAAA,IACP,SAAS,EAAE;AAAA,EACb,EAAE;AAEF,cAAY,OAAO,kBAAkB;AAAA,IACnC,GAAG;AAAA,IACH;AAAA,IACA,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,EACrC,CAAC;AACH;AAaA,SAAS,iBAAiB,OAAe,SAAuB;AAC9D,MAAI,QAAQ,aAAa,SAAU;AAEnC,MAAI;AACF,UAAM,EAAE,SAAS,IAAI,UAAQ,eAAe;AAC5C,UAAM,SAAS,yBAAyB,OAAO,iBAAiB,KAAK;AACrE,aAAS,iBAAiB,MAAM,KAAK,EAAE,OAAO,SAAS,CAAC;AAAA,EAC1D,QAAQ;AAAA,EAER;AACF;AASA,eAAe,OAAsB;AACnC,MAAI;AACF,UAAM,iBAAiB;AAAA,MACrB,QAAQ;AAAA,MACR;AAAA,MACA,eAAe;AAAA,MACf,OAAO;AAAA,IACT,CAAC;AAAA,EACH,SAAS,OAAO;AACd,UAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU;AACrD,QAAI,eAAe,GAAG,EAAE;AAAA,EAC1B;AACF;AAKA,SAAS,eAAqB;AAC5B,MAAI,iCAAiC,OAAO,cAAc,KAAK;AAG/D,OAAK;AAGL,iBAAe,YAAY,MAAM;AAC/B,SAAK;AAAA,EACP,GAAG,OAAO,cAAc;AAC1B;AAKA,SAAS,cAAoB;AAC3B,MAAI,cAAc;AAChB,kBAAc,YAAY;AAC1B,mBAAe;AACf,QAAI,iBAAiB;AAAA,EACvB;AACF;AASA,SAAS,SAAS,QAAsB;AACtC,MAAI,YAAY,MAAM,kBAAkB;AAGxC,cAAY;AAGZ,aAAW,CAAC,KAAK,OAAO,KAAK,gBAAgB;AAC3C,QAAI,0BAA0B,QAAQ,UAAU,SAAS,GAAG,GAAG;AAC/D,QAAI,QAAQ,QAAS,cAAa,QAAQ,OAAO;AACjD,QAAI;AACF,cAAQ,KAAK,KAAK,SAAS;AAAA,IAC7B,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,QAAM,SAAS,WAAW,OAAO,gBAAgB;AACjD,MAAI,QAAQ;AACV,gBAAY,OAAO,kBAAkB;AAAA,MACnC,GAAG;AAAA,MACH,OAAO;AAAA,MACP,oBAAoB,CAAC;AAAA,IACvB,CAAC;AAAA,EACH;AAEA,MAAI,mBAAmB;AACvB,UAAQ,KAAK,CAAC;AAChB;AASA,SAAS,YAA2B;AAClC,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AAGjC,QAAM,cAAc,KAAK,QAAQ,UAAU;AAC3C,MAAI,gBAAgB,MAAM,CAAC,KAAK,cAAc,CAAC,GAAG;AAChD,YAAQ,MAAM,wDAAwD;AACtE,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,YAAY,KAAK,cAAc,CAAC;AAEtC,MAAI;AACF,UAAM,aAAa,OAAO,KAAK,WAAW,QAAQ,EAAE,SAAS,OAAO;AACpE,WAAO,KAAK,MAAM,UAAU;AAAA,EAC9B,SAAS,OAAO;AACd,YAAQ,MAAM,2BAA2B,KAAK;AAC9C,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAKA,eAAe,OAAsB;AAEnC,WAAS,UAAU;AAGnB,kBAAgB,WAAW,OAAO,gBAAgB;AAElD,MAAI,IAAI,OAAO,EAAE,CAAC;AAClB,MAAI,8BAA8B;AAClC,MAAI,YAAY,OAAO,WAAW,EAAE;AACpC,MAAI,sBAAsB,OAAO,gBAAgB,EAAE;AACnD,MAAI,kBAAkB,OAAO,cAAc,IAAI;AAC/C,MAAI,iBAAiB,OAAO,WAAW,EAAE;AACzC,MAAI,mBAAmB,OAAO,aAAa,IAAI;AAC/C,MAAI,IAAI,OAAO,EAAE,CAAC;AAGlB,WAAS,OAAO,kBAAkB,QAAQ,GAAG;AAC7C,MAAI,QAAQ,QAAQ,GAAG,EAAE;AAGzB,iBAAe,IAAI,iBAAiB,OAAO,SAAS;AAGpD,QAAM,gBAA+B;AAAA,IACnC,OAAO;AAAA,IACP,KAAK,QAAQ;AAAA,IACb,aAAa,OAAO;AAAA,IACpB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC,kBAAkB;AAAA,IAClB,oBAAoB,CAAC;AAAA,IACrB,QAAQ;AAAA,MACN,aAAa,OAAO;AAAA,MACpB,WAAW,OAAO;AAAA,MAClB,gBAAgB,OAAO;AAAA,MACvB,aAAa,OAAO;AAAA,MACpB,eAAe,OAAO;AAAA,MACtB,qBAAqB,OAAO;AAAA,MAC5B,kBAAkB,OAAO;AAAA,IAC3B;AAAA,EACF;AACA,cAAY,OAAO,kBAAkB,aAAa;AAGlD,UAAQ,GAAG,WAAW,MAAM,SAAS,SAAS,CAAC;AAC/C,UAAQ,GAAG,UAAU,MAAM,SAAS,QAAQ,CAAC;AAG7C,MAAI,OAAO,qBAAqB;AAC9B,qBAAiB,wBAAwB,YAAY,OAAO,WAAW,EAAE;AAAA,EAC3E;AAGA,eAAa;AACf;AAGA,KAAK,EAAE,MAAM,CAAC,UAAU;AACtB,UAAQ,MAAM,iBAAiB,KAAK;AACpC,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":["appendFileSync","lines","config","log","appendFileSync"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promptprojectmanager/mcp-server",
3
- "version": "4.6.3",
3
+ "version": "4.6.4",
4
4
  "description": "MCP server that exposes Prompt Project Manager project prompts as slash commands",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",