panopticon-cli 0.5.1 → 0.5.3

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.
Files changed (117) hide show
  1. package/dist/{agents-5OPQKM5K.js → agents-DMPT32H7.js} +7 -6
  2. package/dist/{chunk-OWHXCGVO.js → chunk-2V2DQ3IX.js} +378 -101
  3. package/dist/chunk-2V2DQ3IX.js.map +1 -0
  4. package/dist/{chunk-F5555J3A.js → chunk-4HST45MO.js} +13 -27
  5. package/dist/chunk-4HST45MO.js.map +1 -0
  6. package/dist/{chunk-YLPSQAM2.js → chunk-565HZ6VV.js} +2 -2
  7. package/dist/chunk-6N2KBSJA.js +452 -0
  8. package/dist/chunk-6N2KBSJA.js.map +1 -0
  9. package/dist/{chunk-VHKSS7QX.js → chunk-D67AQTHF.js} +21 -19
  10. package/dist/chunk-D67AQTHF.js.map +1 -0
  11. package/dist/{chunk-4YSYJ4HM.js → chunk-DFNVHK3N.js} +2 -2
  12. package/dist/{chunk-7SN4L4PH.js → chunk-HOGYHJ2G.js} +2 -2
  13. package/dist/{chunk-FTCPTHIJ.js → chunk-HRU7S4TA.js} +24 -7
  14. package/dist/chunk-HRU7S4TA.js.map +1 -0
  15. package/dist/{chunk-NLQRED36.js → chunk-KBHRXV5T.js} +3 -3
  16. package/dist/chunk-KBHRXV5T.js.map +1 -0
  17. package/dist/{chunk-CWELWPWQ.js → chunk-MOPGR3CL.js} +1 -1
  18. package/dist/chunk-MOPGR3CL.js.map +1 -0
  19. package/dist/{chunk-2V4NF7J2.js → chunk-RLZQB7HS.js} +2 -2
  20. package/dist/chunk-RLZQB7HS.js.map +1 -0
  21. package/dist/{chunk-76F6DSVS.js → chunk-T7BBPDEJ.js} +11 -7
  22. package/dist/chunk-T7BBPDEJ.js.map +1 -0
  23. package/dist/chunk-USYP2SBE.js +317 -0
  24. package/dist/chunk-USYP2SBE.js.map +1 -0
  25. package/dist/{chunk-JM6V62LT.js → chunk-ZDNQFWR5.js} +2 -2
  26. package/dist/{chunk-JM6V62LT.js.map → chunk-ZDNQFWR5.js.map} +1 -1
  27. package/dist/{chunk-HJSM6E6U.js → chunk-ZP6EWSZV.js} +29 -322
  28. package/dist/chunk-ZP6EWSZV.js.map +1 -0
  29. package/dist/{chunk-PELXV435.js → chunk-ZTYHZMEC.js} +2 -2
  30. package/dist/chunk-ZTYHZMEC.js.map +1 -0
  31. package/dist/cli/index.js +1381 -777
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/config-yaml-OVZLKFMA.js +18 -0
  34. package/dist/dashboard/prompts/merge-agent.md +7 -5
  35. package/dist/dashboard/prompts/review-agent.md +12 -1
  36. package/dist/dashboard/prompts/test-agent.md +3 -1
  37. package/dist/dashboard/public/assets/index-BJKEp64j.css +32 -0
  38. package/dist/dashboard/public/assets/index-CgJjqjAV.js +767 -0
  39. package/dist/dashboard/public/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  40. package/dist/dashboard/public/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  41. package/dist/dashboard/public/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  42. package/dist/dashboard/public/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  43. package/dist/dashboard/public/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  44. package/dist/dashboard/public/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  45. package/dist/dashboard/public/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  46. package/dist/dashboard/public/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  47. package/dist/dashboard/public/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  48. package/dist/dashboard/public/assets/space-grotesk-latin-400-normal-BnQMeOim.woff +0 -0
  49. package/dist/dashboard/public/assets/space-grotesk-latin-400-normal-CJ-V5oYT.woff2 +0 -0
  50. package/dist/dashboard/public/assets/space-grotesk-latin-600-normal-BflQw4A9.woff +0 -0
  51. package/dist/dashboard/public/assets/space-grotesk-latin-600-normal-DjKNqYRj.woff2 +0 -0
  52. package/dist/dashboard/public/assets/space-grotesk-latin-700-normal-CwsQ-cCU.woff +0 -0
  53. package/dist/dashboard/public/assets/space-grotesk-latin-700-normal-RjhwGPKo.woff2 +0 -0
  54. package/dist/dashboard/public/assets/space-grotesk-latin-ext-400-normal-CfP_5XZW.woff2 +0 -0
  55. package/dist/dashboard/public/assets/space-grotesk-latin-ext-400-normal-DRPE3kg4.woff +0 -0
  56. package/dist/dashboard/public/assets/space-grotesk-latin-ext-600-normal-DxxdqCpr.woff2 +0 -0
  57. package/dist/dashboard/public/assets/space-grotesk-latin-ext-600-normal-VcznFIpX.woff +0 -0
  58. package/dist/dashboard/public/assets/space-grotesk-latin-ext-700-normal-BQnZhY3m.woff2 +0 -0
  59. package/dist/dashboard/public/assets/space-grotesk-latin-ext-700-normal-HVCqSBdx.woff +0 -0
  60. package/dist/dashboard/public/assets/space-grotesk-vietnamese-400-normal-B7xT_GF5.woff2 +0 -0
  61. package/dist/dashboard/public/assets/space-grotesk-vietnamese-400-normal-BIWiOVfw.woff +0 -0
  62. package/dist/dashboard/public/assets/space-grotesk-vietnamese-600-normal-D6zpsUhD.woff +0 -0
  63. package/dist/dashboard/public/assets/space-grotesk-vietnamese-600-normal-DUi7WF5p.woff2 +0 -0
  64. package/dist/dashboard/public/assets/space-grotesk-vietnamese-700-normal-DMty7AZE.woff2 +0 -0
  65. package/dist/dashboard/public/assets/space-grotesk-vietnamese-700-normal-Duxec5Rn.woff +0 -0
  66. package/dist/dashboard/public/index.html +5 -3
  67. package/dist/dashboard/server.js +4647 -2723
  68. package/dist/{feedback-writer-VRMMWWTW.js → feedback-writer-T43PI5S2.js} +2 -2
  69. package/dist/{hume-WMAUBBV2.js → hume-CKJJ3OUU.js} +3 -3
  70. package/dist/index.js +4 -3
  71. package/dist/index.js.map +1 -1
  72. package/dist/{projects-CFX3RTDL.js → projects-KVM3MN3Y.js} +2 -2
  73. package/dist/{remote-agents-TFSMW7GN.js → remote-agents-ULPD6C5U.js} +3 -3
  74. package/dist/{remote-workspace-7FPGF2RM.js → remote-workspace-XX6ARE6I.js} +3 -3
  75. package/dist/{review-status-TDPSOU5J.js → review-status-XKUKZF6J.js} +3 -2
  76. package/dist/{specialist-context-WGUUYDWY.js → specialist-context-53AWO6AE.js} +6 -5
  77. package/dist/{specialist-context-WGUUYDWY.js.map → specialist-context-53AWO6AE.js.map} +1 -1
  78. package/dist/{specialist-logs-XJB5TCKJ.js → specialist-logs-QREUJ4HN.js} +6 -5
  79. package/dist/{specialists-5LBRHYFA.js → specialists-2DBBXRCK.js} +6 -5
  80. package/dist/{traefik-WFMQX2LY.js → traefik-5GL3Q7DJ.js} +3 -3
  81. package/dist/{tunnel-W2GZBLEV.js → tunnel-BKC7KLBX.js} +3 -3
  82. package/dist/{workspace-manager-E434Z45T.js → workspace-manager-ALBR62AS.js} +5 -5
  83. package/dist/workspace-manager-ALBR62AS.js.map +1 -0
  84. package/package.json +1 -1
  85. package/scripts/record-cost-event.js +8424 -42
  86. package/scripts/work-agent-stop-hook +221 -24
  87. package/dist/chunk-2V4NF7J2.js.map +0 -1
  88. package/dist/chunk-76F6DSVS.js.map +0 -1
  89. package/dist/chunk-CWELWPWQ.js.map +0 -1
  90. package/dist/chunk-F5555J3A.js.map +0 -1
  91. package/dist/chunk-FTCPTHIJ.js.map +0 -1
  92. package/dist/chunk-HJSM6E6U.js.map +0 -1
  93. package/dist/chunk-NLQRED36.js.map +0 -1
  94. package/dist/chunk-OWHXCGVO.js.map +0 -1
  95. package/dist/chunk-PELXV435.js.map +0 -1
  96. package/dist/chunk-VHKSS7QX.js.map +0 -1
  97. package/dist/chunk-YGJ54GW2.js +0 -96
  98. package/dist/chunk-YGJ54GW2.js.map +0 -1
  99. package/dist/dashboard/public/assets/index-Ce6q21Fm.js +0 -743
  100. package/dist/dashboard/public/assets/index-NzpI0ItZ.css +0 -32
  101. package/dist/git-utils-I2UDKNZH.js +0 -131
  102. package/dist/git-utils-I2UDKNZH.js.map +0 -1
  103. /package/dist/{agents-5OPQKM5K.js.map → agents-DMPT32H7.js.map} +0 -0
  104. /package/dist/{chunk-YLPSQAM2.js.map → chunk-565HZ6VV.js.map} +0 -0
  105. /package/dist/{chunk-4YSYJ4HM.js.map → chunk-DFNVHK3N.js.map} +0 -0
  106. /package/dist/{chunk-7SN4L4PH.js.map → chunk-HOGYHJ2G.js.map} +0 -0
  107. /package/dist/{hume-WMAUBBV2.js.map → config-yaml-OVZLKFMA.js.map} +0 -0
  108. /package/dist/{feedback-writer-VRMMWWTW.js.map → feedback-writer-T43PI5S2.js.map} +0 -0
  109. /package/dist/{projects-CFX3RTDL.js.map → hume-CKJJ3OUU.js.map} +0 -0
  110. /package/dist/{remote-agents-TFSMW7GN.js.map → projects-KVM3MN3Y.js.map} +0 -0
  111. /package/dist/{review-status-TDPSOU5J.js.map → remote-agents-ULPD6C5U.js.map} +0 -0
  112. /package/dist/{remote-workspace-7FPGF2RM.js.map → remote-workspace-XX6ARE6I.js.map} +0 -0
  113. /package/dist/{specialist-logs-XJB5TCKJ.js.map → review-status-XKUKZF6J.js.map} +0 -0
  114. /package/dist/{specialists-5LBRHYFA.js.map → specialist-logs-QREUJ4HN.js.map} +0 -0
  115. /package/dist/{traefik-WFMQX2LY.js.map → specialists-2DBBXRCK.js.map} +0 -0
  116. /package/dist/{tunnel-W2GZBLEV.js.map → traefik-5GL3Q7DJ.js.map} +0 -0
  117. /package/dist/{workspace-manager-E434Z45T.js.map → tunnel-BKC7KLBX.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/tmux.ts","../src/lib/hooks.ts","../src/lib/work-types.ts","../src/lib/model-fallback.ts","../src/lib/smart-model-selector.ts","../src/lib/work-type-router.ts"],"sourcesContent":["import { execSync, exec } from 'child_process';\nimport { promisify } from 'util';\nimport { writeFileSync, chmodSync, appendFileSync, mkdirSync, existsSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { PANOPTICON_HOME } from './paths.js';\n\n/**\n * Log file for tmux sendKeys operations\n * This helps debug mysterious messages appearing in agent prompts\n */\nconst SENDKEYS_LOG_FILE = join(PANOPTICON_HOME, 'logs', 'sendkeys.jsonl');\n\n/**\n * Ensure log directory exists\n */\nfunction ensureLogDir(): void {\n const logDir = join(PANOPTICON_HOME, 'logs');\n if (!existsSync(logDir)) {\n mkdirSync(logDir, { recursive: true });\n }\n}\n\n/**\n * Log a sendKeys operation for debugging\n */\nfunction logSendKeys(sessionName: string, keys: string, caller?: string): void {\n try {\n ensureLogDir();\n\n // Get call stack to identify caller if not provided\n const stack = new Error().stack || '';\n const stackLines = stack.split('\\n').slice(3, 6); // Skip Error, logSendKeys, sendKeys\n const callerInfo = caller || stackLines.map(l => l.trim()).join(' <- ');\n\n const entry = {\n timestamp: new Date().toISOString(),\n sessionName,\n keysLength: keys.length,\n keysPreview: keys.length > 200 ? keys.slice(0, 200) + '...' : keys,\n caller: callerInfo,\n pid: process.pid,\n };\n\n appendFileSync(SENDKEYS_LOG_FILE, JSON.stringify(entry) + '\\n', 'utf-8');\n } catch {\n // Silently fail - logging should never break functionality\n }\n}\n\nexport interface TmuxSession {\n name: string;\n created: Date;\n attached: boolean;\n windows: number;\n}\n\nexport function listSessions(): TmuxSession[] {\n try {\n const output = execSync('tmux list-sessions -F \"#{session_name}|#{session_created}|#{session_attached}|#{session_windows}\"', {\n encoding: 'utf8',\n });\n\n return output.trim().split('\\n').filter(Boolean).map(line => {\n const [name, created, attached, windows] = line.split('|');\n return {\n name,\n created: new Date(parseInt(created) * 1000),\n attached: attached === '1',\n windows: parseInt(windows),\n };\n });\n } catch {\n return []; // No sessions\n }\n}\n\nexport function sessionExists(name: string): boolean {\n try {\n execSync(`tmux has-session -t ${name} 2>/dev/null`);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function createSession(\n name: string,\n cwd: string,\n initialCommand?: string,\n options?: { env?: Record<string, string> }\n): void {\n const escapedCwd = cwd.replace(/\"/g, '\\\\\"');\n\n // Build environment variable flags for tmux\n let envFlags = '';\n if (options?.env) {\n for (const [key, value] of Object.entries(options.env)) {\n envFlags += ` -e ${key}=\"${value.replace(/\"/g, '\\\\\"')}\"`;\n }\n }\n\n // For complex commands (with special chars), start session first then send command\n if (initialCommand && (initialCommand.includes('`') || initialCommand.includes('\\n') || initialCommand.length > 500)) {\n // Create session without command\n execSync(`tmux new-session -d -s ${name} -c \"${escapedCwd}\"${envFlags}`);\n\n // Small delay to let session initialize\n execSync('sleep 0.5');\n\n // Send the command in chunks if needed (tmux has buffer limits)\n // First, write to a temp file and source it\n const tmpFile = `/tmp/pan-cmd-${name}.sh`;\n writeFileSync(tmpFile, initialCommand);\n chmodSync(tmpFile, '755');\n\n // Execute the script\n execSync(`tmux send-keys -t ${name} \"bash ${tmpFile}\"`);\n execSync(`tmux send-keys -t ${name} C-m`);\n } else if (initialCommand) {\n // Simple command - use inline\n const cmd = `tmux new-session -d -s ${name} -c \"${escapedCwd}\"${envFlags} \"${initialCommand.replace(/\"/g, '\\\\\"')}\"`;\n execSync(cmd);\n } else {\n execSync(`tmux new-session -d -s ${name} -c \"${escapedCwd}\"${envFlags}`);\n }\n}\n\nexport function killSession(name: string): void {\n execSync(`tmux kill-session -t ${name}`);\n}\n\nconst execAsync = promisify(exec);\n\n/**\n * Send keys to a tmux session (async, non-blocking).\n * Uses load-buffer + paste-buffer for reliable delivery, with a delay before Enter.\n * MUST be used from the dashboard server and any async context.\n */\nexport async function sendKeysAsync(sessionName: string, keys: string, caller?: string): Promise<void> {\n logSendKeys(sessionName, keys, caller);\n\n // Use a unique named buffer per call to prevent race conditions.\n // The default (unnamed) paste buffer is global — concurrent load-buffer\n // calls from different specialist wakes clobber each other.\n const bufferName = `pan-${process.pid}-${Date.now()}`;\n const tmpFile = `/tmp/pan-sendkeys-${bufferName}.txt`;\n try {\n writeFileSync(tmpFile, keys);\n await execAsync(`tmux load-buffer -b ${bufferName} ${tmpFile}`);\n await execAsync(`tmux paste-buffer -b ${bufferName} -t ${sessionName} -d`);\n await new Promise(r => setTimeout(r, 300));\n await execAsync(`tmux send-keys -t ${sessionName} C-m`);\n } finally {\n try { unlinkSync(tmpFile); } catch {}\n try { await execAsync(`tmux delete-buffer -b ${bufferName} 2>/dev/null`); } catch {}\n }\n}\n\n/**\n * Send keys to a tmux session (sync, blocks event loop).\n * Only use from CLI commands — NEVER from the dashboard server.\n */\nexport function sendKeys(sessionName: string, keys: string, caller?: string): void {\n logSendKeys(sessionName, keys, caller);\n\n const tmpFile = `/tmp/pan-sendkeys-${process.pid}-${Date.now()}.txt`;\n try {\n writeFileSync(tmpFile, keys);\n execSync(`tmux load-buffer ${tmpFile}`);\n execSync(`tmux paste-buffer -t ${sessionName}`);\n execSync(`sleep 0.3`);\n execSync(`tmux send-keys -t ${sessionName} C-m`);\n } finally {\n try { unlinkSync(tmpFile); } catch {}\n }\n}\n\nexport function capturePane(sessionName: string, lines: number = 50): string {\n try {\n return execSync(`tmux capture-pane -t ${sessionName} -p -S -${lines}`, {\n encoding: 'utf8',\n });\n } catch {\n return '';\n }\n}\n\n/**\n * Capture tmux pane output (async, non-blocking).\n * MUST be used from the dashboard server and any async context.\n */\nexport async function capturePaneAsync(sessionName: string, lines: number = 50): Promise<string> {\n try {\n const { stdout } = await execAsync(`tmux capture-pane -t ${sessionName} -p -S -${lines}`, {\n encoding: 'utf-8',\n });\n return stdout;\n } catch {\n return '';\n }\n}\n\n/**\n * Wait for Claude Code to reach its interactive prompt (❯) in a tmux session.\n * Polls tmux output until the prompt appears or timeout is reached.\n *\n * @param sessionName - tmux session name\n * @param timeoutMs - maximum time to wait (default: 15s for fresh start, use 5s for already-running)\n * @returns true if prompt detected, false if timed out\n */\nexport async function waitForClaudePrompt(sessionName: string, timeoutMs: number = 15000): Promise<boolean> {\n const start = Date.now();\n const POLL = 500;\n while (Date.now() - start < timeoutMs) {\n const output = await capturePaneAsync(sessionName, 10);\n // Claude Code shows ❯ when ready for user input.\n // Check that the LAST non-empty line contains ❯ (not a stale prompt from earlier output).\n const lines = output.split('\\n').filter(l => l.trim());\n const lastLine = lines[lines.length - 1] || '';\n if (lastLine.includes('❯')) return true;\n await new Promise(r => setTimeout(r, POLL));\n }\n return false;\n}\n\n/**\n * Verify that a message sent to Claude was actually received and processing started.\n * Compares tmux output before and after to detect new activity (tool calls, responses).\n *\n * @param sessionName - tmux session name\n * @param outputBefore - tmux output snapshot taken BEFORE sending the message\n * @param timeoutMs - maximum time to wait for activity (default: 10s)\n * @returns true if new activity detected, false if timed out\n */\nexport async function confirmDelivery(\n sessionName: string,\n outputBefore: string,\n timeoutMs: number = 10000,\n): Promise<boolean> {\n const start = Date.now();\n const POLL = 1000;\n const beforeLineCount = outputBefore.split('\\n').filter(l => l.trim()).length;\n\n while (Date.now() - start < timeoutMs) {\n await new Promise(r => setTimeout(r, POLL));\n const after = await capturePaneAsync(sessionName, 50);\n const afterLines = after.split('\\n').filter(l => l.trim());\n const afterLineCount = afterLines.length;\n\n // Claude is processing if: new output lines appeared (tool calls: ●, results: ⎿, etc.)\n if (afterLineCount > beforeLineCount + 1) return true;\n\n // Or if we can see activity markers in the new output\n const newOutput = afterLines.slice(beforeLineCount).join('\\n');\n if (\n newOutput.includes('●') || newOutput.includes('⎿') || newOutput.includes('Read') ||\n newOutput.includes('✻') || newOutput.includes('·') || newOutput.includes('✶') ||\n newOutput.includes('✽') || newOutput.includes('✢') || newOutput.includes('Generating') ||\n newOutput.includes('thinking') || newOutput.includes('thought for')\n ) return true;\n }\n return false;\n}\n\nexport function getAgentSessions(): TmuxSession[] {\n return listSessions().filter(s => s.name.startsWith('agent-'));\n}\n","/**\n * FPP Hooks System - Fixed Point Principle\n *\n * \"Any runnable action is a fixed point and must resolve before the system can rest.\"\n *\n * Inspired by Doctor Who: a fixed point in time must occur — it cannot be avoided.\n *\n * Hooks are persistent work queues for agents. When an agent starts,\n * it checks its hook for pending work and executes immediately.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { AGENTS_DIR } from './paths.js';\n\nexport interface HookItem {\n id: string;\n type: 'task' | 'message' | 'notification';\n priority: 'urgent' | 'high' | 'normal' | 'low';\n source: string;\n payload: {\n issueId?: string;\n message?: string;\n action?: string;\n context?: Record<string, any>;\n };\n createdAt: string;\n expiresAt?: string;\n}\n\nexport interface Hook {\n agentId: string;\n items: HookItem[];\n lastChecked?: string;\n}\n\nfunction getHookDir(agentId: string): string {\n return join(AGENTS_DIR, agentId);\n}\n\nfunction getHookFile(agentId: string): string {\n return join(getHookDir(agentId), 'hook.json');\n}\n\nfunction getMailDir(agentId: string): string {\n return join(getHookDir(agentId), 'mail');\n}\n\n/**\n * Initialize hook structure for an agent\n */\nexport function initHook(agentId: string): void {\n const hookDir = getHookDir(agentId);\n const mailDir = getMailDir(agentId);\n\n mkdirSync(hookDir, { recursive: true });\n mkdirSync(mailDir, { recursive: true });\n\n const hookFile = getHookFile(agentId);\n if (!existsSync(hookFile)) {\n const hook: Hook = {\n agentId,\n items: [],\n };\n writeFileSync(hookFile, JSON.stringify(hook, null, 2));\n }\n}\n\n/**\n * Get the hook for an agent\n */\nexport function getHook(agentId: string): Hook | null {\n const hookFile = getHookFile(agentId);\n if (!existsSync(hookFile)) {\n return null;\n }\n\n try {\n const content = readFileSync(hookFile, 'utf-8');\n return JSON.parse(content);\n } catch {\n return null;\n }\n}\n\n/**\n * Add work to an agent's hook (FPP trigger)\n */\nexport function pushToHook(agentId: string, item: Omit<HookItem, 'id' | 'createdAt'>): HookItem {\n initHook(agentId);\n\n const hook = getHook(agentId) || { agentId, items: [] };\n\n const newItem: HookItem = {\n ...item,\n id: `hook-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n createdAt: new Date().toISOString(),\n };\n\n hook.items.push(newItem);\n writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));\n\n return newItem;\n}\n\n/**\n * Check if agent has pending work (FPP check)\n */\nexport function checkHook(agentId: string): { hasWork: boolean; urgentCount: number; items: HookItem[] } {\n const hook = getHook(agentId);\n\n if (!hook || hook.items.length === 0) {\n // Also check mail directory for incoming messages\n const mailDir = getMailDir(agentId);\n if (existsSync(mailDir)) {\n const mails = readdirSync(mailDir).filter((f) => f.endsWith('.json'));\n if (mails.length > 0) {\n // Convert mail to hook items\n const mailItems: HookItem[] = mails.map((file) => {\n try {\n const content = readFileSync(join(mailDir, file), 'utf-8');\n return JSON.parse(content);\n } catch {\n return null;\n }\n }).filter(Boolean) as HookItem[];\n\n return {\n hasWork: mailItems.length > 0,\n urgentCount: mailItems.filter((i) => i.priority === 'urgent').length,\n items: mailItems,\n };\n }\n }\n\n return { hasWork: false, urgentCount: 0, items: [] };\n }\n\n // Filter out expired items\n const now = new Date();\n const activeItems = hook.items.filter((item) => {\n if (item.expiresAt) {\n return new Date(item.expiresAt) > now;\n }\n return true;\n });\n\n // Sort by priority: urgent > high > normal > low\n const priorityOrder = { urgent: 0, high: 1, normal: 2, low: 3 };\n activeItems.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);\n\n return {\n hasWork: activeItems.length > 0,\n urgentCount: activeItems.filter((i) => i.priority === 'urgent').length,\n items: activeItems,\n };\n}\n\n/**\n * Pop the next work item from hook (after execution)\n */\nexport function popFromHook(agentId: string, itemId: string): boolean {\n const hook = getHook(agentId);\n if (!hook) return false;\n\n const index = hook.items.findIndex((i) => i.id === itemId);\n if (index === -1) return false;\n\n hook.items.splice(index, 1);\n hook.lastChecked = new Date().toISOString();\n writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));\n\n return true;\n}\n\n/**\n * Clear all items from hook\n */\nexport function clearHook(agentId: string): void {\n const hook = getHook(agentId);\n if (!hook) return;\n\n hook.items = [];\n hook.lastChecked = new Date().toISOString();\n writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));\n}\n\n/**\n * Reorder hook items by providing a new order of item IDs\n * Used for manual queue management from dashboard\n */\nexport function reorderHookItems(agentId: string, orderedItemIds: string[]): boolean {\n const hook = getHook(agentId);\n if (!hook) return false;\n\n // Validate that all provided IDs exist in the hook\n const existingIds = new Set(hook.items.map((item) => item.id));\n const providedIds = new Set(orderedItemIds);\n\n // Check if all provided IDs exist\n for (const id of orderedItemIds) {\n if (!existingIds.has(id)) {\n console.error(`[hooks] Cannot reorder: item ${id} not found in hook`);\n return false;\n }\n }\n\n // Check if all existing IDs are provided\n if (existingIds.size !== providedIds.size) {\n console.error(`[hooks] Cannot reorder: mismatch in item count (existing: ${existingIds.size}, provided: ${providedIds.size})`);\n return false;\n }\n\n // Build a map for quick lookup\n const itemMap = new Map(hook.items.map((item) => [item.id, item]));\n\n // Reorder items based on provided IDs\n hook.items = orderedItemIds.map((id) => itemMap.get(id)!);\n\n // Write back to file\n writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));\n\n return true;\n}\n\n/**\n * Send a message to an agent's mailbox\n */\nexport function sendMail(\n toAgentId: string,\n from: string,\n message: string,\n priority: HookItem['priority'] = 'normal'\n): void {\n initHook(toAgentId);\n const mailDir = getMailDir(toAgentId);\n\n const mailItem: HookItem = {\n id: `mail-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n type: 'message',\n priority,\n source: from,\n payload: { message },\n createdAt: new Date().toISOString(),\n };\n\n writeFileSync(\n join(mailDir, `${mailItem.id}.json`),\n JSON.stringify(mailItem, null, 2)\n );\n}\n\n/**\n * Get and clear mail for an agent\n */\nexport function collectMail(agentId: string): HookItem[] {\n const mailDir = getMailDir(agentId);\n if (!existsSync(mailDir)) return [];\n\n const mails: HookItem[] = [];\n const files = readdirSync(mailDir).filter((f) => f.endsWith('.json'));\n\n for (const file of files) {\n const filePath = join(mailDir, file);\n try {\n const content = readFileSync(filePath, 'utf-8');\n mails.push(JSON.parse(content));\n unlinkSync(filePath); // Remove after reading\n } catch {\n // Skip invalid mail\n }\n }\n\n return mails;\n}\n\n/**\n * Generate Fixed Point prompt for agent startup\n */\nexport function generateFixedPointPrompt(agentId: string): string | null {\n const { hasWork, urgentCount, items } = checkHook(agentId);\n\n if (!hasWork) return null;\n\n const lines: string[] = [\n '# FPP: Work Found on Your Hook',\n '',\n '> \"Any runnable action is a fixed point and must resolve before the system can rest.\"',\n '',\n ];\n\n if (urgentCount > 0) {\n lines.push(`⚠️ **${urgentCount} URGENT item(s) require immediate attention**`);\n lines.push('');\n }\n\n lines.push(`## Pending Work Items (${items.length})`);\n lines.push('');\n\n for (const item of items) {\n const priorityEmoji = {\n urgent: '🔴',\n high: '🟠',\n normal: '🟢',\n low: '⚪',\n }[item.priority];\n\n lines.push(`### ${priorityEmoji} ${item.type.toUpperCase()}: ${item.id}`);\n lines.push(`- Source: ${item.source}`);\n lines.push(`- Created: ${item.createdAt}`);\n\n if (item.payload.issueId) {\n lines.push(`- Issue: ${item.payload.issueId}`);\n }\n if (item.payload.message) {\n lines.push(`- Message: ${item.payload.message}`);\n }\n if (item.payload.action) {\n lines.push(`- Action: ${item.payload.action}`);\n }\n lines.push('');\n }\n\n lines.push('---');\n lines.push('');\n lines.push('Execute these items in priority order. Use `bd hook pop <id>` after completing each item.');\n\n return lines.join('\\n');\n}\n","/**\n * Work Type Registry\n *\n * Central registry of all work type IDs used for model routing.\n * Each work type represents a specific context where AI agents operate,\n * allowing fine-grained control over which models handle which tasks.\n */\n\n/**\n * Metadata for each work type\n */\nexport interface WorkTypeMetadata {\n /** Broad category this work type belongs to */\n category: 'issue-agent' | 'specialist' | 'subagent' | 'convoy' | 'pre-work' | 'cli';\n /** Optional phase within the category (e.g., for issue-agent phases) */\n phase?: string;\n /** Human-readable description */\n description: string;\n}\n\n/**\n * Complete registry of all 23 work types with metadata\n */\nexport const WORK_TYPES = {\n // Issue agent phases (6)\n 'issue-agent:exploration': {\n phase: 'exploration',\n category: 'issue-agent',\n description: 'Exploring codebase and understanding requirements',\n },\n 'issue-agent:implementation': {\n phase: 'implementation',\n category: 'issue-agent',\n description: 'Writing code to implement features or fixes',\n },\n 'issue-agent:testing': {\n phase: 'testing',\n category: 'issue-agent',\n description: 'Running tests and verifying functionality',\n },\n 'issue-agent:documentation': {\n phase: 'documentation',\n category: 'issue-agent',\n description: 'Writing documentation and updating docs',\n },\n 'issue-agent:review-response': {\n phase: 'review-response',\n category: 'issue-agent',\n description: 'Responding to code review feedback',\n },\n\n // Specialist agents (3)\n 'specialist-review-agent': {\n category: 'specialist',\n description: 'Comprehensive code review specialist',\n },\n 'specialist-test-agent': {\n category: 'specialist',\n description: 'Test generation and verification specialist',\n },\n 'specialist-merge-agent': {\n category: 'specialist',\n description: 'Merge request finalization specialist',\n },\n\n // Subagents (4)\n 'subagent:explore': {\n category: 'subagent',\n description: 'Fast codebase exploration subagent',\n },\n 'subagent:plan': {\n category: 'subagent',\n description: 'Implementation planning subagent',\n },\n 'subagent:bash': {\n category: 'subagent',\n description: 'Command execution specialist subagent',\n },\n 'subagent:general-purpose': {\n category: 'subagent',\n description: 'General-purpose task subagent',\n },\n\n // Convoy members (4)\n 'convoy:security-reviewer': {\n category: 'convoy',\n description: 'Security-focused code reviewer in convoy',\n },\n 'convoy:performance-reviewer': {\n category: 'convoy',\n description: 'Performance-focused code reviewer in convoy',\n },\n 'convoy:correctness-reviewer': {\n category: 'convoy',\n description: 'Correctness-focused code reviewer in convoy',\n },\n 'convoy:synthesis-agent': {\n category: 'convoy',\n description: 'Synthesizes findings from convoy reviewers',\n },\n\n // Pre-work agents (5)\n 'planning-agent': {\n category: 'pre-work',\n description: 'Interactive planning and discovery agent',\n },\n 'prd-agent': {\n category: 'pre-work',\n description: 'Generates Product Requirement Documents',\n },\n 'decomposition-agent': {\n category: 'pre-work',\n description: 'Breaks down work into tasks',\n },\n 'triage-agent': {\n category: 'pre-work',\n description: 'Prioritizes and triages issues',\n },\n\n // CLI contexts (2)\n 'cli:interactive': {\n category: 'cli',\n description: 'Interactive CLI session',\n },\n 'cli:quick-command': {\n category: 'cli',\n description: 'Quick one-off CLI commands',\n },\n} as const;\n\n/**\n * Type-safe work type IDs\n */\nexport type WorkTypeId = keyof typeof WORK_TYPES;\n\n/**\n * Valid work type categories\n */\nexport type WorkTypeCategory = WorkTypeMetadata['category'];\n\n/**\n * Get all work type IDs\n */\nexport function getAllWorkTypes(): WorkTypeId[] {\n return Object.keys(WORK_TYPES) as WorkTypeId[];\n}\n\n/**\n * Get all work types in a specific category\n */\nexport function getWorkTypesByCategory(category: WorkTypeCategory): WorkTypeId[] {\n return getAllWorkTypes().filter((id) => WORK_TYPES[id].category === category);\n}\n\n/**\n * Check if a string is a valid work type ID\n */\nexport function isValidWorkType(id: string): id is WorkTypeId {\n return id in WORK_TYPES;\n}\n\n/**\n * Get metadata for a work type\n */\nexport function getWorkTypeMetadata(id: WorkTypeId): WorkTypeMetadata {\n return WORK_TYPES[id];\n}\n\n/**\n * Get human-readable name for a work type\n */\nexport function getWorkTypeName(id: WorkTypeId): string {\n const metadata = WORK_TYPES[id];\n if ('phase' in metadata && metadata.phase) {\n return `${metadata.category} (${metadata.phase})`;\n }\n return id;\n}\n\n/**\n * Validate work type ID and throw if invalid\n */\nexport function validateWorkType(id: string): asserts id is WorkTypeId {\n if (!isValidWorkType(id)) {\n throw new Error(\n `Invalid work type ID: ${id}. Valid types: ${getAllWorkTypes().join(', ')}`\n );\n }\n}\n","/**\n * Model Fallback Strategy\n *\n * When a non-Anthropic model is selected but its API key is missing,\n * automatically fallback to an equivalent Anthropic model. This ensures\n * Panopticon always works even without configuring external providers.\n */\n\nimport { ModelId, AnthropicModel, OpenAIModel, GoogleModel, ZAIModel } from './settings.js';\n\n/**\n * AI model provider types\n */\nexport type ModelProvider = 'anthropic' | 'openai' | 'google' | 'zai' | 'kimi';\n\n/**\n * Map of model ID to provider\n */\nconst MODEL_PROVIDERS: Record<ModelId, ModelProvider> = {\n // Anthropic models\n 'claude-opus-4-6': 'anthropic',\n 'claude-sonnet-4-6': 'anthropic',\n 'claude-sonnet-4-5': 'anthropic',\n 'claude-haiku-4-5': 'anthropic',\n\n // OpenAI models\n 'gpt-5.2-codex': 'openai',\n 'o3-deep-research': 'openai',\n 'gpt-4o': 'openai',\n 'gpt-4o-mini': 'openai',\n\n // Google models\n 'gemini-3-pro-preview': 'google',\n 'gemini-3-flash-preview': 'google',\n 'gemini-2.5-pro': 'google',\n 'gemini-2.5-flash': 'google',\n\n // Z.AI models\n 'glm-4.7': 'zai',\n 'glm-4.7-flash': 'zai',\n\n // Kimi models\n 'kimi-k2': 'kimi',\n 'kimi-k2.5': 'kimi',\n};\n\n/**\n * Fallback mapping: non-Anthropic model → Anthropic equivalent\n *\n * Mapping strategy:\n * - Premium models (GPT-5.2, O3, Gemini Pro) → Sonnet 4.6 (good balance)\n * - Economy models (GPT-4o-mini, Gemini Flash, GLM Flash) → Haiku 4.5\n * - GPT-4o → Sonnet 4.6 (similar tier)\n * - GLM-4.7 → Haiku 4.5 (economy tier)\n *\n * Note: We intentionally avoid Opus 4.6 as default fallback to keep costs reasonable.\n * Users who want Opus can explicitly set it in their config.\n */\nconst FALLBACK_MAP: Record<string, AnthropicModel> = {\n // OpenAI → Anthropic\n 'gpt-5.2-codex': 'claude-sonnet-4-6', // Premium code model → Sonnet\n 'o3-deep-research': 'claude-sonnet-4-6', // Premium research model → Sonnet\n 'gpt-4o': 'claude-sonnet-4-6', // Flagship model → Sonnet\n 'gpt-4o-mini': 'claude-haiku-4-5', // Economy model → Haiku\n\n // Google → Anthropic\n 'gemini-3-pro-preview': 'claude-sonnet-4-6', // Premium model → Sonnet\n 'gemini-3-flash-preview': 'claude-haiku-4-5', // Fast model → Haiku\n\n // Z.AI → Anthropic\n 'glm-4.7': 'claude-haiku-4-5', // Standard model → Haiku\n 'glm-4.7-flash': 'claude-haiku-4-5', // Fast model → Haiku\n\n // Kimi → Anthropic\n 'kimi-k2': 'claude-sonnet-4-6', // Good balance model → Sonnet\n 'kimi-k2.5': 'claude-sonnet-4-6', // Premium model → Sonnet\n};\n\n/**\n * Default fallback when model not in explicit mapping\n */\nconst DEFAULT_FALLBACK: AnthropicModel = 'claude-sonnet-4-6';\n\n/**\n * Get the provider for a model ID\n */\nexport function getModelProvider(modelId: ModelId): ModelProvider {\n return MODEL_PROVIDERS[modelId];\n}\n\n/**\n * Check if a model requires an external API key\n */\nexport function requiresExternalKey(modelId: ModelId): boolean {\n return getModelProvider(modelId) !== 'anthropic';\n}\n\n/**\n * Get all models for a specific provider\n */\nexport function getModelsByProvider(provider: ModelProvider): ModelId[] {\n return Object.entries(MODEL_PROVIDERS)\n .filter(([_, p]) => p === provider)\n .map(([modelId]) => modelId as ModelId);\n}\n\n/**\n * Check if a provider is enabled (has API key configured)\n *\n * @param provider Provider to check\n * @param enabledProviders Set of enabled provider names\n * @returns true if provider is enabled or is Anthropic (always enabled)\n */\nexport function isProviderEnabled(\n provider: ModelProvider,\n enabledProviders: Set<ModelProvider>\n): boolean {\n // Anthropic is always enabled (required)\n if (provider === 'anthropic') return true;\n\n return enabledProviders.has(provider);\n}\n\n/**\n * Apply fallback strategy for a model\n *\n * If the model's provider is disabled (no API key), return an Anthropic equivalent.\n * Otherwise, return the original model.\n *\n * @param modelId Requested model\n * @param enabledProviders Set of enabled provider names\n * @returns Original model if provider enabled, otherwise Anthropic fallback\n */\nexport function applyFallback(\n modelId: ModelId,\n enabledProviders: Set<ModelProvider>\n): ModelId {\n const provider = getModelProvider(modelId);\n\n // If provider is enabled, use the requested model\n if (isProviderEnabled(provider, enabledProviders)) {\n return modelId;\n }\n\n // Provider disabled - lookup fallback\n const fallback = FALLBACK_MAP[modelId] || DEFAULT_FALLBACK;\n\n // Log fallback for visibility\n console.warn(\n `Model ${modelId} requires ${provider} API key - falling back to ${fallback}`\n );\n\n return fallback;\n}\n\n/**\n * Get the fallback model for a given model (useful for preview/display)\n *\n * @param modelId Model to get fallback for\n * @returns Anthropic fallback model\n */\nexport function getFallbackModel(modelId: ModelId): AnthropicModel {\n // Anthropic models fallback to themselves\n if (getModelProvider(modelId) === 'anthropic') {\n return modelId as AnthropicModel;\n }\n\n return FALLBACK_MAP[modelId] || DEFAULT_FALLBACK;\n}\n\n/**\n * Detect enabled providers from API keys configuration\n *\n * @param apiKeys API keys object from settings\n * @returns Set of enabled provider names\n */\nexport function detectEnabledProviders(apiKeys: {\n openai?: string;\n google?: string;\n zai?: string;\n kimi?: string;\n}): Set<ModelProvider> {\n const enabled = new Set<ModelProvider>(['anthropic']); // Always enabled\n\n // Check each optional provider\n if (apiKeys.openai && apiKeys.openai.trim()) {\n enabled.add('openai');\n }\n if (apiKeys.google && apiKeys.google.trim()) {\n enabled.add('google');\n }\n if (apiKeys.zai && apiKeys.zai.trim()) {\n enabled.add('zai');\n }\n if (apiKeys.kimi && apiKeys.kimi.trim()) {\n enabled.add('kimi');\n }\n\n return enabled;\n}\n\n/**\n * Filter a list of models to only those available with enabled providers\n *\n * @param models List of models to filter\n * @param enabledProviders Set of enabled provider names\n * @returns Filtered list of models\n */\nexport function filterAvailableModels(\n models: ModelId[],\n enabledProviders: Set<ModelProvider>\n): ModelId[] {\n return models.filter((modelId) => {\n const provider = getModelProvider(modelId);\n return isProviderEnabled(provider, enabledProviders);\n });\n}\n\n/**\n * Get all available models (across all enabled providers)\n *\n * @param enabledProviders Set of enabled provider names\n * @returns List of available model IDs\n */\nexport function getAvailableModels(enabledProviders: Set<ModelProvider>): ModelId[] {\n return Object.keys(MODEL_PROVIDERS).filter((modelId) => {\n const provider = MODEL_PROVIDERS[modelId as ModelId];\n return isProviderEnabled(provider, enabledProviders);\n }) as ModelId[];\n}\n","/**\n * Smart Model Selector\n *\n * Intelligently selects the best model for each work type based on:\n * 1. What models the user has enabled (API keys configured)\n * 2. Capability scores for the required skills\n *\n * This is an opinionated system - always pick the BEST model for each job.\n * Users control cost by which providers they enable, not a sensitivity slider.\n */\n\nimport { ModelId } from './settings.js';\nimport { WorkTypeId } from './work-types.js';\nimport {\n MODEL_CAPABILITIES,\n SkillDimension,\n ModelCapability,\n getModelCapability,\n} from './model-capabilities.js';\n\n/**\n * Skill requirements for a work type\n * Higher weight = more important for this task\n */\nexport interface SkillRequirement {\n skill: SkillDimension;\n weight: number; // 0-1, how important this skill is\n}\n\n/**\n * Work type to skill mapping\n * Defines what skills each work type needs\n */\nexport const WORK_TYPE_REQUIREMENTS: Record<WorkTypeId, SkillRequirement[]> = {\n // ═══════════════════════════════════════════════════════════════════════════\n // ISSUE AGENT PHASES\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'issue-agent:exploration': [\n { skill: 'speed', weight: 0.4 }, // Need fast exploration\n { skill: 'context-length', weight: 0.3 }, // Large codebases\n { skill: 'synthesis', weight: 0.3 }, // Understanding structure\n ],\n\n 'issue-agent:implementation': [\n { skill: 'code-generation', weight: 0.6 }, // Primary skill\n { skill: 'debugging', weight: 0.2 }, // Avoiding bugs\n { skill: 'testing', weight: 0.2 }, // Writing testable code\n ],\n\n 'issue-agent:testing': [\n { skill: 'testing', weight: 0.5 }, // Primary skill\n { skill: 'code-generation', weight: 0.3 }, // Writing test code\n { skill: 'debugging', weight: 0.2 }, // Finding edge cases\n ],\n\n 'issue-agent:documentation': [\n { skill: 'documentation', weight: 0.6 }, // Primary skill\n { skill: 'synthesis', weight: 0.3 }, // Summarizing\n { skill: 'speed', weight: 0.1 }, // Fast iteration\n ],\n\n 'issue-agent:review-response': [\n { skill: 'code-review', weight: 0.4 }, // Understanding feedback\n { skill: 'code-generation', weight: 0.3 }, // Making fixes\n { skill: 'debugging', weight: 0.3 }, // Finding issues\n ],\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SPECIALIST AGENTS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'specialist-review-agent': [\n { skill: 'code-review', weight: 0.5 }, // Primary skill\n { skill: 'security', weight: 0.25 }, // Security awareness\n { skill: 'performance', weight: 0.25 }, // Performance awareness\n ],\n\n 'specialist-test-agent': [\n { skill: 'testing', weight: 0.5 }, // Primary skill\n { skill: 'code-generation', weight: 0.3 }, // Writing tests\n { skill: 'debugging', weight: 0.2 }, // Finding issues\n ],\n\n 'specialist-merge-agent': [\n { skill: 'code-review', weight: 0.4 }, // Understanding conflicts\n { skill: 'synthesis', weight: 0.3 }, // Merging changes\n { skill: 'debugging', weight: 0.3 }, // Resolving issues\n ],\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SUBAGENTS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'subagent:explore': [\n { skill: 'speed', weight: 0.5 }, // Need speed\n { skill: 'context-length', weight: 0.3 }, // Large scope\n { skill: 'synthesis', weight: 0.2 }, // Quick understanding\n ],\n\n 'subagent:plan': [\n { skill: 'planning', weight: 0.5 }, // Primary skill\n { skill: 'synthesis', weight: 0.3 }, // Combining info\n { skill: 'speed', weight: 0.2 }, // Quick iteration\n ],\n\n 'subagent:bash': [\n { skill: 'speed', weight: 0.6 }, // Fast execution\n { skill: 'code-generation', weight: 0.3 }, // Command generation\n { skill: 'debugging', weight: 0.1 }, // Error handling\n ],\n\n 'subagent:general-purpose': [\n { skill: 'speed', weight: 0.3 }, // Balanced\n { skill: 'synthesis', weight: 0.3 }, // General understanding\n { skill: 'code-generation', weight: 0.4 }, // General tasks\n ],\n\n // ═══════════════════════════════════════════════════════════════════════════\n // CONVOY MEMBERS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'convoy:security-reviewer': [\n { skill: 'security', weight: 0.7 }, // PRIMARY - never compromise\n { skill: 'code-review', weight: 0.2 }, // Code understanding\n { skill: 'debugging', weight: 0.1 }, // Finding vulnerabilities\n ],\n\n 'convoy:performance-reviewer': [\n { skill: 'performance', weight: 0.6 }, // Primary skill\n { skill: 'code-review', weight: 0.3 }, // Code understanding\n { skill: 'debugging', weight: 0.1 }, // Finding bottlenecks\n ],\n\n 'convoy:correctness-reviewer': [\n { skill: 'code-review', weight: 0.4 }, // Primary skill\n { skill: 'debugging', weight: 0.4 }, // Finding bugs\n { skill: 'testing', weight: 0.2 }, // Test coverage\n ],\n\n 'convoy:synthesis-agent': [\n { skill: 'synthesis', weight: 0.6 }, // Primary skill\n { skill: 'documentation', weight: 0.2 }, // Clear writing\n { skill: 'planning', weight: 0.2 }, // Organizing findings\n ],\n\n // ═══════════════════════════════════════════════════════════════════════════\n // PRE-WORK AGENTS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'planning-agent': [\n { skill: 'planning', weight: 0.5 }, // Primary skill\n { skill: 'synthesis', weight: 0.3 }, // Combining requirements\n { skill: 'documentation', weight: 0.2 }, // Documenting decisions\n ],\n\n 'prd-agent': [\n { skill: 'documentation', weight: 0.5 }, // Primary skill\n { skill: 'planning', weight: 0.3 }, // Structure\n { skill: 'synthesis', weight: 0.2 }, // Combining requirements\n ],\n\n 'decomposition-agent': [\n { skill: 'planning', weight: 0.5 }, // Primary skill\n { skill: 'synthesis', weight: 0.3 }, // Breaking down\n { skill: 'documentation', weight: 0.2 }, // Clear tasks\n ],\n\n 'triage-agent': [\n { skill: 'speed', weight: 0.4 }, // Quick decisions\n { skill: 'synthesis', weight: 0.3 }, // Understanding scope\n { skill: 'planning', weight: 0.3 }, // Prioritization\n ],\n\n // ═══════════════════════════════════════════════════════════════════════════\n // CLI CONTEXTS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'cli:interactive': [\n { skill: 'speed', weight: 0.4 }, // Responsive\n { skill: 'synthesis', weight: 0.3 }, // Understanding context\n { skill: 'code-generation', weight: 0.3 }, // Quick code\n ],\n\n 'cli:quick-command': [\n { skill: 'speed', weight: 0.7 }, // Must be fast\n { skill: 'code-generation', weight: 0.2 }, // Simple generation\n { skill: 'synthesis', weight: 0.1 }, // Quick understanding\n ],\n};\n\n/**\n * Selection result with explanation\n */\nexport interface ModelSelectionResult {\n /** Selected model */\n model: ModelId;\n /** Score that led to selection (0-100) */\n score: number;\n /** Why this model was selected */\n reason: string;\n /** All candidates that were considered */\n candidates: Array<{\n model: ModelId;\n score: number;\n available: boolean;\n }>;\n}\n\n/**\n * Selection options\n */\nexport interface SelectionOptions {\n /**\n * Minimum capability threshold (0-100)\n * Models below this score are excluded\n * Default: 50\n */\n minCapability?: number;\n\n /**\n * Force a specific model (bypass selection)\n */\n forceModel?: ModelId;\n}\n\n/**\n * Calculate weighted skill score for a model given requirements\n */\nfunction calculateSkillScore(\n model: ModelId,\n requirements: SkillRequirement[]\n): number {\n const cap = getModelCapability(model);\n let totalScore = 0;\n let totalWeight = 0;\n\n for (const req of requirements) {\n totalScore += cap.skills[req.skill] * req.weight;\n totalWeight += req.weight;\n }\n\n return totalWeight > 0 ? totalScore / totalWeight : 0;\n}\n\n/**\n * Calculate final selection score - pure capability based\n *\n * We're opinionated: always pick the BEST model for the job.\n * Users control cost by which providers they enable.\n */\nfunction calculateSelectionScore(\n model: ModelId,\n skillScore: number\n): number {\n // Pure quality - just return the skill score\n // Cost control is done by which providers the user enables\n return skillScore;\n}\n\n/**\n * Select the best model for a work type from available models\n */\nexport function selectModel(\n workType: WorkTypeId,\n availableModels: ModelId[],\n options: SelectionOptions = {}\n): ModelSelectionResult {\n const { minCapability = 50, forceModel } = options;\n\n // Force model if specified and available\n if (forceModel) {\n if (availableModels.includes(forceModel)) {\n return {\n model: forceModel,\n score: 100,\n reason: `Forced selection: ${forceModel}`,\n candidates: [{ model: forceModel, score: 100, available: true }],\n };\n }\n // Fall through to normal selection if forced model not available\n }\n\n const requirements = WORK_TYPE_REQUIREMENTS[workType];\n const allModels = Object.keys(MODEL_CAPABILITIES) as ModelId[];\n\n // Calculate scores for all models - pure capability based\n // Users control cost by which providers they enable\n const candidates = allModels.map((model) => {\n const skillScore = calculateSkillScore(model, requirements);\n const selectionScore = calculateSelectionScore(model, skillScore);\n const available = availableModels.includes(model);\n\n return {\n model,\n skillScore,\n score: selectionScore,\n available,\n };\n });\n\n // Filter to available models with minimum capability\n const eligible = candidates.filter(\n (c) => c.available && c.skillScore >= minCapability\n );\n\n // Sort by selection score (descending)\n eligible.sort((a, b) => b.score - a.score);\n\n // Fallback: if no eligible models, use best available regardless of threshold\n if (eligible.length === 0) {\n const fallback = candidates\n .filter((c) => c.available)\n .sort((a, b) => b.score - a.score)[0];\n\n if (!fallback) {\n // No available models at all - use Anthropic default\n return {\n model: 'claude-sonnet-4-6',\n score: 0,\n reason: 'No models available, falling back to default',\n candidates: candidates.map((c) => ({\n model: c.model,\n score: c.score,\n available: c.available,\n })),\n };\n }\n\n return {\n model: fallback.model,\n score: fallback.score,\n reason: `Best available (below min threshold): ${fallback.model}`,\n candidates: candidates.map((c) => ({\n model: c.model,\n score: c.score,\n available: c.available,\n })),\n };\n }\n\n const selected = eligible[0];\n const cap = getModelCapability(selected.model);\n\n // Generate reason\n const topSkills = requirements\n .sort((a, b) => b.weight - a.weight)\n .slice(0, 2)\n .map((r) => r.skill);\n\n const reason = `Best for ${workType}: ${cap.displayName} (${topSkills.join(', ')}: ${Math.round(selected.skillScore)}, cost: $${cap.costPer1MTokens}/1M)`;\n\n return {\n model: selected.model,\n score: selected.score,\n reason,\n candidates: candidates.map((c) => ({\n model: c.model,\n score: c.score,\n available: c.available,\n })),\n };\n}\n\n/**\n * Select models for all work types at once\n */\nexport function selectAllModels(\n availableModels: ModelId[],\n options: SelectionOptions = {}\n): Record<WorkTypeId, ModelSelectionResult> {\n const workTypes = Object.keys(WORK_TYPE_REQUIREMENTS) as WorkTypeId[];\n const results: Record<WorkTypeId, ModelSelectionResult> = {} as Record<\n WorkTypeId,\n ModelSelectionResult\n >;\n\n for (const workType of workTypes) {\n results[workType] = selectModel(workType, availableModels, options);\n }\n\n return results;\n}\n\n/**\n * Get simple model mapping (for backward compatibility with presets)\n */\nexport function getSimpleModelMapping(\n availableModels: ModelId[],\n options: SelectionOptions = {}\n): Record<WorkTypeId, ModelId> {\n const results = selectAllModels(availableModels, options);\n const mapping: Record<WorkTypeId, ModelId> = {} as Record<WorkTypeId, ModelId>;\n\n for (const [workType, result] of Object.entries(results)) {\n mapping[workType as WorkTypeId] = result.model;\n }\n\n return mapping;\n}\n\n/**\n * Pretty print selection results for debugging\n */\nexport function formatSelectionResults(\n results: Record<WorkTypeId, ModelSelectionResult>\n): string {\n const lines: string[] = ['Model Selection Results', '='.repeat(60)];\n\n for (const [workType, result] of Object.entries(results)) {\n lines.push(`${workType}: ${result.model}`);\n lines.push(` Reason: ${result.reason}`);\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\n","/**\n * Work Type Router\n *\n * Routes work types to appropriate models using smart (capability-based) selection.\n * Picks the best model for each job based on:\n * 1. What models the user has enabled (API keys configured)\n * 2. Capability scores for the required skills\n * 3. Cost optimization (configurable)\n */\n\nimport { WorkTypeId, isValidWorkType, validateWorkType, getAllWorkTypes } from './work-types.js';\nimport { ModelId } from './settings.js';\nimport { applyFallback, ModelProvider, getModelsByProvider } from './model-fallback.js';\nimport { loadConfig, NormalizedConfig } from './config-yaml.js';\nimport { selectModel, ModelSelectionResult } from './smart-model-selector.js';\n\n// Re-export WorkTypeId for backward compatibility\nexport type { WorkTypeId } from './work-types.js';\n\n/**\n * Model resolution result with debugging info\n */\nexport interface ModelResolutionResult {\n /** Final model to use */\n model: ModelId;\n /** Work type that was resolved */\n workType: WorkTypeId;\n /** How the model was determined */\n source: 'override' | 'smart';\n /** Whether fallback was applied (provider disabled) */\n usedFallback: boolean;\n /** Original model before fallback */\n originalModel?: ModelId;\n /** Smart selection details */\n selection: {\n score: number;\n reason: string;\n };\n}\n\n/**\n * Work Type Router\n *\n * Main router class for resolving work types to models.\n */\nexport class WorkTypeRouter {\n private config: NormalizedConfig;\n private availableModels: ModelId[] | null = null;\n\n constructor(config?: NormalizedConfig) {\n this.config = config || loadConfig().config;\n }\n\n /**\n * Get list of available models based on enabled providers\n */\n private getAvailableModels(): ModelId[] {\n if (this.availableModels) {\n return this.availableModels;\n }\n\n const available: ModelId[] = [];\n for (const provider of this.config.enabledProviders) {\n available.push(...getModelsByProvider(provider));\n }\n this.availableModels = available;\n return available;\n }\n\n /**\n * Get model for a specific work type\n *\n * Resolution order:\n * 1. Per-project/global override (if configured)\n * 2. Smart selection (capability-based)\n */\n getModel(workTypeId: WorkTypeId): ModelResolutionResult {\n validateWorkType(workTypeId);\n\n let model: ModelId;\n let source: 'override' | 'smart';\n let originalModel: ModelId | undefined;\n let selection: { score: number; reason: string };\n\n // Check for override first\n if (this.config.overrides[workTypeId]) {\n model = this.config.overrides[workTypeId]!;\n source = 'override';\n selection = {\n score: 100,\n reason: `Explicit override: ${model}`,\n };\n } else {\n // Use smart (capability-based) selection\n const availableModels = this.getAvailableModels();\n const result = selectModel(workTypeId, availableModels);\n model = result.model;\n source = 'smart';\n selection = {\n score: result.score,\n reason: result.reason,\n };\n }\n\n // Apply fallback if provider is disabled\n originalModel = model;\n model = applyFallback(model, this.config.enabledProviders);\n\n return {\n model,\n workType: workTypeId,\n source,\n usedFallback: model !== originalModel,\n originalModel: model !== originalModel ? originalModel : undefined,\n selection,\n };\n }\n\n /**\n * Get just the model ID for a work type (convenience method)\n */\n getModelId(workTypeId: WorkTypeId): ModelId {\n return this.getModel(workTypeId).model;\n }\n\n /**\n * Check if a work type has an override configured\n */\n hasOverride(workTypeId: WorkTypeId): boolean {\n return workTypeId in this.config.overrides;\n }\n\n /**\n * Get the set of enabled providers\n */\n getEnabledProviders(): Set<ModelProvider> {\n return this.config.enabledProviders;\n }\n\n /**\n * Get all configured overrides\n */\n getOverrides(): Partial<Record<WorkTypeId, ModelId>> {\n return { ...this.config.overrides };\n }\n\n /**\n * Get API keys configuration\n */\n getApiKeys(): { openai?: string; google?: string; zai?: string; kimi?: string } {\n return { ...this.config.apiKeys };\n }\n\n /**\n * Get Gemini thinking level\n */\n getGeminiThinkingLevel(): 1 | 2 | 3 | 4 {\n return this.config.geminiThinkingLevel;\n }\n\n /**\n * Reload configuration from disk\n */\n reloadConfig(): void {\n this.config = loadConfig().config;\n this.availableModels = null; // Clear cache\n }\n\n /**\n * Get debug information about current configuration\n */\n getDebugInfo(): {\n enabledProviders: string[];\n availableModelCount: number;\n overrideCount: number;\n hasApiKeys: {\n openai: boolean;\n google: boolean;\n zai: boolean;\n kimi: boolean;\n };\n } {\n return {\n enabledProviders: Array.from(this.config.enabledProviders),\n availableModelCount: this.getAvailableModels().length,\n overrideCount: Object.keys(this.config.overrides).length,\n hasApiKeys: {\n openai: !!this.config.apiKeys.openai,\n google: !!this.config.apiKeys.google,\n zai: !!this.config.apiKeys.zai,\n kimi: !!this.config.apiKeys.kimi,\n },\n };\n }\n}\n\n/**\n * Global router instance\n */\nlet globalRouter: WorkTypeRouter | null = null;\n\n/**\n * Get the global work type router instance\n */\nexport function getGlobalRouter(): WorkTypeRouter {\n if (!globalRouter) {\n globalRouter = new WorkTypeRouter();\n }\n return globalRouter;\n}\n\n/**\n * Reset the global router (useful for testing)\n */\nexport function resetGlobalRouter(): void {\n globalRouter = null;\n}\n\n/**\n * Reload global router configuration\n */\nexport function reloadGlobalRouter(): void {\n if (globalRouter) {\n globalRouter.reloadConfig();\n }\n}\n\n/**\n * Get model using the global router\n */\nexport function getModel(workTypeId: WorkTypeId): ModelResolutionResult {\n return getGlobalRouter().getModel(workTypeId);\n}\n\n/**\n * Get just the model ID using the global router\n */\nexport function getModelId(workTypeId: WorkTypeId): ModelId {\n return getGlobalRouter().getModelId(workTypeId);\n}\n\n/**\n * Check for override using the global router\n */\nexport function hasOverride(workTypeId: WorkTypeId): boolean {\n return getGlobalRouter().hasOverride(workTypeId);\n}\n\n/**\n * Get debug info using the global router\n */\nexport function getDebugInfo() {\n return getGlobalRouter().getDebugInfo();\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","writeFileSync","unlinkSync","join"]}
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  init_projects,
7
7
  loadProjectsConfig
8
- } from "./chunk-2V4NF7J2.js";
8
+ } from "./chunk-RLZQB7HS.js";
9
9
  import {
10
10
  SOURCE_TRAEFIK_TEMPLATES,
11
11
  TRAEFIK_CERTS_DIR,
@@ -30,7 +30,7 @@ function generatePanopticonTraefikConfig() {
30
30
  if (!existsSync(templatePath)) {
31
31
  return false;
32
32
  }
33
- const { config } = loadConfig();
33
+ const config = loadConfig();
34
34
  const placeholders = {
35
35
  TRAEFIK_DOMAIN: config.traefik?.domain || "pan.localhost",
36
36
  DASHBOARD_PORT: String(config.dashboard.port),
@@ -151,4 +151,4 @@ export {
151
151
  ensureProjectCerts,
152
152
  cleanupStaleTlsSections
153
153
  };
154
- //# sourceMappingURL=chunk-NLQRED36.js.map
154
+ //# sourceMappingURL=chunk-KBHRXV5T.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/traefik.ts"],"sourcesContent":["/**\n * Traefik Configuration Generator\n *\n * Generates the Panopticon dashboard Traefik routing config\n * from a template, substituting values from config.toml.\n * Also generates TLS certificate configuration from discovered certs.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync, readdirSync } from 'fs';\nimport { join, basename } from 'path';\nimport { execSync } from 'child_process';\nimport { TRAEFIK_DYNAMIC_DIR, TRAEFIK_CERTS_DIR, TRAEFIK_DIR, SOURCE_TRAEFIK_TEMPLATES } from './paths.js';\nimport { loadConfig } from './config.js';\nimport { loadProjectsConfig } from './projects.js';\n\n/**\n * Generate panopticon.yml from template using current config values.\n * Safe to call multiple times (idempotent).\n * Returns true if file was written, false if template not found.\n */\nexport function generatePanopticonTraefikConfig(): boolean {\n const templatePath = join(SOURCE_TRAEFIK_TEMPLATES, 'dynamic', 'panopticon.yml.template');\n if (!existsSync(templatePath)) {\n return false;\n }\n\n const config = loadConfig();\n const placeholders: Record<string, string> = {\n TRAEFIK_DOMAIN: config.traefik?.domain || 'pan.localhost',\n DASHBOARD_PORT: String(config.dashboard.port),\n DASHBOARD_API_PORT: String(config.dashboard.api_port),\n };\n\n let content = readFileSync(templatePath, 'utf-8');\n for (const [key, value] of Object.entries(placeholders)) {\n content = content.replace(new RegExp(`\\\\{\\\\{${key}\\\\}\\\\}`, 'g'), value);\n }\n\n mkdirSync(TRAEFIK_DYNAMIC_DIR, { recursive: true });\n const outputPath = join(TRAEFIK_DYNAMIC_DIR, 'panopticon.yml');\n writeFileSync(outputPath, content, 'utf-8');\n return true;\n}\n\n/**\n * Remove any accidentally-copied .template files from the runtime Traefik dir.\n * Called after copyDirectoryRecursive in pan install.\n */\nexport function cleanupTemplateFiles(): void {\n const copiedTemplate = join(TRAEFIK_DYNAMIC_DIR, 'panopticon.yml.template');\n if (existsSync(copiedTemplate)) {\n unlinkSync(copiedTemplate);\n }\n}\n\n/**\n * Generate tls.yml from all discovered certificate files in the certs directory.\n *\n * Traefik v3 ignores `tls:` sections when they appear in the same dynamic config\n * file as `http:` routers/services. This function creates a dedicated tls.yml file\n * that Traefik's file provider will pick up separately.\n *\n * The first cert found (pan.localhost) is used as the default certificate.\n * All certs are listed in the certificates array for SNI matching.\n *\n * Safe to call multiple times (idempotent).\n * Returns true if file was written, false if no certs found.\n */\nexport function generateTlsConfig(): boolean {\n if (!existsSync(TRAEFIK_CERTS_DIR)) {\n return false;\n }\n\n // Scan for cert files (exclude -key.pem files)\n const files = readdirSync(TRAEFIK_CERTS_DIR);\n const certFiles = files.filter(f => f.endsWith('.pem') && !f.endsWith('-key.pem'));\n\n if (certFiles.length === 0) {\n return false;\n }\n\n // Pair each cert with its key file\n const certPairs: Array<{ certFile: string; keyFile: string }> = [];\n for (const certFile of certFiles) {\n const keyFile = certFile.replace('.pem', '-key.pem');\n if (files.includes(keyFile)) {\n certPairs.push({\n certFile: `/etc/traefik/certs/${certFile}`,\n keyFile: `/etc/traefik/certs/${keyFile}`,\n });\n }\n }\n\n if (certPairs.length === 0) {\n return false;\n }\n\n // Use the pan.localhost cert as default, fall back to first cert\n const defaultCert = certPairs.find(p => p.certFile.includes('pan.localhost')) || certPairs[0];\n\n // Build YAML content\n let yaml = '# Auto-generated TLS configuration — do not edit manually\\n';\n yaml += '# Generated by: pan up / pan install\\n';\n yaml += '# Traefik v3 requires TLS config in a separate dynamic config file\\n\\n';\n yaml += 'tls:\\n';\n yaml += ' stores:\\n';\n yaml += ' default:\\n';\n yaml += ' defaultCertificate:\\n';\n yaml += ` certFile: ${defaultCert.certFile}\\n`;\n yaml += ` keyFile: ${defaultCert.keyFile}\\n`;\n yaml += ' certificates:\\n';\n for (const pair of certPairs) {\n yaml += ` - certFile: ${pair.certFile}\\n`;\n yaml += ` keyFile: ${pair.keyFile}\\n`;\n }\n\n mkdirSync(TRAEFIK_DYNAMIC_DIR, { recursive: true });\n const outputPath = join(TRAEFIK_DYNAMIC_DIR, 'tls.yml');\n writeFileSync(outputPath, yaml, 'utf-8');\n return true;\n}\n\n/**\n * Ensure wildcard certificates exist for all registered projects that have DNS domains.\n *\n * Scans projects.yaml for projects with workspace.dns.domain, and generates\n * mkcert wildcard certs for any that don't already have certs in the Traefik\n * certs directory.\n *\n * Returns array of domains that had certs generated.\n */\nexport function ensureProjectCerts(): string[] {\n // Check mkcert is available\n try {\n execSync('which mkcert', { stdio: 'pipe' });\n } catch {\n return [];\n }\n\n const projectsConfig = loadProjectsConfig();\n const generated: string[] = [];\n\n for (const [, project] of Object.entries(projectsConfig.projects)) {\n const domain = project.workspace?.dns?.domain;\n if (!domain) continue;\n\n const certFile = join(TRAEFIK_CERTS_DIR, `_wildcard.${domain}.pem`);\n const keyFile = join(TRAEFIK_CERTS_DIR, `_wildcard.${domain}-key.pem`);\n\n if (existsSync(certFile) && existsSync(keyFile)) {\n continue;\n }\n\n // Generate cert for this project's domain\n mkdirSync(TRAEFIK_CERTS_DIR, { recursive: true });\n try {\n execSync(\n `mkcert -cert-file \"${certFile}\" -key-file \"${keyFile}\" \"${domain}\" \"*.${domain}\" 2>/dev/null`,\n { stdio: 'pipe' }\n );\n generated.push(domain);\n } catch {\n // mkcert failed — skip this domain\n }\n }\n\n return generated;\n}\n\n/**\n * Remove stale `tls:` sections from runtime config files.\n *\n * Traefik v3 ignores tls: in static config (traefik.yml) and in dynamic\n * config files that also contain http: routers. This function strips those\n * dead sections to avoid confusion.\n *\n * Called during `pan up` to clean up configs from older Panopticon versions.\n */\nexport function cleanupStaleTlsSections(): void {\n // Clean static config (traefik.yml)\n const staticConfig = join(TRAEFIK_DIR, 'traefik.yml');\n if (existsSync(staticConfig)) {\n const content = readFileSync(staticConfig, 'utf-8');\n // Remove tls: section at the end of the file\n const cleaned = content.replace(/\\n# TLS Configuration\\ntls:\\n(?: .*\\n)*/g, '\\n');\n if (cleaned !== content) {\n writeFileSync(staticConfig, cleaned, 'utf-8');\n }\n }\n\n // Clean dynamic panopticon.yml (regenerated from template, but also clean runtime copy)\n const dynamicConfig = join(TRAEFIK_DYNAMIC_DIR, 'panopticon.yml');\n if (existsSync(dynamicConfig)) {\n const content = readFileSync(dynamicConfig, 'utf-8');\n // Remove standalone tls: section (not nested under http: routers)\n const cleaned = content.replace(/\\ntls:\\n (?:stores|certificates):\\n(?: .*\\n)*/g, '\\n');\n if (cleaned !== content) {\n writeFileSync(dynamicConfig, cleaned, 'utf-8');\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -29,4 +29,4 @@ export {
29
29
  getDefaultWorkspaceConfig,
30
30
  init_workspace_config
31
31
  };
32
- //# sourceMappingURL=chunk-CWELWPWQ.js.map
32
+ //# sourceMappingURL=chunk-MOPGR3CL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/workspace-config.ts"],"sourcesContent":["/**\n * Workspace Configuration Types\n *\n * Defines the schema for project workspace configuration in projects.yaml\n */\n\nexport interface RepoConfig {\n /** Name of the repo in the workspace (e.g., 'fe', 'api') */\n name: string;\n /** Path to source repo relative to project root */\n path: string;\n /** Branch prefix for feature branches (default: 'feature/') */\n branch_prefix?: string;\n /** Default branch to create feature branches from (default: 'main') */\n default_branch?: string;\n}\n\nexport interface DnsConfig {\n /** Base domain (e.g., 'myn.test') */\n domain: string;\n /**\n * DNS entry patterns. Supports placeholders:\n * - {{FEATURE_FOLDER}}: e.g., 'feature-min-123'\n * - {{FEATURE_NAME}}: e.g., 'min-123'\n * - {{DOMAIN}}: the domain value\n */\n entries: string[];\n /** How to sync DNS: 'wsl2hosts' | 'hosts_file' | 'dnsmasq' */\n sync_method?: 'wsl2hosts' | 'hosts_file' | 'dnsmasq';\n}\n\nexport interface PortConfig {\n /** Port range [start, end] */\n range: [number, number];\n}\n\nexport interface DockerConfig {\n /** Path to Traefik compose file (relative to project root) */\n traefik?: string;\n /** Path to devcontainer template directory */\n compose_template?: string;\n}\n\nexport interface AgentTemplateConfig {\n /** Path to agent template directory */\n template_dir: string;\n /** Files to process with placeholder replacement */\n templates?: Array<{\n source: string;\n target: string;\n }>;\n /** Directories to copy from project template into workspace */\n copy_dirs?: string[];\n /** @deprecated Use copy_dirs instead */\n symlinks?: string[];\n}\n\nexport interface EnvConfig {\n /** Environment variable template with placeholders */\n template?: string;\n /** Additional env vars from secrets */\n secrets_file?: string;\n}\n\nexport interface ServiceConfig {\n /** Service name (e.g., 'api', 'frontend') */\n name: string;\n /** Path relative to workspace (e.g., 'api', 'fe') */\n path: string;\n /** Command to start the service natively (e.g., './run-dev.sh', 'pnpm start') */\n start_command: string;\n /** Command to start inside Docker container (if different) */\n docker_command?: string;\n /** Health check URL pattern (supports placeholders) */\n health_url?: string;\n /** Port the service runs on */\n port?: number;\n}\n\nexport interface TestConfig {\n /** Test type: 'maven' | 'vitest' | 'playwright' | 'jest' | 'pytest' | 'cargo' */\n type: string;\n /** Path to test directory (relative to workspace) */\n path: string;\n /** Command to run tests */\n command: string;\n /** Run inside container for feature workspaces */\n container?: boolean;\n /** Container name pattern (uses {{FEATURE_FOLDER}}) */\n container_name?: string;\n /** Additional environment variables */\n env?: Record<string, string>;\n}\n\nexport interface QualityGateConfig {\n /** Command to run (e.g., 'pnpm lint', 'pnpm typecheck') */\n command: string;\n /** Path relative to workspace (e.g., 'frontend' for polyrepo) */\n path?: string;\n /** If true, merge is blocked on failure (default: true) */\n required?: boolean;\n /** Additional environment variables */\n env?: Record<string, string>;\n /** When to run: before push (default) or after push */\n phase?: 'pre_push' | 'post_push';\n /** Gate type: shell command (default) or HTTP health check */\n type?: 'command' | 'http_health';\n /** URL for http_health type */\n url?: string;\n /** Seconds to wait for deployment before checking (http_health only) */\n wait?: number;\n /** Expected HTTP status code (http_health only, default: 200) */\n expect_status?: number;\n}\n\nexport interface DatabaseConfig {\n /** Path to seed file for database initialization */\n seed_file?: string;\n /** Command to run after loading seed (e.g., sanitization script) */\n seed_command?: string;\n /** Command to create snapshots from external source (e.g., kubectl exec pg_dump) */\n snapshot_command?: string;\n /** External database connection for direct access */\n external_db?: {\n host: string;\n port?: number;\n database: string;\n user?: string;\n /** Environment variable name containing password */\n password_env?: string;\n };\n /** Container name pattern (supports {{PROJECT}} placeholder) */\n container_name?: string;\n /** Migration tool configuration */\n migrations?: {\n type: 'flyway' | 'liquibase' | 'prisma' | 'typeorm' | 'custom';\n path?: string;\n command?: string;\n };\n}\n\nexport interface TunnelHostname {\n /** Hostname pattern (supports {{FEATURE_FOLDER}} etc.) e.g., \"api-{{FEATURE_FOLDER}}.mindyournow.com\" */\n pattern: string;\n /** HTTP Host header for Traefik routing e.g., \"api-{{FEATURE_FOLDER}}.myn.localhost\" */\n http_host_header?: string;\n /** Skip TLS verification for local dev (default: true) */\n no_tls_verify?: boolean;\n}\n\nexport interface TunnelConfig {\n /** Tunnel provider (currently only Cloudflare) */\n provider: 'cloudflare';\n /** Cloudflare tunnel ID */\n tunnel_id: string;\n /** Cloudflare account ID */\n account_id: string;\n /** Cloudflare zone ID */\n zone_id: string;\n /** Path to credentials file (cert.pem) containing API token */\n credentials_file: string;\n /** Service target for ingress rules (e.g., \"https://localhost\") */\n service_target: string;\n /** Hostnames to create ingress rules + DNS records for */\n hostnames: TunnelHostname[];\n}\n\nexport interface HumeConfig {\n /** Env var name containing the Hume API key (default: HUME_API_KEY) */\n api_key_env?: string;\n /** Config ID of the production/template config to clone from */\n template_config_id: string;\n /** Config name pattern for workspaces (supports placeholders) */\n name_pattern: string;\n /** BYOLLM callback URL pattern (supports placeholders) */\n byollm_url_pattern: string;\n}\n\nexport interface WorkspaceConfig {\n /** Workspace type: 'polyrepo' (multiple git repos) or 'monorepo' (single repo, default) */\n type?: 'polyrepo' | 'monorepo';\n /** Where to create workspaces (relative to project path) */\n workspaces_dir?: string;\n /** Default branch for all repos (default: 'main'). Can be overridden per-repo. */\n default_branch?: string;\n /** Git repositories to include (for polyrepo) */\n repos?: RepoConfig[];\n /** DNS configuration */\n dns?: DnsConfig;\n /** Port assignments for services */\n ports?: Record<string, PortConfig>;\n /** Docker configuration */\n docker?: DockerConfig;\n /** Database seeding configuration */\n database?: DatabaseConfig;\n /** Agent configuration templates */\n agent?: AgentTemplateConfig;\n /** Environment variables */\n env?: EnvConfig;\n /** Service definitions for startup commands */\n services?: ServiceConfig[];\n /** Cloudflare tunnel configuration for external access */\n tunnel?: TunnelConfig;\n /** Hume EVI config management for workspace lifecycle */\n hume?: HumeConfig;\n /** PRD directory path (relative to project path, default: 'docs/prds') */\n prdDir?: string;\n}\n\nexport interface TestsConfig {\n [name: string]: TestConfig;\n}\n\nexport interface ProjectConfig {\n name: string;\n path: string;\n linear_team?: string;\n github_repo?: string;\n gitlab_repo?: string;\n\n /** Workspace configuration */\n workspace?: WorkspaceConfig;\n\n /** Test configuration */\n tests?: TestsConfig;\n\n /** Issue routing rules */\n issue_routing?: Array<{\n labels?: string[];\n path: string;\n default?: boolean;\n }>;\n\n /** Legacy: custom workspace command (deprecated, use workspace config) */\n workspace_command?: string;\n workspace_remove_command?: string;\n}\n\nexport interface ProjectsConfig {\n projects: Record<string, ProjectConfig>;\n}\n\n/**\n * Template placeholders that can be used in configuration\n */\nexport interface TemplatePlaceholders {\n FEATURE_NAME: string; // e.g., 'min-123'\n FEATURE_FOLDER: string; // e.g., 'feature-min-123'\n BRANCH_NAME: string; // e.g., 'feature/min-123'\n COMPOSE_PROJECT: string; // e.g., 'myn-feature-min-123'\n DOMAIN: string; // e.g., 'myn.test'\n PROJECT_NAME: string; // e.g., 'myn'\n PROJECT_PATH: string; // e.g., '/home/user/Projects/myn'\n PROJECTS_DIR: string; // e.g., '/home/user/Projects' (parent of PROJECT_PATH)\n WORKSPACE_PATH: string; // e.g., '/home/user/Projects/myn/workspaces/feature-min-123'\n HOME?: string; // e.g., '/home/user' (for docker-compose path sanitization)\n}\n\n/**\n * Replace template placeholders in a string\n */\nexport function replacePlaceholders(template: string, placeholders: TemplatePlaceholders): string {\n let result = template;\n for (const [key, value] of Object.entries(placeholders)) {\n result = result.replace(new RegExp(`\\\\{\\\\{${key}\\\\}\\\\}`, 'g'), value);\n }\n return result;\n}\n\n/**\n * Get default workspace config for a monorepo project\n */\nexport function getDefaultWorkspaceConfig(): WorkspaceConfig {\n return {\n type: 'monorepo',\n workspaces_dir: 'workspaces',\n };\n}\n\n/**\n * Service templates for common project types\n * These provide sensible defaults that can be overridden\n */\nexport const SERVICE_TEMPLATES: Record<string, Partial<ServiceConfig>> = {\n // Frontend frameworks\n 'react': {\n start_command: 'npm start',\n docker_command: 'npm start',\n port: 3000,\n },\n 'react-vite': {\n start_command: 'npm run dev',\n docker_command: 'npm run dev',\n port: 5173,\n },\n 'react-pnpm': {\n start_command: 'pnpm start',\n docker_command: 'pnpm start',\n port: 3000,\n },\n 'nextjs': {\n start_command: 'npm run dev',\n docker_command: 'npm run dev',\n port: 3000,\n },\n 'vue': {\n start_command: 'npm run dev',\n docker_command: 'npm run dev',\n port: 5173,\n },\n 'angular': {\n start_command: 'ng serve',\n docker_command: 'ng serve',\n port: 4200,\n },\n\n // Backend frameworks\n 'spring-boot-maven': {\n start_command: './mvnw spring-boot:run',\n docker_command: './mvnw spring-boot:run',\n port: 8080,\n },\n 'spring-boot-gradle': {\n start_command: './gradlew bootRun',\n docker_command: './gradlew bootRun',\n port: 8080,\n },\n 'express': {\n start_command: 'npm start',\n docker_command: 'npm start',\n port: 3000,\n },\n 'fastapi': {\n start_command: 'uvicorn main:app --reload',\n docker_command: 'uvicorn main:app --host 0.0.0.0 --reload',\n port: 8000,\n },\n 'django': {\n start_command: 'python manage.py runserver',\n docker_command: 'python manage.py runserver 0.0.0.0:8000',\n port: 8000,\n },\n 'rails': {\n start_command: 'rails server',\n docker_command: 'rails server -b 0.0.0.0',\n port: 3000,\n },\n 'go': {\n start_command: 'go run .',\n docker_command: 'go run .',\n port: 8080,\n },\n 'rust-cargo': {\n start_command: 'cargo run',\n docker_command: 'cargo run',\n port: 8080,\n },\n};\n\n/**\n * Get service config from template with overrides\n */\nexport function getServiceFromTemplate(\n templateName: string,\n overrides: Partial<ServiceConfig>\n): ServiceConfig {\n const template = SERVICE_TEMPLATES[templateName] || {};\n return {\n name: overrides.name || templateName,\n path: overrides.path || '.',\n start_command: overrides.start_command || template.start_command || 'npm start',\n docker_command: overrides.docker_command || template.docker_command,\n health_url: overrides.health_url,\n port: overrides.port || template.port,\n };\n}\n"],"mappings":";;;;;;AAqQO,SAAS,oBAAoB,UAAkB,cAA4C;AAChG,MAAI,SAAS;AACb,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,YAAY,GAAG;AACvD,aAAS,OAAO,QAAQ,IAAI,OAAO,SAAS,GAAG,UAAU,GAAG,GAAG,KAAK;AAAA,EACtE;AACA,SAAO;AACT;AAKO,SAAS,4BAA6C;AAC3D,SAAO;AAAA,IACL,MAAM;AAAA,IACN,gBAAgB;AAAA,EAClB;AACF;AArRA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":[]}
@@ -32,7 +32,7 @@ __export(projects_exports, {
32
32
  unregisterProject: () => unregisterProject
33
33
  });
34
34
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
35
- import { join } from "path";
35
+ import { join, resolve } from "path";
36
36
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
37
37
  function loadProjectsConfig() {
38
38
  if (!existsSync(PROJECTS_CONFIG_FILE)) {
@@ -270,4 +270,4 @@ export {
270
270
  projects_exports,
271
271
  init_projects
272
272
  };
273
- //# sourceMappingURL=chunk-2V4NF7J2.js.map
273
+ //# sourceMappingURL=chunk-RLZQB7HS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/projects.ts"],"sourcesContent":["/**\n * Project Registry - Multi-project support for Panopticon\n *\n * Maps Linear team prefixes and labels to project paths for workspace creation.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join, resolve } from 'path';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport { PANOPTICON_HOME } from './paths.js';\nimport type { QualityGateConfig } from './workspace-config.js';\n\nexport const PROJECTS_CONFIG_FILE = join(PANOPTICON_HOME, 'projects.yaml');\n\n/**\n * Issue routing rule - routes issues with certain labels to specific paths\n */\nexport interface IssueRoutingRule {\n labels?: string[];\n default?: boolean;\n path: string;\n}\n\n/**\n * Workspace configuration (imported from workspace-config.ts for full details)\n */\nexport interface WorkspaceConfig {\n type?: 'polyrepo' | 'monorepo';\n workspaces_dir?: string;\n repos?: Array<{ name: string; path: string; branch_prefix?: string }>;\n dns?: { domain: string; entries: string[]; sync_method?: 'wsl2hosts' | 'hosts_file' | 'dnsmasq' };\n ports?: Record<string, { range: [number, number] }>;\n docker?: { traefik?: string; compose_template?: string };\n database?: { seed_file?: string; container_name?: string; [key: string]: any };\n agent?: { template_dir: string; templates?: Array<{ source: string; target: string }>; copy_dirs?: string[]; symlinks?: string[] };\n env?: { template?: string; secrets_file?: string };\n services?: Array<{ name: string; path: string; start_command: string; docker_command?: string; health_url?: string; port?: number }>;\n}\n\n/**\n * Test configuration\n */\nexport interface TestConfig {\n type: string;\n path: string;\n command: string;\n container?: boolean;\n container_name?: string;\n env?: Record<string, string>;\n}\n\n/**\n * Specialist configuration for per-project specialists\n */\nexport interface SpecialistConfig {\n /** Number of recent runs to include in context digest (default: 5) */\n context_runs?: number;\n /** Model to use for generating context digests (null = same as specialist) */\n digest_model?: string | null;\n /** Log retention policy */\n retention?: {\n /** Maximum days to keep logs */\n max_days: number;\n /** Maximum number of runs to keep (whichever is more permissive) */\n max_runs: number;\n };\n /** Per-specialist prompt overrides */\n prompts?: {\n 'review-agent'?: string;\n 'test-agent'?: string;\n 'merge-agent'?: string;\n };\n}\n\n/**\n * Project configuration\n */\nexport interface ProjectConfig {\n name: string;\n path: string;\n linear_team?: string;\n github_repo?: string; // e.g. \"owner/repo\"\n gitlab_repo?: string; // e.g. \"group/repo\"\n issue_routing?: IssueRoutingRule[];\n /** Workspace configuration */\n workspace?: WorkspaceConfig;\n /** Test configuration by name */\n tests?: Record<string, TestConfig>;\n /** Custom command to create workspaces (e.g., infra/new-feature for MYN) */\n workspace_command?: string;\n /** Custom command to remove workspaces */\n workspace_remove_command?: string;\n /** Rally project OID (e.g., \"/project/822404704163\") for per-project Rally scoping */\n rally_project?: string;\n /** Specialist agent configuration */\n specialists?: SpecialistConfig;\n /** Quality gates run by merge-agent before pushing (lint, typecheck, prod build, etc.) */\n quality_gates?: Record<string, QualityGateConfig>;\n /**\n * Path to the repo where per-project cost WAL files live.\n * Defaults to `path` (the project repo itself).\n * For polyrepo setups, point this at the docs/shared repo.\n */\n events_repo?: string;\n /**\n * Subdirectory within events_repo where cost JSONL files are stored.\n * Defaults to \".panopticon/events\".\n */\n events_path?: string;\n}\n\n/**\n * Full projects configuration file\n */\nexport interface ProjectsConfig {\n projects: Record<string, ProjectConfig>;\n}\n\n/**\n * Resolved project info for workspace creation\n */\nexport interface ResolvedProject {\n projectKey: string;\n projectName: string;\n projectPath: string;\n linearTeam?: string;\n}\n\n/**\n * Load projects configuration from ~/.panopticon/projects.yaml\n */\nexport function loadProjectsConfig(): ProjectsConfig {\n if (!existsSync(PROJECTS_CONFIG_FILE)) {\n return { projects: {} };\n }\n\n try {\n const content = readFileSync(PROJECTS_CONFIG_FILE, 'utf-8');\n const config = parseYaml(content) as ProjectsConfig;\n return config || { projects: {} };\n } catch (error: any) {\n console.error(`Failed to parse projects.yaml: ${error.message}`);\n return { projects: {} };\n }\n}\n\n/**\n * Save projects configuration\n */\nexport function saveProjectsConfig(config: ProjectsConfig): void {\n const dir = PANOPTICON_HOME;\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n const yaml = stringifyYaml(config, { indent: 2 });\n writeFileSync(PROJECTS_CONFIG_FILE, yaml, 'utf-8');\n}\n\n/**\n * Get a list of all registered projects\n */\nexport function listProjects(): Array<{ key: string; config: ProjectConfig }> {\n const config = loadProjectsConfig();\n return Object.entries(config.projects).map(([key, projectConfig]) => ({\n key,\n config: projectConfig,\n }));\n}\n\n/**\n * Add or update a project in the registry\n */\nexport function registerProject(key: string, projectConfig: ProjectConfig): void {\n const config = loadProjectsConfig();\n config.projects[key] = projectConfig;\n saveProjectsConfig(config);\n}\n\n/**\n * Remove a project from the registry\n */\nexport function unregisterProject(key: string): boolean {\n const config = loadProjectsConfig();\n if (config.projects[key]) {\n delete config.projects[key];\n saveProjectsConfig(config);\n return true;\n }\n return false;\n}\n\n/**\n * Extract Linear team prefix from an issue ID\n * E.g., \"MIN-123\" -> \"MIN\", \"PAN-456\" -> \"PAN\"\n */\nexport function extractTeamPrefix(issueId: string): string | null {\n const match = issueId.match(/^([A-Z]+)-\\d+$/i);\n return match ? match[1].toUpperCase() : null;\n}\n\n/**\n * Find project by Linear team prefix\n */\nexport function findProjectByTeam(teamPrefix: string): ProjectConfig | null {\n const config = loadProjectsConfig();\n\n for (const [, projectConfig] of Object.entries(config.projects)) {\n if (projectConfig.linear_team?.toUpperCase() === teamPrefix.toUpperCase()) {\n return projectConfig;\n }\n }\n\n return null;\n}\n\n/**\n * Find project by workspace path.\n * Matches any project whose root path is an ancestor of the given path.\n * Used to resolve the tracker (GitHub/GitLab) from a workspace directory.\n */\nexport function findProjectByPath(workspacePath: string): ProjectConfig | null {\n const config = loadProjectsConfig();\n const normalizedTarget = resolve(workspacePath);\n\n for (const [, projectConfig] of Object.entries(config.projects)) {\n const normalizedProject = resolve(projectConfig.path);\n if (normalizedTarget === normalizedProject || normalizedTarget.startsWith(normalizedProject + '/')) {\n return projectConfig;\n }\n }\n\n return null;\n}\n\n\n/**\n * Resolve the correct project path for an issue based on labels\n *\n * @param project - The project config\n * @param labels - Array of label names from the Linear issue\n * @returns The resolved path (may differ from project.path based on routing rules)\n */\nexport function resolveProjectPath(project: ProjectConfig, labels: string[] = []): string {\n if (!project.issue_routing || project.issue_routing.length === 0) {\n return project.path;\n }\n\n // Normalize labels to lowercase for comparison\n const normalizedLabels = labels.map(l => l.toLowerCase());\n\n // First, check label-based routing rules\n for (const rule of project.issue_routing) {\n if (rule.labels && rule.labels.length > 0) {\n const ruleLabels = rule.labels.map(l => l.toLowerCase());\n const hasMatch = ruleLabels.some(label => normalizedLabels.includes(label));\n if (hasMatch) {\n return rule.path;\n }\n }\n }\n\n // Then, find default rule\n for (const rule of project.issue_routing) {\n if (rule.default) {\n return rule.path;\n }\n }\n\n // Fall back to project path\n return project.path;\n}\n\n/**\n * Resolve project from an issue ID (and optional labels)\n *\n * @param issueId - Linear issue ID (e.g., \"MIN-123\")\n * @param labels - Optional array of label names\n * @returns Resolved project info or null if not found\n */\nexport function resolveProjectFromIssue(\n issueId: string,\n labels: string[] = []\n): ResolvedProject | null {\n const teamPrefix = extractTeamPrefix(issueId);\n if (!teamPrefix) {\n return null;\n }\n\n const config = loadProjectsConfig();\n\n // Find project by team prefix\n for (const [key, projectConfig] of Object.entries(config.projects)) {\n if (projectConfig.linear_team?.toUpperCase() === teamPrefix) {\n const resolvedPath = resolveProjectPath(projectConfig, labels);\n return {\n projectKey: key,\n projectName: projectConfig.name,\n projectPath: resolvedPath,\n linearTeam: projectConfig.linear_team,\n };\n }\n }\n\n return null;\n}\n\n/**\n * Get a project by key\n */\nexport function getProject(key: string): ProjectConfig | null {\n const config = loadProjectsConfig();\n return config.projects[key] || null;\n}\n\n/**\n * Check if projects.yaml exists and has any projects\n */\nexport function hasProjects(): boolean {\n const config = loadProjectsConfig();\n return Object.keys(config.projects).length > 0;\n}\n\n/**\n * Create a default projects.yaml with example structure\n */\nexport function createDefaultProjectsConfig(): ProjectsConfig {\n const defaultConfig: ProjectsConfig = {\n projects: {\n // Example project - commented out in actual file\n },\n };\n\n return defaultConfig;\n}\n\n/**\n * Initialize projects.yaml with example configuration\n */\nexport function initializeProjectsConfig(): void {\n if (existsSync(PROJECTS_CONFIG_FILE)) {\n console.log(`Projects config already exists at ${PROJECTS_CONFIG_FILE}`);\n return;\n }\n\n const exampleYaml = `# Panopticon Project Registry\n# Maps Linear teams to project paths for workspace creation\n\nprojects:\n # Example: Mind Your Now project\n # myn:\n # name: \"Mind Your Now\"\n # path: /home/user/projects/myn\n # linear_team: MIN\n # issue_routing:\n # # Route docs/marketing issues to docs repo\n # - labels: [docs, marketing, seo, landing-pages]\n # path: /home/user/projects/myn/docs\n # # Default: main repo\n # - default: true\n # path: /home/user/projects/myn\n # specialists:\n # context_runs: 5\n # digest_model: null # Use same model as specialist\n # retention:\n # max_days: 30\n # max_runs: 50\n # prompts:\n # review-agent: |\n # Pay special attention to:\n # - Database migration safety\n # - API backward compatibility\n\n # Example: Panopticon itself\n # panopticon:\n # name: \"Panopticon\"\n # path: /home/user/projects/panopticon\n # linear_team: PAN\n`;\n\n const dir = PANOPTICON_HOME;\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n writeFileSync(PROJECTS_CONFIG_FILE, exampleYaml, 'utf-8');\n console.log(`Created example projects config at ${PROJECTS_CONFIG_FILE}`);\n}\n\n/**\n * Default specialist configuration values\n */\nconst DEFAULT_SPECIALIST_CONFIG: Required<SpecialistConfig> = {\n context_runs: 5,\n digest_model: null,\n retention: {\n max_days: 30,\n max_runs: 50,\n },\n prompts: {},\n};\n\n/**\n * Get specialist configuration for a project with defaults\n *\n * @param projectKey - Project key\n * @returns Specialist config with defaults applied\n */\nexport function getSpecialistConfig(projectKey: string): Required<SpecialistConfig> {\n const project = getProject(projectKey);\n\n if (!project || !project.specialists) {\n return DEFAULT_SPECIALIST_CONFIG;\n }\n\n return {\n context_runs: project.specialists.context_runs ?? DEFAULT_SPECIALIST_CONFIG.context_runs,\n digest_model: project.specialists.digest_model ?? DEFAULT_SPECIALIST_CONFIG.digest_model,\n retention: {\n max_days: project.specialists.retention?.max_days ?? DEFAULT_SPECIALIST_CONFIG.retention.max_days,\n max_runs: project.specialists.retention?.max_runs ?? DEFAULT_SPECIALIST_CONFIG.retention.max_runs,\n },\n prompts: project.specialists.prompts ?? DEFAULT_SPECIALIST_CONFIG.prompts,\n };\n}\n\n/**\n * Get retention policy for a project's specialists\n *\n * @param projectKey - Project key\n * @returns Retention policy\n */\nexport function getSpecialistRetention(projectKey: string): { max_days: number; max_runs: number } {\n const config = getSpecialistConfig(projectKey);\n return config.retention;\n}\n\n/**\n * Find all projects that have a rally_project configured.\n * Returns array of { key, config } for projects with Rally project OIDs.\n */\nexport function findProjectsByRallyProject(): Array<{ key: string; config: ProjectConfig }> {\n const config = loadProjectsConfig();\n return Object.entries(config.projects)\n .filter(([, projectConfig]) => !!projectConfig.rally_project)\n .map(([key, projectConfig]) => ({ key, config: projectConfig }));\n}\n\n/**\n * Get custom prompt override for a specialist (if configured)\n *\n * @param projectKey - Project key\n * @param specialistType - Specialist type\n * @returns Custom prompt or null if not configured\n */\nexport function getSpecialistPromptOverride(\n projectKey: string,\n specialistType: 'review-agent' | 'test-agent' | 'merge-agent'\n): string | null {\n const config = getSpecialistConfig(projectKey);\n return config.prompts[specialistType] || null;\n}\n"],"mappings":";;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAMA,SAAS,YAAY,cAAc,eAAe,iBAAiB;AACnE,SAAS,MAAM,eAAe;AAC9B,SAAS,SAAS,WAAW,aAAa,qBAAqB;AA2HxD,SAAS,qBAAqC;AACnD,MAAI,CAAC,WAAW,oBAAoB,GAAG;AACrC,WAAO,EAAE,UAAU,CAAC,EAAE;AAAA,EACxB;AAEA,MAAI;AACF,UAAM,UAAU,aAAa,sBAAsB,OAAO;AAC1D,UAAM,SAAS,UAAU,OAAO;AAChC,WAAO,UAAU,EAAE,UAAU,CAAC,EAAE;AAAA,EAClC,SAAS,OAAY;AACnB,YAAQ,MAAM,kCAAkC,MAAM,OAAO,EAAE;AAC/D,WAAO,EAAE,UAAU,CAAC,EAAE;AAAA,EACxB;AACF;AAKO,SAAS,mBAAmB,QAA8B;AAC/D,QAAM,MAAM;AACZ,MAAI,CAAC,WAAW,GAAG,GAAG;AACpB,cAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,EACpC;AAEA,QAAM,OAAO,cAAc,QAAQ,EAAE,QAAQ,EAAE,CAAC;AAChD,gBAAc,sBAAsB,MAAM,OAAO;AACnD;AAKO,SAAS,eAA8D;AAC5E,QAAM,SAAS,mBAAmB;AAClC,SAAO,OAAO,QAAQ,OAAO,QAAQ,EAAE,IAAI,CAAC,CAAC,KAAK,aAAa,OAAO;AAAA,IACpE;AAAA,IACA,QAAQ;AAAA,EACV,EAAE;AACJ;AAKO,SAAS,gBAAgB,KAAa,eAAoC;AAC/E,QAAM,SAAS,mBAAmB;AAClC,SAAO,SAAS,GAAG,IAAI;AACvB,qBAAmB,MAAM;AAC3B;AAKO,SAAS,kBAAkB,KAAsB;AACtD,QAAM,SAAS,mBAAmB;AAClC,MAAI,OAAO,SAAS,GAAG,GAAG;AACxB,WAAO,OAAO,SAAS,GAAG;AAC1B,uBAAmB,MAAM;AACzB,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAMO,SAAS,kBAAkB,SAAgC;AAChE,QAAM,QAAQ,QAAQ,MAAM,iBAAiB;AAC7C,SAAO,QAAQ,MAAM,CAAC,EAAE,YAAY,IAAI;AAC1C;AAKO,SAAS,kBAAkB,YAA0C;AAC1E,QAAM,SAAS,mBAAmB;AAElC,aAAW,CAAC,EAAE,aAAa,KAAK,OAAO,QAAQ,OAAO,QAAQ,GAAG;AAC/D,QAAI,cAAc,aAAa,YAAY,MAAM,WAAW,YAAY,GAAG;AACzE,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAOO,SAAS,kBAAkB,eAA6C;AAC7E,QAAM,SAAS,mBAAmB;AAClC,QAAM,mBAAmB,QAAQ,aAAa;AAE9C,aAAW,CAAC,EAAE,aAAa,KAAK,OAAO,QAAQ,OAAO,QAAQ,GAAG;AAC/D,UAAM,oBAAoB,QAAQ,cAAc,IAAI;AACpD,QAAI,qBAAqB,qBAAqB,iBAAiB,WAAW,oBAAoB,GAAG,GAAG;AAClG,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAUO,SAAS,mBAAmB,SAAwB,SAAmB,CAAC,GAAW;AACxF,MAAI,CAAC,QAAQ,iBAAiB,QAAQ,cAAc,WAAW,GAAG;AAChE,WAAO,QAAQ;AAAA,EACjB;AAGA,QAAM,mBAAmB,OAAO,IAAI,OAAK,EAAE,YAAY,CAAC;AAGxD,aAAW,QAAQ,QAAQ,eAAe;AACxC,QAAI,KAAK,UAAU,KAAK,OAAO,SAAS,GAAG;AACzC,YAAM,aAAa,KAAK,OAAO,IAAI,OAAK,EAAE,YAAY,CAAC;AACvD,YAAM,WAAW,WAAW,KAAK,WAAS,iBAAiB,SAAS,KAAK,CAAC;AAC1E,UAAI,UAAU;AACZ,eAAO,KAAK;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAGA,aAAW,QAAQ,QAAQ,eAAe;AACxC,QAAI,KAAK,SAAS;AAChB,aAAO,KAAK;AAAA,IACd;AAAA,EACF;AAGA,SAAO,QAAQ;AACjB;AASO,SAAS,wBACd,SACA,SAAmB,CAAC,GACI;AACxB,QAAM,aAAa,kBAAkB,OAAO;AAC5C,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,mBAAmB;AAGlC,aAAW,CAAC,KAAK,aAAa,KAAK,OAAO,QAAQ,OAAO,QAAQ,GAAG;AAClE,QAAI,cAAc,aAAa,YAAY,MAAM,YAAY;AAC3D,YAAM,eAAe,mBAAmB,eAAe,MAAM;AAC7D,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,aAAa,cAAc;AAAA,QAC3B,aAAa;AAAA,QACb,YAAY,cAAc;AAAA,MAC5B;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,WAAW,KAAmC;AAC5D,QAAM,SAAS,mBAAmB;AAClC,SAAO,OAAO,SAAS,GAAG,KAAK;AACjC;AAKO,SAAS,cAAuB;AACrC,QAAM,SAAS,mBAAmB;AAClC,SAAO,OAAO,KAAK,OAAO,QAAQ,EAAE,SAAS;AAC/C;AAKO,SAAS,8BAA8C;AAC5D,QAAM,gBAAgC;AAAA,IACpC,UAAU;AAAA;AAAA,IAEV;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,2BAAiC;AAC/C,MAAI,WAAW,oBAAoB,GAAG;AACpC,YAAQ,IAAI,qCAAqC,oBAAoB,EAAE;AACvE;AAAA,EACF;AAEA,QAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmCpB,QAAM,MAAM;AACZ,MAAI,CAAC,WAAW,GAAG,GAAG;AACpB,cAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,EACpC;AAEA,gBAAc,sBAAsB,aAAa,OAAO;AACxD,UAAQ,IAAI,sCAAsC,oBAAoB,EAAE;AAC1E;AAqBO,SAAS,oBAAoB,YAAgD;AAClF,QAAM,UAAU,WAAW,UAAU;AAErC,MAAI,CAAC,WAAW,CAAC,QAAQ,aAAa;AACpC,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,cAAc,QAAQ,YAAY,gBAAgB,0BAA0B;AAAA,IAC5E,cAAc,QAAQ,YAAY,gBAAgB,0BAA0B;AAAA,IAC5E,WAAW;AAAA,MACT,UAAU,QAAQ,YAAY,WAAW,YAAY,0BAA0B,UAAU;AAAA,MACzF,UAAU,QAAQ,YAAY,WAAW,YAAY,0BAA0B,UAAU;AAAA,IAC3F;AAAA,IACA,SAAS,QAAQ,YAAY,WAAW,0BAA0B;AAAA,EACpE;AACF;AAQO,SAAS,uBAAuB,YAA4D;AACjG,QAAM,SAAS,oBAAoB,UAAU;AAC7C,SAAO,OAAO;AAChB;AAMO,SAAS,6BAA4E;AAC1F,QAAM,SAAS,mBAAmB;AAClC,SAAO,OAAO,QAAQ,OAAO,QAAQ,EAClC,OAAO,CAAC,CAAC,EAAE,aAAa,MAAM,CAAC,CAAC,cAAc,aAAa,EAC3D,IAAI,CAAC,CAAC,KAAK,aAAa,OAAO,EAAE,KAAK,QAAQ,cAAc,EAAE;AACnE;AASO,SAAS,4BACd,YACA,gBACe;AACf,QAAM,SAAS,oBAAoB,UAAU;AAC7C,SAAO,OAAO,QAAQ,cAAc,KAAK;AAC3C;AA7cA,IAYa,sBA4XP;AAxYN;AAAA;AAAA;AASA;AAGO,IAAM,uBAAuB,KAAK,iBAAiB,eAAe;AA4XzE,IAAM,4BAAwD;AAAA,MAC5D,cAAc;AAAA,MACd,cAAc;AAAA,MACd,WAAW;AAAA,QACT,UAAU;AAAA,QACV,UAAU;AAAA,MACZ;AAAA,MACA,SAAS,CAAC;AAAA,IACZ;AAAA;AAAA;","names":[]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  init_config_yaml,
3
3
  loadConfig
4
- } from "./chunk-HJSM6E6U.js";
4
+ } from "./chunk-ZP6EWSZV.js";
5
5
  import {
6
6
  IssueNotFoundError,
7
7
  NotImplementedError,
@@ -245,6 +245,10 @@ var init_linear = __esm({
245
245
 
246
246
  // src/lib/tracker/github.ts
247
247
  import { Octokit } from "@octokit/rest";
248
+ function parseIssueNumber(id) {
249
+ const match = id.match(/(\d+)$/);
250
+ return match ? parseInt(match[1], 10) : NaN;
251
+ }
248
252
  var GitHubTracker;
249
253
  var init_github = __esm({
250
254
  "src/lib/tracker/github.ts"() {
@@ -282,7 +286,7 @@ var init_github = __esm({
282
286
  }
283
287
  async getIssue(id) {
284
288
  try {
285
- const issueNumber = parseInt(id.replace(/^#/, ""), 10);
289
+ const issueNumber = parseIssueNumber(id);
286
290
  if (isNaN(issueNumber)) {
287
291
  throw new IssueNotFoundError(id, "github");
288
292
  }
@@ -300,7 +304,7 @@ var init_github = __esm({
300
304
  }
301
305
  }
302
306
  async updateIssue(id, update) {
303
- const issueNumber = parseInt(id.replace(/^#/, ""), 10);
307
+ const issueNumber = parseIssueNumber(id);
304
308
  const updatePayload = {};
305
309
  if (update.title !== void 0) {
306
310
  updatePayload.title = update.title;
@@ -337,7 +341,7 @@ var init_github = __esm({
337
341
  return this.normalizeIssue(issue);
338
342
  }
339
343
  async getComments(issueId) {
340
- const issueNumber = parseInt(issueId.replace(/^#/, ""), 10);
344
+ const issueNumber = parseIssueNumber(issueId);
341
345
  const { data: comments } = await this.octokit.issues.listComments({
342
346
  owner: this.owner,
343
347
  repo: this.repo,
@@ -353,7 +357,7 @@ var init_github = __esm({
353
357
  }));
354
358
  }
355
359
  async addComment(issueId, body) {
356
- const issueNumber = parseInt(issueId.replace(/^#/, ""), 10);
360
+ const issueNumber = parseIssueNumber(issueId);
357
361
  const { data: comment } = await this.octokit.issues.createComment({
358
362
  owner: this.owner,
359
363
  repo: this.repo,
@@ -370,7 +374,7 @@ var init_github = __esm({
370
374
  };
371
375
  }
372
376
  async transitionIssue(id, state) {
373
- const issueNumber = parseInt(id.replace(/^#/, ""), 10);
377
+ const issueNumber = parseIssueNumber(id);
374
378
  if (state === "in_progress") {
375
379
  await this.ensureLabelExists("in-progress", "In progress", "0075ca");
376
380
  await this.octokit.issues.addLabels({
@@ -636,4 +640,4 @@ export {
636
640
  getAllTrackers,
637
641
  init_factory
638
642
  };
639
- //# sourceMappingURL=chunk-76F6DSVS.js.map
643
+ //# sourceMappingURL=chunk-T7BBPDEJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/tracker/linear.ts","../src/lib/tracker/github.ts","../src/lib/tracker/gitlab.ts","../src/lib/tracker/factory.ts"],"sourcesContent":["/**\n * Linear Issue Tracker Adapter\n *\n * Implements IssueTracker interface for Linear.\n */\n\nimport { LinearClient } from '@linear/sdk';\nimport type {\n Issue,\n IssueFilters,\n IssueState,\n IssueTracker,\n IssueUpdate,\n NewIssue,\n Comment,\n TrackerType,\n} from './interface.js';\nimport { IssueNotFoundError, TrackerAuthError } from './interface.js';\n\n// Map Linear state types to our normalized states\nconst STATE_MAP: Record<string, IssueState> = {\n backlog: 'open',\n unstarted: 'open',\n started: 'in_progress',\n completed: 'closed',\n canceled: 'closed',\n};\n\nexport class LinearTracker implements IssueTracker {\n readonly name: TrackerType = 'linear';\n private client: LinearClient;\n private defaultTeam?: string;\n\n constructor(apiKey: string, options?: { team?: string }) {\n if (!apiKey) {\n throw new TrackerAuthError('linear', 'API key is required');\n }\n this.client = new LinearClient({ apiKey });\n this.defaultTeam = options?.team;\n }\n\n async listIssues(filters?: IssueFilters): Promise<Issue[]> {\n const team = filters?.team ?? this.defaultTeam;\n\n const result = await this.client.issues({\n first: filters?.limit ?? 50,\n filter: {\n team: team ? { key: { eq: team } } : undefined,\n state: filters?.state\n ? { type: { eq: this.reverseMapState(filters.state) } }\n : filters?.includeClosed\n ? undefined\n : { type: { neq: 'completed' } },\n labels: filters?.labels?.length\n ? { name: { in: filters.labels } }\n : undefined,\n assignee: filters?.assignee\n ? { name: { containsIgnoreCase: filters.assignee } }\n : undefined,\n },\n });\n\n const issues: Issue[] = [];\n for (const node of result.nodes) {\n issues.push(await this.normalizeIssue(node));\n }\n return issues;\n }\n\n async getIssue(id: string): Promise<Issue> {\n try {\n // Check if it's a UUID (36 chars with hyphens)\n const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id);\n\n if (isUuid) {\n // Fetch directly by UUID\n const issue = await this.client.issue(id);\n if (issue) {\n return this.normalizeIssue(issue);\n }\n } else {\n // Parse identifier (e.g., MIN-630) and search\n const match = id.match(/^([A-Z]+)-(\\d+)$/i);\n if (match) {\n const [, teamKey, number] = match;\n // Use searchIssues which supports identifier matching\n const results = await this.client.searchIssues(id, { first: 1 });\n if (results.nodes.length > 0) {\n return this.normalizeIssue(results.nodes[0]);\n }\n }\n }\n\n throw new IssueNotFoundError(id, 'linear');\n } catch (error) {\n if (error instanceof IssueNotFoundError) throw error;\n throw new IssueNotFoundError(id, 'linear');\n }\n }\n\n async updateIssue(id: string, update: IssueUpdate): Promise<Issue> {\n const issue = await this.getIssue(id);\n\n const updatePayload: Record<string, unknown> = {};\n\n if (update.title !== undefined) {\n updatePayload.title = update.title;\n }\n if (update.description !== undefined) {\n updatePayload.description = update.description;\n }\n if (update.priority !== undefined) {\n updatePayload.priority = update.priority;\n }\n if (update.dueDate !== undefined) {\n updatePayload.dueDate = update.dueDate;\n }\n if (update.state !== undefined) {\n // Need to find the state ID - this is complex in Linear\n // For now, we'll use the transition method\n await this.transitionIssue(id, update.state);\n }\n if (update.labels !== undefined) {\n // Need to look up label IDs - complex operation\n // TODO: Implement label updates\n }\n\n if (Object.keys(updatePayload).length > 0) {\n await this.client.updateIssue(issue.id, updatePayload);\n }\n\n return this.getIssue(id);\n }\n\n async createIssue(newIssue: NewIssue): Promise<Issue> {\n const team = newIssue.team ?? this.defaultTeam;\n\n if (!team) {\n throw new Error('Team is required to create an issue');\n }\n\n // Get team ID from key\n const teams = await this.client.teams({\n filter: { key: { eq: team } },\n });\n\n if (teams.nodes.length === 0) {\n throw new Error(`Team not found: ${team}`);\n }\n\n const teamId = teams.nodes[0].id;\n\n const result = await this.client.createIssue({\n teamId,\n title: newIssue.title,\n description: newIssue.description,\n priority: newIssue.priority,\n dueDate: newIssue.dueDate,\n });\n\n const created = await result.issue;\n if (!created) {\n throw new Error('Failed to create issue');\n }\n\n return this.normalizeIssue(created);\n }\n\n async getComments(issueId: string): Promise<Comment[]> {\n const issue = await this.client.issue(issueId);\n const comments = await issue.comments();\n\n return comments.nodes.map((c) => ({\n id: c.id,\n issueId,\n body: c.body,\n author: c.user?.then((u) => u?.name ?? 'Unknown') as unknown as string, // Simplified\n createdAt: c.createdAt.toISOString(),\n updatedAt: c.updatedAt.toISOString(),\n }));\n }\n\n async addComment(issueId: string, body: string): Promise<Comment> {\n const result = await this.client.createComment({\n issueId,\n body,\n });\n\n const comment = await result.comment;\n if (!comment) {\n throw new Error('Failed to create comment');\n }\n\n return {\n id: comment.id,\n issueId,\n body: comment.body,\n author: 'Panopticon', // Simplified\n createdAt: comment.createdAt.toISOString(),\n updatedAt: comment.updatedAt.toISOString(),\n };\n }\n\n async transitionIssue(id: string, state: IssueState): Promise<void> {\n // Resolve the Linear issue directly (avoid normalizeIssue which may fail on SDK edge cases)\n let linearIssue: any;\n const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id);\n if (isUuid) {\n linearIssue = await this.client.issue(id);\n } else {\n const results = await this.client.searchIssues(id, { first: 1 });\n if (results.nodes.length > 0) {\n linearIssue = results.nodes[0];\n } else {\n throw new IssueNotFoundError(id, 'linear');\n }\n }\n\n // Get workflow states for the issue's team\n const team = await linearIssue.team;\n if (!team) {\n throw new Error('Could not determine issue team');\n }\n\n const states = await team.states();\n const targetStateType = this.reverseMapState(state);\n\n // Find a state matching the target type.\n // Multiple states can share the same type (e.g., \"In Planning\", \"In Progress\", \"In Review\"\n // are all type \"started\"). Prefer the one with the lowest position (most basic/default state\n // for that type), which matches Linear's convention.\n const matchingStates = states.nodes\n .filter((s: any) => s.type === targetStateType)\n .sort((a: any, b: any) => (a.position ?? 0) - (b.position ?? 0));\n const targetState = matchingStates[0];\n if (!targetState) {\n throw new Error(`No state found matching type: ${targetStateType}`);\n }\n\n await this.client.updateIssue(linearIssue.id, {\n stateId: targetState.id,\n });\n }\n\n async linkPR(issueId: string, prUrl: string): Promise<void> {\n const issue = await this.getIssue(issueId);\n\n await this.client.createAttachment({\n issueId: issue.id,\n title: 'Pull Request',\n url: prUrl,\n });\n }\n\n private async normalizeIssue(linearIssue: any): Promise<Issue> {\n const state = await linearIssue.state;\n const assignee = await linearIssue.assignee;\n const labels = await linearIssue.labels();\n\n // Handle dueDate - can be Date, string, or undefined\n let dueDate: string | undefined;\n if (linearIssue.dueDate) {\n dueDate = linearIssue.dueDate instanceof Date\n ? linearIssue.dueDate.toISOString()\n : String(linearIssue.dueDate);\n }\n\n return {\n id: linearIssue.id,\n ref: linearIssue.identifier,\n title: linearIssue.title,\n description: linearIssue.description ?? '',\n state: this.mapState(state?.type ?? 'backlog'),\n labels: labels?.nodes?.map((l: any) => l.name) ?? [],\n assignee: assignee?.name,\n url: linearIssue.url,\n tracker: 'linear',\n priority: linearIssue.priority,\n dueDate,\n createdAt: linearIssue.createdAt instanceof Date\n ? linearIssue.createdAt.toISOString()\n : String(linearIssue.createdAt),\n updatedAt: linearIssue.updatedAt instanceof Date\n ? linearIssue.updatedAt.toISOString()\n : String(linearIssue.updatedAt),\n };\n }\n\n private mapState(linearState: string): IssueState {\n return STATE_MAP[linearState] ?? 'open';\n }\n\n private reverseMapState(state: IssueState): string {\n switch (state) {\n case 'open':\n return 'unstarted';\n case 'in_progress':\n return 'started';\n case 'closed':\n return 'completed';\n default:\n return 'unstarted';\n }\n }\n}\n","/**\n * GitHub Issues Tracker Adapter\n *\n * Implements IssueTracker interface for GitHub Issues.\n */\n\nimport { Octokit } from '@octokit/rest';\nimport type {\n Issue,\n IssueFilters,\n IssueState,\n IssueTracker,\n IssueUpdate,\n NewIssue,\n Comment,\n TrackerType,\n} from './interface.js';\nimport { IssueNotFoundError, TrackerAuthError } from './interface.js';\n\n/**\n * Extract issue number from various formats: \"300\", \"#300\", \"PAN-300\"\n */\nfunction parseIssueNumber(id: string): number {\n const match = id.match(/(\\d+)$/);\n return match ? parseInt(match[1], 10) : NaN;\n}\n\nexport class GitHubTracker implements IssueTracker {\n readonly name: TrackerType = 'github';\n private octokit: Octokit;\n private owner: string;\n private repo: string;\n\n constructor(token: string, owner: string, repo: string) {\n if (!token) {\n throw new TrackerAuthError('github', 'Token is required');\n }\n if (!owner || !repo) {\n throw new Error('GitHub owner and repo are required');\n }\n\n this.octokit = new Octokit({ auth: token });\n this.owner = owner;\n this.repo = repo;\n }\n\n async listIssues(filters?: IssueFilters): Promise<Issue[]> {\n const state = this.mapStateToGitHub(filters?.state);\n\n const response = await this.octokit.issues.listForRepo({\n owner: this.owner,\n repo: this.repo,\n state: filters?.includeClosed ? 'all' : state,\n labels: filters?.labels?.join(',') || undefined,\n assignee: filters?.assignee || undefined,\n per_page: filters?.limit ?? 50,\n });\n\n // Filter out pull requests (GitHub API returns both)\n const issues = response.data.filter((item) => !item.pull_request);\n\n return issues.map((issue) => this.normalizeIssue(issue));\n }\n\n async getIssue(id: string): Promise<Issue> {\n try {\n // Parse the issue number from refs like \"#42\" or just \"42\"\n const issueNumber = parseIssueNumber(id);\n\n if (isNaN(issueNumber)) {\n throw new IssueNotFoundError(id, 'github');\n }\n\n const { data: issue } = await this.octokit.issues.get({\n owner: this.owner,\n repo: this.repo,\n issue_number: issueNumber,\n });\n\n return this.normalizeIssue(issue);\n } catch (error: any) {\n if (error?.status === 404) {\n throw new IssueNotFoundError(id, 'github');\n }\n throw error;\n }\n }\n\n async updateIssue(id: string, update: IssueUpdate): Promise<Issue> {\n const issueNumber = parseIssueNumber(id);\n\n const updatePayload: Record<string, unknown> = {};\n\n if (update.title !== undefined) {\n updatePayload.title = update.title;\n }\n if (update.description !== undefined) {\n updatePayload.body = update.description;\n }\n if (update.state !== undefined) {\n updatePayload.state = update.state === 'closed' ? 'closed' : 'open';\n }\n if (update.labels !== undefined) {\n updatePayload.labels = update.labels;\n }\n if (update.assignee !== undefined) {\n updatePayload.assignees = update.assignee ? [update.assignee] : [];\n }\n\n await this.octokit.issues.update({\n owner: this.owner,\n repo: this.repo,\n issue_number: issueNumber,\n ...updatePayload,\n });\n\n return this.getIssue(id);\n }\n\n async createIssue(newIssue: NewIssue): Promise<Issue> {\n const { data: issue } = await this.octokit.issues.create({\n owner: this.owner,\n repo: this.repo,\n title: newIssue.title,\n body: newIssue.description,\n labels: newIssue.labels,\n assignees: newIssue.assignee ? [newIssue.assignee] : undefined,\n });\n\n return this.normalizeIssue(issue);\n }\n\n async getComments(issueId: string): Promise<Comment[]> {\n const issueNumber = parseIssueNumber(issueId);\n\n const { data: comments } = await this.octokit.issues.listComments({\n owner: this.owner,\n repo: this.repo,\n issue_number: issueNumber,\n });\n\n return comments.map((c) => ({\n id: String(c.id),\n issueId,\n body: c.body ?? '',\n author: c.user?.login ?? 'Unknown',\n createdAt: c.created_at,\n updatedAt: c.updated_at,\n }));\n }\n\n async addComment(issueId: string, body: string): Promise<Comment> {\n const issueNumber = parseIssueNumber(issueId);\n\n const { data: comment } = await this.octokit.issues.createComment({\n owner: this.owner,\n repo: this.repo,\n issue_number: issueNumber,\n body,\n });\n\n return {\n id: String(comment.id),\n issueId,\n body: comment.body ?? '',\n author: comment.user?.login ?? 'Unknown',\n createdAt: comment.created_at,\n updatedAt: comment.updated_at,\n };\n }\n\n async transitionIssue(id: string, state: IssueState): Promise<void> {\n const issueNumber = parseIssueNumber(id);\n\n if (state === 'in_progress') {\n // GitHub has no native \"in progress\" state — use a label instead.\n await this.ensureLabelExists('in-progress', 'In progress', '0075ca');\n await this.octokit.issues.addLabels({\n owner: this.owner,\n repo: this.repo,\n issue_number: issueNumber,\n labels: ['in-progress'],\n });\n } else {\n // Remove in-progress label when moving to open or closed\n const issue = await this.getIssue(id);\n if (issue.labels?.includes('in-progress')) {\n await this.octokit.issues.removeLabel({\n owner: this.owner,\n repo: this.repo,\n issue_number: issueNumber,\n name: 'in-progress',\n }).catch(() => {/* label may not exist, ignore */});\n }\n await this.updateIssue(id, { state });\n }\n }\n\n /** Ensure a label exists in the repo, creating it if needed. */\n private async ensureLabelExists(name: string, description: string, color: string): Promise<void> {\n try {\n await this.octokit.issues.getLabel({ owner: this.owner, repo: this.repo, name });\n } catch {\n await this.octokit.issues.createLabel({\n owner: this.owner,\n repo: this.repo,\n name,\n description,\n color,\n }).catch(() => {/* race condition: another process created it first */});\n }\n }\n\n async linkPR(issueId: string, prUrl: string): Promise<void> {\n // GitHub auto-links PRs that mention issues\n // Add a comment with the PR link\n await this.addComment(\n issueId,\n `Linked Pull Request: ${prUrl}`\n );\n }\n\n private normalizeIssue(ghIssue: any): Issue {\n const labels: string[] = ghIssue.labels.map((l: any) =>\n typeof l === 'string' ? l : l.name\n );\n return {\n id: String(ghIssue.id),\n ref: `#${ghIssue.number}`,\n title: ghIssue.title,\n description: ghIssue.body ?? '',\n state: this.mapStateFromGitHub(ghIssue.state, labels),\n labels,\n assignee: ghIssue.assignee?.login,\n url: ghIssue.html_url,\n tracker: 'github',\n priority: undefined, // GitHub doesn't have priority\n dueDate: undefined, // GitHub doesn't have due dates on issues\n createdAt: ghIssue.created_at,\n updatedAt: ghIssue.updated_at,\n };\n }\n\n private mapStateFromGitHub(ghState: string, labels: string[] = []): IssueState {\n if (ghState === 'closed') return 'closed';\n if (labels.includes('in-progress')) return 'in_progress';\n return 'open';\n }\n\n private mapStateToGitHub(\n state?: IssueState\n ): 'open' | 'closed' | 'all' {\n if (!state) return 'open';\n if (state === 'closed') return 'closed';\n return 'open'; // Both 'open' and 'in_progress' map to 'open'\n }\n}\n","/**\n * GitLab Issues Tracker Adapter (Stub)\n *\n * Placeholder implementation for GitLab Issues support.\n * Full implementation will use @gitbeaker/rest.\n */\n\nimport type {\n Issue,\n IssueFilters,\n IssueState,\n IssueTracker,\n IssueUpdate,\n NewIssue,\n Comment,\n TrackerType,\n} from './interface.js';\nimport { NotImplementedError } from './interface.js';\n\nexport class GitLabTracker implements IssueTracker {\n readonly name: TrackerType = 'gitlab';\n\n constructor(\n private token: string,\n private projectId: string\n ) {\n // Stub - will initialize @gitbeaker client when implemented\n }\n\n async listIssues(_filters?: IssueFilters): Promise<Issue[]> {\n throw new NotImplementedError(\n 'GitLab tracker is not yet implemented. Coming soon!'\n );\n }\n\n async getIssue(_id: string): Promise<Issue> {\n throw new NotImplementedError(\n 'GitLab tracker is not yet implemented. Coming soon!'\n );\n }\n\n async updateIssue(_id: string, _update: IssueUpdate): Promise<Issue> {\n throw new NotImplementedError(\n 'GitLab tracker is not yet implemented. Coming soon!'\n );\n }\n\n async createIssue(_issue: NewIssue): Promise<Issue> {\n throw new NotImplementedError(\n 'GitLab tracker is not yet implemented. Coming soon!'\n );\n }\n\n async getComments(_issueId: string): Promise<Comment[]> {\n throw new NotImplementedError(\n 'GitLab tracker is not yet implemented. Coming soon!'\n );\n }\n\n async addComment(_issueId: string, _body: string): Promise<Comment> {\n throw new NotImplementedError(\n 'GitLab tracker is not yet implemented. Coming soon!'\n );\n }\n\n async transitionIssue(_id: string, _state: IssueState): Promise<void> {\n throw new NotImplementedError(\n 'GitLab tracker is not yet implemented. Coming soon!'\n );\n }\n\n async linkPR(_issueId: string, _prUrl: string): Promise<void> {\n throw new NotImplementedError(\n 'GitLab tracker is not yet implemented. Coming soon!'\n );\n }\n}\n","/**\n * Tracker Factory\n *\n * Creates appropriate tracker instances based on configuration.\n */\n\nimport type { IssueTracker, TrackerType } from './interface.js';\nimport { TrackerAuthError } from './interface.js';\nimport { LinearTracker } from './linear.js';\nimport { GitHubTracker } from './github.js';\nimport { GitLabTracker } from './gitlab.js';\nimport { RallyTracker } from './rally.js';\nimport type { TrackersConfig } from '../config.js';\nimport { loadConfig as loadYamlConfig } from '../config-yaml.js';\n\n// Configuration for a single tracker\nexport interface TrackerConfig {\n type: TrackerType;\n\n // Linear-specific\n apiKeyEnv?: string;\n team?: string;\n\n // GitHub-specific\n tokenEnv?: string;\n owner?: string;\n repo?: string;\n\n // GitLab-specific\n projectId?: string;\n\n // Rally-specific\n server?: string;\n workspace?: string;\n project?: string;\n}\n\n// Multi-tracker configuration (re-exported from config.ts)\n// Note: Use TrackersConfig from config.ts for full type with nested configs\n\n/**\n * Get tracker API key from config.yaml (Settings page).\n * This is checked FIRST — env vars are the fallback, not the other way around.\n */\nfunction getTrackerKeyFromConfig(trackerType: TrackerType): string | undefined {\n try {\n const { config: yamlConfig } = loadYamlConfig();\n return yamlConfig.trackerKeys[trackerType];\n } catch {\n return undefined;\n }\n}\n\n/**\n * Create a tracker instance from configuration.\n * Priority: config.yaml (Settings) > environment variable > custom env var name\n */\nexport function createTracker(config: TrackerConfig): IssueTracker {\n switch (config.type) {\n case 'linear': {\n const configKey = getTrackerKeyFromConfig('linear');\n const envKey = config.apiKeyEnv\n ? process.env[config.apiKeyEnv]\n : process.env.LINEAR_API_KEY;\n const apiKey = configKey || envKey;\n\n if (!apiKey) {\n throw new TrackerAuthError(\n 'linear',\n `API key not found. Configure in Settings or set ${config.apiKeyEnv ?? 'LINEAR_API_KEY'} environment variable.`\n );\n }\n\n return new LinearTracker(apiKey, { team: config.team });\n }\n\n case 'github': {\n const configKey = getTrackerKeyFromConfig('github');\n const envToken = config.tokenEnv\n ? process.env[config.tokenEnv]\n : process.env.GITHUB_TOKEN;\n const token = configKey || envToken;\n\n if (!token) {\n throw new TrackerAuthError(\n 'github',\n `Token not found. Configure in Settings or set ${config.tokenEnv ?? 'GITHUB_TOKEN'} environment variable.`\n );\n }\n\n if (!config.owner || !config.repo) {\n throw new Error(\n 'GitHub tracker requires owner and repo configuration'\n );\n }\n\n return new GitHubTracker(token, config.owner, config.repo);\n }\n\n case 'gitlab': {\n const configKey = getTrackerKeyFromConfig('gitlab');\n const envToken = config.tokenEnv\n ? process.env[config.tokenEnv]\n : process.env.GITLAB_TOKEN;\n const token = configKey || envToken;\n\n if (!token) {\n throw new TrackerAuthError(\n 'gitlab',\n `Token not found. Configure in Settings or set ${config.tokenEnv ?? 'GITLAB_TOKEN'} environment variable.`\n );\n }\n\n if (!config.projectId) {\n throw new Error('GitLab tracker requires projectId configuration');\n }\n\n return new GitLabTracker(token, config.projectId);\n }\n\n case 'rally': {\n const configKey = getTrackerKeyFromConfig('rally');\n const envKey = config.apiKeyEnv\n ? process.env[config.apiKeyEnv]\n : process.env.RALLY_API_KEY;\n const apiKey = configKey || envKey;\n\n if (!apiKey) {\n throw new TrackerAuthError(\n 'rally',\n `API key not found. Configure in Settings or set ${config.apiKeyEnv ?? 'RALLY_API_KEY'} environment variable.`\n );\n }\n\n return new RallyTracker({\n apiKey,\n server: config.server,\n workspace: config.workspace,\n project: config.project,\n });\n }\n\n default:\n throw new Error(`Unknown tracker type: ${config.type}`);\n }\n}\n\n/**\n * Create tracker from trackers configuration section\n */\nexport function createTrackerFromConfig(\n trackersConfig: TrackersConfig,\n trackerType: TrackerType\n): IssueTracker {\n const config = trackersConfig[trackerType];\n\n if (!config) {\n throw new Error(\n `No configuration found for tracker: ${trackerType}. Add [trackers.${trackerType}] to config.`\n );\n }\n\n return createTracker({ ...config, type: trackerType });\n}\n\n/**\n * Get the primary tracker from configuration\n */\nexport function getPrimaryTracker(trackersConfig: TrackersConfig): IssueTracker {\n return createTrackerFromConfig(trackersConfig, trackersConfig.primary);\n}\n\n/**\n * Get the secondary tracker from configuration (if configured)\n */\nexport function getSecondaryTracker(\n trackersConfig: TrackersConfig\n): IssueTracker | null {\n if (!trackersConfig.secondary) {\n return null;\n }\n return createTrackerFromConfig(trackersConfig, trackersConfig.secondary);\n}\n\n/**\n * Get all configured trackers\n */\nexport function getAllTrackers(trackersConfig: TrackersConfig): IssueTracker[] {\n const trackers: IssueTracker[] = [getPrimaryTracker(trackersConfig)];\n\n const secondary = getSecondaryTracker(trackersConfig);\n if (secondary) {\n trackers.push(secondary);\n }\n\n return trackers;\n}\n"],"mappings":"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","names":[]}