panopticon-cli 0.5.9 → 0.5.11

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 (70) hide show
  1. package/dist/{agents-M2ZOZL3P.js → agents-RL2KUSP3.js} +3 -3
  2. package/dist/{archive-planning-U3AZAKWI.js → archive-planning-54J6EP6A.js} +3 -3
  3. package/dist/{chunk-WEQW3EAT.js → chunk-F4XS2FQN.js} +3 -2
  4. package/dist/chunk-F4XS2FQN.js.map +1 -0
  5. package/dist/{chunk-OJF4QS3S.js → chunk-GIW2TUWI.js} +2 -2
  6. package/dist/{chunk-GM22HPYS.js → chunk-H7T35QDO.js} +21 -3
  7. package/dist/chunk-H7T35QDO.js.map +1 -0
  8. package/dist/{chunk-MJXYTGK5.js → chunk-JZWCL5S5.js} +2 -2
  9. package/dist/{chunk-3WDSD2VK.js → chunk-NLN3ZLCN.js} +186 -88
  10. package/dist/chunk-NLN3ZLCN.js.map +1 -0
  11. package/dist/{chunk-QQ27EVBD.js → chunk-OMOEGJDB.js} +3 -3
  12. package/dist/{chunk-4R6ATXYI.js → chunk-PFA5XE2V.js} +1 -37
  13. package/dist/chunk-PFA5XE2V.js.map +1 -0
  14. package/dist/{chunk-6OYUJ4AJ.js → chunk-R47UJWF6.js} +2 -2
  15. package/dist/{chunk-KPGVCGST.js → chunk-S7EJ2OLR.js} +10 -4
  16. package/dist/chunk-S7EJ2OLR.js.map +1 -0
  17. package/dist/{chunk-R4KPLLRB.js → chunk-SFX3BG6N.js} +1 -1
  18. package/dist/chunk-SFX3BG6N.js.map +1 -0
  19. package/dist/clean-planning-V4SSVU26.js +9 -0
  20. package/dist/cli/index.js +1111 -901
  21. package/dist/cli/index.js.map +1 -1
  22. package/dist/close-issue-5OMOP2FU.js +9 -0
  23. package/dist/compact-beads-YQDVF6FQ.js +9 -0
  24. package/dist/dashboard/prompts/merge-agent.md +11 -0
  25. package/dist/dashboard/prompts/review-agent.md +9 -0
  26. package/dist/dashboard/prompts/test-agent.md +9 -0
  27. package/dist/dashboard/prompts/work-agent.md +10 -2
  28. package/dist/dashboard/public/assets/index-5hYjhhGn.js +826 -0
  29. package/dist/dashboard/public/assets/index-DIFh3T1V.css +32 -0
  30. package/dist/dashboard/public/index.html +2 -2
  31. package/dist/dashboard/server.js +2405 -1754
  32. package/dist/index.d.ts +8 -3
  33. package/dist/index.js +3 -3
  34. package/dist/{label-cleanup-4HJVX6NP.js → label-cleanup-4IVZIPGK.js} +2 -2
  35. package/dist/{merge-agent-756U4NPX.js → merge-agent-7L7MWJEC.js} +12 -12
  36. package/dist/{specialist-context-UBVUUFJV.js → specialist-context-L37RF6Z5.js} +3 -3
  37. package/dist/{specialist-logs-FQRI3AIS.js → specialist-logs-B7UC3UDO.js} +3 -3
  38. package/dist/{specialists-CXRGSJY3.js → specialists-X4OGA7WX.js} +3 -3
  39. package/dist/{workspace-manager-OWHLR5BL.js → workspace-manager-6RP5A5HF.js} +2 -2
  40. package/package.json +1 -1
  41. package/skills/pan-new-project/SKILL.md +1 -1
  42. package/skills/pan-oversee/SKILL.md +45 -10
  43. package/skills/plan/SKILL.md +336 -0
  44. package/dist/chunk-3WDSD2VK.js.map +0 -1
  45. package/dist/chunk-4R6ATXYI.js.map +0 -1
  46. package/dist/chunk-GM22HPYS.js.map +0 -1
  47. package/dist/chunk-KPGVCGST.js.map +0 -1
  48. package/dist/chunk-R4KPLLRB.js.map +0 -1
  49. package/dist/chunk-WEQW3EAT.js.map +0 -1
  50. package/dist/clean-planning-7Z5YY64X.js +0 -9
  51. package/dist/close-issue-CTZK777I.js +0 -9
  52. package/dist/compact-beads-72SHALOL.js +0 -9
  53. package/dist/dashboard/public/assets/index-Bx4NCn9A.css +0 -32
  54. package/dist/dashboard/public/assets/index-DqPey4Of.js +0 -756
  55. package/skills/opus-plan/SKILL.md +0 -400
  56. /package/dist/{agents-M2ZOZL3P.js.map → agents-RL2KUSP3.js.map} +0 -0
  57. /package/dist/{archive-planning-U3AZAKWI.js.map → archive-planning-54J6EP6A.js.map} +0 -0
  58. /package/dist/{chunk-OJF4QS3S.js.map → chunk-GIW2TUWI.js.map} +0 -0
  59. /package/dist/{chunk-MJXYTGK5.js.map → chunk-JZWCL5S5.js.map} +0 -0
  60. /package/dist/{chunk-QQ27EVBD.js.map → chunk-OMOEGJDB.js.map} +0 -0
  61. /package/dist/{chunk-6OYUJ4AJ.js.map → chunk-R47UJWF6.js.map} +0 -0
  62. /package/dist/{clean-planning-7Z5YY64X.js.map → clean-planning-V4SSVU26.js.map} +0 -0
  63. /package/dist/{close-issue-CTZK777I.js.map → close-issue-5OMOP2FU.js.map} +0 -0
  64. /package/dist/{compact-beads-72SHALOL.js.map → compact-beads-YQDVF6FQ.js.map} +0 -0
  65. /package/dist/{label-cleanup-4HJVX6NP.js.map → label-cleanup-4IVZIPGK.js.map} +0 -0
  66. /package/dist/{merge-agent-756U4NPX.js.map → merge-agent-7L7MWJEC.js.map} +0 -0
  67. /package/dist/{specialist-context-UBVUUFJV.js.map → specialist-context-L37RF6Z5.js.map} +0 -0
  68. /package/dist/{specialist-logs-FQRI3AIS.js.map → specialist-logs-B7UC3UDO.js.map} +0 -0
  69. /package/dist/{specialists-CXRGSJY3.js.map → specialists-X4OGA7WX.js.map} +0 -0
  70. /package/dist/{workspace-manager-OWHLR5BL.js.map → workspace-manager-6RP5A5HF.js.map} +0 -0
