@promptprojectmanager/mcp-server 4.6.2 → 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.
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/convex-client.ts","../src/server.ts","../src/types.ts","../src/watcher/watcher_controller.ts","../src/prompt-builder.ts"],"sourcesContent":["import minimist from \"minimist\";\nimport { createConvexClient } from \"./convex-client.js\";\nimport { startServer } from \"./server.js\";\nimport type { ServerConfig } from \"./types.js\";\n\n/**\n * Main entry point for the MCP server\n */\nasync function main() {\n try {\n // Parse CLI arguments\n const argv = minimist(process.argv.slice(2));\n const isDev = argv.dev === true;\n\n // Check for project token authentication\n const projectToken = process.env.PPM_PROJECT_TOKEN;\n\n if (!projectToken) {\n console.error(\n \"[MCP] ERROR: Missing authentication. Set PPM_PROJECT_TOKEN environment variable.\"\n );\n console.error(\n \"[MCP] Example:\"\n );\n console.error(\"[MCP] export PPM_PROJECT_TOKEN=wst_... # Get from project settings\");\n process.exit(1);\n }\n\n console.error(\"[MCP] Auth mode: Project Token\");\n\n // Create Convex client\n const convexClient = createConvexClient(isDev);\n const convexUrl = isDev\n ? \"https://hallowed-shrimp-344.convex.cloud\"\n : \"https://trustworthy-squirrel-735.convex.cloud\";\n\n // Build server config\n const config: ServerConfig = {\n projectToken,\n isDev,\n convexUrl,\n };\n\n // Start server - this will keep the process alive via stdio transport\n await startServer(config, convexClient);\n\n // This line should never be reached if the promise in startServer never resolves\n console.error(\"[MCP] WARNING: startServer promise resolved unexpectedly!\");\n } catch (error) {\n console.error(\n \"[MCP] FATAL ERROR:\",\n error instanceof Error ? error.message : \"Unknown error\"\n );\n if (error instanceof Error && error.stack) {\n console.error(error.stack);\n }\n process.exit(1);\n }\n}\n\n// Handle graceful shutdown\nprocess.on(\"SIGINT\", () => {\n console.error(\"[MCP] Received SIGINT, shutting down gracefully...\");\n process.exit(0);\n});\n\nprocess.on(\"SIGTERM\", () => {\n console.error(\"[MCP] Received SIGTERM, shutting down gracefully...\");\n process.exit(0);\n});\n\nmain();\n","import { ConvexHttpClient } from \"convex/browser\";\n\nconst PROD_URL = \"https://trustworthy-squirrel-735.convex.cloud\";\nconst DEV_URL = \"https://hallowed-shrimp-344.convex.cloud\";\n\n/**\n * Create a Convex HTTP client for the appropriate deployment\n */\nexport function createConvexClient(isDev: boolean): ConvexHttpClient {\n const url = isDev ? DEV_URL : PROD_URL;\n return new ConvexHttpClient(url);\n}\n","import { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport {\n CallToolRequestSchema,\n GetPromptRequestSchema,\n ListPromptsRequestSchema,\n ListToolsRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport type { ConvexHttpClient } from \"convex/browser\";\nimport type {\n ServerConfig,\n ProjectTokenValidation,\n WorkTicketResult,\n AccessDeniedResult,\n CreateTicketResult,\n CloseTicketResult,\n UpdateTicketResult,\n GetTicketResult,\n ListTicketItem,\n SearchTicketItem,\n} from \"./types.js\";\nimport {\n parseWorkArgs,\n parseCloseArgs,\n parseCreateArgs,\n parseSearchArgs,\n parseGetArgs,\n parseUpdateArgs,\n parsePromptsRunArgs,\n parsePromptsListArgs,\n parsePromptsUpdateArgs,\n parseWatcherStartArgs,\n isAccessDenied,\n} from \"./types.js\";\nimport type { UpdatePromptResult } from \"./types.js\";\n\n// Import watcher controller for YOLO automation (daemon-based)\nimport {\n startWatcher,\n stopWatcher,\n getWatcherStatus,\n} from \"./watcher/index.js\";\n\n/**\n * Helper type for Convex HTTP client with string-based function references.\n * This allows us to use string function names while maintaining type safety\n * for the response types through explicit casting.\n */\ntype ConvexClientWithStringRefs = ConvexHttpClient & {\n mutation: <T>(name: string, args: Record<string, unknown>) => Promise<T>;\n query: <T>(name: string, args: Record<string, unknown>) => Promise<T>;\n};\n\nimport { fetchAndExecuteAccountScopedPrompt, AmbiguousPromptError, fetchAccountScopedPromptMetadataByToken } from \"./prompt-builder.js\";\n// Note: McpCommandMetadata removed - commands resource eliminated\n// Note: buildPromptNameFromMetadata, fetchAndExecutePrompt removed - prompts now account-scoped\n\n/**\n * Build authentication arguments for Convex mutations/queries.\n * All auth is now via project token.\n */\nfunction buildAuthArgs(config: ServerConfig): { projectToken: string } {\n return { projectToken: config.projectToken };\n}\n\n/**\n * Built-in system tools (no global tools - all are project-scoped now)\n */\nconst SYSTEM_TOOLS: {\n name: string;\n description: string;\n inputSchema: object;\n promptContent: string;\n isSlashCommand: boolean; // true = appears as /ppm:name, false = tool only\n}[] = [\n // All tools are now project-scoped (see dynamic*Tools arrays below)\n];\n\n/**\n * Initialize and start the MCP server\n */\nexport async function startServer(\n config: ServerConfig,\n convexClientRaw: ConvexHttpClient\n): Promise<void> {\n // Cast to our typed client interface for string-based function references\n const convexClient = convexClientRaw as ConvexClientWithStringRefs;\n // Validate project token authentication\n console.error(\"[MCP] Validating project token...\");\n const validation = await validateProjectToken(convexClient, config.projectToken);\n if (!validation.valid) {\n throw new Error(`Invalid project token: ${validation.error}`);\n }\n const tokenProjectSlug = validation.projectSlug;\n console.error(`[MCP] Project token validated for project \"${tokenProjectSlug}\"`);\n // Token mode provides full prompt access via the token owner's account\n\n // Fetch account-scoped prompts (global MCP tools)\n // Account-scoped prompts: Available as global tools (e.g., \"code-review\")\n // Token mode: Single project for tickets (determined by token)\n console.error(\"[MCP] Fetching prompt metadata...\");\n\n // Project token mode - full prompt access via token's owner\n // The token's ownerId is used to look up all account-scoped prompts\n const accountScopedPrompts = await fetchAccountScopedPromptMetadataByToken(\n convexClient,\n config.projectToken\n );\n console.error(`[MCP] Project token mode: loaded ${accountScopedPrompts.length} prompts`);\n\n console.error(`[MCP] Found ${accountScopedPrompts.length} account-scoped prompts (global tools)`);\n console.error(`[MCP] Ticket project scope: ${tokenProjectSlug} (token-scoped)`);\n\n if (accountScopedPrompts.length === 0) {\n console.error(\n \"[MCP] WARNING: No prompts found. Create prompts in the 'prompts' section to expose them via MCP.\"\n );\n }\n\n // Build dynamic ticket tools (single set since token scopes to one project)\n // Tool names no longer prefixed with project slug - token already scopes access\n const projectSlug = tokenProjectSlug!;\n const dynamicTicketTools: { name: string; description: string; slashDescription: string; projectSlug: string; type: 'work' | 'close' | 'create' | 'search' | 'get' | 'list' | 'update' | 'yolo_start' | 'yolo_stop' | 'yolo_status' }[] = [\n // Unified work command (replaces both tickets_open and tickets_work)\n {\n name: `tickets_work`,\n description: `Get work from the \"${projectSlug}\" project. With no args: gets next ticket from open queue. With ticket slug/number: opens or resumes that specific ticket.`,\n slashDescription: `Get next ticket from queue, or work on specific ticket by slug/number`,\n projectSlug,\n type: 'work',\n },\n {\n name: `tickets_close`,\n description: `Mark a working ticket as completed in the \"${projectSlug}\" project`,\n slashDescription: `Mark a working ticket as completed`,\n projectSlug,\n type: 'close',\n },\n {\n name: `tickets_create`,\n description: `Create a new ticket in the \"${projectSlug}\" project queue`,\n slashDescription: `Create a new ticket in the backlog queue`,\n projectSlug,\n type: 'create',\n },\n {\n name: `tickets_search`,\n description: `Search for tickets by content in the \"${projectSlug}\" project`,\n slashDescription: `Search for tickets by content`,\n projectSlug,\n type: 'search',\n },\n {\n name: `tickets_get`,\n description: `Get a specific ticket by number or slug from \"${projectSlug}\" (read-only)`,\n slashDescription: `Get a specific ticket by number or slug (read-only)`,\n projectSlug,\n type: 'get',\n },\n {\n name: `tickets_list`,\n description: `List active tickets in the \"${projectSlug}\" project (backlog + open + working)`,\n slashDescription: `List active tickets (backlog + open + working)`,\n projectSlug,\n type: 'list',\n },\n {\n name: `tickets_update`,\n description: `Update a ticket in the \"${projectSlug}\" project by appending content with timestamp`,\n slashDescription: `Update a ticket by appending content with timestamp`,\n projectSlug,\n type: 'update',\n },\n // YOLO orchestrator tools - sub-agent based autonomous ticket execution\n {\n name: `tickets_yolo_start`,\n description: `Start the YOLO ticket watcher for \"${projectSlug}\". Polls for open tickets with yolo flag and spawns Terminal windows with Claude Code to execute them autonomously.`,\n slashDescription: `Start YOLO orchestrator for autonomous ticket execution`,\n projectSlug,\n type: 'yolo_start',\n },\n {\n name: `tickets_yolo_stop`,\n description: `Stop the YOLO ticket watcher for \"${projectSlug}\"`,\n slashDescription: `Stop the YOLO orchestrator`,\n projectSlug,\n type: 'yolo_stop',\n },\n {\n name: `tickets_yolo_status`,\n description: `Get YOLO watcher status for \"${projectSlug}\"`,\n slashDescription: `Check YOLO orchestrator status and pending tickets`,\n projectSlug,\n type: 'yolo_status',\n },\n ];\n\n console.error(`[MCP] Registering ${dynamicTicketTools.length} ticket tools...`);\n\n // Build global prompt tools (not project-scoped)\n // Renamed from system_* to prompts_* for clarity\n const globalPromptTools: { name: string; description: string; slashDescription: string }[] = [\n {\n name: \"prompts_run\",\n description: \"Execute a prompt by slug. Use prompts_list to list available prompts.\",\n slashDescription: \"Execute a prompt by slug. Use prompts_list to list available prompts.\",\n },\n {\n name: \"prompts_list\",\n description: \"List all available prompts\",\n slashDescription: \"List all available prompts\",\n },\n {\n name: \"prompts_update\",\n description: \"Read or update a prompt. Without content: returns prompt with context and editing guidance. With content: saves new version.\",\n slashDescription: \"Read or update a prompt by slug\",\n },\n ];\n\n console.error(`[MCP] Registering ${globalPromptTools.length} global prompt tools...`);\n\n // Build dynamic per-prompt tools (each prompt as its own tool)\n // Now global: Direct invocation like `code-review` instead of `projects:code-review`\n const dynamicPromptTools: {\n name: string; // \"code-review\" (global, no project prefix)\n description: string;\n promptSlug: string;\n }[] = [];\n\n for (const prompt of accountScopedPrompts) {\n // Build description with optional folder prefix\n const folderPrefix = prompt.folderSlug ? `[${prompt.folderSlug}] ` : '';\n const baseDescription = prompt.description || `Execute the \"${prompt.slug}\" prompt`;\n\n dynamicPromptTools.push({\n name: prompt.slug, // Global tool name, no project prefix\n description: folderPrefix + baseDescription,\n promptSlug: prompt.slug,\n });\n }\n\n console.error(`[MCP] Registering ${dynamicPromptTools.length} per-prompt tools (global)...`);\n\n // Note: Commands resource removed - users create slash commands directly from flattened prompts\n\n // Check for duplicate prompt names (now global, no project prefix)\n const promptNames = new Set<string>();\n const duplicates: string[] = [];\n accountScopedPrompts.forEach((p) => {\n if (promptNames.has(p.slug)) {\n duplicates.push(p.slug);\n }\n promptNames.add(p.slug);\n });\n\n if (duplicates.length > 0) {\n console.error(\n `[MCP] WARNING: Duplicate prompt slugs detected: ${duplicates.join(\", \")}. Only the first occurrence will be registered.`\n );\n }\n\n // Create MCP server\n const server = new Server(\n {\n name: \"ppm-mcp-server\",\n version: \"3.1.0\",\n },\n {\n capabilities: {\n prompts: {},\n tools: {},\n },\n }\n );\n\n // Register list_prompts handler\n // Prompts are now global (account-scoped), tickets remain project-scoped\n server.setRequestHandler(ListPromptsRequestSchema, async () => {\n // Build ticket prompt schemas (project-scoped)\n const ticketPromptSchemas = dynamicTicketTools.map((tt) => ({\n name: tt.name,\n description: tt.slashDescription,\n }));\n\n // Build global prompt schemas\n const promptToolSchemas = globalPromptTools.map((st) => ({\n name: st.name,\n description: st.slashDescription,\n }));\n\n return {\n prompts: [\n ...promptToolSchemas, // prompts_run, prompts_list, prompts_update (global)\n ...ticketPromptSchemas, // tickets_* (token-scoped)\n ],\n };\n });\n\n // Register get_prompt handler\n server.setRequestHandler(GetPromptRequestSchema, async (request) => {\n const promptName = request.params.name;\n\n // Check for system tools first (as prompts for slash command access)\n const systemTool = SYSTEM_TOOLS.find((st) => st.name === promptName);\n if (systemTool) {\n return {\n description: systemTool.description,\n messages: [\n {\n role: \"user\" as const,\n content: {\n type: \"text\" as const,\n text: systemTool.promptContent,\n },\n },\n ],\n };\n }\n\n // Check for ticket commands (Ticket 135, 149, 153 - handle as slash commands)\n // Handle exact matches and tickets_work with optional argument (e.g., \"tickets_work 102\")\n const ticketTool = dynamicTicketTools.find((tt) => tt.name === promptName);\n const ticketWorkMatch = promptName.match(/^tickets_work\\s+(.+)$/);\n\n if (ticketTool || ticketWorkMatch) {\n let promptContent: string;\n let description: string;\n\n if (ticketWorkMatch) {\n // Handle tickets_work with specific ticket argument\n const ticketArg = ticketWorkMatch[1].trim();\n description = `Work on ticket \"${ticketArg}\" from \"${projectSlug}\"`;\n promptContent = `Get work on ticket \"${ticketArg}\" from the \"${projectSlug}\" project.\\n\\nCall the \\`tickets_work\\` tool with ticketSlug: \"${ticketArg}\".\\n\\nThis will open the ticket if it's in the open queue, or resume it if already working.`;\n } else if (ticketTool!.type === 'work') {\n // Unified work command (replaces open + work)\n description = ticketTool!.description;\n promptContent = `Get work from the \"${ticketTool!.projectSlug}\" project.\\n\\nCall the \\`tickets_work\\` tool to get the next ticket from the open queue.\\n\\nYou can also specify a ticket: /ppm:tickets_work <number-or-slug>\\n\\nThis unified command handles both opening new tickets and resuming in-progress work.`;\n } else if (ticketTool!.type === 'create') {\n // Ticket 149: tickets:create slash command\n description = ticketTool!.description;\n promptContent = `Create a new ticket in the \"${ticketTool!.projectSlug}\" project queue.\n\n## How This Works\nThe user provides **instructions** (not raw content). You interpret those instructions to generate the ticket.\n\n## Instructions\n1. **Read the user's request** - they may reference a file, ask you to summarize a session, or describe what they want\n2. **Process the input** - read files, extract relevant sections, summarize as needed\n3. **Generate ticket content** with a clear, descriptive title as the first line\n4. **Call the tool** with the generated content\n\n## Content Format\n\\`\\`\\`\n[Clear descriptive title - this becomes the slug]\n\n[Body content - tasks, description, context, etc.]\n\\`\\`\\`\n\n## Examples\n- \"create a ticket from /path/to/plan.md\" → Read file, use as ticket content\n- \"summarize our brainstorm into a ticket\" → Extract key points from conversation\n- \"create a ticket for the auth bug we discussed\" → Generate from session context\n- \"just the tasks from this file\" → Extract only task items\n\nCall the \\`${ticketTool!.name}\\` tool with the final generated content.`;\n } else if (ticketTool!.type === 'close') {\n description = ticketTool!.description;\n promptContent = `Close a working ticket in the \"${ticketTool!.projectSlug}\" project.\\n\\nCall the \\`${ticketTool!.name}\\` tool with the ticket number or slug.`;\n } else {\n // Fallback for unknown type\n description = ticketTool!.description;\n promptContent = `Use the \\`${ticketTool!.name}\\` tool.`;\n }\n\n return {\n description,\n messages: [\n {\n role: \"user\" as const,\n content: {\n type: \"text\" as const,\n text: promptContent,\n },\n },\n ],\n };\n }\n\n // Check for global prompts_list command\n if (promptName === \"prompts_list\") {\n return {\n description: \"List all available prompts\",\n messages: [\n {\n role: \"user\" as const,\n content: {\n type: \"text\" as const,\n text: `List all available prompts.\n\nCall the \\`prompts_list\\` tool with optional \\`search\\` parameter to filter results.`,\n },\n },\n ],\n };\n }\n\n // Check for global prompts_run command (with optional prompt slug argument)\n const runPromptMatch = promptName.match(/^prompts_run\\s+(.+)$/);\n\n if (promptName === \"prompts_run\" || runPromptMatch) {\n let promptContent: string;\n let description: string;\n\n if (runPromptMatch) {\n // Handle prompts_run with specific prompt slug argument\n const promptSlug = runPromptMatch[1].trim();\n description = `Execute prompt \"${promptSlug}\"`;\n promptContent = `Execute the \"${promptSlug}\" prompt.\\n\\nCall the \\`prompts_run\\` tool with slug: \"${promptSlug}\".`;\n } else {\n // Base prompts_run without argument - show help\n description = \"Execute a prompt by slug\";\n promptContent = `Execute a prompt by slug.\n\n## Usage\nCall the \\`prompts_run\\` tool with the prompt slug.\n\n## Available Prompts\nUse \\`prompts_list\\` to list all available prompts.\n\n## Example\n/ppm:prompts_run code-review\n\nThis will execute the \"code-review\" prompt.`;\n }\n\n return {\n description,\n messages: [\n {\n role: \"user\" as const,\n content: {\n type: \"text\" as const,\n text: promptContent,\n },\n },\n ],\n };\n }\n\n // Note: Command handling removed - commands resource eliminated\n\n // Unknown prompt\n throw new Error(`Unknown prompt: ${promptName}. Use prompts_run to execute prompts.`);\n });\n\n // Register list_tools handler\n // Note: Per-prompt tool registration removed in favor of project-scoped run_prompt tools\n // This reduces token overhead and allows dynamic prompt discovery without server restart\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n // Build tools array: system tools + ticket tools + project:run_prompt tools\n // Individual prompts no longer registered as separate tools\n const tools = [\n // System tools with full input schemas\n ...SYSTEM_TOOLS.map((st) => ({\n name: st.name,\n description: st.description,\n inputSchema: st.inputSchema,\n })),\n // Dynamic ticket tools per project (Ticket 135, 149, 151, 153, unified work)\n ...dynamicTicketTools.map((tt) => {\n // Build inputSchema based on tool type\n let inputSchema: { type: \"object\"; properties: Record<string, object>; required: string[] };\n\n if (tt.type === 'close') {\n // close requires a ticketSlug\n inputSchema = {\n type: \"object\" as const,\n properties: {\n ticketSlug: {\n type: \"string\",\n description: \"Ticket number (e.g., '102') or full slug (e.g., '102-fix-auth')\",\n },\n },\n required: [\"ticketSlug\"],\n };\n } else if (tt.type === 'work') {\n // Unified work command (replaces open + work)\n inputSchema = {\n type: \"object\" as const,\n properties: {\n ticketSlug: {\n type: \"string\",\n description: \"Optional: Ticket number (e.g., '102') or full slug. If not provided, gets next ticket from open queue.\",\n },\n },\n required: [],\n };\n } else if (tt.type === 'create') {\n inputSchema = {\n type: \"object\" as const,\n properties: {\n content: {\n type: \"string\",\n description: \"The generated ticket content. First line should be a clear descriptive title (becomes the slug). Body contains tasks, description, context as needed.\",\n },\n tags: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Optional array of tag slugs to assign (e.g., [\\\"bug\\\", \\\"urgent\\\"]). Invalid tags are ignored.\",\n },\n },\n required: [\"content\"],\n };\n } else if (tt.type === 'search') {\n // Ticket 155: tickets:search\n inputSchema = {\n type: \"object\" as const,\n properties: {\n query: {\n type: \"string\",\n description: \"Search query (min 3 characters)\",\n },\n },\n required: [\"query\"],\n };\n } else if (tt.type === 'get') {\n // Ticket 155: tickets:get (read-only inspection)\n inputSchema = {\n type: \"object\" as const,\n properties: {\n ticketSlug: {\n type: \"string\",\n description: \"Ticket number (e.g., '102') or full slug\",\n },\n },\n required: [\"ticketSlug\"],\n };\n } else if (tt.type === 'list') {\n // Ticket 155: tickets:list (no parameters - shows active queue)\n inputSchema = {\n type: \"object\" as const,\n properties: {},\n required: [],\n };\n } else if (tt.type === 'update') {\n // Ticket 293: tickets:update (append content to ticket)\n inputSchema = {\n type: \"object\" as const,\n properties: {\n ticketSlug: {\n type: \"string\",\n description: \"Ticket number (e.g., '102') or full slug (e.g., '102-fix-auth')\",\n },\n content: {\n type: \"string\",\n description: \"Update content to append to the ticket (markdown supported)\",\n },\n },\n required: [\"ticketSlug\", \"content\"],\n };\n } else if (tt.type === 'yolo_start') {\n // YOLO watcher: start the daemon\n inputSchema = {\n type: \"object\" as const,\n properties: {\n pollIntervalMs: {\n type: \"number\",\n description: \"Milliseconds between polls (default: 30000 = 30s)\",\n },\n maxParallel: {\n type: \"number\",\n description: \"Maximum concurrent ticket executions (default: 1)\",\n },\n ticketTimeout: {\n type: \"number\",\n description: \"Timeout for ticket execution in ms (default: 1800000 = 30 min)\",\n },\n enableNotifications: {\n type: \"boolean\",\n description: \"Show macOS notifications (default: true)\",\n },\n workingDirectory: {\n type: \"string\",\n description: \"Working directory for Claude Code sessions (default: current working directory)\",\n },\n },\n required: [],\n };\n } else if (tt.type === 'yolo_stop') {\n // YOLO watcher: stop the daemon\n inputSchema = {\n type: \"object\" as const,\n properties: {},\n required: [],\n };\n } else if (tt.type === 'yolo_status') {\n // YOLO watcher: get status\n inputSchema = {\n type: \"object\" as const,\n properties: {},\n required: [],\n };\n } else {\n // Fallback: no params\n inputSchema = {\n type: \"object\" as const,\n properties: {},\n required: [],\n };\n }\n\n return {\n name: tt.name,\n description: tt.description,\n inputSchema,\n };\n }),\n // Global prompts_run tool\n {\n name: \"prompts_run\",\n description: \"Execute a prompt by slug. Use prompts_list to list available prompts.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n slug: {\n type: \"string\",\n description: \"Prompt slug to execute (e.g., 'code-review', 'plan')\",\n },\n },\n required: [\"slug\"],\n },\n },\n // Global prompts_list tool\n {\n name: \"prompts_list\",\n description: \"List all available prompts\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n search: {\n type: \"string\",\n description: \"Optional search term to filter prompts by name or description (case-insensitive)\",\n },\n },\n },\n },\n // Global prompts_update tool (CLI-based prompt editing)\n {\n name: \"prompts_update\",\n description: \"Read or update a prompt. Without content: returns prompt with context and editing guidance. With content: saves new version.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n slug: {\n type: \"string\",\n description: \"Prompt slug to read or update (supports fuzzy matching)\",\n },\n content: {\n type: \"string\",\n description: \"New prompt content (omit for read-only mode)\",\n },\n changeLogMessage: {\n type: \"string\",\n description: \"Optional changelog message for the version\",\n },\n },\n required: [\"slug\"],\n },\n },\n // Dynamic per-prompt tools (each prompt as its own global tool)\n ...dynamicPromptTools.map((pt) => ({\n name: pt.name,\n description: pt.description,\n inputSchema: {\n type: \"object\" as const,\n properties: {},\n required: [],\n },\n })),\n ];\n\n return { tools };\n });\n\n // Register call_tool handler\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const toolName = request.params.name;\n\n // Handle global prompts_run tool\n if (toolName === \"prompts_run\") {\n const parsedArgs = parsePromptsRunArgs(request.params.arguments);\n\n if (!parsedArgs) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing 'slug' parameter. Provide prompt slug (e.g., 'code-review', 'plan').\n\nUse \\`prompts_list\\` to list available prompts.`,\n },\n ],\n isError: true,\n };\n }\n\n const { slug: promptSlug } = parsedArgs;\n\n // Execute the prompt using account-scoped lookup\n try {\n const result = await fetchAndExecuteAccountScopedPrompt(\n promptSlug,\n config,\n convexClient\n );\n\n // Convert prompt result to tool result format\n const promptText = result.messages\n .map((msg) => msg.content.text)\n .join('\\n\\n');\n\n return {\n content: [\n {\n type: \"text\",\n text: promptText,\n },\n ],\n };\n } catch (error) {\n // Handle ambiguous prompt matches with helpful suggestions\n if (error instanceof AmbiguousPromptError) {\n const suggestionsList = error.suggestions.map((s) => ` • ${s}`).join(\"\\n\");\n console.error(`[MCP] ${toolName} ambiguous match:`, error.suggestions);\n return {\n content: [\n {\n type: \"text\",\n text: `Multiple prompts match \"${promptSlug}\". Please specify one of:\\n\\n${suggestionsList}\\n\\nExample: \\`prompts_run ${error.suggestions[0]}\\``,\n },\n ],\n isError: true,\n };\n }\n\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] ${toolName} error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error executing prompt \"${promptSlug}\": ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n }\n\n // Handle global prompts_list tool (account-scoped prompt listing)\n // Uses cached metadata (lightweight) - no need to fetch full content for listing\n if (toolName === \"prompts_list\") {\n const { search: searchTerm } = parsePromptsListArgs(request.params.arguments);\n\n // Use account-scoped prompts (global, no project filter)\n let filteredPrompts = [...accountScopedPrompts];\n\n // Filter by search term if provided\n if (searchTerm) {\n const lowerSearch = searchTerm.toLowerCase();\n filteredPrompts = filteredPrompts.filter(\n (p) =>\n p.slug.toLowerCase().includes(lowerSearch) ||\n p.description?.toLowerCase().includes(lowerSearch)\n );\n }\n\n // Sort prompts by slug\n filteredPrompts.sort((a, b) => a.slug.localeCompare(b.slug));\n\n // Build user prompts list\n const userPromptList = filteredPrompts\n .map((p) => {\n const desc = p.description || \"No description\";\n return `• ${p.slug}\\n Description: ${desc}`;\n })\n .join(\"\\n\\n\");\n\n const summary = searchTerm\n ? `Found ${filteredPrompts.length} prompt(s) matching \"${searchTerm}\":`\n : `Available prompts (${filteredPrompts.length} total):`;\n\n return {\n content: [\n {\n type: \"text\",\n text: userPromptList\n ? `${summary}\\n\\n${userPromptList}`\n : `${summary}\\n\\nNo prompts found.`,\n },\n ],\n };\n }\n\n // Handle global prompts_update tool (CLI-based prompt editing)\n if (toolName === \"prompts_update\") {\n const parsedArgs = parsePromptsUpdateArgs(request.params.arguments);\n\n if (!parsedArgs) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing 'slug' parameter. Provide prompt slug to read or update.\n\nUsage:\n- Read mode: prompts_update { slug: \"my-prompt\" }\n- Write mode: prompts_update { slug: \"my-prompt\", content: \"new content...\" }`,\n },\n ],\n isError: true,\n };\n }\n\n try {\n const result = await convexClient.mutation<UpdatePromptResult>(\n \"mcp_prompts:updateMcpPrompt\",\n {\n ...buildAuthArgs(config),\n promptSlug: parsedArgs.slug,\n content: parsedArgs.content,\n changeLogMessage: parsedArgs.changeLogMessage,\n }\n );\n\n if (!result.success) {\n // Handle error (not found, ambiguous)\n const errorResult = result as { success: false; error: string; suggestions?: string[] };\n if (errorResult.suggestions) {\n const suggestionsList = errorResult.suggestions.map((s) => ` • ${s}`).join(\"\\n\");\n return {\n content: [\n {\n type: \"text\",\n text: `${errorResult.error}\\n\\nDid you mean one of these?\\n${suggestionsList}`,\n },\n ],\n isError: true,\n };\n }\n return {\n content: [{ type: \"text\", text: errorResult.error }],\n isError: true,\n };\n }\n\n // Handle read mode response\n if (result.mode === \"read\") {\n const readResult = result as {\n mode: \"read\";\n matchType: string;\n slug: string;\n description: string;\n content: string;\n version: number;\n context: object;\n editingGuidance: string;\n hasAiTags?: boolean;\n aiTags?: string[];\n };\n\n // Format the response with all context\n const matchNote = readResult.matchType !== \"exact\"\n ? `\\n_Note: Matched via ${readResult.matchType} match_`\n : \"\";\n\n // AI tag notice if tags detected\n const aiTagNotice = readResult.hasAiTags\n ? `\\n\\n## ⚡ AI Tags Detected\\nThis prompt contains AI tags that need processing: **${readResult.aiTags?.join(\", \")}**\\n\\nProcess these tags by reading the instructions in each tag, generating appropriate content, and saving the result with the content parameter.`\n : \"\";\n\n return {\n content: [\n {\n type: \"text\",\n text: `# Prompt: ${readResult.slug} (v${readResult.version})${matchNote}${aiTagNotice}\n\n## Description\n${readResult.description || \"No description\"}\n\n## Content\n\\`\\`\\`\n${readResult.content}\n\\`\\`\\`\n\n## Available Resources\n\\`\\`\\`json\n${JSON.stringify(readResult.context, null, 2)}\n\\`\\`\\`\n\n${readResult.editingGuidance}\n\n---\n_To update this prompt, call prompts_update with content parameter._`,\n },\n ],\n };\n }\n\n // Handle write mode response\n if (result.mode === \"write\") {\n const writeResult = result as {\n mode: \"write\";\n slug: string;\n version: number;\n updatedAt: string;\n message: string;\n };\n\n return {\n content: [\n {\n type: \"text\",\n text: `✅ ${writeResult.message}\n\nPrompt: ${writeResult.slug}\nVersion: ${writeResult.version}\nUpdated: ${writeResult.updatedAt}`,\n },\n ],\n };\n }\n\n // Shouldn't reach here, but handle gracefully\n return {\n content: [{ type: \"text\", text: \"Unknown response format\" }],\n isError: true,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] prompts_update error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error updating prompt: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n }\n\n // Handle ticket tool invocations (Ticket 135, 153, unified work)\n const ticketTool = dynamicTicketTools.find((tt) => tt.name === toolName);\n if (ticketTool) {\n if (ticketTool.type === 'work') {\n // Unified work command (replaces both open and work)\n const { ticketSlug } = parseWorkArgs(request.params.arguments);\n\n try {\n // Call mutation (not query) - this can move ticket from open to working\n const result = await convexClient.mutation(\n \"mcp_tickets:workMcpTicket\",\n {\n ...buildAuthArgs(config),\n projectSlug: ticketTool.projectSlug,\n ticketSlug, // Optional: specific ticket to work on\n }\n ) as WorkTicketResult | AccessDeniedResult | null;\n\n if (!result) {\n const message = ticketSlug\n ? `Ticket \"${ticketSlug}\" not found or not in open/working status in project \"${ticketTool.projectSlug}\".`\n : `No open tickets in project \"${ticketTool.projectSlug}\".`;\n return {\n content: [\n {\n type: \"text\",\n text: message,\n },\n ],\n };\n }\n\n // Handle access denied response\n if (isAccessDenied(result)) {\n return {\n content: [\n {\n type: \"text\",\n text: `Access denied: ${result.error}`,\n },\n ],\n isError: true,\n };\n }\n\n // Now TypeScript knows result is WorkTicketResult\n const workResult = result as WorkTicketResult;\n const startedInfo = workResult.startedAt\n ? `\\nStarted: ${new Date(workResult.startedAt).toISOString()}`\n : '';\n\n // Different footer based on whether this was a fresh open or resumption\n const statusNote = workResult.wasOpened\n ? `_Ticket moved to working status. ${workResult.remainingTickets} ticket(s) remaining in queue._`\n : `_Resuming work on this ticket. ${workResult.remainingTickets} ticket(s) in queue._`;\n\n return {\n content: [\n {\n type: \"text\",\n text: `# Ticket: ${workResult.slug} [WORKING]${startedInfo}\n\n${workResult.content}\n\n---\n${statusNote}`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_work error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error getting work: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'create') {\n // Handle tickets:create (Ticket 149)\n const parsedArgs = parseCreateArgs(request.params.arguments);\n\n if (!parsedArgs) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing content parameter. Provide the ticket content (first line becomes the slug).`,\n },\n ],\n isError: true,\n };\n }\n\n const { content, tags } = parsedArgs;\n\n try {\n const result = await convexClient.mutation(\n \"mcp_tickets:createMcpTicket\",\n {\n ...buildAuthArgs(config),\n projectSlug: ticketTool.projectSlug,\n content,\n tags,\n }\n ) as CreateTicketResult;\n\n return {\n content: [\n {\n type: \"text\",\n text: `✅ Created ticket [${result.slug}] in backlog for project \"${ticketTool.projectSlug}\".\n\nPosition: #${result.position} of ${result.totalBacklog} backlog tickets\nPreview: ${result.preview}\n\n_Ticket created in backlog. Use \\`tickets_work ${result.slug}\\` to move it to working status._`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_create error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error creating ticket: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'close') {\n // Ticket 151: Handle tickets:close\n const parsedArgs = parseCloseArgs(request.params.arguments);\n\n if (!parsedArgs) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing ticketSlug parameter. Usage: Provide a ticket number (e.g., \"102\") or full slug (e.g., \"102-fix-auth\").`,\n },\n ],\n isError: true,\n };\n }\n\n const { ticketSlug } = parsedArgs;\n\n try {\n const result = await convexClient.mutation(\n \"mcp_tickets:closeMcpTicket\",\n {\n ...buildAuthArgs(config),\n projectSlug: ticketTool.projectSlug,\n ticketSlug,\n }\n ) as CloseTicketResult;\n\n return {\n content: [\n {\n type: \"text\",\n text: `✅ Ticket [${result.slug}] closed in project \"${ticketTool.projectSlug}\".\n\nClosed at: ${new Date(result.closedAt).toISOString()}\n\n_Reminder: Ensure all embedded \\`[RUN_PROMPT ...]\\` directives were executed before closing._`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_close error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error closing ticket: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'search') {\n // Ticket 155: Handle tickets:search\n const parsedArgs = parseSearchArgs(request.params.arguments);\n\n if (!parsedArgs) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Search query must be at least 3 characters`,\n },\n ],\n isError: true,\n };\n }\n\n const { query } = parsedArgs;\n\n try {\n const result = await convexClient.query(\n \"mcp_tickets:searchMcpTickets\",\n {\n ...buildAuthArgs(config),\n projectSlug: ticketTool.projectSlug,\n query,\n }\n ) as SearchTicketItem[];\n\n if (result.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No tickets found matching \"${query}\" in project \"${ticketTool.projectSlug}\".`,\n },\n ],\n };\n }\n\n // Format as list with status badges\n const formattedList = result\n .map((t) => {\n const statusBadge = t.status === 'open' ? '🟢' : t.status === 'working' ? '🟡' : t.status === 'closed' ? '⚫' : '⚪';\n const num = t.ticketNumber ? `#${t.ticketNumber}` : '';\n return `${statusBadge} ${num} ${t.slug}\\n ${t.matchSnippet}`;\n })\n .join('\\n\\n');\n\n return {\n content: [\n {\n type: \"text\",\n text: `Found ${result.length} ticket(s) matching \"${query}\":\\n\\n${formattedList}`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_search error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error searching tickets: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'get') {\n // Ticket 155: Handle tickets:get (read-only inspection)\n const parsedArgs = parseGetArgs(request.params.arguments);\n\n if (!parsedArgs) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing ticketSlug parameter. Provide a ticket number (e.g., \"102\") or full slug.`,\n },\n ],\n isError: true,\n };\n }\n\n const { ticketSlug } = parsedArgs;\n\n try {\n // Uses existing getMcpTicket query - read-only inspection\n const result = await convexClient.query(\n \"mcp_tickets:getMcpTicket\",\n {\n ...buildAuthArgs(config),\n projectSlug: ticketTool.projectSlug,\n ticketSlug,\n }\n ) as GetTicketResult | AccessDeniedResult | null;\n\n if (!result) {\n return {\n content: [\n {\n type: \"text\",\n text: `Ticket \"${ticketSlug}\" not found in project \"${ticketTool.projectSlug}\".`,\n },\n ],\n };\n }\n\n // Handle access denied response\n if (isAccessDenied(result)) {\n return {\n content: [\n {\n type: \"text\",\n text: `Access denied: ${result.error}`,\n },\n ],\n isError: true,\n };\n }\n\n // Now TypeScript knows result is GetTicketResult\n const getResult = result as GetTicketResult;\n\n // Build status line\n const statusBadge = getResult.status.toUpperCase();\n const startedInfo = getResult.startedAt ? `\\nStarted: ${new Date(getResult.startedAt).toISOString()}` : '';\n const closedInfo = getResult.closedAt ? `\\nClosed: ${new Date(getResult.closedAt).toISOString()}` : '';\n\n return {\n content: [\n {\n type: \"text\",\n text: `# Ticket: ${getResult.slug} [${statusBadge}]${startedInfo}${closedInfo}\n\n${getResult.content}\n\n---\n_Read-only inspection. Use tickets_work to start working on this ticket._`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_get error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error getting ticket: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'list') {\n // Ticket 155: Handle tickets:list (active queue)\n try {\n const result = await convexClient.query(\n \"mcp_tickets:listMcpTickets\",\n {\n ...buildAuthArgs(config),\n projectSlug: ticketTool.projectSlug,\n }\n ) as ListTicketItem[];\n\n if (result.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No active tickets in project \"${ticketTool.projectSlug}\". All tickets are closed or archived.`,\n },\n ],\n };\n }\n\n // Group by status for clear display\n const openTickets = result.filter(t => t.status === 'open');\n const workingTickets = result.filter(t => t.status === 'working');\n const backlogTickets = result.filter(t => t.status === 'backlog');\n\n const formatTicketLine = (t: typeof result[0]) => {\n const num = t.ticketNumber ? `#${t.ticketNumber}` : '';\n return ` ${t.position}. ${num} ${t.slug}\\n ${t.preview}`;\n };\n\n const sections: string[] = [];\n\n if (openTickets.length > 0) {\n sections.push(`**🟢 Open (${openTickets.length})**\\n${openTickets.map(formatTicketLine).join('\\n')}`);\n }\n if (workingTickets.length > 0) {\n sections.push(`**🟡 Working (${workingTickets.length})**\\n${workingTickets.map(formatTicketLine).join('\\n')}`);\n }\n if (backlogTickets.length > 0) {\n sections.push(`**⚪ Backlog (${backlogTickets.length})**\\n${backlogTickets.map(formatTicketLine).join('\\n')}`);\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: `# Active Queue: ${ticketTool.projectSlug}\\n\\n${result.length} ticket(s) in queue\\n\\n${sections.join('\\n\\n')}`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_list error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error listing tickets: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'update') {\n // Ticket 293: Handle tickets:update (append content to ticket)\n const parsedArgs = parseUpdateArgs(request.params.arguments);\n\n if (!parsedArgs) {\n // Determine which argument is missing for a more specific error message\n const args = request.params.arguments as Record<string, unknown> | undefined;\n const hasTicketSlug = typeof args?.ticketSlug === 'string' && args.ticketSlug;\n const hasContent = typeof args?.content === 'string' && args.content;\n\n if (!hasTicketSlug) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing ticketSlug parameter. Provide a ticket number (e.g., \"102\") or full slug.`,\n },\n ],\n isError: true,\n };\n }\n if (!hasContent) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing content parameter. Provide the update content to append to the ticket.`,\n },\n ],\n isError: true,\n };\n }\n // Should not reach here, but just in case\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing required parameters.`,\n },\n ],\n isError: true,\n };\n }\n\n const { ticketSlug, content } = parsedArgs;\n\n try {\n const result = await convexClient.mutation(\n \"mcp_tickets:updateMcpTicket\",\n {\n ...buildAuthArgs(config),\n projectSlug: ticketTool.projectSlug,\n ticketSlug,\n content,\n }\n ) as UpdateTicketResult;\n\n return {\n content: [\n {\n type: \"text\",\n text: `✅ Ticket [${result.slug}] updated in project \"${ticketTool.projectSlug}\".\nUpdated at: ${new Date(result.updatedAt).toISOString()}\n_Ticket content has been appended with your update._`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_update error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error updating ticket: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'yolo_start') {\n // YOLO watcher: spawn background daemon for automatic ticket execution\n const args = parseWatcherStartArgs(request.params.arguments);\n\n // Get working directory - use provided or fall back to cwd\n const workingDirectory = args.workingDirectory ?? process.cwd();\n\n try {\n const result = startWatcher(\n {\n projectSlug: ticketTool.projectSlug,\n projectToken: config.projectToken,\n convexUrl: config.convexUrl,\n workingDirectory,\n },\n args\n );\n\n if (result.success) {\n const configInfo = result.config\n ? `\\n\\n**Configuration:**\\n- Poll Interval: ${result.config.pollIntervalMs / 1000}s\\n- Max Parallel: ${result.config.maxParallel}\\n- Ticket Timeout: ${result.config.ticketTimeout / 1000 / 60} min\\n- Working Directory: ${result.config.workingDirectory}`\n : \"\";\n\n const text = result.alreadyRunning\n ? `ℹ️ ${result.message}${configInfo}`\n : `✅ ${result.message}\\n\\nThe daemon is now polling for YOLO-flagged tickets and will spawn Claude CLI processes to execute them.\\n\\nLogs: \\`.ppm/yolo/logs/\\`${configInfo}`;\n\n return {\n content: [{ type: \"text\", text }],\n };\n } else {\n return {\n content: [{ type: \"text\", text: `❌ ${result.message}` }],\n isError: true,\n };\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_yolo_start error:`, error);\n return {\n content: [{ type: \"text\", text: `Error starting watcher daemon: ${errorMessage}` }],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'yolo_stop') {\n // YOLO watcher: stop the background daemon\n const workingDirectory = process.cwd();\n\n try {\n const result = stopWatcher(workingDirectory);\n\n if (result.success) {\n return {\n content: [{\n type: \"text\",\n text: result.wasRunning\n ? `✅ ${result.message}\\n\\nThe daemon has been stopped. Any running ticket executions will complete.`\n : `ℹ️ ${result.message}`,\n }],\n };\n } else {\n return {\n content: [{ type: \"text\", text: `❌ ${result.message}` }],\n isError: true,\n };\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_yolo_stop error:`, error);\n return {\n content: [{ type: \"text\", text: `Error stopping watcher daemon: ${errorMessage}` }],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'yolo_status') {\n // YOLO watcher: get daemon status\n const workingDirectory = process.cwd();\n\n try {\n const result = getWatcherStatus(workingDirectory);\n\n // Return the formatted status\n if (result.formattedStatus) {\n return {\n content: [{ type: \"text\", text: result.formattedStatus }],\n };\n }\n\n return {\n content: [{ type: \"text\", text: `ℹ️ ${result.message ?? 'No watcher status available'}` }],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_yolo_status error:`, error);\n return {\n content: [{ type: \"text\", text: `Error getting watcher status: ${errorMessage}` }],\n isError: true,\n };\n }\n }\n }\n\n // Handle per-prompt tool invocations (e.g., \"code-review\" - now global)\n const promptTool = dynamicPromptTools.find((pt) => pt.name === toolName);\n if (promptTool) {\n try {\n const result = await fetchAndExecuteAccountScopedPrompt(\n promptTool.promptSlug,\n config,\n convexClient\n );\n\n const promptText = result.messages\n .map((msg) => msg.content.text)\n .join('\\n\\n');\n\n return {\n content: [{ type: \"text\", text: promptText }],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] ${toolName} error:`, error);\n return {\n content: [{ type: \"text\", text: `Error executing prompt: ${errorMessage}` }],\n isError: true,\n };\n }\n }\n\n // Unknown tool\n throw new Error(`Unknown tool: ${toolName}. Use prompts_run to execute prompts by name, or check available tools.`);\n });\n\n // Start server with stdio transport\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n console.error(\"[MCP] Server started successfully\");\n console.error(`[MCP] Deployment: ${config.isDev ? \"DEVELOPMENT\" : \"PRODUCTION\"}`);\n console.error(`[MCP] Convex URL: ${config.convexUrl}`);\n console.error(`[MCP] Data mode: REAL-TIME (fetches fresh data on each invocation)`);\n\n // List all prompts\n const allPromptNames = [...Array.from(promptNames)].sort();\n console.error(`[MCP] Prompts available: ${allPromptNames.join(\", \")}`);\n console.error(`[MCP] - Total prompts: ${promptNames.size}`);\n\n // Keep the event loop alive with a heartbeat\n // This prevents Node from exiting when there are no active handles\n setInterval(() => {\n // Heartbeat every 60 seconds to keep process alive\n }, 60000);\n\n // Return a promise that never resolves to keep the server running\n return new Promise<void>(() => {\n // The transport handles stdin/stdout communication\n // The interval above keeps the event loop active\n });\n}\n\n/**\n * Validate project token with Convex\n *\n * Project tokens are now stored directly on the projects table.\n * Returns project details if valid, null if invalid.\n */\n/**\n * Response type from validateProjectToken query\n */\ninterface ValidateProjectTokenResponse {\n valid: boolean;\n projectId?: string;\n projectSlug?: string;\n ownerId?: string;\n}\n\nasync function validateProjectToken(\n client: ConvexHttpClient,\n token: string\n): Promise<ProjectTokenValidation> {\n try {\n // Cast to our typed client for string-based function references\n const typedClient = client as ConvexClientWithStringRefs;\n const result = await typedClient.query<ValidateProjectTokenResponse | null>(\n \"projects:validateProjectToken\",\n { token }\n );\n\n if (result && result.valid) {\n return {\n valid: true,\n projectId: result.projectId,\n projectSlug: result.projectSlug,\n ownerId: result.ownerId,\n };\n }\n return { valid: false, error: \"Invalid project token\" };\n } catch (error) {\n return {\n valid: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n };\n }\n}\n\n// Note: fetchMcpPromptMetadata removed - prompts now account-scoped\n// Note: fetchMcpCommands and buildCommandName removed - commands resource eliminated\n// Note: API key-based functions removed - all auth now uses project tokens\n","// ============================================================================\n// MCP Tool Argument Types\n// ============================================================================\n\n/**\n * Arguments for the tickets_work tool\n * Optional ticketSlug: if provided, work on specific ticket; otherwise get next from queue\n */\nexport interface WorkToolArgs {\n ticketSlug?: string;\n}\n\n/**\n * Arguments for the tickets_close tool\n * Required ticketSlug to identify which ticket to close\n */\nexport interface CloseToolArgs {\n ticketSlug: string;\n}\n\n/**\n * Arguments for the tickets_create tool\n * Required content where first line becomes the slug\n * Optional tags array for assigning existing tags at creation\n */\nexport interface CreateToolArgs {\n content: string;\n tags?: string[];\n}\n\n/**\n * Arguments for the tickets_search tool\n * Required query string (min 3 characters)\n */\nexport interface SearchToolArgs {\n query: string;\n}\n\n/**\n * Arguments for the tickets_get tool\n * Required ticketSlug to identify which ticket to retrieve\n */\nexport interface GetToolArgs {\n ticketSlug: string;\n}\n\n/**\n * Arguments for the tickets_list tool\n * No arguments required - returns all active tickets\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface ListToolArgs {}\n\n/**\n * Arguments for the tickets_update tool\n * Required ticketSlug and content to append\n */\nexport interface UpdateToolArgs {\n ticketSlug: string;\n content: string;\n}\n\n/**\n * Arguments for the prompts_run tool\n * Required slug to identify which prompt to execute\n */\nexport interface PromptsRunArgs {\n slug: string;\n}\n\n/**\n * Arguments for the prompts_list tool\n * Optional search term to filter prompts\n */\nexport interface PromptsListArgs {\n search?: string;\n}\n\n/**\n * Arguments for the prompts_update tool\n * Required slug, optional content for write mode\n */\nexport interface PromptsUpdateArgs {\n slug: string;\n content?: string;\n changeLogMessage?: string;\n}\n\n// ============================================================================\n// Convex Response Types (for MCP mutations/queries)\n// ============================================================================\n\n/**\n * Response from workMcpTicket mutation\n */\nexport interface WorkTicketResult {\n slug: string;\n ticketNumber?: number;\n status: string;\n content: string;\n startedAt?: number;\n remainingTickets: number;\n wasOpened: boolean;\n}\n\n/**\n * Access denied response from ticket operations\n */\nexport interface AccessDeniedResult {\n error: string;\n accessDenied: true;\n}\n\n/**\n * Type guard to check if a result is an access denied response\n */\nexport function isAccessDenied(result: unknown): result is AccessDeniedResult {\n return (\n typeof result === 'object' &&\n result !== null &&\n 'accessDenied' in result &&\n (result as AccessDeniedResult).accessDenied === true\n );\n}\n\n/**\n * Response from createMcpTicket mutation\n */\nexport interface CreateTicketResult {\n slug: string;\n ticketNumber?: number;\n preview: string;\n position: number;\n totalBacklog: number;\n}\n\n/**\n * Response from closeMcpTicket mutation\n */\nexport interface CloseTicketResult {\n slug: string;\n status: string;\n closedAt: number;\n}\n\n/**\n * Response from updateMcpTicket mutation\n */\nexport interface UpdateTicketResult {\n slug: string;\n ticketNumber?: number;\n status: string;\n updatedAt: number;\n}\n\n/**\n * Response from getMcpTicket query\n */\nexport interface GetTicketResult {\n slug: string;\n ticketNumber?: number;\n content: string;\n status: string;\n createdAt: number;\n startedAt?: number;\n closedAt?: number;\n}\n\n/**\n * Response from listMcpTickets query\n */\nexport interface ListTicketItem {\n position: number;\n slug: string;\n ticketNumber?: number;\n status: string;\n preview: string;\n createdAt: number;\n startedAt?: number;\n}\n\n/**\n * Response from searchMcpTickets query\n */\nexport interface SearchTicketItem {\n slug: string;\n ticketNumber?: number;\n status: string;\n matchSnippet: string;\n createdAt: number;\n}\n\n/**\n * Response from prompt metadata query\n */\nexport interface PromptMetadata {\n slug: string;\n description?: string;\n folderSlug?: string;\n}\n\n/**\n * Response from updateMcpPrompt mutation (read mode)\n */\nexport interface UpdatePromptReadResult {\n success: true;\n mode: \"read\";\n matchType: \"exact\" | \"prefix\" | \"contains\";\n slug: string;\n description: string;\n content: string;\n version: number;\n context: {\n variables: Array<{ slug: string; reference: string; type: string; description: string }>;\n snippets: Array<{ slug: string; reference: string; description: string }>;\n prompts: Array<{ slug: string; reference: string; description: string }>;\n };\n editingGuidance: string;\n}\n\n/**\n * Response from updateMcpPrompt mutation (write mode)\n */\nexport interface UpdatePromptWriteResult {\n success: true;\n mode: \"write\";\n slug: string;\n version: number;\n updatedAt: string;\n message: string;\n}\n\n/**\n * Response from updateMcpPrompt mutation (error)\n */\nexport interface UpdatePromptErrorResult {\n success: false;\n error: string;\n suggestions?: string[];\n}\n\n/**\n * Union type for all updateMcpPrompt responses\n */\nexport type UpdatePromptResult =\n | UpdatePromptReadResult\n | UpdatePromptWriteResult\n | UpdatePromptErrorResult;\n\n/**\n * Account-scoped prompt match result types\n */\nexport type AccountScopedPromptResult =\n | { matchType: \"exact\" | \"prefix\" | \"contains\"; slug: string; description?: string; flattenedPrompt?: string }\n | { matchType: \"ambiguous\"; suggestions: string[] }\n | null;\n\n// ============================================================================\n// Argument Parsing Functions\n// ============================================================================\n\n/**\n * Parse and validate arguments for tickets_work tool\n */\nexport function parseWorkArgs(args: unknown): WorkToolArgs {\n const parsed = args as Record<string, unknown> | undefined;\n return {\n ticketSlug: typeof parsed?.ticketSlug === 'string' ? parsed.ticketSlug : undefined,\n };\n}\n\n/**\n * Parse and validate arguments for tickets_close tool\n * Returns null if required argument is missing\n */\nexport function parseCloseArgs(args: unknown): CloseToolArgs | null {\n const parsed = args as Record<string, unknown> | undefined;\n const ticketSlug = typeof parsed?.ticketSlug === 'string' ? parsed.ticketSlug : undefined;\n if (!ticketSlug) return null;\n return { ticketSlug };\n}\n\n/**\n * Parse and validate arguments for tickets_create tool\n * Returns null if required argument is missing\n */\nexport function parseCreateArgs(args: unknown): CreateToolArgs | null {\n const parsed = args as Record<string, unknown> | undefined;\n const content = typeof parsed?.content === 'string' ? parsed.content : undefined;\n if (!content) return null;\n\n // Parse optional tags array - filter to valid strings only\n let tags: string[] | undefined;\n if (Array.isArray(parsed?.tags)) {\n tags = parsed.tags.filter((t): t is string => typeof t === 'string');\n if (tags.length === 0) tags = undefined;\n }\n\n return { content, tags };\n}\n\n/**\n * Parse and validate arguments for tickets_search tool\n * Returns null if required argument is missing or too short\n */\nexport function parseSearchArgs(args: unknown): SearchToolArgs | null {\n const parsed = args as Record<string, unknown> | undefined;\n const query = typeof parsed?.query === 'string' ? parsed.query : undefined;\n if (!query || query.length < 3) return null;\n return { query };\n}\n\n/**\n * Parse and validate arguments for tickets_get tool\n * Returns null if required argument is missing\n */\nexport function parseGetArgs(args: unknown): GetToolArgs | null {\n const parsed = args as Record<string, unknown> | undefined;\n const ticketSlug = typeof parsed?.ticketSlug === 'string' ? parsed.ticketSlug : undefined;\n if (!ticketSlug) return null;\n return { ticketSlug };\n}\n\n/**\n * Parse arguments for tickets_list tool (no args required)\n */\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport function parseListArgs(args: unknown): ListToolArgs {\n return {};\n}\n\n/**\n * Parse and validate arguments for tickets_update tool\n * Returns null if any required argument is missing\n */\nexport function parseUpdateArgs(args: unknown): UpdateToolArgs | null {\n const parsed = args as Record<string, unknown> | undefined;\n const ticketSlug = typeof parsed?.ticketSlug === 'string' ? parsed.ticketSlug : undefined;\n const content = typeof parsed?.content === 'string' ? parsed.content : undefined;\n if (!ticketSlug || !content) return null;\n return { ticketSlug, content };\n}\n\n/**\n * Parse and validate arguments for prompts_run tool\n * Returns null if required argument is missing\n */\nexport function parsePromptsRunArgs(args: unknown): PromptsRunArgs | null {\n const parsed = args as Record<string, unknown> | undefined;\n const slug = typeof parsed?.slug === 'string' ? parsed.slug : undefined;\n if (!slug) return null;\n return { slug };\n}\n\n/**\n * Parse arguments for prompts_list tool\n */\nexport function parsePromptsListArgs(args: unknown): PromptsListArgs {\n const parsed = args as Record<string, unknown> | undefined;\n return {\n search: typeof parsed?.search === 'string' ? parsed.search : undefined,\n };\n}\n\n/**\n * Parse and validate arguments for prompts_update tool\n * Returns null if required slug is missing\n */\nexport function parsePromptsUpdateArgs(args: unknown): PromptsUpdateArgs | null {\n const parsed = args as Record<string, unknown> | undefined;\n const slug = typeof parsed?.slug === 'string' ? parsed.slug : undefined;\n if (!slug) return null;\n return {\n slug,\n content: typeof parsed?.content === 'string' ? parsed.content : undefined,\n changeLogMessage: typeof parsed?.changeLogMessage === 'string' ? parsed.changeLogMessage : undefined,\n };\n}\n\n// ============================================================================\n// Legacy Types (kept for backwards compatibility)\n// ============================================================================\n\n/**\n * @deprecated Legacy type - prompts are now account-scoped (no projectSlug)\n * Kept for backwards compatibility during transition period.\n */\nexport interface McpPrompt {\n slug: string;\n name: string;\n description?: string;\n flattenedPrompt?: string;\n folderPath?: string; // Folder organization (used for ZIP downloads, not slash commands)\n projectSlug?: string; // @deprecated - prompts are now account-scoped\n projectName?: string; // @deprecated - prompts are now account-scoped\n}\n\n/**\n * @deprecated Legacy type - prompts are now account-scoped (no projectSlug)\n * Kept for backwards compatibility during transition period.\n */\nexport interface McpPromptMetadata {\n slug: string;\n description?: string;\n projectSlug?: string; // @deprecated - prompts are now account-scoped\n}\n\n// Note: McpCommandMetadata and McpCommandFull removed - commands resource eliminated\n\n/**\n * @deprecated Legacy type - prompts are now account-scoped (no projectSlug)\n * Kept for backwards compatibility during transition period.\n */\nexport interface McpPromptFull {\n slug: string;\n description?: string;\n flattenedPrompt?: string;\n projectSlug?: string; // @deprecated - prompts are now account-scoped\n}\n\n/**\n * @deprecated Legacy type - use account-scoped prompt queries instead\n * Kept for backwards compatibility during transition period.\n */\nexport type PromptMatchResult =\n | { matchType: \"exact\" | \"prefix\" | \"contains\"; slug: string; description?: string; flattenedPrompt?: string; projectSlug: string }\n | { matchType: \"ambiguous\"; suggestions: string[] }\n | null;\n\n/**\n * Configuration for the MCP server\n *\n * Authentication: Project Token (PPM_PROJECT_TOKEN)\n * Provides scoped access to a single project for both prompts and tickets.\n * The token's ownerId determines which prompts are accessible (account-scoped).\n *\n * Note: API Key auth (PPM_API_KEY) has been deprecated.\n * All authentication now uses project tokens.\n */\nexport interface ServerConfig {\n projectToken: string; // Required - scoped access to a single project\n isDev: boolean;\n convexUrl: string;\n}\n\n/**\n * Result of project token validation\n * Token is now stored directly on the projects table (single token per project)\n */\nexport interface ProjectTokenValidation {\n valid: boolean;\n projectId?: string;\n projectSlug?: string;\n ownerId?: string;\n error?: string;\n}\n\n// ============================================================================\n// YOLO Watcher Types\n// ============================================================================\n\n/**\n * Arguments for the tickets_yolo_start tool\n * All fields are optional - defaults are applied by the watcher\n */\nexport interface WatcherStartToolArgs {\n pollIntervalMs?: number;\n maxParallel?: number;\n ticketTimeout?: number;\n enableNotifications?: boolean;\n workingDirectory?: string;\n}\n\n/**\n * Parse and validate arguments for tickets_yolo_start tool\n * All arguments are optional with sensible defaults\n */\nexport function parseWatcherStartArgs(args: unknown): WatcherStartToolArgs {\n const parsed = args as Record<string, unknown> | undefined;\n\n return {\n pollIntervalMs: typeof parsed?.pollIntervalMs === \"number\" ? parsed.pollIntervalMs : undefined,\n maxParallel: typeof parsed?.maxParallel === \"number\" ? parsed.maxParallel : undefined,\n ticketTimeout: typeof parsed?.ticketTimeout === \"number\" ? parsed.ticketTimeout : undefined,\n enableNotifications: typeof parsed?.enableNotifications === \"boolean\" ? parsed.enableNotifications : undefined,\n workingDirectory: typeof parsed?.workingDirectory === \"string\" ? parsed.workingDirectory : undefined,\n };\n}\n","/**\n * Watcher Controller\n *\n * Handles MCP tool requests for YOLO watcher management:\n * - tickets_yolo_start: Spawn background daemon for automatic ticket execution\n * - tickets_yolo_stop: Stop the background daemon\n * - tickets_yolo_status: Get current status including completed tickets\n *\n * The daemon approach (Phase 3) uses a detached background process that:\n * - Polls for YOLO tickets at configurable intervals\n * - Spawns headless `claude` CLI processes for execution\n * - Captures output to log files\n * - Survives MCP session restarts\n */\n\nimport { spawn } from \"child_process\";\nimport { fileURLToPath } from \"url\";\nimport { dirname, join } from \"path\";\nimport type {\n WatcherConfig,\n WatcherStatus,\n WatcherStartArgs,\n CompletedTicket,\n} from \"./watcher_types.js\";\nimport {\n readStatus,\n writeStatus,\n readPid,\n isWatcherRunning,\n getWatcherDir,\n removePid,\n readTicketLog,\n} from \"./watcher_state.js\";\n\n// Re-export default config for use by callers\nexport { DEFAULT_WATCHER_CONFIG } from \"./watcher_types.js\";\n\n// ============================================================================\n// Result Types\n// ============================================================================\n\n/**\n * Result from tickets_yolo_start tool\n */\nexport interface WatcherStartResult {\n success: boolean;\n message: string;\n pid?: number;\n alreadyRunning?: boolean;\n config?: Omit<WatcherConfig, \"projectToken\">;\n}\n\n/**\n * Result from tickets_yolo_stop tool\n */\nexport interface WatcherStopResult {\n success: boolean;\n message: string;\n wasRunning: boolean;\n}\n\n/**\n * Result from tickets_yolo_status tool\n */\nexport interface WatcherStatusResult {\n success: boolean;\n status?: WatcherStatus;\n formattedStatus?: string;\n message?: string;\n}\n\n// ============================================================================\n// Start Watcher Daemon\n// ============================================================================\n\n/**\n * Start the YOLO watcher daemon.\n *\n * Spawns a detached background process that polls for tickets and\n * executes them using headless Claude CLI.\n *\n * @param baseConfig - Base configuration (projectSlug, token, convexUrl, workDir)\n * @param args - Optional overrides from MCP tool arguments\n * @returns Result with PID or error\n */\nexport function startWatcher(\n baseConfig: {\n projectSlug: string;\n projectToken: string;\n convexUrl: string;\n workingDirectory: string;\n },\n args: WatcherStartArgs | undefined\n): WatcherStartResult {\n const workingDirectory = args?.workingDirectory ?? baseConfig.workingDirectory;\n\n // Check if already running\n if (isWatcherRunning(workingDirectory)) {\n const existingPid = readPid(workingDirectory);\n const status = readStatus(workingDirectory);\n\n return {\n success: true,\n message: `Watcher already running (PID: ${existingPid})`,\n pid: existingPid,\n alreadyRunning: true,\n config: status?.config,\n };\n }\n\n // Build full configuration\n const config: WatcherConfig = {\n projectSlug: baseConfig.projectSlug,\n projectToken: baseConfig.projectToken,\n convexUrl: baseConfig.convexUrl,\n pollIntervalMs: args?.pollIntervalMs ?? 30000,\n maxParallel: args?.maxParallel ?? 1,\n ticketTimeout: args?.ticketTimeout ?? 1800000,\n enableNotifications: args?.enableNotifications ?? true,\n workingDirectory,\n };\n\n // Ensure state directory exists\n getWatcherDir(workingDirectory);\n\n // Get path to daemon script\n // The daemon is bundled alongside the main entry point\n const daemonPath = getDaemonPath();\n\n // Encode config as base64 to avoid shell escaping issues\n const configJson = JSON.stringify(config);\n const configB64 = Buffer.from(configJson).toString(\"base64\");\n\n try {\n // Spawn daemon as detached process\n const child = spawn(\"node\", [daemonPath, \"--config\", configB64], {\n detached: true,\n stdio: [\"ignore\", \"ignore\", \"ignore\"],\n cwd: workingDirectory,\n });\n\n // Unref so parent can exit independently\n child.unref();\n\n const pid = child.pid;\n\n if (!pid) {\n return {\n success: false,\n message: \"Failed to spawn daemon - no PID returned\",\n };\n }\n\n // Return success - daemon will write its own status file\n // Give it a moment to initialize\n const configWithoutToken: Omit<WatcherConfig, \"projectToken\"> = {\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 return {\n success: true,\n message: `Watcher started (PID: ${pid})`,\n pid,\n alreadyRunning: false,\n config: configWithoutToken,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n return {\n success: false,\n message: `Failed to start watcher: ${errorMessage}`,\n };\n }\n}\n\n/**\n * Get the path to the daemon script.\n *\n * The daemon is bundled as a separate entry point by tsup.\n * Since the source is in src/watcher/watcher_daemon.ts, the output\n * will be at dist/watcher/watcher_daemon.js.\n */\nfunction getDaemonPath(): string {\n // Get directory of current module (this file is in dist/ when bundled)\n const currentFile = fileURLToPath(import.meta.url);\n const currentDir = dirname(currentFile);\n\n // Daemon is in watcher/ subdirectory when bundled\n return join(currentDir, \"watcher\", \"watcher_daemon.js\");\n}\n\n// ============================================================================\n// Stop Watcher Daemon\n// ============================================================================\n\n/**\n * Stop the YOLO watcher daemon.\n *\n * Sends SIGTERM to the daemon process to trigger graceful shutdown.\n *\n * @param workingDirectory - Project root directory\n * @returns Result indicating success/failure\n */\nexport function stopWatcher(workingDirectory: string): WatcherStopResult {\n const pid = readPid(workingDirectory);\n\n if (!pid) {\n return {\n success: true,\n message: \"No watcher running (no PID file)\",\n wasRunning: false,\n };\n }\n\n // Check if process is actually running\n if (!isWatcherRunning(workingDirectory)) {\n // Clean up stale files\n removePid(workingDirectory);\n\n return {\n success: true,\n message: \"Watcher was not running (stale PID file cleaned up)\",\n wasRunning: false,\n };\n }\n\n try {\n // Send SIGTERM for graceful shutdown\n process.kill(pid, \"SIGTERM\");\n\n // Update status file\n const status = readStatus(workingDirectory);\n if (status) {\n writeStatus(workingDirectory, {\n ...status,\n state: \"stopped\",\n });\n }\n\n // Clean up PID file\n removePid(workingDirectory);\n\n return {\n success: true,\n message: `Watcher stopped (PID: ${pid})`,\n wasRunning: true,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n\n // Clean up even on error\n removePid(workingDirectory);\n\n return {\n success: false,\n message: `Failed to stop watcher: ${errorMessage}`,\n wasRunning: true,\n };\n }\n}\n\n// ============================================================================\n// Get Status\n// ============================================================================\n\n/**\n * Get the current watcher status.\n *\n * Reads from the status file and formats for display.\n *\n * @param workingDirectory - Project root directory\n * @returns Current status with formatted output\n */\nexport function getWatcherStatus(workingDirectory: string): WatcherStatusResult {\n const status = readStatus(workingDirectory);\n\n // Check if daemon is actually running\n const isRunning = isWatcherRunning(workingDirectory);\n\n // Update status if it says running but daemon is dead\n if (status?.state === \"running\" && !isRunning) {\n const updatedStatus: WatcherStatus = {\n ...status,\n state: \"stopped\",\n };\n writeStatus(workingDirectory, updatedStatus);\n\n return {\n success: true,\n status: updatedStatus,\n formattedStatus: formatStatus(updatedStatus),\n message: \"Watcher died unexpectedly - status updated\",\n };\n }\n\n if (!status) {\n return {\n success: true,\n message: \"No watcher session found for this project\",\n formattedStatus: formatStatus(null),\n };\n }\n\n return {\n success: true,\n status,\n formattedStatus: formatStatus(status),\n };\n}\n\n// ============================================================================\n// Status Formatting\n// ============================================================================\n\n/**\n * Format watcher status for display.\n */\nfunction formatStatus(status: WatcherStatus | null): string {\n if (!status) {\n return `# YOLO Watcher Status\n\n**State**: Not started\n\n_Use \\`tickets_yolo_start\\` to begin watching for YOLO tickets._`;\n }\n\n const lines: string[] = [\n \"# YOLO Watcher Status\",\n \"\",\n `**State**: ${status.state}`,\n `**Project**: ${status.projectSlug}`,\n ];\n\n if (status.pid) {\n lines.push(`**Daemon PID**: ${status.pid}`);\n }\n\n if (status.startedAt) {\n lines.push(`**Started**: ${status.startedAt}`);\n }\n\n if (status.lastPollAt) {\n lines.push(`**Last Poll**: ${status.lastPollAt}`);\n }\n\n lines.push(`**Tickets Processed**: ${status.ticketsProcessed}`);\n\n // Configuration\n if (status.config) {\n lines.push(\"\");\n lines.push(\"## Configuration\");\n lines.push(`- Poll Interval: ${status.config.pollIntervalMs / 1000}s`);\n lines.push(`- Max Parallel: ${status.config.maxParallel}`);\n lines.push(`- Ticket Timeout: ${status.config.ticketTimeout / 1000 / 60} min`);\n lines.push(`- Notifications: ${status.config.enableNotifications ? \"enabled\" : \"disabled\"}`);\n }\n\n // Currently executing\n if (status.currentlyExecuting && status.currentlyExecuting.length > 0) {\n lines.push(\"\");\n lines.push(\"## Currently Executing\");\n for (const exec of status.currentlyExecuting) {\n const num = exec.ticketNumber ? `#${exec.ticketNumber}` : \"\";\n const elapsed = getElapsedTime(exec.startedAt);\n lines.push(`- **${num} ${exec.ticketSlug}** (${elapsed})`);\n }\n }\n\n // Recently completed\n if (status.completedTickets && status.completedTickets.length > 0) {\n lines.push(\"\");\n lines.push(\"## Recently Completed\");\n for (const completed of status.completedTickets.slice(0, 10)) {\n const num = completed.ticketNumber ? `#${completed.ticketNumber}` : \"\";\n const emoji = completed.success ? \"✅\" : \"❌\";\n const duration = formatDuration(completed.durationMs);\n const summary = completed.summary ? ` - ${completed.summary.slice(0, 50)}` : \"\";\n lines.push(`- ${emoji} **${num} ${completed.ticketSlug}** (${duration})${summary}`);\n }\n }\n\n if (status.lastError) {\n lines.push(\"\");\n lines.push(\"## Last Error\");\n lines.push(`\\`${status.lastError}\\``);\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Get human-readable elapsed time.\n */\nfunction getElapsedTime(startedAt: string): string {\n const start = new Date(startedAt).getTime();\n const elapsed = Date.now() - start;\n\n const minutes = Math.floor(elapsed / 1000 / 60);\n const seconds = Math.floor((elapsed / 1000) % 60);\n\n if (minutes > 0) {\n return `${minutes}m ${seconds}s`;\n }\n return `${seconds}s`;\n}\n\n/**\n * Format duration in milliseconds to human-readable string.\n */\nfunction formatDuration(ms: number): string {\n if (ms < 1000) {\n return `${ms}ms`;\n }\n\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n\n if (minutes > 0) {\n const remainingSeconds = seconds % 60;\n return `${minutes}m ${remainingSeconds}s`;\n }\n return `${seconds}s`;\n}\n","import type { ConvexHttpClient } from \"convex/browser\";\nimport type { ServerConfig, PromptMetadata, AccountScopedPromptResult } from \"./types.js\";\n\n/**\n * Helper type for Convex HTTP client with string-based function references.\n * This allows us to use string function names while maintaining type safety\n * for the response types through explicit casting.\n */\ntype ConvexClientWithStringRefs = ConvexHttpClient & {\n query: <T>(name: string, args: Record<string, unknown>) => Promise<T>;\n};\n\n/**\n * Sanitizes a string for use in MCP tool names.\n * Ensures output matches MCP protocol pattern: ^[a-zA-Z0-9_-]{1,64}$\n *\n * @param str - Raw string to sanitize (project slug, folder path, or prompt slug)\n * @returns Sanitized string safe for MCP tool names\n */\nexport function sanitizeForMcp(str: string): string {\n return str\n .trim() // Remove leading/trailing whitespace first\n .toLowerCase() // Convert to lowercase for consistency\n .replace(/[^a-z0-9_-]/g, '-') // Replace ALL invalid chars (including spaces!) with hyphens\n .replace(/-+/g, '-') // Collapse multiple consecutive hyphens into one\n .replace(/^-+|-+$/g, '') // Remove leading and trailing hyphens\n .trim(); // Final trim to ensure no whitespace\n}\n\n// Note: The following legacy functions have been removed as prompts are now account-scoped:\n// - buildPromptName (was project:prompt format, now prompts are global)\n// - buildPromptNameFromMetadata (was project:prompt format)\n// - buildPromptSchema (used buildPromptName)\n// - buildPromptHandler (used project-scoped getMcpPrompts query)\n// - fetchAndExecutePrompt (used project-scoped getMcpPromptBySlug query)\n//\n// Use fetchAndExecuteAccountScopedPrompt instead for all prompt execution.\n\n/**\n * Custom error class for ambiguous prompt matches\n * Contains suggestions for the user to choose from\n */\nexport class AmbiguousPromptError extends Error {\n suggestions: string[];\n\n constructor(promptSlug: string, suggestions: string[]) {\n super(`Multiple prompts match \"${promptSlug}\". Please be more specific.`);\n this.name = \"AmbiguousPromptError\";\n this.suggestions = suggestions;\n }\n}\n\n/**\n * Fetch account-scoped prompt metadata using project token auth.\n *\n * @param client - Convex HTTP client\n * @param projectToken - Project token for authentication\n * @returns Account-scoped prompts for this project\n */\nexport async function fetchAccountScopedPromptMetadataByToken(\n client: ConvexHttpClient,\n projectToken: string\n): Promise<PromptMetadata[]> {\n try {\n // Cast to our typed client for string-based function references\n const typedClient = client as ConvexClientWithStringRefs;\n const metadata = await typedClient.query<PromptMetadata[]>(\n \"mcp_prompts:getAccountScopedPromptMetadataByToken\",\n { projectToken }\n );\n return metadata;\n } catch (error) {\n throw new Error(\n `Failed to fetch prompts via project token: ${error instanceof Error ? error.message : \"Unknown error\"}`\n );\n }\n}\n\n// Note: AccountScopedPromptResult type is imported from ./types.js\n\n/**\n * Fetch a single account-scoped prompt and execute it\n *\n * Account-scoped prompts are global (no project prefix required).\n * This is for prompts that don't have a projectId.\n *\n * Uses project token authentication: access via token's ownerId.\n *\n * Matching algorithm supports exact, prefix, and contains matching.\n */\nexport async function fetchAndExecuteAccountScopedPrompt(\n promptSlug: string,\n config: ServerConfig,\n convexClient: ConvexHttpClient\n): Promise<{ messages: Array<{ role: string; content: { type: \"text\"; text: string } }> }> {\n // Cast to our typed client for string-based function references\n const typedClient = convexClient as ConvexClientWithStringRefs;\n\n // Project token auth - access via token's owner\n const result = await typedClient.query<AccountScopedPromptResult>(\n \"mcp_prompts:getAccountScopedPromptBySlugByToken\",\n {\n projectToken: config.projectToken,\n promptSlug,\n }\n );\n\n // Handle null - no matches found\n if (!result) {\n throw new Error(\n `Prompt \"${promptSlug}\" not found. Use system:prompts to list available prompts.`\n );\n }\n\n // Handle ambiguous matches - throw custom error with suggestions\n if (result.matchType === \"ambiguous\") {\n throw new AmbiguousPromptError(promptSlug, result.suggestions);\n }\n\n // Handle successful match (exact, prefix, or contains)\n if (!result.flattenedPrompt) {\n throw new Error(\n `Prompt \"${result.slug}\" has no flattened content. Please re-save the prompt to regenerate it.`\n );\n }\n\n // Log match type for debugging\n const matchNote = result.matchType === \"exact\"\n ? \"exact match\"\n : result.matchType === \"prefix\"\n ? `prefix match → ${result.slug}`\n : `contains match → ${result.slug}`;\n console.error(`[MCP] Fetched account-scoped prompt: ${promptSlug} (${matchNote})`);\n\n // Return raw flattened prompt (client will parse YAML front matter)\n return {\n messages: [\n {\n role: \"user\",\n content: {\n type: \"text\" as const,\n text: result.flattenedPrompt,\n },\n },\n ],\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/convex-client.ts","../src/server.ts","../src/types.ts","../src/watcher/watcher_controller.ts","../src/prompt-builder.ts"],"sourcesContent":["import minimist from \"minimist\";\nimport { createConvexClient } from \"./convex-client.js\";\nimport { startServer } from \"./server.js\";\nimport type { ServerConfig } from \"./types.js\";\n\n/**\n * Main entry point for the MCP server\n */\nasync function main() {\n try {\n // Parse CLI arguments\n const argv = minimist(process.argv.slice(2));\n const isDev = argv.dev === true;\n\n // Check for project token authentication\n const projectToken = process.env.PPM_PROJECT_TOKEN;\n\n if (!projectToken) {\n console.error(\n \"[MCP] ERROR: Missing authentication. Set PPM_PROJECT_TOKEN environment variable.\"\n );\n console.error(\n \"[MCP] Example:\"\n );\n console.error(\"[MCP] export PPM_PROJECT_TOKEN=wst_... # Get from project settings\");\n process.exit(1);\n }\n\n console.error(\"[MCP] Auth mode: Project Token\");\n\n // Create Convex client\n const convexClient = createConvexClient(isDev);\n const convexUrl = isDev\n ? \"https://hallowed-shrimp-344.convex.cloud\"\n : \"https://trustworthy-squirrel-735.convex.cloud\";\n\n // Build server config\n const config: ServerConfig = {\n projectToken,\n isDev,\n convexUrl,\n };\n\n // Start server - this will keep the process alive via stdio transport\n await startServer(config, convexClient);\n\n // This line should never be reached if the promise in startServer never resolves\n console.error(\"[MCP] WARNING: startServer promise resolved unexpectedly!\");\n } catch (error) {\n console.error(\n \"[MCP] FATAL ERROR:\",\n error instanceof Error ? error.message : \"Unknown error\"\n );\n if (error instanceof Error && error.stack) {\n console.error(error.stack);\n }\n process.exit(1);\n }\n}\n\n// Handle graceful shutdown\nprocess.on(\"SIGINT\", () => {\n console.error(\"[MCP] Received SIGINT, shutting down gracefully...\");\n process.exit(0);\n});\n\nprocess.on(\"SIGTERM\", () => {\n console.error(\"[MCP] Received SIGTERM, shutting down gracefully...\");\n process.exit(0);\n});\n\nmain();\n","import { ConvexHttpClient } from \"convex/browser\";\n\nconst PROD_URL = \"https://trustworthy-squirrel-735.convex.cloud\";\nconst DEV_URL = \"https://hallowed-shrimp-344.convex.cloud\";\n\n/**\n * Create a Convex HTTP client for the appropriate deployment\n */\nexport function createConvexClient(isDev: boolean): ConvexHttpClient {\n const url = isDev ? DEV_URL : PROD_URL;\n return new ConvexHttpClient(url);\n}\n","import { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport {\n CallToolRequestSchema,\n GetPromptRequestSchema,\n ListPromptsRequestSchema,\n ListToolsRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport type { ConvexHttpClient } from \"convex/browser\";\nimport type {\n ServerConfig,\n ProjectTokenValidation,\n WorkTicketResult,\n AccessDeniedResult,\n CreateTicketResult,\n CloseTicketResult,\n UpdateTicketResult,\n GetTicketResult,\n ListTicketItem,\n SearchTicketItem,\n} from \"./types.js\";\nimport {\n parseWorkArgs,\n parseCloseArgs,\n parseCreateArgs,\n parseSearchArgs,\n parseGetArgs,\n parseUpdateArgs,\n parsePromptsRunArgs,\n parsePromptsListArgs,\n parsePromptsUpdateArgs,\n parseWatcherStartArgs,\n isAccessDenied,\n} from \"./types.js\";\nimport type { UpdatePromptResult } from \"./types.js\";\n\n// Import watcher controller for YOLO automation (daemon-based)\nimport {\n startWatcher,\n stopWatcher,\n getWatcherStatus,\n} from \"./watcher/index.js\";\n\n/**\n * Helper type for Convex HTTP client with string-based function references.\n * This allows us to use string function names while maintaining type safety\n * for the response types through explicit casting.\n */\ntype ConvexClientWithStringRefs = ConvexHttpClient & {\n mutation: <T>(name: string, args: Record<string, unknown>) => Promise<T>;\n query: <T>(name: string, args: Record<string, unknown>) => Promise<T>;\n};\n\nimport { fetchAndExecuteAccountScopedPrompt, AmbiguousPromptError, fetchAccountScopedPromptMetadataByToken } from \"./prompt-builder.js\";\n// Note: McpCommandMetadata removed - commands resource eliminated\n// Note: buildPromptNameFromMetadata, fetchAndExecutePrompt removed - prompts now account-scoped\n\n/**\n * Build authentication arguments for Convex mutations/queries.\n * All auth is now via project token.\n */\nfunction buildAuthArgs(config: ServerConfig): { projectToken: string } {\n return { projectToken: config.projectToken };\n}\n\n/**\n * Built-in system tools (no global tools - all are project-scoped now)\n */\nconst SYSTEM_TOOLS: {\n name: string;\n description: string;\n inputSchema: object;\n promptContent: string;\n isSlashCommand: boolean; // true = appears as /ppm:name, false = tool only\n}[] = [\n // All tools are now project-scoped (see dynamic*Tools arrays below)\n];\n\n/**\n * Initialize and start the MCP server\n */\nexport async function startServer(\n config: ServerConfig,\n convexClientRaw: ConvexHttpClient\n): Promise<void> {\n // Cast to our typed client interface for string-based function references\n const convexClient = convexClientRaw as ConvexClientWithStringRefs;\n // Validate project token authentication\n console.error(\"[MCP] Validating project token...\");\n const validation = await validateProjectToken(convexClient, config.projectToken);\n if (!validation.valid) {\n throw new Error(`Invalid project token: ${validation.error}`);\n }\n const tokenProjectSlug = validation.projectSlug;\n console.error(`[MCP] Project token validated for project \"${tokenProjectSlug}\"`);\n // Token mode provides full prompt access via the token owner's account\n\n // Fetch account-scoped prompts (global MCP tools)\n // Account-scoped prompts: Available as global tools (e.g., \"code-review\")\n // Token mode: Single project for tickets (determined by token)\n console.error(\"[MCP] Fetching prompt metadata...\");\n\n // Project token mode - full prompt access via token's owner\n // The token's ownerId is used to look up all account-scoped prompts\n const accountScopedPrompts = await fetchAccountScopedPromptMetadataByToken(\n convexClient,\n config.projectToken\n );\n console.error(`[MCP] Project token mode: loaded ${accountScopedPrompts.length} prompts`);\n\n console.error(`[MCP] Found ${accountScopedPrompts.length} account-scoped prompts (global tools)`);\n console.error(`[MCP] Ticket project scope: ${tokenProjectSlug} (token-scoped)`);\n\n if (accountScopedPrompts.length === 0) {\n console.error(\n \"[MCP] WARNING: No prompts found. Create prompts in the 'prompts' section to expose them via MCP.\"\n );\n }\n\n // Build dynamic ticket tools (single set since token scopes to one project)\n // Tool names no longer prefixed with project slug - token already scopes access\n const projectSlug = tokenProjectSlug!;\n const dynamicTicketTools: { name: string; description: string; slashDescription: string; projectSlug: string; type: 'work' | 'close' | 'create' | 'search' | 'get' | 'list' | 'update' | 'yolo_start' | 'yolo_stop' | 'yolo_status' }[] = [\n // Unified work command (replaces both tickets_open and tickets_work)\n {\n name: `tickets_work`,\n description: `Get work from the \"${projectSlug}\" project. With no args: gets next ticket from open queue. With ticket slug/number: opens or resumes that specific ticket.`,\n slashDescription: `Get next ticket from queue, or work on specific ticket by slug/number`,\n projectSlug,\n type: 'work',\n },\n {\n name: `tickets_close`,\n description: `Mark a working ticket as completed in the \"${projectSlug}\" project`,\n slashDescription: `Mark a working ticket as completed`,\n projectSlug,\n type: 'close',\n },\n {\n name: `tickets_create`,\n description: `Create a new ticket in the \"${projectSlug}\" project queue`,\n slashDescription: `Create a new ticket in the backlog queue`,\n projectSlug,\n type: 'create',\n },\n {\n name: `tickets_search`,\n description: `Search for tickets by content in the \"${projectSlug}\" project`,\n slashDescription: `Search for tickets by content`,\n projectSlug,\n type: 'search',\n },\n {\n name: `tickets_get`,\n description: `Get a specific ticket by number or slug from \"${projectSlug}\" (read-only)`,\n slashDescription: `Get a specific ticket by number or slug (read-only)`,\n projectSlug,\n type: 'get',\n },\n {\n name: `tickets_list`,\n description: `List active tickets in the \"${projectSlug}\" project (backlog + open + working)`,\n slashDescription: `List active tickets (backlog + open + working)`,\n projectSlug,\n type: 'list',\n },\n {\n name: `tickets_update`,\n description: `Update a ticket in the \"${projectSlug}\" project by appending content with timestamp`,\n slashDescription: `Update a ticket by appending content with timestamp`,\n projectSlug,\n type: 'update',\n },\n // YOLO orchestrator tools - sub-agent based autonomous ticket execution\n {\n name: `tickets_yolo_start`,\n description: `Start the YOLO ticket watcher for \"${projectSlug}\". Polls for open tickets with yolo flag and spawns Terminal windows with Claude Code to execute them autonomously.`,\n slashDescription: `Start YOLO orchestrator for autonomous ticket execution`,\n projectSlug,\n type: 'yolo_start',\n },\n {\n name: `tickets_yolo_stop`,\n description: `Stop the YOLO ticket watcher for \"${projectSlug}\"`,\n slashDescription: `Stop the YOLO orchestrator`,\n projectSlug,\n type: 'yolo_stop',\n },\n {\n name: `tickets_yolo_status`,\n description: `Get YOLO watcher status for \"${projectSlug}\"`,\n slashDescription: `Check YOLO orchestrator status and pending tickets`,\n projectSlug,\n type: 'yolo_status',\n },\n ];\n\n console.error(`[MCP] Registering ${dynamicTicketTools.length} ticket tools...`);\n\n // Build global prompt tools (not project-scoped)\n // Renamed from system_* to prompts_* for clarity\n const globalPromptTools: { name: string; description: string; slashDescription: string }[] = [\n {\n name: \"prompts_run\",\n description: \"Execute a prompt by slug. Use prompts_list to list available prompts.\",\n slashDescription: \"Execute a prompt by slug. Use prompts_list to list available prompts.\",\n },\n {\n name: \"prompts_list\",\n description: \"List all available prompts\",\n slashDescription: \"List all available prompts\",\n },\n {\n name: \"prompts_update\",\n description: \"Read or update a prompt. Without content: returns prompt with context and editing guidance. With content: saves new version.\",\n slashDescription: \"Read or update a prompt by slug\",\n },\n ];\n\n console.error(`[MCP] Registering ${globalPromptTools.length} global prompt tools...`);\n\n // Build dynamic per-prompt tools (each prompt as its own tool)\n // Now global: Direct invocation like `code-review` instead of `projects:code-review`\n const dynamicPromptTools: {\n name: string; // \"code-review\" (global, no project prefix)\n description: string;\n promptSlug: string;\n }[] = [];\n\n for (const prompt of accountScopedPrompts) {\n // Build description with optional folder prefix\n const folderPrefix = prompt.folderSlug ? `[${prompt.folderSlug}] ` : '';\n const baseDescription = prompt.description || `Execute the \"${prompt.slug}\" prompt`;\n\n dynamicPromptTools.push({\n name: prompt.slug, // Global tool name, no project prefix\n description: folderPrefix + baseDescription,\n promptSlug: prompt.slug,\n });\n }\n\n console.error(`[MCP] Registering ${dynamicPromptTools.length} per-prompt tools (global)...`);\n\n // Note: Commands resource removed - users create slash commands directly from flattened prompts\n\n // Check for duplicate prompt names (now global, no project prefix)\n const promptNames = new Set<string>();\n const duplicates: string[] = [];\n accountScopedPrompts.forEach((p) => {\n if (promptNames.has(p.slug)) {\n duplicates.push(p.slug);\n }\n promptNames.add(p.slug);\n });\n\n if (duplicates.length > 0) {\n console.error(\n `[MCP] WARNING: Duplicate prompt slugs detected: ${duplicates.join(\", \")}. Only the first occurrence will be registered.`\n );\n }\n\n // Create MCP server\n const server = new Server(\n {\n name: \"ppm-mcp-server\",\n version: \"3.1.0\",\n },\n {\n capabilities: {\n prompts: {},\n tools: {},\n },\n }\n );\n\n // Register list_prompts handler\n // Prompts are now global (account-scoped), tickets remain project-scoped\n server.setRequestHandler(ListPromptsRequestSchema, async () => {\n // Build ticket prompt schemas (project-scoped)\n const ticketPromptSchemas = dynamicTicketTools.map((tt) => ({\n name: tt.name,\n description: tt.slashDescription,\n }));\n\n // Build global prompt schemas\n const promptToolSchemas = globalPromptTools.map((st) => ({\n name: st.name,\n description: st.slashDescription,\n }));\n\n return {\n prompts: [\n ...promptToolSchemas, // prompts_run, prompts_list, prompts_update (global)\n ...ticketPromptSchemas, // tickets_* (token-scoped)\n ],\n };\n });\n\n // Register get_prompt handler\n server.setRequestHandler(GetPromptRequestSchema, async (request) => {\n const promptName = request.params.name;\n\n // Check for system tools first (as prompts for slash command access)\n const systemTool = SYSTEM_TOOLS.find((st) => st.name === promptName);\n if (systemTool) {\n return {\n description: systemTool.description,\n messages: [\n {\n role: \"user\" as const,\n content: {\n type: \"text\" as const,\n text: systemTool.promptContent,\n },\n },\n ],\n };\n }\n\n // Check for ticket commands (Ticket 135, 149, 153 - handle as slash commands)\n // Handle exact matches and tickets_work with optional argument (e.g., \"tickets_work 102\")\n const ticketTool = dynamicTicketTools.find((tt) => tt.name === promptName);\n const ticketWorkMatch = promptName.match(/^tickets_work\\s+(.+)$/);\n\n if (ticketTool || ticketWorkMatch) {\n let promptContent: string;\n let description: string;\n\n if (ticketWorkMatch) {\n // Handle tickets_work with specific ticket argument\n const ticketArg = ticketWorkMatch[1].trim();\n description = `Work on ticket \"${ticketArg}\" from \"${projectSlug}\"`;\n promptContent = `Get work on ticket \"${ticketArg}\" from the \"${projectSlug}\" project.\\n\\nCall the \\`tickets_work\\` tool with ticketSlug: \"${ticketArg}\".\\n\\nThis will open the ticket if it's in the open queue, or resume it if already working.`;\n } else if (ticketTool!.type === 'work') {\n // Unified work command (replaces open + work)\n description = ticketTool!.description;\n promptContent = `Get work from the \"${ticketTool!.projectSlug}\" project.\\n\\nCall the \\`tickets_work\\` tool to get the next ticket from the open queue.\\n\\nYou can also specify a ticket: /ppm:tickets_work <number-or-slug>\\n\\nThis unified command handles both opening new tickets and resuming in-progress work.`;\n } else if (ticketTool!.type === 'create') {\n // Ticket 149: tickets:create slash command\n description = ticketTool!.description;\n promptContent = `Create a new ticket in the \"${ticketTool!.projectSlug}\" project queue.\n\n## How This Works\nThe user provides **instructions** (not raw content). You interpret those instructions to generate the ticket.\n\n## Instructions\n1. **Read the user's request** - they may reference a file, ask you to summarize a session, or describe what they want\n2. **Process the input** - read files, extract relevant sections, summarize as needed\n3. **Generate ticket content** with a clear, descriptive title as the first line\n4. **Call the tool** with the generated content\n\n## Content Format\n\\`\\`\\`\n[Clear descriptive title - this becomes the slug]\n\n[Body content - tasks, description, context, etc.]\n\\`\\`\\`\n\n## Examples\n- \"create a ticket from /path/to/plan.md\" → Read file, use as ticket content\n- \"summarize our brainstorm into a ticket\" → Extract key points from conversation\n- \"create a ticket for the auth bug we discussed\" → Generate from session context\n- \"just the tasks from this file\" → Extract only task items\n\nCall the \\`${ticketTool!.name}\\` tool with the final generated content.`;\n } else if (ticketTool!.type === 'close') {\n description = ticketTool!.description;\n promptContent = `Close a working ticket in the \"${ticketTool!.projectSlug}\" project.\\n\\nCall the \\`${ticketTool!.name}\\` tool with the ticket number or slug.`;\n } else {\n // Fallback for unknown type\n description = ticketTool!.description;\n promptContent = `Use the \\`${ticketTool!.name}\\` tool.`;\n }\n\n return {\n description,\n messages: [\n {\n role: \"user\" as const,\n content: {\n type: \"text\" as const,\n text: promptContent,\n },\n },\n ],\n };\n }\n\n // Check for global prompts_list command\n if (promptName === \"prompts_list\") {\n return {\n description: \"List all available prompts\",\n messages: [\n {\n role: \"user\" as const,\n content: {\n type: \"text\" as const,\n text: `List all available prompts.\n\nCall the \\`prompts_list\\` tool with optional \\`search\\` parameter to filter results.`,\n },\n },\n ],\n };\n }\n\n // Check for global prompts_run command (with optional prompt slug argument)\n const runPromptMatch = promptName.match(/^prompts_run\\s+(.+)$/);\n\n if (promptName === \"prompts_run\" || runPromptMatch) {\n let promptContent: string;\n let description: string;\n\n if (runPromptMatch) {\n // Handle prompts_run with specific prompt slug argument\n const promptSlug = runPromptMatch[1].trim();\n description = `Execute prompt \"${promptSlug}\"`;\n promptContent = `Execute the \"${promptSlug}\" prompt.\\n\\nCall the \\`prompts_run\\` tool with slug: \"${promptSlug}\".`;\n } else {\n // Base prompts_run without argument - show help\n description = \"Execute a prompt by slug\";\n promptContent = `Execute a prompt by slug.\n\n## Usage\nCall the \\`prompts_run\\` tool with the prompt slug.\n\n## Available Prompts\nUse \\`prompts_list\\` to list all available prompts.\n\n## Example\n/ppm:prompts_run code-review\n\nThis will execute the \"code-review\" prompt.`;\n }\n\n return {\n description,\n messages: [\n {\n role: \"user\" as const,\n content: {\n type: \"text\" as const,\n text: promptContent,\n },\n },\n ],\n };\n }\n\n // Note: Command handling removed - commands resource eliminated\n\n // Unknown prompt\n throw new Error(`Unknown prompt: ${promptName}. Use prompts_run to execute prompts.`);\n });\n\n // Register list_tools handler\n // Note: Per-prompt tool registration removed in favor of project-scoped run_prompt tools\n // This reduces token overhead and allows dynamic prompt discovery without server restart\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n // Build tools array: system tools + ticket tools + project:run_prompt tools\n // Individual prompts no longer registered as separate tools\n const tools = [\n // System tools with full input schemas\n ...SYSTEM_TOOLS.map((st) => ({\n name: st.name,\n description: st.description,\n inputSchema: st.inputSchema,\n })),\n // Dynamic ticket tools per project (Ticket 135, 149, 151, 153, unified work)\n ...dynamicTicketTools.map((tt) => {\n // Build inputSchema based on tool type\n let inputSchema: { type: \"object\"; properties: Record<string, object>; required: string[] };\n\n if (tt.type === 'close') {\n // close requires a ticketSlug\n inputSchema = {\n type: \"object\" as const,\n properties: {\n ticketSlug: {\n type: \"string\",\n description: \"Ticket number (e.g., '102') or full slug (e.g., '102-fix-auth')\",\n },\n },\n required: [\"ticketSlug\"],\n };\n } else if (tt.type === 'work') {\n // Unified work command (replaces open + work)\n inputSchema = {\n type: \"object\" as const,\n properties: {\n ticketSlug: {\n type: \"string\",\n description: \"Optional: Ticket number (e.g., '102') or full slug. If not provided, gets next ticket from open queue.\",\n },\n },\n required: [],\n };\n } else if (tt.type === 'create') {\n inputSchema = {\n type: \"object\" as const,\n properties: {\n content: {\n type: \"string\",\n description: \"The generated ticket content. First line should be a clear descriptive title (becomes the slug). Body contains tasks, description, context as needed.\",\n },\n tags: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Optional array of tag slugs to assign (e.g., [\\\"bug\\\", \\\"urgent\\\"]). Invalid tags are ignored.\",\n },\n },\n required: [\"content\"],\n };\n } else if (tt.type === 'search') {\n // Ticket 155: tickets:search\n inputSchema = {\n type: \"object\" as const,\n properties: {\n query: {\n type: \"string\",\n description: \"Search query (min 3 characters)\",\n },\n },\n required: [\"query\"],\n };\n } else if (tt.type === 'get') {\n // Ticket 155: tickets:get (read-only inspection)\n inputSchema = {\n type: \"object\" as const,\n properties: {\n ticketSlug: {\n type: \"string\",\n description: \"Ticket number (e.g., '102') or full slug\",\n },\n },\n required: [\"ticketSlug\"],\n };\n } else if (tt.type === 'list') {\n // Ticket 155: tickets:list (no parameters - shows active queue)\n inputSchema = {\n type: \"object\" as const,\n properties: {},\n required: [],\n };\n } else if (tt.type === 'update') {\n // Ticket 293: tickets:update (append content to ticket)\n inputSchema = {\n type: \"object\" as const,\n properties: {\n ticketSlug: {\n type: \"string\",\n description: \"Ticket number (e.g., '102') or full slug (e.g., '102-fix-auth')\",\n },\n content: {\n type: \"string\",\n description: \"Update content to append to the ticket (markdown supported)\",\n },\n },\n required: [\"ticketSlug\", \"content\"],\n };\n } else if (tt.type === 'yolo_start') {\n // YOLO watcher: start the daemon\n inputSchema = {\n type: \"object\" as const,\n properties: {\n pollIntervalMs: {\n type: \"number\",\n description: \"Milliseconds between polls (default: 30000 = 30s)\",\n },\n maxParallel: {\n type: \"number\",\n description: \"Maximum concurrent ticket executions (default: 1)\",\n },\n ticketTimeout: {\n type: \"number\",\n description: \"Timeout for ticket execution in ms (default: 1800000 = 30 min)\",\n },\n enableNotifications: {\n type: \"boolean\",\n description: \"Show macOS notifications (default: true)\",\n },\n workingDirectory: {\n type: \"string\",\n description: \"Working directory for Claude Code sessions (default: current working directory)\",\n },\n },\n required: [],\n };\n } else if (tt.type === 'yolo_stop') {\n // YOLO watcher: stop the daemon\n inputSchema = {\n type: \"object\" as const,\n properties: {},\n required: [],\n };\n } else if (tt.type === 'yolo_status') {\n // YOLO watcher: get status\n inputSchema = {\n type: \"object\" as const,\n properties: {},\n required: [],\n };\n } else {\n // Fallback: no params\n inputSchema = {\n type: \"object\" as const,\n properties: {},\n required: [],\n };\n }\n\n return {\n name: tt.name,\n description: tt.description,\n inputSchema,\n };\n }),\n // Global prompts_run tool\n {\n name: \"prompts_run\",\n description: \"Execute a prompt by slug. Use prompts_list to list available prompts.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n slug: {\n type: \"string\",\n description: \"Prompt slug to execute (e.g., 'code-review', 'plan')\",\n },\n },\n required: [\"slug\"],\n },\n },\n // Global prompts_list tool\n {\n name: \"prompts_list\",\n description: \"List all available prompts\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n search: {\n type: \"string\",\n description: \"Optional search term to filter prompts by name or description (case-insensitive)\",\n },\n },\n },\n },\n // Global prompts_update tool (CLI-based prompt editing)\n {\n name: \"prompts_update\",\n description: \"Read or update a prompt. Without content: returns prompt with context and editing guidance. With content: saves new version.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n slug: {\n type: \"string\",\n description: \"Prompt slug to read or update (supports fuzzy matching)\",\n },\n content: {\n type: \"string\",\n description: \"New prompt content (omit for read-only mode)\",\n },\n changeLogMessage: {\n type: \"string\",\n description: \"Optional changelog message for the version\",\n },\n },\n required: [\"slug\"],\n },\n },\n // Dynamic per-prompt tools (each prompt as its own global tool)\n ...dynamicPromptTools.map((pt) => ({\n name: pt.name,\n description: pt.description,\n inputSchema: {\n type: \"object\" as const,\n properties: {},\n required: [],\n },\n })),\n ];\n\n return { tools };\n });\n\n // Register call_tool handler\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const toolName = request.params.name;\n\n // Handle global prompts_run tool\n if (toolName === \"prompts_run\") {\n const parsedArgs = parsePromptsRunArgs(request.params.arguments);\n\n if (!parsedArgs) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing 'slug' parameter. Provide prompt slug (e.g., 'code-review', 'plan').\n\nUse \\`prompts_list\\` to list available prompts.`,\n },\n ],\n isError: true,\n };\n }\n\n const { slug: promptSlug } = parsedArgs;\n\n // Execute the prompt using account-scoped lookup\n try {\n const result = await fetchAndExecuteAccountScopedPrompt(\n promptSlug,\n config,\n convexClient\n );\n\n // Convert prompt result to tool result format\n const promptText = result.messages\n .map((msg) => msg.content.text)\n .join('\\n\\n');\n\n return {\n content: [\n {\n type: \"text\",\n text: promptText,\n },\n ],\n };\n } catch (error) {\n // Handle ambiguous prompt matches with helpful suggestions\n if (error instanceof AmbiguousPromptError) {\n const suggestionsList = error.suggestions.map((s) => ` • ${s}`).join(\"\\n\");\n console.error(`[MCP] ${toolName} ambiguous match:`, error.suggestions);\n return {\n content: [\n {\n type: \"text\",\n text: `Multiple prompts match \"${promptSlug}\". Please specify one of:\\n\\n${suggestionsList}\\n\\nExample: \\`prompts_run ${error.suggestions[0]}\\``,\n },\n ],\n isError: true,\n };\n }\n\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] ${toolName} error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error executing prompt \"${promptSlug}\": ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n }\n\n // Handle global prompts_list tool (account-scoped prompt listing)\n // Uses cached metadata (lightweight) - no need to fetch full content for listing\n if (toolName === \"prompts_list\") {\n const { search: searchTerm } = parsePromptsListArgs(request.params.arguments);\n\n // Use account-scoped prompts (global, no project filter)\n let filteredPrompts = [...accountScopedPrompts];\n\n // Filter by search term if provided\n if (searchTerm) {\n const lowerSearch = searchTerm.toLowerCase();\n filteredPrompts = filteredPrompts.filter(\n (p) =>\n p.slug.toLowerCase().includes(lowerSearch) ||\n p.description?.toLowerCase().includes(lowerSearch)\n );\n }\n\n // Sort prompts by slug\n filteredPrompts.sort((a, b) => a.slug.localeCompare(b.slug));\n\n // Build user prompts list\n const userPromptList = filteredPrompts\n .map((p) => {\n const desc = p.description || \"No description\";\n return `• ${p.slug}\\n Description: ${desc}`;\n })\n .join(\"\\n\\n\");\n\n const summary = searchTerm\n ? `Found ${filteredPrompts.length} prompt(s) matching \"${searchTerm}\":`\n : `Available prompts (${filteredPrompts.length} total):`;\n\n return {\n content: [\n {\n type: \"text\",\n text: userPromptList\n ? `${summary}\\n\\n${userPromptList}`\n : `${summary}\\n\\nNo prompts found.`,\n },\n ],\n };\n }\n\n // Handle global prompts_update tool (CLI-based prompt editing)\n if (toolName === \"prompts_update\") {\n const parsedArgs = parsePromptsUpdateArgs(request.params.arguments);\n\n if (!parsedArgs) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing 'slug' parameter. Provide prompt slug to read or update.\n\nUsage:\n- Read mode: prompts_update { slug: \"my-prompt\" }\n- Write mode: prompts_update { slug: \"my-prompt\", content: \"new content...\" }`,\n },\n ],\n isError: true,\n };\n }\n\n try {\n const result = await convexClient.mutation<UpdatePromptResult>(\n \"mcp_prompts:updateMcpPrompt\",\n {\n ...buildAuthArgs(config),\n promptSlug: parsedArgs.slug,\n content: parsedArgs.content,\n changeLogMessage: parsedArgs.changeLogMessage,\n }\n );\n\n if (!result.success) {\n // Handle error (not found, ambiguous)\n const errorResult = result as { success: false; error: string; suggestions?: string[] };\n if (errorResult.suggestions) {\n const suggestionsList = errorResult.suggestions.map((s) => ` • ${s}`).join(\"\\n\");\n return {\n content: [\n {\n type: \"text\",\n text: `${errorResult.error}\\n\\nDid you mean one of these?\\n${suggestionsList}`,\n },\n ],\n isError: true,\n };\n }\n return {\n content: [{ type: \"text\", text: errorResult.error }],\n isError: true,\n };\n }\n\n // Handle read mode response\n if (result.mode === \"read\") {\n const readResult = result as {\n mode: \"read\";\n matchType: string;\n slug: string;\n description: string;\n content: string;\n version: number;\n context: object;\n editingGuidance: string;\n hasAiTags?: boolean;\n aiTags?: string[];\n };\n\n // Format the response with all context\n const matchNote = readResult.matchType !== \"exact\"\n ? `\\n_Note: Matched via ${readResult.matchType} match_`\n : \"\";\n\n // AI tag notice if tags detected\n const aiTagNotice = readResult.hasAiTags\n ? `\\n\\n## ⚡ AI Tags Detected\\nThis prompt contains AI tags that need processing: **${readResult.aiTags?.join(\", \")}**\\n\\nProcess these tags by reading the instructions in each tag, generating appropriate content, and saving the result with the content parameter.`\n : \"\";\n\n return {\n content: [\n {\n type: \"text\",\n text: `# Prompt: ${readResult.slug} (v${readResult.version})${matchNote}${aiTagNotice}\n\n## Description\n${readResult.description || \"No description\"}\n\n## Content\n\\`\\`\\`\n${readResult.content}\n\\`\\`\\`\n\n## Available Resources\n\\`\\`\\`json\n${JSON.stringify(readResult.context, null, 2)}\n\\`\\`\\`\n\n${readResult.editingGuidance}\n\n---\n_To update this prompt, call prompts_update with content parameter._`,\n },\n ],\n };\n }\n\n // Handle write mode response\n if (result.mode === \"write\") {\n const writeResult = result as {\n mode: \"write\";\n slug: string;\n version: number;\n updatedAt: string;\n message: string;\n };\n\n return {\n content: [\n {\n type: \"text\",\n text: `✅ ${writeResult.message}\n\nPrompt: ${writeResult.slug}\nVersion: ${writeResult.version}\nUpdated: ${writeResult.updatedAt}`,\n },\n ],\n };\n }\n\n // Shouldn't reach here, but handle gracefully\n return {\n content: [{ type: \"text\", text: \"Unknown response format\" }],\n isError: true,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] prompts_update error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error updating prompt: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n }\n\n // Handle ticket tool invocations (Ticket 135, 153, unified work)\n const ticketTool = dynamicTicketTools.find((tt) => tt.name === toolName);\n if (ticketTool) {\n if (ticketTool.type === 'work') {\n // Unified work command (replaces both open and work)\n const { ticketSlug } = parseWorkArgs(request.params.arguments);\n\n try {\n // Call mutation (not query) - this can move ticket from open to working\n const result = await convexClient.mutation(\n \"mcp_tickets:workMcpTicket\",\n {\n ...buildAuthArgs(config),\n projectSlug: ticketTool.projectSlug,\n ticketSlug, // Optional: specific ticket to work on\n }\n ) as WorkTicketResult | AccessDeniedResult | null;\n\n if (!result) {\n const message = ticketSlug\n ? `Ticket \"${ticketSlug}\" not found or not in open/working status in project \"${ticketTool.projectSlug}\".`\n : `No open tickets in project \"${ticketTool.projectSlug}\".`;\n return {\n content: [\n {\n type: \"text\",\n text: message,\n },\n ],\n };\n }\n\n // Handle access denied response\n if (isAccessDenied(result)) {\n return {\n content: [\n {\n type: \"text\",\n text: `Access denied: ${result.error}`,\n },\n ],\n isError: true,\n };\n }\n\n // Now TypeScript knows result is WorkTicketResult\n const workResult = result as WorkTicketResult;\n const startedInfo = workResult.startedAt\n ? `\\nStarted: ${new Date(workResult.startedAt).toISOString()}`\n : '';\n\n // Different footer based on whether this was a fresh open or resumption\n const statusNote = workResult.wasOpened\n ? `_Ticket moved to working status. ${workResult.remainingTickets} ticket(s) remaining in queue._`\n : `_Resuming work on this ticket. ${workResult.remainingTickets} ticket(s) in queue._`;\n\n return {\n content: [\n {\n type: \"text\",\n text: `# Ticket: ${workResult.slug} [WORKING]${startedInfo}\n\n${workResult.content}\n\n---\n${statusNote}`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_work error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error getting work: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'create') {\n // Handle tickets:create (Ticket 149)\n const parsedArgs = parseCreateArgs(request.params.arguments);\n\n if (!parsedArgs) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing content parameter. Provide the ticket content (first line becomes the slug).`,\n },\n ],\n isError: true,\n };\n }\n\n const { content, tags } = parsedArgs;\n\n try {\n const result = await convexClient.mutation(\n \"mcp_tickets:createMcpTicket\",\n {\n ...buildAuthArgs(config),\n projectSlug: ticketTool.projectSlug,\n content,\n tags,\n }\n ) as CreateTicketResult;\n\n return {\n content: [\n {\n type: \"text\",\n text: `✅ Created ticket [${result.slug}] in backlog for project \"${ticketTool.projectSlug}\".\n\nPosition: #${result.position} of ${result.totalBacklog} backlog tickets\nPreview: ${result.preview}\n\n_Ticket created in backlog. Use \\`tickets_work ${result.slug}\\` to move it to working status._`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_create error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error creating ticket: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'close') {\n // Ticket 151: Handle tickets:close\n const parsedArgs = parseCloseArgs(request.params.arguments);\n\n if (!parsedArgs) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing ticketSlug parameter. Usage: Provide a ticket number (e.g., \"102\") or full slug (e.g., \"102-fix-auth\").`,\n },\n ],\n isError: true,\n };\n }\n\n const { ticketSlug } = parsedArgs;\n\n try {\n const result = await convexClient.mutation(\n \"mcp_tickets:closeMcpTicket\",\n {\n ...buildAuthArgs(config),\n projectSlug: ticketTool.projectSlug,\n ticketSlug,\n }\n ) as CloseTicketResult;\n\n return {\n content: [\n {\n type: \"text\",\n text: `✅ Ticket [${result.slug}] closed in project \"${ticketTool.projectSlug}\".\n\nClosed at: ${new Date(result.closedAt).toISOString()}\n\n_Reminder: Ensure all embedded \\`[RUN_PROMPT ...]\\` directives were executed before closing._`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_close error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error closing ticket: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'search') {\n // Ticket 155: Handle tickets:search\n const parsedArgs = parseSearchArgs(request.params.arguments);\n\n if (!parsedArgs) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Search query must be at least 3 characters`,\n },\n ],\n isError: true,\n };\n }\n\n const { query } = parsedArgs;\n\n try {\n const result = await convexClient.query(\n \"mcp_tickets:searchMcpTickets\",\n {\n ...buildAuthArgs(config),\n projectSlug: ticketTool.projectSlug,\n query,\n }\n ) as SearchTicketItem[];\n\n if (result.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No tickets found matching \"${query}\" in project \"${ticketTool.projectSlug}\".`,\n },\n ],\n };\n }\n\n // Format as list with status badges\n const formattedList = result\n .map((t) => {\n const statusBadge = t.status === 'open' ? '🟢' : t.status === 'working' ? '🟡' : t.status === 'closed' ? '⚫' : '⚪';\n const num = t.ticketNumber ? `#${t.ticketNumber}` : '';\n return `${statusBadge} ${num} ${t.slug}\\n ${t.matchSnippet}`;\n })\n .join('\\n\\n');\n\n return {\n content: [\n {\n type: \"text\",\n text: `Found ${result.length} ticket(s) matching \"${query}\":\\n\\n${formattedList}`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_search error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error searching tickets: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'get') {\n // Ticket 155: Handle tickets:get (read-only inspection)\n const parsedArgs = parseGetArgs(request.params.arguments);\n\n if (!parsedArgs) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing ticketSlug parameter. Provide a ticket number (e.g., \"102\") or full slug.`,\n },\n ],\n isError: true,\n };\n }\n\n const { ticketSlug } = parsedArgs;\n\n try {\n // Uses existing getMcpTicket query - read-only inspection\n const result = await convexClient.query(\n \"mcp_tickets:getMcpTicket\",\n {\n ...buildAuthArgs(config),\n projectSlug: ticketTool.projectSlug,\n ticketSlug,\n }\n ) as GetTicketResult | AccessDeniedResult | null;\n\n if (!result) {\n return {\n content: [\n {\n type: \"text\",\n text: `Ticket \"${ticketSlug}\" not found in project \"${ticketTool.projectSlug}\".`,\n },\n ],\n };\n }\n\n // Handle access denied response\n if (isAccessDenied(result)) {\n return {\n content: [\n {\n type: \"text\",\n text: `Access denied: ${result.error}`,\n },\n ],\n isError: true,\n };\n }\n\n // Now TypeScript knows result is GetTicketResult\n const getResult = result as GetTicketResult;\n\n // Build status line\n const statusBadge = getResult.status.toUpperCase();\n const startedInfo = getResult.startedAt ? `\\nStarted: ${new Date(getResult.startedAt).toISOString()}` : '';\n const closedInfo = getResult.closedAt ? `\\nClosed: ${new Date(getResult.closedAt).toISOString()}` : '';\n\n return {\n content: [\n {\n type: \"text\",\n text: `# Ticket: ${getResult.slug} [${statusBadge}]${startedInfo}${closedInfo}\n\n${getResult.content}\n\n---\n_Read-only inspection. Use tickets_work to start working on this ticket._`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_get error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error getting ticket: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'list') {\n // Ticket 155: Handle tickets:list (active queue)\n try {\n const result = await convexClient.query(\n \"mcp_tickets:listMcpTickets\",\n {\n ...buildAuthArgs(config),\n projectSlug: ticketTool.projectSlug,\n }\n ) as ListTicketItem[];\n\n if (result.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No active tickets in project \"${ticketTool.projectSlug}\". All tickets are closed or archived.`,\n },\n ],\n };\n }\n\n // Group by status for clear display\n const openTickets = result.filter(t => t.status === 'open');\n const workingTickets = result.filter(t => t.status === 'working');\n const backlogTickets = result.filter(t => t.status === 'backlog');\n\n const formatTicketLine = (t: typeof result[0]) => {\n const num = t.ticketNumber ? `#${t.ticketNumber}` : '';\n return ` ${t.position}. ${num} ${t.slug}\\n ${t.preview}`;\n };\n\n const sections: string[] = [];\n\n if (openTickets.length > 0) {\n sections.push(`**🟢 Open (${openTickets.length})**\\n${openTickets.map(formatTicketLine).join('\\n')}`);\n }\n if (workingTickets.length > 0) {\n sections.push(`**🟡 Working (${workingTickets.length})**\\n${workingTickets.map(formatTicketLine).join('\\n')}`);\n }\n if (backlogTickets.length > 0) {\n sections.push(`**⚪ Backlog (${backlogTickets.length})**\\n${backlogTickets.map(formatTicketLine).join('\\n')}`);\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: `# Active Queue: ${ticketTool.projectSlug}\\n\\n${result.length} ticket(s) in queue\\n\\n${sections.join('\\n\\n')}`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_list error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error listing tickets: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'update') {\n // Ticket 293: Handle tickets:update (append content to ticket)\n const parsedArgs = parseUpdateArgs(request.params.arguments);\n\n if (!parsedArgs) {\n // Determine which argument is missing for a more specific error message\n const args = request.params.arguments as Record<string, unknown> | undefined;\n const hasTicketSlug = typeof args?.ticketSlug === 'string' && args.ticketSlug;\n const hasContent = typeof args?.content === 'string' && args.content;\n\n if (!hasTicketSlug) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing ticketSlug parameter. Provide a ticket number (e.g., \"102\") or full slug.`,\n },\n ],\n isError: true,\n };\n }\n if (!hasContent) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing content parameter. Provide the update content to append to the ticket.`,\n },\n ],\n isError: true,\n };\n }\n // Should not reach here, but just in case\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing required parameters.`,\n },\n ],\n isError: true,\n };\n }\n\n const { ticketSlug, content } = parsedArgs;\n\n try {\n const result = await convexClient.mutation(\n \"mcp_tickets:updateMcpTicket\",\n {\n ...buildAuthArgs(config),\n projectSlug: ticketTool.projectSlug,\n ticketSlug,\n content,\n }\n ) as UpdateTicketResult;\n\n return {\n content: [\n {\n type: \"text\",\n text: `✅ Ticket [${result.slug}] updated in project \"${ticketTool.projectSlug}\".\nUpdated at: ${new Date(result.updatedAt).toISOString()}\n_Ticket content has been appended with your update._`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_update error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error updating ticket: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'yolo_start') {\n // YOLO watcher: spawn background daemon for automatic ticket execution\n const args = parseWatcherStartArgs(request.params.arguments);\n\n // Get working directory - use provided or fall back to cwd\n const workingDirectory = args.workingDirectory ?? process.cwd();\n\n try {\n const result = startWatcher(\n {\n projectSlug: ticketTool.projectSlug,\n projectToken: config.projectToken,\n convexUrl: config.convexUrl,\n workingDirectory,\n },\n args\n );\n\n if (result.success) {\n const configInfo = result.config\n ? `\\n\\n**Configuration:**\\n- Poll Interval: ${result.config.pollIntervalMs / 1000}s\\n- Max Parallel: ${result.config.maxParallel}\\n- Ticket Timeout: ${result.config.ticketTimeout / 1000 / 60} min\\n- Working Directory: ${result.config.workingDirectory}`\n : \"\";\n\n const text = result.alreadyRunning\n ? `ℹ️ ${result.message}${configInfo}`\n : `✅ ${result.message}\\n\\nThe daemon is now polling for YOLO-flagged tickets and will spawn Claude CLI processes to execute them.\\n\\nLogs: \\`.ppm/yolo/logs/\\`${configInfo}`;\n\n return {\n content: [{ type: \"text\", text }],\n };\n } else {\n return {\n content: [{ type: \"text\", text: `❌ ${result.message}` }],\n isError: true,\n };\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_yolo_start error:`, error);\n return {\n content: [{ type: \"text\", text: `Error starting watcher daemon: ${errorMessage}` }],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'yolo_stop') {\n // YOLO watcher: stop the background daemon\n const workingDirectory = process.cwd();\n\n try {\n const result = stopWatcher(workingDirectory);\n\n if (result.success) {\n return {\n content: [{\n type: \"text\",\n text: result.wasRunning\n ? `✅ ${result.message}\\n\\nThe daemon has been stopped. Any running ticket executions will complete.`\n : `ℹ️ ${result.message}`,\n }],\n };\n } else {\n return {\n content: [{ type: \"text\", text: `❌ ${result.message}` }],\n isError: true,\n };\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_yolo_stop error:`, error);\n return {\n content: [{ type: \"text\", text: `Error stopping watcher daemon: ${errorMessage}` }],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'yolo_status') {\n // YOLO watcher: get daemon status\n const workingDirectory = process.cwd();\n\n try {\n const result = getWatcherStatus(workingDirectory);\n\n // Return the formatted status\n if (result.formattedStatus) {\n return {\n content: [{ type: \"text\", text: result.formattedStatus }],\n };\n }\n\n return {\n content: [{ type: \"text\", text: `ℹ️ ${result.message ?? 'No watcher status available'}` }],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_yolo_status error:`, error);\n return {\n content: [{ type: \"text\", text: `Error getting watcher status: ${errorMessage}` }],\n isError: true,\n };\n }\n }\n }\n\n // Handle per-prompt tool invocations (e.g., \"code-review\" - now global)\n const promptTool = dynamicPromptTools.find((pt) => pt.name === toolName);\n if (promptTool) {\n try {\n const result = await fetchAndExecuteAccountScopedPrompt(\n promptTool.promptSlug,\n config,\n convexClient\n );\n\n const promptText = result.messages\n .map((msg) => msg.content.text)\n .join('\\n\\n');\n\n return {\n content: [{ type: \"text\", text: promptText }],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] ${toolName} error:`, error);\n return {\n content: [{ type: \"text\", text: `Error executing prompt: ${errorMessage}` }],\n isError: true,\n };\n }\n }\n\n // Unknown tool\n throw new Error(`Unknown tool: ${toolName}. Use prompts_run to execute prompts by name, or check available tools.`);\n });\n\n // Start server with stdio transport\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n console.error(\"[MCP] Server started successfully\");\n console.error(`[MCP] Deployment: ${config.isDev ? \"DEVELOPMENT\" : \"PRODUCTION\"}`);\n console.error(`[MCP] Convex URL: ${config.convexUrl}`);\n console.error(`[MCP] Data mode: REAL-TIME (fetches fresh data on each invocation)`);\n\n // List all prompts\n const allPromptNames = [...Array.from(promptNames)].sort();\n console.error(`[MCP] Prompts available: ${allPromptNames.join(\", \")}`);\n console.error(`[MCP] - Total prompts: ${promptNames.size}`);\n\n // Keep the event loop alive with a heartbeat\n // This prevents Node from exiting when there are no active handles\n setInterval(() => {\n // Heartbeat every 60 seconds to keep process alive\n }, 60000);\n\n // Return a promise that never resolves to keep the server running\n return new Promise<void>(() => {\n // The transport handles stdin/stdout communication\n // The interval above keeps the event loop active\n });\n}\n\n/**\n * Validate project token with Convex\n *\n * Project tokens are now stored directly on the projects table.\n * Returns project details if valid, null if invalid.\n */\n/**\n * Response type from validateProjectToken query\n */\ninterface ValidateProjectTokenResponse {\n valid: boolean;\n projectId?: string;\n projectSlug?: string;\n ownerId?: string;\n}\n\nasync function validateProjectToken(\n client: ConvexHttpClient,\n token: string\n): Promise<ProjectTokenValidation> {\n try {\n // Cast to our typed client for string-based function references\n const typedClient = client as ConvexClientWithStringRefs;\n const result = await typedClient.query<ValidateProjectTokenResponse | null>(\n \"projects:validateProjectToken\",\n { token }\n );\n\n if (result && result.valid) {\n return {\n valid: true,\n projectId: result.projectId,\n projectSlug: result.projectSlug,\n ownerId: result.ownerId,\n };\n }\n return { valid: false, error: \"Invalid project token\" };\n } catch (error) {\n return {\n valid: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n };\n }\n}\n\n// Note: fetchMcpPromptMetadata removed - prompts now account-scoped\n// Note: fetchMcpCommands and buildCommandName removed - commands resource eliminated\n// Note: API key-based functions removed - all auth now uses project tokens\n","// ============================================================================\n// MCP Tool Argument Types\n// ============================================================================\n\n/**\n * Arguments for the tickets_work tool\n * Optional ticketSlug: if provided, work on specific ticket; otherwise get next from queue\n */\nexport interface WorkToolArgs {\n ticketSlug?: string;\n}\n\n/**\n * Arguments for the tickets_close tool\n * Required ticketSlug to identify which ticket to close\n */\nexport interface CloseToolArgs {\n ticketSlug: string;\n}\n\n/**\n * Arguments for the tickets_create tool\n * Required content where first line becomes the slug\n * Optional tags array for assigning existing tags at creation\n */\nexport interface CreateToolArgs {\n content: string;\n tags?: string[];\n}\n\n/**\n * Arguments for the tickets_search tool\n * Required query string (min 3 characters)\n */\nexport interface SearchToolArgs {\n query: string;\n}\n\n/**\n * Arguments for the tickets_get tool\n * Required ticketSlug to identify which ticket to retrieve\n */\nexport interface GetToolArgs {\n ticketSlug: string;\n}\n\n/**\n * Arguments for the tickets_list tool\n * No arguments required - returns all active tickets\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface ListToolArgs {}\n\n/**\n * Arguments for the tickets_update tool\n * Required ticketSlug and content to append\n */\nexport interface UpdateToolArgs {\n ticketSlug: string;\n content: string;\n}\n\n/**\n * Arguments for the prompts_run tool\n * Required slug to identify which prompt to execute\n */\nexport interface PromptsRunArgs {\n slug: string;\n}\n\n/**\n * Arguments for the prompts_list tool\n * Optional search term to filter prompts\n */\nexport interface PromptsListArgs {\n search?: string;\n}\n\n/**\n * Arguments for the prompts_update tool\n * Required slug, optional content for write mode\n */\nexport interface PromptsUpdateArgs {\n slug: string;\n content?: string;\n changeLogMessage?: string;\n}\n\n// ============================================================================\n// Convex Response Types (for MCP mutations/queries)\n// ============================================================================\n\n/**\n * Response from workMcpTicket mutation\n */\nexport interface WorkTicketResult {\n slug: string;\n ticketNumber?: number;\n status: string;\n content: string;\n startedAt?: number;\n remainingTickets: number;\n wasOpened: boolean;\n}\n\n/**\n * Access denied response from ticket operations\n */\nexport interface AccessDeniedResult {\n error: string;\n accessDenied: true;\n}\n\n/**\n * Type guard to check if a result is an access denied response\n */\nexport function isAccessDenied(result: unknown): result is AccessDeniedResult {\n return (\n typeof result === 'object' &&\n result !== null &&\n 'accessDenied' in result &&\n (result as AccessDeniedResult).accessDenied === true\n );\n}\n\n/**\n * Response from createMcpTicket mutation\n */\nexport interface CreateTicketResult {\n slug: string;\n ticketNumber?: number;\n preview: string;\n position: number;\n totalBacklog: number;\n}\n\n/**\n * Response from closeMcpTicket mutation\n */\nexport interface CloseTicketResult {\n slug: string;\n status: string;\n closedAt: number;\n}\n\n/**\n * Response from updateMcpTicket mutation\n */\nexport interface UpdateTicketResult {\n slug: string;\n ticketNumber?: number;\n status: string;\n updatedAt: number;\n}\n\n/**\n * Response from getMcpTicket query\n */\nexport interface GetTicketResult {\n slug: string;\n ticketNumber?: number;\n content: string;\n status: string;\n createdAt: number;\n startedAt?: number;\n closedAt?: number;\n}\n\n/**\n * Response from listMcpTickets query\n */\nexport interface ListTicketItem {\n position: number;\n slug: string;\n ticketNumber?: number;\n status: string;\n preview: string;\n createdAt: number;\n startedAt?: number;\n}\n\n/**\n * Response from searchMcpTickets query\n */\nexport interface SearchTicketItem {\n slug: string;\n ticketNumber?: number;\n status: string;\n matchSnippet: string;\n createdAt: number;\n}\n\n/**\n * Response from prompt metadata query\n */\nexport interface PromptMetadata {\n slug: string;\n description?: string;\n folderSlug?: string;\n}\n\n/**\n * Response from updateMcpPrompt mutation (read mode)\n */\nexport interface UpdatePromptReadResult {\n success: true;\n mode: \"read\";\n matchType: \"exact\" | \"prefix\" | \"contains\";\n slug: string;\n description: string;\n content: string;\n version: number;\n context: {\n variables: Array<{ slug: string; reference: string; type: string; description: string }>;\n snippets: Array<{ slug: string; reference: string; description: string }>;\n prompts: Array<{ slug: string; reference: string; description: string }>;\n };\n editingGuidance: string;\n}\n\n/**\n * Response from updateMcpPrompt mutation (write mode)\n */\nexport interface UpdatePromptWriteResult {\n success: true;\n mode: \"write\";\n slug: string;\n version: number;\n updatedAt: string;\n message: string;\n}\n\n/**\n * Response from updateMcpPrompt mutation (error)\n */\nexport interface UpdatePromptErrorResult {\n success: false;\n error: string;\n suggestions?: string[];\n}\n\n/**\n * Union type for all updateMcpPrompt responses\n */\nexport type UpdatePromptResult =\n | UpdatePromptReadResult\n | UpdatePromptWriteResult\n | UpdatePromptErrorResult;\n\n/**\n * Account-scoped prompt match result types\n */\nexport type AccountScopedPromptResult =\n | { matchType: \"exact\" | \"prefix\" | \"contains\"; slug: string; description?: string; flattenedPrompt?: string }\n | { matchType: \"ambiguous\"; suggestions: string[] }\n | null;\n\n// ============================================================================\n// Argument Parsing Functions\n// ============================================================================\n\n/**\n * Parse and validate arguments for tickets_work tool\n */\nexport function parseWorkArgs(args: unknown): WorkToolArgs {\n const parsed = args as Record<string, unknown> | undefined;\n return {\n ticketSlug: typeof parsed?.ticketSlug === 'string' ? parsed.ticketSlug : undefined,\n };\n}\n\n/**\n * Parse and validate arguments for tickets_close tool\n * Returns null if required argument is missing\n */\nexport function parseCloseArgs(args: unknown): CloseToolArgs | null {\n const parsed = args as Record<string, unknown> | undefined;\n const ticketSlug = typeof parsed?.ticketSlug === 'string' ? parsed.ticketSlug : undefined;\n if (!ticketSlug) return null;\n return { ticketSlug };\n}\n\n/**\n * Parse and validate arguments for tickets_create tool\n * Returns null if required argument is missing\n */\nexport function parseCreateArgs(args: unknown): CreateToolArgs | null {\n const parsed = args as Record<string, unknown> | undefined;\n const content = typeof parsed?.content === 'string' ? parsed.content : undefined;\n if (!content) return null;\n\n // Parse optional tags array - filter to valid strings only\n let tags: string[] | undefined;\n if (Array.isArray(parsed?.tags)) {\n tags = parsed.tags.filter((t): t is string => typeof t === 'string');\n if (tags.length === 0) tags = undefined;\n }\n\n return { content, tags };\n}\n\n/**\n * Parse and validate arguments for tickets_search tool\n * Returns null if required argument is missing or too short\n */\nexport function parseSearchArgs(args: unknown): SearchToolArgs | null {\n const parsed = args as Record<string, unknown> | undefined;\n const query = typeof parsed?.query === 'string' ? parsed.query : undefined;\n if (!query || query.length < 3) return null;\n return { query };\n}\n\n/**\n * Parse and validate arguments for tickets_get tool\n * Returns null if required argument is missing\n */\nexport function parseGetArgs(args: unknown): GetToolArgs | null {\n const parsed = args as Record<string, unknown> | undefined;\n const ticketSlug = typeof parsed?.ticketSlug === 'string' ? parsed.ticketSlug : undefined;\n if (!ticketSlug) return null;\n return { ticketSlug };\n}\n\n/**\n * Parse arguments for tickets_list tool (no args required)\n */\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport function parseListArgs(args: unknown): ListToolArgs {\n return {};\n}\n\n/**\n * Parse and validate arguments for tickets_update tool\n * Returns null if any required argument is missing\n */\nexport function parseUpdateArgs(args: unknown): UpdateToolArgs | null {\n const parsed = args as Record<string, unknown> | undefined;\n const ticketSlug = typeof parsed?.ticketSlug === 'string' ? parsed.ticketSlug : undefined;\n const content = typeof parsed?.content === 'string' ? parsed.content : undefined;\n if (!ticketSlug || !content) return null;\n return { ticketSlug, content };\n}\n\n/**\n * Parse and validate arguments for prompts_run tool\n * Returns null if required argument is missing\n */\nexport function parsePromptsRunArgs(args: unknown): PromptsRunArgs | null {\n const parsed = args as Record<string, unknown> | undefined;\n const slug = typeof parsed?.slug === 'string' ? parsed.slug : undefined;\n if (!slug) return null;\n return { slug };\n}\n\n/**\n * Parse arguments for prompts_list tool\n */\nexport function parsePromptsListArgs(args: unknown): PromptsListArgs {\n const parsed = args as Record<string, unknown> | undefined;\n return {\n search: typeof parsed?.search === 'string' ? parsed.search : undefined,\n };\n}\n\n/**\n * Parse and validate arguments for prompts_update tool\n * Returns null if required slug is missing\n */\nexport function parsePromptsUpdateArgs(args: unknown): PromptsUpdateArgs | null {\n const parsed = args as Record<string, unknown> | undefined;\n const slug = typeof parsed?.slug === 'string' ? parsed.slug : undefined;\n if (!slug) return null;\n return {\n slug,\n content: typeof parsed?.content === 'string' ? parsed.content : undefined,\n changeLogMessage: typeof parsed?.changeLogMessage === 'string' ? parsed.changeLogMessage : undefined,\n };\n}\n\n// ============================================================================\n// Legacy Types (kept for backwards compatibility)\n// ============================================================================\n\n/**\n * @deprecated Legacy type - prompts are now account-scoped (no projectSlug)\n * Kept for backwards compatibility during transition period.\n */\nexport interface McpPrompt {\n slug: string;\n name: string;\n description?: string;\n flattenedPrompt?: string;\n folderPath?: string; // Folder organization (used for ZIP downloads, not slash commands)\n projectSlug?: string; // @deprecated - prompts are now account-scoped\n projectName?: string; // @deprecated - prompts are now account-scoped\n}\n\n/**\n * @deprecated Legacy type - prompts are now account-scoped (no projectSlug)\n * Kept for backwards compatibility during transition period.\n */\nexport interface McpPromptMetadata {\n slug: string;\n description?: string;\n projectSlug?: string; // @deprecated - prompts are now account-scoped\n}\n\n// Note: McpCommandMetadata and McpCommandFull removed - commands resource eliminated\n\n/**\n * @deprecated Legacy type - prompts are now account-scoped (no projectSlug)\n * Kept for backwards compatibility during transition period.\n */\nexport interface McpPromptFull {\n slug: string;\n description?: string;\n flattenedPrompt?: string;\n projectSlug?: string; // @deprecated - prompts are now account-scoped\n}\n\n/**\n * @deprecated Legacy type - use account-scoped prompt queries instead\n * Kept for backwards compatibility during transition period.\n */\nexport type PromptMatchResult =\n | { matchType: \"exact\" | \"prefix\" | \"contains\"; slug: string; description?: string; flattenedPrompt?: string; projectSlug: string }\n | { matchType: \"ambiguous\"; suggestions: string[] }\n | null;\n\n/**\n * Configuration for the MCP server\n *\n * Authentication: Project Token (PPM_PROJECT_TOKEN)\n * Provides scoped access to a single project for both prompts and tickets.\n * The token's ownerId determines which prompts are accessible (account-scoped).\n *\n * Note: API Key auth (PPM_API_KEY) has been deprecated.\n * All authentication now uses project tokens.\n */\nexport interface ServerConfig {\n projectToken: string; // Required - scoped access to a single project\n isDev: boolean;\n convexUrl: string;\n}\n\n/**\n * Result of project token validation\n * Token is now stored directly on the projects table (single token per project)\n */\nexport interface ProjectTokenValidation {\n valid: boolean;\n projectId?: string;\n projectSlug?: string;\n ownerId?: string;\n error?: string;\n}\n\n// ============================================================================\n// YOLO Watcher Types\n// ============================================================================\n\n/**\n * Arguments for the tickets_yolo_start tool\n * All fields are optional - defaults are applied by the watcher\n */\nexport interface WatcherStartToolArgs {\n pollIntervalMs?: number;\n maxParallel?: number;\n ticketTimeout?: number;\n enableNotifications?: boolean;\n workingDirectory?: string;\n}\n\n/**\n * Parse and validate arguments for tickets_yolo_start tool\n * All arguments are optional with sensible defaults\n */\nexport function parseWatcherStartArgs(args: unknown): WatcherStartToolArgs {\n const parsed = args as Record<string, unknown> | undefined;\n\n return {\n pollIntervalMs: typeof parsed?.pollIntervalMs === \"number\" ? parsed.pollIntervalMs : undefined,\n maxParallel: typeof parsed?.maxParallel === \"number\" ? parsed.maxParallel : undefined,\n ticketTimeout: typeof parsed?.ticketTimeout === \"number\" ? parsed.ticketTimeout : undefined,\n enableNotifications: typeof parsed?.enableNotifications === \"boolean\" ? parsed.enableNotifications : undefined,\n workingDirectory: typeof parsed?.workingDirectory === \"string\" ? parsed.workingDirectory : undefined,\n };\n}\n","/**\n * Watcher Controller\n *\n * Handles MCP tool requests for YOLO watcher management:\n * - tickets_yolo_start: Spawn background daemon for automatic ticket execution\n * - tickets_yolo_stop: Stop the background daemon\n * - tickets_yolo_status: Get current status including completed tickets\n *\n * The daemon approach (Phase 3) uses a detached background process that:\n * - Polls for YOLO tickets at configurable intervals\n * - Spawns headless `claude` CLI processes for execution\n * - Captures output to log files\n * - Survives MCP session restarts\n */\n\nimport { spawn } from \"child_process\";\nimport { fileURLToPath } from \"url\";\nimport { dirname, join } from \"path\";\nimport type {\n WatcherConfig,\n WatcherStatus,\n WatcherStartArgs,\n CompletedTicket,\n} from \"./watcher_types.js\";\nimport {\n readStatus,\n writeStatus,\n readPid,\n isWatcherRunning,\n getWatcherDir,\n removePid,\n readTicketLog,\n} from \"./watcher_state.js\";\n\n// Re-export default config for use by callers\nexport { DEFAULT_WATCHER_CONFIG } from \"./watcher_types.js\";\n\n// ============================================================================\n// Result Types\n// ============================================================================\n\n/**\n * Result from tickets_yolo_start tool\n */\nexport interface WatcherStartResult {\n success: boolean;\n message: string;\n pid?: number;\n alreadyRunning?: boolean;\n config?: Omit<WatcherConfig, \"projectToken\">;\n}\n\n/**\n * Result from tickets_yolo_stop tool\n */\nexport interface WatcherStopResult {\n success: boolean;\n message: string;\n wasRunning: boolean;\n}\n\n/**\n * Result from tickets_yolo_status tool\n */\nexport interface WatcherStatusResult {\n success: boolean;\n status?: WatcherStatus;\n formattedStatus?: string;\n message?: string;\n}\n\n// ============================================================================\n// Start Watcher Daemon\n// ============================================================================\n\n/**\n * Start the YOLO watcher daemon.\n *\n * Spawns a detached background process that polls for tickets and\n * executes them using headless Claude CLI.\n *\n * @param baseConfig - Base configuration (projectSlug, token, convexUrl, workDir)\n * @param args - Optional overrides from MCP tool arguments\n * @returns Result with PID or error\n */\nexport function startWatcher(\n baseConfig: {\n projectSlug: string;\n projectToken: string;\n convexUrl: string;\n workingDirectory: string;\n },\n args: WatcherStartArgs | undefined\n): WatcherStartResult {\n const workingDirectory = args?.workingDirectory ?? baseConfig.workingDirectory;\n\n // Check if already running\n if (isWatcherRunning(workingDirectory)) {\n const existingPid = readPid(workingDirectory);\n const status = readStatus(workingDirectory);\n\n return {\n success: true,\n message: `Watcher already running (PID: ${existingPid})`,\n pid: existingPid,\n alreadyRunning: true,\n config: status?.config,\n };\n }\n\n // Build full configuration\n const config: WatcherConfig = {\n projectSlug: baseConfig.projectSlug,\n projectToken: baseConfig.projectToken,\n convexUrl: baseConfig.convexUrl,\n pollIntervalMs: args?.pollIntervalMs ?? 30000,\n maxParallel: args?.maxParallel ?? 1,\n ticketTimeout: args?.ticketTimeout ?? 1800000,\n enableNotifications: args?.enableNotifications ?? true,\n workingDirectory,\n };\n\n // Ensure state directory exists\n getWatcherDir(workingDirectory);\n\n // Get path to daemon script\n // The daemon is bundled alongside the main entry point\n const daemonPath = getDaemonPath();\n\n // Encode config as base64 to avoid shell escaping issues\n const configJson = JSON.stringify(config);\n const configB64 = Buffer.from(configJson).toString(\"base64\");\n\n try {\n // Spawn daemon as detached process via node\n const child = spawn(\"node\", [daemonPath, \"--config\", configB64], {\n detached: true,\n stdio: [\"ignore\", \"ignore\", \"ignore\"],\n cwd: workingDirectory,\n });\n\n // Unref so parent can exit independently\n child.unref();\n\n const pid = child.pid;\n\n if (!pid) {\n return {\n success: false,\n message: \"Failed to spawn daemon - no PID returned\",\n };\n }\n\n // Return success - daemon will write its own status file\n // Give it a moment to initialize\n const configWithoutToken: Omit<WatcherConfig, \"projectToken\"> = {\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 return {\n success: true,\n message: `Watcher started (PID: ${pid})`,\n pid,\n alreadyRunning: false,\n config: configWithoutToken,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n return {\n success: false,\n message: `Failed to start watcher: ${errorMessage}`,\n };\n }\n}\n\n/**\n * Get the path to the daemon script.\n *\n * The daemon is bundled as a separate entry point by tsup.\n * Output is at dist/watcher/watcher_daemon.js.\n */\nfunction getDaemonPath(): string {\n // Get directory of current module (this file is bundled into dist/index.js)\n const currentFile = fileURLToPath(import.meta.url);\n const currentDir = dirname(currentFile);\n\n // Daemon is in watcher/ subdirectory\n return join(currentDir, \"watcher\", \"watcher_daemon.js\");\n}\n\n// ============================================================================\n// Stop Watcher Daemon\n// ============================================================================\n\n/**\n * Stop the YOLO watcher daemon.\n *\n * Sends SIGTERM to the daemon process to trigger graceful shutdown.\n *\n * @param workingDirectory - Project root directory\n * @returns Result indicating success/failure\n */\nexport function stopWatcher(workingDirectory: string): WatcherStopResult {\n const pid = readPid(workingDirectory);\n\n if (!pid) {\n return {\n success: true,\n message: \"No watcher running (no PID file)\",\n wasRunning: false,\n };\n }\n\n // Check if process is actually running\n if (!isWatcherRunning(workingDirectory)) {\n // Clean up stale files\n removePid(workingDirectory);\n\n return {\n success: true,\n message: \"Watcher was not running (stale PID file cleaned up)\",\n wasRunning: false,\n };\n }\n\n try {\n // Send SIGTERM for graceful shutdown\n process.kill(pid, \"SIGTERM\");\n\n // Update status file\n const status = readStatus(workingDirectory);\n if (status) {\n writeStatus(workingDirectory, {\n ...status,\n state: \"stopped\",\n });\n }\n\n // Clean up PID file\n removePid(workingDirectory);\n\n return {\n success: true,\n message: `Watcher stopped (PID: ${pid})`,\n wasRunning: true,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n\n // Clean up even on error\n removePid(workingDirectory);\n\n return {\n success: false,\n message: `Failed to stop watcher: ${errorMessage}`,\n wasRunning: true,\n };\n }\n}\n\n// ============================================================================\n// Get Status\n// ============================================================================\n\n/**\n * Get the current watcher status.\n *\n * Reads from the status file and formats for display.\n *\n * @param workingDirectory - Project root directory\n * @returns Current status with formatted output\n */\nexport function getWatcherStatus(workingDirectory: string): WatcherStatusResult {\n const status = readStatus(workingDirectory);\n\n // Check if daemon is actually running\n const isRunning = isWatcherRunning(workingDirectory);\n\n // Update status if it says running but daemon is dead\n if (status?.state === \"running\" && !isRunning) {\n const updatedStatus: WatcherStatus = {\n ...status,\n state: \"stopped\",\n };\n writeStatus(workingDirectory, updatedStatus);\n\n return {\n success: true,\n status: updatedStatus,\n formattedStatus: formatStatus(updatedStatus),\n message: \"Watcher died unexpectedly - status updated\",\n };\n }\n\n if (!status) {\n return {\n success: true,\n message: \"No watcher session found for this project\",\n formattedStatus: formatStatus(null),\n };\n }\n\n return {\n success: true,\n status,\n formattedStatus: formatStatus(status),\n };\n}\n\n// ============================================================================\n// Status Formatting\n// ============================================================================\n\n/**\n * Format watcher status for display.\n */\nfunction formatStatus(status: WatcherStatus | null): string {\n if (!status) {\n return `# YOLO Watcher Status\n\n**State**: Not started\n\n_Use \\`tickets_yolo_start\\` to begin watching for YOLO tickets._`;\n }\n\n const lines: string[] = [\n \"# YOLO Watcher Status\",\n \"\",\n `**State**: ${status.state}`,\n `**Project**: ${status.projectSlug}`,\n ];\n\n if (status.pid) {\n lines.push(`**Daemon PID**: ${status.pid}`);\n }\n\n if (status.startedAt) {\n lines.push(`**Started**: ${status.startedAt}`);\n }\n\n if (status.lastPollAt) {\n lines.push(`**Last Poll**: ${status.lastPollAt}`);\n }\n\n lines.push(`**Tickets Processed**: ${status.ticketsProcessed}`);\n\n // Configuration\n if (status.config) {\n lines.push(\"\");\n lines.push(\"## Configuration\");\n lines.push(`- Poll Interval: ${status.config.pollIntervalMs / 1000}s`);\n lines.push(`- Max Parallel: ${status.config.maxParallel}`);\n lines.push(`- Ticket Timeout: ${status.config.ticketTimeout / 1000 / 60} min`);\n lines.push(`- Notifications: ${status.config.enableNotifications ? \"enabled\" : \"disabled\"}`);\n }\n\n // Currently executing\n if (status.currentlyExecuting && status.currentlyExecuting.length > 0) {\n lines.push(\"\");\n lines.push(\"## Currently Executing\");\n for (const exec of status.currentlyExecuting) {\n const num = exec.ticketNumber ? `#${exec.ticketNumber}` : \"\";\n const elapsed = getElapsedTime(exec.startedAt);\n lines.push(`- **${num} ${exec.ticketSlug}** (${elapsed})`);\n }\n }\n\n // Recently completed\n if (status.completedTickets && status.completedTickets.length > 0) {\n lines.push(\"\");\n lines.push(\"## Recently Completed\");\n for (const completed of status.completedTickets.slice(0, 10)) {\n const num = completed.ticketNumber ? `#${completed.ticketNumber}` : \"\";\n const emoji = completed.success ? \"✅\" : \"❌\";\n const duration = formatDuration(completed.durationMs);\n const summary = completed.summary ? ` - ${completed.summary.slice(0, 50)}` : \"\";\n lines.push(`- ${emoji} **${num} ${completed.ticketSlug}** (${duration})${summary}`);\n }\n }\n\n if (status.lastError) {\n lines.push(\"\");\n lines.push(\"## Last Error\");\n lines.push(`\\`${status.lastError}\\``);\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Get human-readable elapsed time.\n */\nfunction getElapsedTime(startedAt: string): string {\n const start = new Date(startedAt).getTime();\n const elapsed = Date.now() - start;\n\n const minutes = Math.floor(elapsed / 1000 / 60);\n const seconds = Math.floor((elapsed / 1000) % 60);\n\n if (minutes > 0) {\n return `${minutes}m ${seconds}s`;\n }\n return `${seconds}s`;\n}\n\n/**\n * Format duration in milliseconds to human-readable string.\n */\nfunction formatDuration(ms: number): string {\n if (ms < 1000) {\n return `${ms}ms`;\n }\n\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n\n if (minutes > 0) {\n const remainingSeconds = seconds % 60;\n return `${minutes}m ${remainingSeconds}s`;\n }\n return `${seconds}s`;\n}\n","import type { ConvexHttpClient } from \"convex/browser\";\nimport type { ServerConfig, PromptMetadata, AccountScopedPromptResult } from \"./types.js\";\n\n/**\n * Helper type for Convex HTTP client with string-based function references.\n * This allows us to use string function names while maintaining type safety\n * for the response types through explicit casting.\n */\ntype ConvexClientWithStringRefs = ConvexHttpClient & {\n query: <T>(name: string, args: Record<string, unknown>) => Promise<T>;\n};\n\n/**\n * Sanitizes a string for use in MCP tool names.\n * Ensures output matches MCP protocol pattern: ^[a-zA-Z0-9_-]{1,64}$\n *\n * @param str - Raw string to sanitize (project slug, folder path, or prompt slug)\n * @returns Sanitized string safe for MCP tool names\n */\nexport function sanitizeForMcp(str: string): string {\n return str\n .trim() // Remove leading/trailing whitespace first\n .toLowerCase() // Convert to lowercase for consistency\n .replace(/[^a-z0-9_-]/g, '-') // Replace ALL invalid chars (including spaces!) with hyphens\n .replace(/-+/g, '-') // Collapse multiple consecutive hyphens into one\n .replace(/^-+|-+$/g, '') // Remove leading and trailing hyphens\n .trim(); // Final trim to ensure no whitespace\n}\n\n// Note: The following legacy functions have been removed as prompts are now account-scoped:\n// - buildPromptName (was project:prompt format, now prompts are global)\n// - buildPromptNameFromMetadata (was project:prompt format)\n// - buildPromptSchema (used buildPromptName)\n// - buildPromptHandler (used project-scoped getMcpPrompts query)\n// - fetchAndExecutePrompt (used project-scoped getMcpPromptBySlug query)\n//\n// Use fetchAndExecuteAccountScopedPrompt instead for all prompt execution.\n\n/**\n * Custom error class for ambiguous prompt matches\n * Contains suggestions for the user to choose from\n */\nexport class AmbiguousPromptError extends Error {\n suggestions: string[];\n\n constructor(promptSlug: string, suggestions: string[]) {\n super(`Multiple prompts match \"${promptSlug}\". Please be more specific.`);\n this.name = \"AmbiguousPromptError\";\n this.suggestions = suggestions;\n }\n}\n\n/**\n * Fetch account-scoped prompt metadata using project token auth.\n *\n * @param client - Convex HTTP client\n * @param projectToken - Project token for authentication\n * @returns Account-scoped prompts for this project\n */\nexport async function fetchAccountScopedPromptMetadataByToken(\n client: ConvexHttpClient,\n projectToken: string\n): Promise<PromptMetadata[]> {\n try {\n // Cast to our typed client for string-based function references\n const typedClient = client as ConvexClientWithStringRefs;\n const metadata = await typedClient.query<PromptMetadata[]>(\n \"mcp_prompts:getAccountScopedPromptMetadataByToken\",\n { projectToken }\n );\n return metadata;\n } catch (error) {\n throw new Error(\n `Failed to fetch prompts via project token: ${error instanceof Error ? error.message : \"Unknown error\"}`\n );\n }\n}\n\n// Note: AccountScopedPromptResult type is imported from ./types.js\n\n/**\n * Fetch a single account-scoped prompt and execute it\n *\n * Account-scoped prompts are global (no project prefix required).\n * This is for prompts that don't have a projectId.\n *\n * Uses project token authentication: access via token's ownerId.\n *\n * Matching algorithm supports exact, prefix, and contains matching.\n */\nexport async function fetchAndExecuteAccountScopedPrompt(\n promptSlug: string,\n config: ServerConfig,\n convexClient: ConvexHttpClient\n): Promise<{ messages: Array<{ role: string; content: { type: \"text\"; text: string } }> }> {\n // Cast to our typed client for string-based function references\n const typedClient = convexClient as ConvexClientWithStringRefs;\n\n // Project token auth - access via token's owner\n const result = await typedClient.query<AccountScopedPromptResult>(\n \"mcp_prompts:getAccountScopedPromptBySlugByToken\",\n {\n projectToken: config.projectToken,\n promptSlug,\n }\n );\n\n // Handle null - no matches found\n if (!result) {\n throw new Error(\n `Prompt \"${promptSlug}\" not found. Use system:prompts to list available prompts.`\n );\n }\n\n // Handle ambiguous matches - throw custom error with suggestions\n if (result.matchType === \"ambiguous\") {\n throw new AmbiguousPromptError(promptSlug, result.suggestions);\n }\n\n // Handle successful match (exact, prefix, or contains)\n if (!result.flattenedPrompt) {\n throw new Error(\n `Prompt \"${result.slug}\" has no flattened content. Please re-save the prompt to regenerate it.`\n );\n }\n\n // Log match type for debugging\n const matchNote = result.matchType === \"exact\"\n ? \"exact match\"\n : result.matchType === \"prefix\"\n ? `prefix match → ${result.slug}`\n : `contains match → ${result.slug}`;\n console.error(`[MCP] Fetched account-scoped prompt: ${promptSlug} (${matchNote})`);\n\n // Return raw flattened prompt (client will parse YAML front matter)\n return {\n messages: [\n {\n role: \"user\",\n content: {\n type: \"text\" as const,\n text: result.flattenedPrompt,\n },\n },\n ],\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
#!/usr/bin/env node
|
|
3
1
|
import {
|
|
4
2
|
__require,
|
|
5
3
|
addCompletedTicket,
|
|
@@ -139,19 +137,20 @@ async function claimTicket(client, config2, ticketSlug) {
|
|
|
139
137
|
projectSlug: config2.projectSlug,
|
|
140
138
|
ticketSlug
|
|
141
139
|
});
|
|
142
|
-
if (!result
|
|
140
|
+
if (!result) {
|
|
143
141
|
return {
|
|
144
142
|
success: false,
|
|
145
|
-
error:
|
|
143
|
+
error: "Ticket not found or not claimable"
|
|
146
144
|
};
|
|
147
145
|
}
|
|
148
146
|
return {
|
|
149
147
|
success: true,
|
|
150
148
|
ticket: {
|
|
151
|
-
slug: result.
|
|
152
|
-
ticketNumber: result.
|
|
153
|
-
content: result.
|
|
154
|
-
flattenedContent: result.
|
|
149
|
+
slug: result.slug,
|
|
150
|
+
ticketNumber: result.ticketNumber,
|
|
151
|
+
content: result.content,
|
|
152
|
+
flattenedContent: result.content
|
|
153
|
+
// workMcpTicket returns flattenedContent as 'content'
|
|
155
154
|
}
|
|
156
155
|
};
|
|
157
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":";;;;;;;;;;;;;;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;AAEF,UAAM,SAAS,MAAM,OAAO,SASzB,6BAA6B;AAAA,MAC9B,cAAcA,QAAO;AAAA,MACrB,aAAaA,QAAO;AAAA,MACpB;AAAA,IACF,CAAC;AAED,QAAI,CAAC,OAAO,WAAW,CAAC,OAAO,QAAQ;AACrC,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,OAAO,SAAS;AAAA,MACzB;AAAA,IACF;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,QAAQ;AAAA,QACN,MAAM,OAAO,OAAO;AAAA,QACpB,cAAc,OAAO,OAAO;AAAA,QAC5B,SAAS,OAAO,OAAO;AAAA,QACvB,kBAAkB,OAAO,OAAO;AAAA,MAClC;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"]}
|
|
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":"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","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
|
+
"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",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"dist"
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
|
-
"build": "tsup",
|
|
14
|
+
"build": "tsup && npm run postbuild",
|
|
15
|
+
"postbuild": "node -e \"const fs=require('fs');const f='dist/watcher/watcher_daemon.js';let c=fs.readFileSync(f,'utf8');c=c.replace(/^#!.*\\n/,'');fs.writeFileSync(f,c);\"",
|
|
15
16
|
"dev": "tsup --watch",
|
|
16
17
|
"test": "vitest run",
|
|
17
18
|
"prepublishOnly": "npm run build"
|