@@ -283,18 +283,6 @@ var init_work_types = __esm({
283
283
  category: "pre-work",
284
284
  description: "Interactive planning and discovery agent"
285
285
  },
286
- "prd-agent": {
287
- category: "pre-work",
288
- description: "Generates Product Requirement Documents"
289
- },
290
- "decomposition-agent": {
291
- category: "pre-work",
292
- description: "Breaks down work into tasks"
293
- },
294
- "triage-agent": {
295
- category: "pre-work",
296
- description: "Prioritizes and triages issues"
297
- },
298
286
  // CLI contexts (2)
299
287
  "cli:interactive": {
300
288
  category: "cli",
@@ -629,30 +617,6 @@ var init_smart_model_selector = __esm({
629
617
  { skill: "documentation", weight: 0.2 }
630
618
  // Documenting decisions
631
619
  ],
632
- "prd-agent": [
633
- { skill: "documentation", weight: 0.5 },
634
- // Primary skill
635
- { skill: "planning", weight: 0.3 },
636
- // Structure
637
- { skill: "synthesis", weight: 0.2 }
638
- // Combining requirements
639
- ],
640
- "decomposition-agent": [
641
- { skill: "planning", weight: 0.5 },
642
- // Primary skill
643
- { skill: "synthesis", weight: 0.3 },
644
- // Breaking down
645
- { skill: "documentation", weight: 0.2 }
646
- // Clear tasks
647
- ],
648
- "triage-agent": [
649
- { skill: "speed", weight: 0.4 },
650
- // Quick decisions
651
- { skill: "synthesis", weight: 0.3 },
652
- // Understanding scope
653
- { skill: "planning", weight: 0.3 }
654
- // Prioritization
655
- ],
656
620
  // ═══════════════════════════════════════════════════════════════════════════
657
621
  // CLI CONTEXTS
658
622
  // ═══════════════════════════════════════════════════════════════════════════
@@ -832,4 +796,4 @@ export {
832
796
  getModelId,
833
797
  init_work_type_router
834
798
  };
835
- //# sourceMappingURL=chunk-4R6ATXYI.js.map
799
+ //# sourceMappingURL=chunk-PFA5XE2V.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../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":["/**\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 // 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 // ═══════════════════════════════════════════════════════════════════════════\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":[]}
@@ -2,7 +2,7 @@ import {
2
2
  stepFailed,
3
3
  stepOk,
4
4
  stepSkipped
5
- } from "./chunk-R4KPLLRB.js";
5
+ } from "./chunk-SFX3BG6N.js";
6
6
  import {
7
7
  ARCHIVES_DIR,
8
8
  PROJECT_DOCS_SUBDIR,
@@ -143,4 +143,4 @@ export {
143
143
  archiveWorkspaceArtifacts,
144
144
  archivePlanning
145
145
  };
146
- //# sourceMappingURL=chunk-6OYUJ4AJ.js.map
146
+ //# sourceMappingURL=chunk-R47UJWF6.js.map
@@ -215,7 +215,9 @@ async function createWorktree(repoPath, targetPath, branchName, defaultBranch =
215
215
  await execAsync("git worktree prune", { cwd: repoPath });
216
216
  const { stdout: localBranches } = await execAsync("git branch --list", { cwd: repoPath });
217
217
  const { stdout: remoteBranches } = await execAsync("git branch -r --list", { cwd: repoPath });
218
- const branchExists = localBranches.includes(branchName) || remoteBranches.includes(`origin/${branchName}`);
218
+ const localList = localBranches.split("\n").map((b) => b.replace(/^[*+\s]+/, "").trim()).filter(Boolean);
219
+ const remoteList = remoteBranches.split("\n").map((b) => b.trim()).filter(Boolean);
220
+ const branchExists = localList.includes(branchName) || remoteList.includes(`origin/${branchName}`);
219
221
  if (branchExists) {
220
222
  await execAsync(`git worktree add "${targetPath}" "${branchName}"`, { cwd: repoPath });
221
223
  } else {
@@ -299,6 +301,10 @@ function processTemplates(templateDir, targetDir, placeholders, templates) {
299
301
  const content = readFileSync2(sourcePath, "utf-8");
300
302
  const processed = replacePlaceholders(content, placeholders);
301
303
  writeFileSync2(targetPath, processed);
304
+ const targetName = file.replace(".template", "");
305
+ if (targetName === "dev" || targetName.endsWith(".sh")) {
306
+ chmodSync(targetPath, 493);
307
+ }
302
308
  steps.push(`Processed template: ${file}`);
303
309
  }
304
310
  }
@@ -577,10 +583,10 @@ VITE_HUME_CONFIG_ID=${humeResult.configId}
577
583
  }
578
584
  if (startDocker) {
579
585
  if (workspaceConfig.docker?.traefik) {
580
- const traefikPath = join2(projectConfig.path, workspaceConfig.docker.traefik);
586
+ const traefikPath = join2(homedir(), ".panopticon", "traefik", "docker-compose.yml");
581
587
  if (existsSync2(traefikPath)) {
582
588
  try {
583
- await execAsync(`docker compose -f "${traefikPath}" up -d`, { cwd: projectConfig.path });
589
+ await execAsync(`docker compose -f "${traefikPath}" up -d`, { cwd: join2(homedir(), ".panopticon", "traefik") });
584
590
  result.steps.push("Started Traefik");
585
591
  } catch (error) {
586
592
  const msg = error?.message || String(error);
@@ -846,4 +852,4 @@ export {
846
852
  removeWorkspace,
847
853
  init_workspace_manager
848
854
  };
849
- //# sourceMappingURL=chunk-KPGVCGST.js.map
855
+ //# sourceMappingURL=chunk-S7EJ2OLR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/skills-merge.ts","../src/lib/workspace-manager.ts"],"sourcesContent":["import {\n existsSync,\n readdirSync,\n mkdirSync,\n readFileSync,\n writeFileSync,\n copyFileSync,\n statSync,\n} from 'fs';\nimport { join, relative, dirname } from 'path';\nimport { SKILLS_DIR, CACHE_AGENTS_DIR, CACHE_RULES_DIR } from './paths.js';\nimport {\n readManifest,\n writeManifest,\n collectSourceFiles,\n hashFile,\n setManifestEntry,\n compareFileToManifest,\n type Manifest,\n} from './manifest.js';\n\nexport interface MergeResult {\n added: string[];\n updated: string[];\n skipped: string[];\n overlayed: string[];\n}\n\n/**\n * Copy all files from a source directory into a target directory,\n * preserving subdirectory structure. Returns the list of relative paths copied.\n */\nfunction copyTree(sourceDir: string, targetDir: string): string[] {\n const copied: string[] = [];\n if (!existsSync(sourceDir)) return copied;\n\n function walk(dir: string): void {\n const entries = readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n walk(fullPath);\n } else if (entry.isFile()) {\n const rel = relative(sourceDir, fullPath);\n const targetPath = join(targetDir, rel);\n mkdirSync(dirname(targetPath), { recursive: true });\n copyFileSync(fullPath, targetPath);\n copied.push(rel);\n }\n }\n }\n\n walk(sourceDir);\n return copied;\n}\n\n/**\n * Merge Panopticon skills, agents, and rules into a workspace using file copies.\n *\n * Flow:\n * 1. Copy from cache (skills, agent-definitions, rules) → workspace/.claude/\n * 2. Write manifest tracking what was placed\n *\n * Project template overlay is handled separately by workspace-manager.ts\n * (processTemplates + createSymlinks → now also copy-based).\n */\nexport function mergeSkillsIntoWorkspace(workspacePath: string): MergeResult {\n const claudeDir = join(workspacePath, '.claude');\n const manifestPath = join(claudeDir, '.panopticon-manifest.json');\n const manifest = readManifest(manifestPath);\n\n const result: MergeResult = {\n added: [],\n updated: [],\n skipped: [],\n overlayed: [],\n };\n\n // Ensure base directories exist\n mkdirSync(join(claudeDir, 'skills'), { recursive: true });\n mkdirSync(join(claudeDir, 'agents'), { recursive: true });\n\n // Sources to copy: category → source cache directory\n const sources: Array<{ category: string; sourceDir: string; targetSubdir: string }> = [\n { category: 'skills', sourceDir: SKILLS_DIR, targetSubdir: 'skills' },\n { category: 'agents', sourceDir: CACHE_AGENTS_DIR, targetSubdir: 'agents' },\n { category: 'rules', sourceDir: CACHE_RULES_DIR, targetSubdir: 'rules' },\n ];\n\n for (const { category, sourceDir, targetSubdir } of sources) {\n if (!existsSync(sourceDir)) continue;\n\n const prefix = targetSubdir ? `${targetSubdir}/` : '';\n const files = collectSourceFiles(sourceDir, '');\n\n for (const file of files) {\n const relativePath = `${prefix}${file.relativePath}`;\n const targetPath = join(claudeDir, relativePath);\n const sourceHash = hashFile(file.absolutePath);\n\n // Check status against manifest\n const status = compareFileToManifest(targetPath, relativePath, manifest);\n\n switch (status.action) {\n case 'new':\n // File doesn't exist at target — copy it\n mkdirSync(dirname(targetPath), { recursive: true });\n copyFileSync(file.absolutePath, targetPath);\n setManifestEntry(manifest, relativePath, sourceHash, 'panopticon');\n result.added.push(relativePath);\n break;\n\n case 'update':\n // File exists and matches manifest — safe to overwrite with latest\n copyFileSync(file.absolutePath, targetPath);\n setManifestEntry(manifest, relativePath, sourceHash, 'panopticon');\n result.updated.push(relativePath);\n break;\n\n case 'modified':\n // User modified the file — skip to preserve their changes\n result.skipped.push(`${relativePath} (modified by user)`);\n break;\n\n case 'user-owned':\n // File exists but wasn't placed by us — never touch\n result.skipped.push(`${relativePath} (user-owned)`);\n break;\n }\n }\n }\n\n // Write updated manifest\n writeManifest(manifestPath, manifest);\n\n return result;\n}\n\n/**\n * Apply project template overlay on top of Panopticon base files in a workspace.\n *\n * This copies files from the project's agent template directory into\n * workspace/.claude/, overwriting Panopticon files where the project\n * provides its own version. Updates the manifest with source=\"project-template\".\n *\n * @param workspacePath - Path to the workspace\n * @param templateDir - Absolute path to the project's agent template directory\n * @param templates - Optional list of specific template files to process (source → target mappings)\n */\nexport function applyProjectTemplateOverlay(\n workspacePath: string,\n templateDir: string,\n templates?: Array<{ source: string; target: string }>,\n): string[] {\n const claudeDir = join(workspacePath, '.claude');\n const manifestPath = join(claudeDir, '.panopticon-manifest.json');\n const manifest = readManifest(manifestPath);\n const overlayed: string[] = [];\n\n if (!existsSync(templateDir)) return overlayed;\n\n if (templates && templates.length > 0) {\n // Process specific template mappings\n for (const { source, target } of templates) {\n const sourcePath = join(templateDir, source);\n if (!existsSync(sourcePath)) continue;\n\n const targetPath = join(workspacePath, target);\n mkdirSync(dirname(targetPath), { recursive: true });\n\n // Read template content and check if it's a template file\n if (source.endsWith('.template')) {\n // Template files are handled by workspace-manager's processTemplates\n // We just track them in the manifest after they're processed\n continue;\n }\n\n copyFileSync(sourcePath, targetPath);\n\n // Track in manifest if it's under .claude/\n if (target.startsWith('.claude/')) {\n const relativePath = target.slice('.claude/'.length);\n const hash = hashFile(targetPath);\n setManifestEntry(manifest, relativePath, hash, 'project-template');\n overlayed.push(relativePath);\n }\n }\n } else {\n // Copy all .claude/ subdirectories from template dir\n const claudeInTemplate = join(templateDir, '.claude');\n if (existsSync(claudeInTemplate)) {\n const copied = copyTree(claudeInTemplate, claudeDir);\n for (const rel of copied) {\n const targetPath = join(claudeDir, rel);\n const hash = hashFile(targetPath);\n setManifestEntry(manifest, rel, hash, 'project-template');\n overlayed.push(rel);\n }\n }\n }\n\n // Write updated manifest\n writeManifest(manifestPath, manifest);\n\n return overlayed;\n}\n\n// ─── Legacy exports (kept for migration, to be removed in future) ───\n\n/**\n * @deprecated No longer needed — skills are copies, not symlinks. Kept for migration.\n */\nexport function cleanupGitignore(gitignorePath: string): {\n cleaned: boolean;\n duplicatesRemoved: number;\n entriesAfter: number;\n} {\n if (!existsSync(gitignorePath)) {\n return { cleaned: false, duplicatesRemoved: 0, entriesAfter: 0 };\n }\n\n const PANOPTICON_HEADER = '# Panopticon-managed symlinks (not committed)';\n let content: string;\n try {\n content = readFileSync(gitignorePath, 'utf-8');\n } catch {\n return { cleaned: false, duplicatesRemoved: 0, entriesAfter: 0 };\n }\n\n // If no Panopticon section, nothing to clean\n if (!content.includes(PANOPTICON_HEADER)) {\n return { cleaned: false, duplicatesRemoved: 0, entriesAfter: 0 };\n }\n\n // Remove the entire Panopticon section (skills are copies now, not symlinks)\n const lines = content.split('\\n');\n const newLines: string[] = [];\n let inPanopticonSection = false;\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed === PANOPTICON_HEADER) {\n inPanopticonSection = true;\n continue;\n }\n if (inPanopticonSection) {\n if (trimmed.startsWith('#') && trimmed !== '') {\n inPanopticonSection = false;\n newLines.push(line);\n } else if (trimmed === '') {\n // Skip blank lines in Panopticon section\n continue;\n }\n // Skip entries in Panopticon section\n continue;\n }\n newLines.push(line);\n }\n\n // Write cleaned file\n try {\n writeFileSync(gitignorePath, newLines.join('\\n'), 'utf-8');\n return { cleaned: true, duplicatesRemoved: 0, entriesAfter: 0 };\n } catch {\n return { cleaned: false, duplicatesRemoved: 0, entriesAfter: 0 };\n }\n}\n\n/**\n * @deprecated No longer needed — skills are copies, not symlinks. Kept for migration.\n */\nexport function cleanupWorkspaceGitignore(workspacePath: string): {\n cleaned: boolean;\n duplicatesRemoved: number;\n entriesAfter: number;\n} {\n const gitignorePath = join(workspacePath, '.claude', 'skills', '.gitignore');\n return cleanupGitignore(gitignorePath);\n}\n","/**\n * Workspace Manager\n *\n * Handles workspace creation and removal for both monorepo and polyrepo projects.\n */\n\nimport { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, copyFileSync, symlinkSync, chmodSync, realpathSync, rmSync, statSync } from 'fs';\nimport { join, dirname, basename, extname, resolve } from 'path';\nimport { homedir } from 'os';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport {\n ProjectConfig,\n WorkspaceConfig,\n TemplatePlaceholders,\n replacePlaceholders,\n getDefaultWorkspaceConfig,\n} from './workspace-config.js';\nimport { addDnsEntry, removeDnsEntry, syncDnsToWindows } from './dns.js';\nimport { addTunnelIngress, removeTunnelIngress } from './tunnel.js';\nimport { createHumeConfig, deleteHumeConfig } from './hume.js';\nimport { mergeSkillsIntoWorkspace } from './skills-merge.js';\n\nconst execAsync = promisify(exec);\n\nexport interface WorkspaceCreateOptions {\n projectConfig: ProjectConfig;\n featureName: string;\n startDocker?: boolean;\n dryRun?: boolean;\n}\n\nexport interface WorkspaceCreateResult {\n success: boolean;\n workspacePath: string;\n errors: string[];\n steps: string[];\n}\n\n/**\n * Create placeholders for template substitution\n */\nfunction createPlaceholders(\n projectConfig: ProjectConfig,\n featureName: string,\n workspacePath: string\n): TemplatePlaceholders {\n const featureFolder = `feature-${featureName}`;\n const domain = projectConfig.workspace?.dns?.domain || 'localhost';\n\n return {\n FEATURE_NAME: featureName,\n FEATURE_FOLDER: featureFolder,\n BRANCH_NAME: `feature/${featureName}`,\n COMPOSE_PROJECT: `${basename(projectConfig.path)}-${featureFolder}`,\n DOMAIN: domain,\n PROJECT_NAME: basename(projectConfig.path),\n PROJECT_PATH: projectConfig.path,\n PROJECTS_DIR: dirname(projectConfig.path),\n WORKSPACE_PATH: workspacePath,\n HOME: homedir(),\n };\n}\n\n/**\n * Sanitize docker-compose files to use platform-agnostic paths\n * Replaces hardcoded /home/username paths with ${HOME}\n */\nfunction sanitizeComposeFile(filePath: string): void {\n if (!existsSync(filePath)) return;\n\n let content = readFileSync(filePath, 'utf-8');\n const originalContent = content;\n\n // Pattern to match hardcoded home paths like /home/username or /Users/username\n // Replace with ${HOME} which docker-compose expands\n const homePatterns = [\n /\\/home\\/[a-zA-Z0-9_-]+\\//g, // Linux: /home/username/\n /\\/Users\\/[a-zA-Z0-9_-]+\\//g, // macOS: /Users/username/\n ];\n\n for (const pattern of homePatterns) {\n content = content.replace(pattern, '${HOME}/');\n }\n\n if (content !== originalContent) {\n writeFileSync(filePath, content, 'utf-8');\n }\n}\n\n/**\n * Validate feature name (alphanumeric and hyphens only)\n */\nfunction validateFeatureName(name: string): boolean {\n return /^[a-zA-Z0-9-]+$/.test(name);\n}\n\n/**\n * Create a git worktree\n * @param repoPath Path to the source git repository\n * @param targetPath Where to create the worktree\n * @param branchName Name of the feature branch to create/checkout\n * @param defaultBranch Base branch to create new branches from (default: 'main')\n */\nasync function createWorktree(\n repoPath: string,\n targetPath: string,\n branchName: string,\n defaultBranch: string = 'main'\n): Promise<{ success: boolean; message: string }> {\n try {\n // Fetch latest from origin\n await execAsync('git fetch origin', { cwd: repoPath });\n\n // Prune stale worktree entries (e.g., from deleted workspaces)\n await execAsync('git worktree prune', { cwd: repoPath });\n\n // Check if branch exists locally or remotely (exact match, not substring)\n const { stdout: localBranches } = await execAsync('git branch --list', { cwd: repoPath });\n const { stdout: remoteBranches } = await execAsync('git branch -r --list', { cwd: repoPath });\n\n const localList = localBranches.split('\\n').map(b => b.replace(/^[*+\\s]+/, '').trim()).filter(Boolean);\n const remoteList = remoteBranches.split('\\n').map(b => b.trim()).filter(Boolean);\n const branchExists =\n localList.includes(branchName) ||\n remoteList.includes(`origin/${branchName}`);\n\n if (branchExists) {\n await execAsync(`git worktree add \"${targetPath}\" \"${branchName}\"`, { cwd: repoPath });\n } else {\n // Create new branch from the configured default branch\n await execAsync(`git worktree add \"${targetPath}\" -b \"${branchName}\" \"${defaultBranch}\"`, { cwd: repoPath });\n }\n\n // Configure beads role so agents don't get \"beads.role not configured\" warnings\n await execAsync('git config beads.role agent', { cwd: targetPath }).catch(() => {});\n\n return { success: true, message: `Created worktree at ${targetPath}` };\n } catch (error) {\n return { success: false, message: `Failed to create worktree: ${error}` };\n }\n}\n\n/**\n * Remove a git worktree\n */\nasync function removeWorktree(\n repoPath: string,\n targetPath: string,\n branchName: string\n): Promise<{ success: boolean; message: string }> {\n try {\n // Remove worktree\n await execAsync(`git worktree remove \"${targetPath}\" --force`, { cwd: repoPath }).catch(() => {});\n\n // Optionally delete the branch\n await execAsync(`git branch -D \"${branchName}\"`, { cwd: repoPath }).catch(() => {});\n\n return { success: true, message: `Removed worktree at ${targetPath}` };\n } catch (error) {\n return { success: false, message: `Failed to remove worktree: ${error}` };\n }\n}\n\n// DNS functions (addWsl2HostEntry, removeWsl2HostEntry, syncDnsToWindows)\n// are now in src/lib/dns.ts and imported above\n\n/**\n * Assign a port from a range\n */\nfunction assignPort(\n portFile: string,\n featureFolder: string,\n range: [number, number]\n): number {\n // Ensure port file exists\n if (!existsSync(portFile)) {\n mkdirSync(dirname(portFile), { recursive: true });\n writeFileSync(portFile, '');\n }\n\n const content = readFileSync(portFile, 'utf-8');\n const lines = content.split('\\n').filter(Boolean);\n\n // Check if already assigned\n for (const line of lines) {\n const [folder, port] = line.split(':');\n if (folder === featureFolder) {\n return parseInt(port, 10);\n }\n }\n\n // Find next available port\n const usedPorts = new Set(lines.map(l => parseInt(l.split(':')[1], 10)));\n for (let port = range[0]; port <= range[1]; port++) {\n if (!usedPorts.has(port)) {\n writeFileSync(portFile, content + (content.endsWith('\\n') ? '' : '\\n') + `${featureFolder}:${port}\\n`);\n return port;\n }\n }\n\n throw new Error(`No available ports in range ${range[0]}-${range[1]}`);\n}\n\n/**\n * Release a port assignment\n */\nfunction releasePort(portFile: string, featureFolder: string): boolean {\n try {\n if (!existsSync(portFile)) return true;\n\n let content = readFileSync(portFile, 'utf-8');\n const lines = content.split('\\n').filter(line => !line.startsWith(`${featureFolder}:`));\n writeFileSync(portFile, lines.join('\\n'));\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Process template files with placeholder replacement\n */\nfunction processTemplates(\n templateDir: string,\n targetDir: string,\n placeholders: TemplatePlaceholders,\n templates?: Array<{ source: string; target: string }>\n): string[] {\n const steps: string[] = [];\n\n if (!existsSync(templateDir)) {\n return steps;\n }\n\n // If specific templates are defined, process those\n if (templates && templates.length > 0) {\n for (const { source, target } of templates) {\n const sourcePath = join(templateDir, source);\n const targetPath = join(targetDir, target);\n\n if (existsSync(sourcePath)) {\n const content = readFileSync(sourcePath, 'utf-8');\n const processed = replacePlaceholders(content, placeholders);\n mkdirSync(dirname(targetPath), { recursive: true });\n writeFileSync(targetPath, processed);\n steps.push(`Processed template: ${source} -> ${target}`);\n }\n }\n } else {\n // Process all .template files\n const files = readdirSync(templateDir);\n for (const file of files) {\n if (file.endsWith('.template')) {\n const sourcePath = join(templateDir, file);\n const targetPath = join(targetDir, file.replace('.template', ''));\n\n const content = readFileSync(sourcePath, 'utf-8');\n const processed = replacePlaceholders(content, placeholders);\n writeFileSync(targetPath, processed);\n // Shell scripts need execute permission\n const targetName = file.replace('.template', '');\n if (targetName === 'dev' || targetName.endsWith('.sh')) {\n chmodSync(targetPath, 0o755);\n }\n steps.push(`Processed template: ${file}`);\n }\n }\n }\n\n return steps;\n}\n\n/**\n * @deprecated Use copyProjectTemplateDirs instead. Kept for non-.claude paths.\n */\nfunction createSymlinks(\n sourceDir: string,\n targetDir: string,\n symlinks: string[]\n): string[] {\n const steps: string[] = [];\n\n for (const symlink of symlinks) {\n const sourcePath = join(sourceDir, symlink);\n const targetPath = join(targetDir, symlink);\n\n if (existsSync(sourcePath)) {\n mkdirSync(dirname(targetPath), { recursive: true });\n try {\n symlinkSync(sourcePath, targetPath);\n steps.push(`Created symlink: ${symlink}`);\n } catch {\n // Symlink might already exist\n }\n }\n }\n\n return steps;\n}\n\n/**\n * Copy project template directories into workspace (replaces symlinks).\n * Recursively copies all files from each source directory.\n */\nconst TEXT_EXTENSIONS = new Set([\n '.md', '.sh', '.yml', '.yaml', '.json', '.ts', '.js', '.env', '.txt', '.toml', '.template',\n]);\n\nfunction copyProjectTemplateDirs(\n sourceDir: string,\n targetDir: string,\n dirs: string[],\n placeholders?: TemplatePlaceholders\n): string[] {\n const steps: string[] = [];\n\n for (const dir of dirs) {\n const sourcePath = join(sourceDir, dir);\n const targetPath = join(targetDir, dir);\n\n if (!existsSync(sourcePath)) continue;\n\n // Recursively copy all files, applying placeholder substitution to text files\n function copyDir(src: string, dest: string): number {\n let count = 0;\n mkdirSync(dest, { recursive: true });\n const entries = readdirSync(src, { withFileTypes: true });\n for (const entry of entries) {\n const srcEntry = join(src, entry.name);\n const destEntry = join(dest, entry.name);\n if (entry.isDirectory()) {\n count += copyDir(srcEntry, destEntry);\n } else if (entry.isFile()) {\n const ext = extname(entry.name).toLowerCase();\n if (placeholders && TEXT_EXTENSIONS.has(ext)) {\n const content = readFileSync(srcEntry, 'utf-8');\n writeFileSync(destEntry, replacePlaceholders(content, placeholders));\n } else {\n copyFileSync(srcEntry, destEntry);\n }\n count++;\n }\n }\n return count;\n }\n\n const count = copyDir(sourcePath, targetPath);\n steps.push(`Copied ${count} files from project template: ${dir}`);\n }\n\n return steps;\n}\n\n/**\n * Create a workspace\n */\nexport async function createWorkspace(options: WorkspaceCreateOptions): Promise<WorkspaceCreateResult> {\n const { projectConfig, featureName, startDocker, dryRun } = options;\n const result: WorkspaceCreateResult = {\n success: true,\n workspacePath: '',\n errors: [],\n steps: [],\n };\n\n // Validate feature name\n if (!validateFeatureName(featureName)) {\n result.success = false;\n result.errors.push('Invalid feature name. Use alphanumeric and hyphens only.');\n return result;\n }\n\n // Reject 'main' as feature name\n if (featureName === 'main') {\n result.success = false;\n result.errors.push('Cannot create workspace for \"main\". Use base repos directly.');\n return result;\n }\n\n const workspaceConfig = projectConfig.workspace || getDefaultWorkspaceConfig();\n const workspacesDir = join(projectConfig.path, workspaceConfig.workspaces_dir || 'workspaces');\n const featureFolder = `feature-${featureName}`;\n const workspacePath = join(workspacesDir, featureFolder);\n result.workspacePath = workspacePath;\n\n // Check if workspace already exists\n if (existsSync(workspacePath)) {\n result.success = false;\n result.errors.push(`Workspace already exists at ${workspacePath}`);\n return result;\n }\n\n if (dryRun) {\n result.steps.push('[DRY RUN] Would create workspace at: ' + workspacePath);\n return result;\n }\n\n // Create placeholders\n const placeholders = createPlaceholders(projectConfig, featureName, workspacePath);\n\n // Create workspace directory\n mkdirSync(workspacePath, { recursive: true });\n result.steps.push('Created workspace directory');\n\n // Handle polyrepo vs monorepo\n if (workspaceConfig.type === 'polyrepo' && workspaceConfig.repos) {\n // Create worktrees for each repo\n for (const repo of workspaceConfig.repos) {\n // Resolve symlinks to get the actual git repository path\n // (e.g., myn/frontend -> ../frontend needs to resolve to actual path)\n const rawRepoPath = join(projectConfig.path, repo.path);\n const repoPath = existsSync(rawRepoPath) ? realpathSync(rawRepoPath) : rawRepoPath;\n const targetPath = join(workspacePath, repo.name);\n const branchPrefix = repo.branch_prefix || 'feature/';\n const branchName = `${branchPrefix}${featureName}`;\n // Per-repo default_branch overrides workspace-level, falls back to 'main'\n const defaultBranch = repo.default_branch || workspaceConfig.default_branch || 'main';\n\n const worktreeResult = await createWorktree(repoPath, targetPath, branchName, defaultBranch);\n if (worktreeResult.success) {\n result.steps.push(`Created worktree for ${repo.name}: ${branchName} (from ${defaultBranch})`);\n } else {\n result.errors.push(`${repo.name}: ${worktreeResult.message}`);\n result.success = false; // Fail the entire workspace creation if any worktree fails\n }\n }\n } else {\n // Monorepo: create single worktree\n const branchName = `feature/${featureName}`;\n const defaultBranch = workspaceConfig.default_branch || 'main';\n const worktreeResult = await createWorktree(projectConfig.path, workspacePath, branchName, defaultBranch);\n if (worktreeResult.success) {\n result.steps.push(`Created worktree: ${branchName} (from ${defaultBranch})`);\n } else {\n result.errors.push(worktreeResult.message);\n result.success = false; // Fail the entire workspace creation if worktree fails\n }\n }\n\n // Remove stale .planning/ directory inherited from main branch.\n // This contains STATE.md and other planning artifacts from a PREVIOUS issue.\n // If left in place, the new agent reads it and works on the wrong issue.\n // SAFETY: resolve() to absolute path and verify it's under a known workspace prefix\n // to prevent path traversal from ever reaching rmSync.\n const resolvedWorkspace = resolve(workspacePath);\n const resolvedPlanning = resolve(resolvedWorkspace, '.planning');\n const isUnderWorkspacesDir = resolvedWorkspace.match(/\\/workspaces\\/feature-[a-z0-9-]+$/);\n if (\n isUnderWorkspacesDir &&\n resolvedPlanning === join(resolvedWorkspace, '.planning') &&\n existsSync(join(resolvedWorkspace, '.git')) &&\n existsSync(resolvedPlanning)\n ) {\n rmSync(resolvedPlanning, { recursive: true, force: true });\n result.steps.push('Removed stale .planning/ directory from previous issue');\n }\n\n // Sanitize any docker-compose files in the workspace to use platform-agnostic paths\n // This handles files inherited from worktrees that may have hardcoded home paths\n const devcontainerDir = join(workspacePath, '.devcontainer');\n if (existsSync(devcontainerDir)) {\n const composeFiles = readdirSync(devcontainerDir)\n .filter(f => f.includes('compose') && (f.endsWith('.yml') || f.endsWith('.yaml')));\n for (const composeFile of composeFiles) {\n sanitizeComposeFile(join(devcontainerDir, composeFile));\n }\n if (composeFiles.length > 0) {\n result.steps.push(`Sanitized ${composeFiles.length} compose file(s) for platform compatibility`);\n }\n }\n\n // Setup TLDR code analysis for workspace (after worktree creation to ensure directory is ready)\n try {\n // Check if python3 is available\n await execAsync('python3 --version');\n const venvPath = join(workspacePath, '.venv');\n const tldrBin = join(venvPath, 'bin', 'tldr');\n\n // Check if main branch already has a working venv with llm-tldr\n const mainVenvTldr = join(projectConfig.path, '.venv', 'bin', 'tldr');\n const mainVenvExists = existsSync(mainVenvTldr);\n\n if (mainVenvExists) {\n // Copy the entire venv from main — faster than pip install (seconds vs 30s+)\n await execAsync(`cp -a \"${join(projectConfig.path, '.venv')}\" \"${venvPath}\"`);\n result.steps.push('Copied Python venv from main branch');\n } else {\n // Create fresh venv and install llm-tldr\n await execAsync(`python3 -m venv \"${venvPath}\"`, { cwd: workspacePath });\n const pipPath = join(venvPath, 'bin', 'pip');\n await execAsync(`\"${pipPath}\" install llm-tldr`, { cwd: workspacePath, timeout: 120000 });\n result.steps.push('Created Python venv and installed llm-tldr');\n\n // Apply .tsx/.jsx support patch (upstream llm-tldr only checks .ts)\n const patchScript = join(projectConfig.path, 'scripts', 'patches', 'llm-tldr-tsx-support.py');\n if (existsSync(patchScript)) {\n await execAsync(`python3 \"${patchScript}\" \"${venvPath}\"`);\n result.steps.push('Applied llm-tldr .tsx/.jsx patch');\n }\n }\n\n // Verify tldr binary exists after setup\n if (!existsSync(tldrBin)) {\n result.steps.push('TLDR setup incomplete: tldr binary not found after venv creation');\n } else {\n // Copy .tldr index from main branch if it exists\n const mainTldrDir = join(projectConfig.path, '.tldr');\n const workspaceTldrDir = join(workspacePath, '.tldr');\n\n if (existsSync(mainTldrDir)) {\n await execAsync(`cp -r \"${mainTldrDir}\" \"${workspaceTldrDir}\"`);\n result.steps.push('Copied TLDR index from main branch');\n }\n\n // Start TLDR daemon for this workspace\n const { getTldrDaemonService } = await import('./tldr-daemon.js');\n const tldrService = getTldrDaemonService(workspacePath, venvPath);\n await tldrService.start(true);\n result.steps.push('Started TLDR daemon');\n\n // Warm the index in the background — ensures workspaces always have a working index\n // even when the main branch cache was empty (nothing to copy)\n try {\n await tldrService.warm(true); // background=true: non-blocking\n result.steps.push('TLDR index warm initiated (background)');\n } catch {\n // Non-fatal — daemon may not support warm yet\n }\n }\n } catch (error: any) {\n // TLDR setup is optional — don't fail workspace creation, but log clearly\n if (error.message?.includes('python3')) {\n result.steps.push('Skipped TLDR setup (python3 not available)');\n } else {\n console.warn(`⚠ TLDR setup failed: ${error.message}`);\n result.steps.push(`TLDR setup failed: ${error.message}`);\n }\n }\n\n // Configure DNS\n if (workspaceConfig.dns) {\n const dnsMethod = workspaceConfig.dns.sync_method || 'wsl2hosts';\n for (const entryPattern of workspaceConfig.dns.entries) {\n const hostname = replacePlaceholders(entryPattern, placeholders);\n\n if (addDnsEntry(dnsMethod, hostname)) {\n result.steps.push(`Added DNS entry: ${hostname} (${dnsMethod})`);\n }\n }\n\n // Sync to Windows if using wsl2hosts method\n if (dnsMethod === 'wsl2hosts') {\n const synced = await syncDnsToWindows();\n if (synced) {\n result.steps.push('Synced DNS to Windows hosts file');\n }\n }\n }\n\n // Assign ports\n if (workspaceConfig.ports) {\n for (const [portName, portConfig] of Object.entries(workspaceConfig.ports)) {\n const portFile = join(projectConfig.path, `.${portName}-ports`);\n try {\n const port = assignPort(portFile, featureFolder, portConfig.range);\n result.steps.push(`Assigned ${portName} port: ${port}`);\n // Add to placeholders for use in templates\n (placeholders as any)[`${portName.toUpperCase()}_PORT`] = String(port);\n } catch (error) {\n result.errors.push(`Failed to assign ${portName} port: ${error}`);\n }\n }\n }\n\n // Install base Panopticon skills/agents/rules from cache\n const mergeResult = mergeSkillsIntoWorkspace(workspacePath);\n const mergeTotal = mergeResult.added.length + mergeResult.updated.length;\n if (mergeTotal > 0) {\n result.steps.push(`Installed ${mergeTotal} Panopticon files (${mergeResult.added.length} new, ${mergeResult.updated.length} updated)`);\n }\n\n // Process agent templates (project template overlay — wins over Panopticon base)\n if (workspaceConfig.agent?.template_dir) {\n const templateDir = join(projectConfig.path, workspaceConfig.agent.template_dir);\n\n // Process template files\n const templateSteps = processTemplates(\n templateDir,\n workspacePath,\n placeholders,\n workspaceConfig.agent.templates\n );\n result.steps.push(...templateSteps);\n\n // Copy .claude/ directories from project template (copy_dirs replaces legacy symlinks)\n const dirsToSync = workspaceConfig.agent.copy_dirs || workspaceConfig.agent.symlinks;\n if (dirsToSync) {\n const copySteps = copyProjectTemplateDirs(templateDir, workspacePath, dirsToSync, placeholders);\n result.steps.push(...copySteps);\n }\n }\n\n // Generate .env file\n if (workspaceConfig.env?.template) {\n const envContent = replacePlaceholders(workspaceConfig.env.template, placeholders);\n writeFileSync(join(workspacePath, '.env'), envContent);\n result.steps.push('Created .env file');\n }\n\n // Process Docker compose templates\n if (workspaceConfig.docker?.compose_template) {\n const templateDir = join(projectConfig.path, workspaceConfig.docker.compose_template);\n const devcontainerDir = join(workspacePath, '.devcontainer');\n mkdirSync(devcontainerDir, { recursive: true });\n\n const templateSteps = processTemplates(templateDir, devcontainerDir, placeholders);\n result.steps.push(...templateSteps);\n\n // Copy non-template files (like Dockerfile)\n if (existsSync(templateDir)) {\n const files = readdirSync(templateDir);\n for (const file of files) {\n if (!file.endsWith('.template')) {\n const sourcePath = join(templateDir, file);\n const targetPath = join(devcontainerDir, file);\n copyFileSync(sourcePath, targetPath);\n }\n }\n }\n\n // Sanitize docker-compose files to use platform-agnostic paths\n // This fixes hardcoded /home/username or /Users/username paths\n const composeFiles = readdirSync(devcontainerDir)\n .filter(f => f.includes('compose') && (f.endsWith('.yml') || f.endsWith('.yaml')));\n for (const composeFile of composeFiles) {\n sanitizeComposeFile(join(devcontainerDir, composeFile));\n }\n if (composeFiles.length > 0) {\n result.steps.push(`Sanitized ${composeFiles.length} compose file(s) for platform compatibility`);\n }\n\n // Create ./dev symlink at workspace root pointing to .devcontainer/dev\n // Symlink keeps changes in sync - editing ./dev updates .devcontainer/dev\n const devScriptInContainer = join(devcontainerDir, 'dev');\n const devScriptAtRoot = join(workspacePath, 'dev');\n if (existsSync(devScriptInContainer) && !existsSync(devScriptAtRoot)) {\n try {\n symlinkSync('.devcontainer/dev', devScriptAtRoot);\n chmodSync(devScriptInContainer, 0o755); // Make executable\n result.steps.push('Created ./dev symlink');\n } catch (error) {\n result.errors.push(`Failed to create ./dev symlink: ${error}`);\n }\n }\n }\n\n // Note: Beads initialization is handled by the calling command (workspace.ts)\n // With beads v0.47.1+, worktrees use shared database with labels for isolation\n // The workspace.ts command creates a bead with workspace:issue-id label\n\n // Set up Cloudflare tunnel for external access (before Docker so containers can use tunnel URLs)\n if (workspaceConfig.tunnel) {\n const tunnelResult = await addTunnelIngress(workspaceConfig.tunnel, placeholders);\n result.steps.push(...tunnelResult.steps);\n if (!tunnelResult.success) {\n result.errors.push('Tunnel setup had failures (see steps for details)');\n }\n }\n\n // Create Hume EVI config and write env file for Docker (before Docker so containers pick up the config ID)\n if (workspaceConfig.hume) {\n const humeResult = await createHumeConfig(workspaceConfig.hume, placeholders);\n result.steps.push(...humeResult.steps);\n if (humeResult.configId) {\n writeFileSync(\n join(workspacePath, '.hume-config'),\n `HUME_CONFIG_ID=${humeResult.configId}\\nVITE_HUME_CONFIG_ID=${humeResult.configId}\\n`,\n );\n result.steps.push('Wrote .hume-config with Hume EVI config ID');\n }\n if (!humeResult.success) {\n result.errors.push('Hume EVI config setup had failures (see steps for details)');\n }\n }\n\n // Start Docker containers if requested\n if (startDocker) {\n // Check for Traefik\n if (workspaceConfig.docker?.traefik) {\n // Always use the installed Traefik location (~/.panopticon/traefik/), not the\n // template source in projects.yaml. The template is copied to ~/.panopticon/traefik/\n // during `pan install`, and the installed copy has the correct volume mounts\n // (dynamic configs, certs) relative to ~/.panopticon/traefik/.\n const traefikPath = join(homedir(), '.panopticon', 'traefik', 'docker-compose.yml');\n if (existsSync(traefikPath)) {\n try {\n await execAsync(`docker compose -f \"${traefikPath}\" up -d`, { cwd: join(homedir(), '.panopticon', 'traefik') });\n result.steps.push('Started Traefik');\n } catch (error: any) {\n const msg = error?.message || String(error);\n if (msg.includes('port is already allocated') || msg.includes('address already in use')) {\n // Traefik (or another reverse proxy) is already running — not an error\n result.steps.push('Traefik already running (port in use)');\n } else {\n result.errors.push(`Failed to start Traefik: ${error}`);\n }\n }\n }\n }\n\n // Start workspace containers\n const composeLocations = [\n join(workspacePath, 'docker-compose.yml'),\n join(workspacePath, 'docker-compose.yaml'),\n join(workspacePath, '.devcontainer', 'docker-compose.yml'),\n join(workspacePath, '.devcontainer', 'docker-compose.devcontainer.yml'),\n ];\n\n for (const composePath of composeLocations) {\n if (existsSync(composePath)) {\n try {\n // Don't pass -p: the compose file's `name:` field is the authority.\n // Passing -p with a different value creates a second Docker project\n // on container restart, splitting services onto separate networks.\n await execAsync(`docker compose -f \"${composePath}\" up -d --build`, { cwd: dirname(composePath), timeout: 300000 });\n result.steps.push(`Started containers from ${basename(composePath)}`);\n } catch (error) {\n result.errors.push(`Failed to start containers: ${error}`);\n }\n break;\n }\n }\n }\n\n // Pre-trust workspace directory in Claude Code so agents don't get the trust prompt\n try {\n preTrustDirectory(workspacePath);\n result.steps.push('Pre-trusted workspace in Claude Code');\n } catch {\n // Non-fatal — agent can still work, user will just see trust prompt\n }\n\n result.success = result.errors.length === 0;\n return result;\n}\n\n/**\n * Pre-register a directory as trusted in Claude Code's ~/.claude.json.\n * This prevents the \"Quick safety check: Is this a project you created or one you trust?\" prompt\n * when agents are spawned in dynamically-created workspace directories.\n */\nexport function preTrustDirectory(dirPath: string): void {\n const claudeJsonPath = join(homedir(), '.claude.json');\n if (!existsSync(claudeJsonPath)) return;\n\n const data = JSON.parse(readFileSync(claudeJsonPath, 'utf8'));\n if (!data.projects) data.projects = {};\n\n // Only add if not already present\n if (data.projects[dirPath]) {\n if (!data.projects[dirPath].hasTrustDialogAccepted) {\n data.projects[dirPath].hasTrustDialogAccepted = true;\n writeFileSync(claudeJsonPath, JSON.stringify(data, null, 2), 'utf8');\n }\n return;\n }\n\n data.projects[dirPath] = {\n allowedTools: [],\n mcpContextUris: [],\n mcpServers: {},\n enabledMcpjsonServers: [],\n disabledMcpjsonServers: [],\n hasTrustDialogAccepted: true,\n projectOnboardingSeenCount: 0,\n hasClaudeMdExternalIncludesApproved: false,\n hasClaudeMdExternalIncludesWarningShown: false,\n };\n\n writeFileSync(claudeJsonPath, JSON.stringify(data, null, 2), 'utf8');\n}\n\nexport interface WorkspaceRemoveOptions {\n projectConfig: ProjectConfig;\n featureName: string;\n dryRun?: boolean;\n}\n\nexport interface WorkspaceRemoveResult {\n success: boolean;\n errors: string[];\n steps: string[];\n}\n\n/**\n * Result of Docker container cleanup for a workspace.\n */\nexport interface DockerCleanupResult {\n /** Whether compose files were found (containers may or may not have been running) */\n containersFound: boolean;\n /** Human-readable log of cleanup steps taken */\n steps: string[];\n}\n\n/**\n * Stop Docker containers and clean up Docker-created files for a workspace.\n *\n * Extracted as a standalone function so it can be used by:\n * - removeWorkspace() during normal workspace removal\n * - deep-wipe endpoint for complete issue cleanup\n * - workspace-migrate for pre-migration cleanup\n *\n * Failures are logged but never thrown — callers should not fail if Docker is unavailable.\n */\nexport async function stopWorkspaceDocker(\n workspacePath: string,\n projectName: string,\n featureName: string,\n): Promise<DockerCleanupResult> {\n const result: DockerCleanupResult = {\n containersFound: false,\n steps: [],\n };\n\n // Find all compose files in devcontainer directory (some projects use multiple)\n const devcontainerDir = join(workspacePath, '.devcontainer');\n const composeFiles: string[] = [];\n\n if (existsSync(devcontainerDir)) {\n const possibleFiles = [\n 'docker-compose.devcontainer.yml',\n 'docker-compose.yml',\n 'compose.yml',\n 'compose.infra.yml',\n 'compose.override.yml',\n ];\n for (const file of possibleFiles) {\n const fullPath = join(devcontainerDir, file);\n if (existsSync(fullPath)) {\n composeFiles.push(fullPath);\n }\n }\n }\n\n // Fallback: check for compose file in workspace root\n if (composeFiles.length === 0) {\n const rootCompose = join(workspacePath, 'docker-compose.yml');\n if (existsSync(rootCompose)) {\n composeFiles.push(rootCompose);\n }\n }\n\n if (composeFiles.length > 0) {\n result.containersFound = true;\n try {\n const fileFlags = composeFiles.map(f => `-f \"${f}\"`).join(' ');\n const cwd = existsSync(devcontainerDir) ? devcontainerDir : workspacePath;\n\n // Derive compose project name from the dev script (same logic as dashboard)\n // or fall back to \"{projectName}-feature-{featureName}\" convention.\n let composeProjectName = `${projectName}-feature-${featureName}`;\n const devScriptPaths = [\n join(workspacePath, '.devcontainer', 'dev'),\n join(workspacePath, 'dev'),\n ];\n for (const devPath of devScriptPaths) {\n try {\n if (existsSync(devPath)) {\n const content = readFileSync(devPath, 'utf-8');\n const match = content.match(/COMPOSE_PROJECT_NAME=\"([^$\"]*)\\$\\{FEATURE_FOLDER\\}\"/);\n if (match) {\n composeProjectName = `${match[1]}feature-${featureName}`;\n break;\n }\n const literalMatch = content.match(/COMPOSE_PROJECT_NAME=\"([^\"]+)\"/);\n if (literalMatch) {\n composeProjectName = literalMatch[1];\n break;\n }\n }\n } catch {\n // Fall through to default\n }\n }\n\n await execAsync(`docker compose ${fileFlags} -p \"${composeProjectName}\" down -v --remove-orphans`, {\n cwd,\n timeout: 60000,\n });\n result.steps.push(`Stopped Docker containers (${composeFiles.length} compose files)`);\n } catch (error: any) {\n // Log but don't fail — containers might not be running\n result.steps.push(`Docker cleanup attempted (${error.message?.split('\\n')[0] || 'containers may not be running'})`);\n }\n }\n\n // Clean up Docker-created files (root-owned in containers)\n try {\n await execAsync(\n `docker run --rm -v \"${workspacePath}:/workspace\" alpine sh -c \"find /workspace -user root -delete 2>&1 | tail -100 || true\"`,\n { timeout: 30000, maxBuffer: 10 * 1024 * 1024 }\n );\n result.steps.push('Cleaned up Docker-created files');\n } catch {\n // Alpine container might not be available\n }\n\n return result;\n}\n\n/**\n * Remove a workspace\n */\nexport async function removeWorkspace(options: WorkspaceRemoveOptions): Promise<WorkspaceRemoveResult> {\n const { projectConfig, featureName, dryRun } = options;\n const result: WorkspaceRemoveResult = {\n success: true,\n errors: [],\n steps: [],\n };\n\n const workspaceConfig = projectConfig.workspace || getDefaultWorkspaceConfig();\n const workspacesDir = join(projectConfig.path, workspaceConfig.workspaces_dir || 'workspaces');\n const featureFolder = `feature-${featureName}`;\n const workspacePath = join(workspacesDir, featureFolder);\n\n if (!existsSync(workspacePath)) {\n result.success = false;\n result.errors.push(`Workspace not found at ${workspacePath}`);\n return result;\n }\n\n if (dryRun) {\n result.steps.push('[DRY RUN] Would remove workspace at: ' + workspacePath);\n return result;\n }\n\n // Stop TLDR daemon for workspace (if it exists)\n const venvPath = join(workspacePath, '.venv');\n if (existsSync(venvPath)) {\n try {\n const { getTldrDaemonService } = await import('./tldr-daemon.js');\n const tldrService = getTldrDaemonService(workspacePath, venvPath);\n await tldrService.stop();\n result.steps.push('Stopped TLDR daemon');\n } catch (error: any) {\n // Non-fatal - daemon may not be running\n console.warn(`⚠ Failed to stop TLDR daemon: ${error?.message}`);\n }\n }\n\n // Stop Docker containers and clean up Docker-created files\n const dockerResult = await stopWorkspaceDocker(workspacePath, projectConfig.name || 'workspace', featureName);\n result.steps.push(...dockerResult.steps);\n\n // Remove worktrees\n if (workspaceConfig.type === 'polyrepo' && workspaceConfig.repos) {\n for (const repo of workspaceConfig.repos) {\n const repoPath = join(projectConfig.path, repo.path);\n const targetPath = join(workspacePath, repo.name);\n const branchPrefix = repo.branch_prefix || 'feature/';\n const branchName = `${branchPrefix}${featureName}`;\n\n const worktreeResult = await removeWorktree(repoPath, targetPath, branchName);\n if (worktreeResult.success) {\n result.steps.push(`Removed worktree for ${repo.name}`);\n } else {\n result.errors.push(worktreeResult.message);\n }\n }\n } else {\n // Monorepo: remove single worktree\n const branchName = `feature/${featureName}`;\n const worktreeResult = await removeWorktree(projectConfig.path, workspacePath, branchName);\n if (worktreeResult.success) {\n result.steps.push('Removed worktree');\n } else {\n result.errors.push(worktreeResult.message);\n }\n }\n\n // Remove DNS entries\n if (workspaceConfig.dns) {\n const placeholders = createPlaceholders(projectConfig, featureName, workspacePath);\n\n const dnsMethod = workspaceConfig.dns.sync_method || 'wsl2hosts';\n for (const entryPattern of workspaceConfig.dns.entries) {\n const hostname = replacePlaceholders(entryPattern, placeholders);\n if (removeDnsEntry(dnsMethod, hostname)) {\n result.steps.push(`Removed DNS entry: ${hostname}`);\n }\n }\n }\n\n // Remove Cloudflare tunnel entries\n if (workspaceConfig.tunnel) {\n const placeholders = createPlaceholders(projectConfig, featureName, workspacePath);\n const tunnelResult = await removeTunnelIngress(workspaceConfig.tunnel, placeholders);\n result.steps.push(...tunnelResult.steps);\n }\n\n // Remove Hume EVI config\n if (workspaceConfig.hume) {\n const placeholders = createPlaceholders(projectConfig, featureName, workspacePath);\n const humeResult = await deleteHumeConfig(workspaceConfig.hume, placeholders);\n result.steps.push(...humeResult.steps);\n }\n\n // Release ports\n if (workspaceConfig.ports) {\n for (const [portName] of Object.entries(workspaceConfig.ports)) {\n const portFile = join(projectConfig.path, `.${portName}-ports`);\n if (releasePort(portFile, featureFolder)) {\n result.steps.push(`Released ${portName} port`);\n }\n }\n }\n\n // Remove workspace directory\n try {\n await execAsync(`rm -rf \"${workspacePath}\"`, { maxBuffer: 10 * 1024 * 1024 });\n result.steps.push('Removed workspace directory');\n } catch (error) {\n result.errors.push(`Failed to remove workspace directory: ${error}`);\n }\n\n result.success = result.errors.length === 0;\n return result;\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","writeFileSync","readFileSync","readdirSync","copyFileSync","join","dirname","copyDir","count","devcontainerDir"]}
@@ -33,4 +33,4 @@ export {
33
33
  stepFailed,
34
34
  getLinearApiKey
35
35
  };
36
- //# sourceMappingURL=chunk-R4KPLLRB.js.map
36
+ //# sourceMappingURL=chunk-SFX3BG6N.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/lifecycle/types.ts"],"sourcesContent":["/**\n * Shared types for lifecycle operations.\n *\n * Every atomic operation returns a StepResult. Workflows compose\n * multiple operations and return a WorkflowResult.\n */\n\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\n\nexport interface StepResult {\n step: string;\n success: boolean;\n skipped: boolean; // true if operation was a no-op (idempotent)\n error?: string;\n details?: string[]; // human-readable log of what was done\n}\n\nexport interface WorkflowResult {\n workflow: 'approve' | 'close' | 'close-out' | 'deep-wipe';\n issueId: string;\n success: boolean; // true only if ALL non-skipped steps succeeded\n steps: StepResult[];\n duration: number; // ms\n}\n\n/** Context shared across lifecycle operations */\nexport interface LifecycleContext {\n issueId: string;\n projectPath: string;\n /** Project name (for Docker compose project naming + placeholders) */\n projectName?: string;\n /** GitHub issue metadata (populated for PAN- issues) */\n github?: {\n owner: string;\n repo: string;\n number: number;\n };\n /** Rally configuration (populated for Rally-tracked issues) */\n rally?: {\n apiKey: string;\n server?: string;\n workspace?: string;\n project?: string;\n };\n}\n\n/** Options for teardown-workspace */\nexport interface TeardownOptions {\n /** Delete feature branches (local + remote). Default: false */\n deleteBranches?: boolean;\n /** Skip Docker container cleanup. Default: false */\n skipDocker?: boolean;\n /** Delete workspace directory (worktree + files). Default: true */\n deleteWorkspace?: boolean;\n /** Clear beads for this issue from project root. Default: false.\n * Only set to true for destructive wipe — normal completion should preserve beads. */\n clearBeads?: boolean;\n /** Project-specific workspace config for tunnel/Hume cleanup */\n workspaceConfig?: {\n tunnel?: any;\n hume?: any;\n dns?: { domain?: string };\n };\n /** Project name (for Docker compose project naming + placeholders) */\n projectName?: string;\n}\n\n/** Options for archive-planning */\nexport interface ArchiveOptions {\n /** Push git commits to remote after archiving. Default: true */\n pushToRemote?: boolean;\n}\n\n/** Options for the approve workflow */\nexport interface ApproveOptions {\n /** Skip the merge step (e.g. if already merged). Default: false */\n skipMerge?: boolean;\n /** Skip beads compaction. Default: false */\n skipBeadsCompaction?: boolean;\n}\n\n/** Options for the deep-wipe workflow */\nexport interface DeepWipeOptions {\n /** Delete workspace directory. Default: true */\n deleteWorkspace?: boolean;\n /** Delete git branches (local + remote). Default: true */\n deleteBranches?: boolean;\n /** Reset issue to backlog/open state. Default: true */\n resetIssue?: boolean;\n /** Project-specific workspace config for tunnel/Hume cleanup */\n workspaceConfig?: {\n tunnel?: any;\n hume?: any;\n dns?: { domain?: string };\n };\n /** Project name (for Docker compose project naming + placeholders) */\n projectName?: string;\n}\n\n/** Helper to create a successful step result */\nexport function stepOk(step: string, details?: string[]): StepResult {\n return { step, success: true, skipped: false, details };\n}\n\n/** Helper to create a skipped step result */\nexport function stepSkipped(step: string, details?: string[]): StepResult {\n return { step, success: true, skipped: true, details };\n}\n\n/** Helper to create a failed step result */\nexport function stepFailed(step: string, error: string, details?: string[]): StepResult {\n return { step, success: false, skipped: false, error, details };\n}\n\n/**\n * Get LINEAR_API_KEY from environment or .panopticon.env.\n * Shared across lifecycle modules.\n */\nexport function getLinearApiKey(): string | null {\n if (process.env.LINEAR_API_KEY) return process.env.LINEAR_API_KEY;\n const envFile = join(homedir(), '.panopticon.env');\n if (existsSync(envFile)) {\n const content = readFileSync(envFile, 'utf-8');\n const match = content.match(/LINEAR_API_KEY=(.+)/);\n if (match) return match[1].trim();\n }\n return null;\n}\n"],"mappings":";;;;;AAAA;AAOA,SAAS,YAAY,oBAAoB;AACzC,SAAS,YAAY;AACrB,SAAS,eAAe;AA6FjB,SAAS,OAAO,MAAc,SAAgC;AACnE,SAAO,EAAE,MAAM,SAAS,MAAM,SAAS,OAAO,QAAQ;AACxD;AAGO,SAAS,YAAY,MAAc,SAAgC;AACxE,SAAO,EAAE,MAAM,SAAS,MAAM,SAAS,MAAM,QAAQ;AACvD;AAGO,SAAS,WAAW,MAAc,OAAe,SAAgC;AACtF,SAAO,EAAE,MAAM,SAAS,OAAO,SAAS,OAAO,OAAO,QAAQ;AAChE;AAMO,SAAS,kBAAiC;AAC/C,MAAI,QAAQ,IAAI,eAAgB,QAAO,QAAQ,IAAI;AACnD,QAAM,UAAU,KAAK,QAAQ,GAAG,iBAAiB;AACjD,MAAI,WAAW,OAAO,GAAG;AACvB,UAAM,UAAU,aAAa,SAAS,OAAO;AAC7C,UAAM,QAAQ,QAAQ,MAAM,qBAAqB;AACjD,QAAI,MAAO,QAAO,MAAM,CAAC,EAAE,KAAK;AAAA,EAClC;AACA,SAAO;AACT;","names":[]}
@@ -0,0 +1,9 @@
1
+ import {
2
+ cleanPlanningArtifacts
3
+ } from "./chunk-F4XS2FQN.js";
4
+ import "./chunk-SFX3BG6N.js";
5
+ import "./chunk-ZHC57RCV.js";
6
+ export {
7
+ cleanPlanningArtifacts
8
+ };
9
+ //# sourceMappingURL=clean-planning-V4SSVU26.js.map