@iloom/cli 0.13.1 → 0.13.2

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 (148) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +1 -1
  3. package/dist/CLAUDE.md +66 -0
  4. package/dist/{ClaudeContextManager-ZH6LEA5I.js → ClaudeContextManager-KJ4VEA2F.js} +5 -5
  5. package/dist/{ClaudeService-YR66WXZN.js → ClaudeService-WTJO4UW6.js} +4 -4
  6. package/dist/{IssueTrackerFactory-O2ZBA666.js → IssueTrackerFactory-UEJALI4X.js} +3 -3
  7. package/dist/{LoomLauncher-V54ENBEF.js → LoomLauncher-KG2VBNQA.js} +5 -5
  8. package/dist/{PromptTemplateManager-4RFELNYY.js → PromptTemplateManager-QIUVJP6S.js} +2 -2
  9. package/dist/README.md +1 -1
  10. package/dist/{SettingsManager-SLSYEYDZ.js → SettingsManager-PVHBSCMI.js} +2 -2
  11. package/dist/agents/CLAUDE.md +68 -0
  12. package/dist/agents/iloom-code-reviewer.md +1 -0
  13. package/dist/agents/iloom-wave-verifier.md +1 -36
  14. package/dist/{build-ZTGWDHWU.js → build-2FXDYEZQ.js} +6 -6
  15. package/dist/{chunk-LNY2Y32V.js → chunk-2WRD6Y5E.js} +2 -2
  16. package/dist/{chunk-WYDLOQYO.js → chunk-32D4CWWH.js} +2 -2
  17. package/dist/{chunk-KGOBNC5A.js → chunk-5W44AI63.js} +3 -3
  18. package/dist/{chunk-PPQ5LV7U.js → chunk-D6FU4DLN.js} +2 -2
  19. package/dist/{chunk-PS6K2AOV.js → chunk-DMNI225H.js} +4 -4
  20. package/dist/{chunk-55NTREIU.js → chunk-DYLOITSO.js} +55 -35
  21. package/dist/chunk-DYLOITSO.js.map +1 -0
  22. package/dist/{chunk-T4KFKKEB.js → chunk-H4TSDALC.js} +6 -6
  23. package/dist/{chunk-J5JOJPK3.js → chunk-L3P3YJCE.js} +2 -2
  24. package/dist/{chunk-MRPIDNZU.js → chunk-LDE6VNG5.js} +1 -1
  25. package/dist/chunk-LDE6VNG5.js.map +1 -0
  26. package/dist/{chunk-F5NKWLMQ.js → chunk-MNPKEWBQ.js} +9 -5
  27. package/dist/chunk-MNPKEWBQ.js.map +1 -0
  28. package/dist/{chunk-EHAITKLS.js → chunk-MPHSR6GA.js} +3 -3
  29. package/dist/{chunk-HWDQRW3O.js → chunk-OHX3PSAY.js} +2 -2
  30. package/dist/{chunk-C2BVNJW5.js → chunk-OIJNBFMP.js} +2 -2
  31. package/dist/{chunk-TJDKGKQV.js → chunk-OMV47LLA.js} +2 -2
  32. package/dist/{chunk-P5MNWBLH.js → chunk-OVW26FHW.js} +19 -7
  33. package/dist/chunk-OVW26FHW.js.map +1 -0
  34. package/dist/{chunk-KCAWSZUO.js → chunk-R2EFSRKR.js} +10 -10
  35. package/dist/{chunk-QNRXRSKC.js → chunk-RP6MHV24.js} +9 -9
  36. package/dist/chunk-RP6MHV24.js.map +1 -0
  37. package/dist/{chunk-UXBVDD7U.js → chunk-U2OPXZ6E.js} +282 -44
  38. package/dist/chunk-U2OPXZ6E.js.map +1 -0
  39. package/dist/{chunk-T4NESGYB.js → chunk-UMAOVKQX.js} +3 -3
  40. package/dist/{chunk-E5OM25WK.js → chunk-UQWMPQ2Q.js} +2 -2
  41. package/dist/{chunk-ZEFTWM5Z.js → chunk-VUIPDX3T.js} +2 -2
  42. package/dist/{chunk-GQDVH6FA.js → chunk-XC5JKRSH.js} +2 -2
  43. package/dist/{chunk-G2DGDCDP.js → chunk-Y2JHYPMX.js} +15 -13
  44. package/dist/chunk-Y2JHYPMX.js.map +1 -0
  45. package/dist/{chunk-ERMEYFT6.js → chunk-YVNG35OW.js} +2 -2
  46. package/dist/{chunk-7TN5VW4I.js → chunk-Z32HPRZF.js} +2 -1
  47. package/dist/chunk-Z32HPRZF.js.map +1 -0
  48. package/dist/{chunk-GPBX2BY2.js → chunk-ZWXJ7G2C.js} +2 -2
  49. package/dist/{cleanup-BCVY7PEF.js → cleanup-I62RA5TZ.js} +16 -16
  50. package/dist/cli.js +101 -64
  51. package/dist/cli.js.map +1 -1
  52. package/dist/{commit-L5JNBU4U.js → commit-7RI2JFFW.js} +6 -6
  53. package/dist/{compile-GPJOHXH4.js → compile-NWTMKAGL.js} +6 -6
  54. package/dist/{contribute-QEGCI4PS.js → contribute-QWPOT4QR.js} +3 -3
  55. package/dist/{dev-server-UQKNKU2S.js → dev-server-OZ6KKKTR.js} +58 -27
  56. package/dist/dev-server-OZ6KKKTR.js.map +1 -0
  57. package/dist/{feedback-2LWXKLQZ.js → feedback-G63MODT2.js} +4 -4
  58. package/dist/{git-IS7AV3ED.js → git-ZTMT6OAI.js} +3 -3
  59. package/dist/{ignite-VQDJQ37S.js → ignite-GUYKYC5G.js} +11 -11
  60. package/dist/index.d.ts +30 -3
  61. package/dist/index.js +8 -4
  62. package/dist/index.js.map +1 -1
  63. package/dist/{init-7SDJUAEZ.js → init-AMLCFVXG.js} +9 -7
  64. package/dist/init-AMLCFVXG.js.map +1 -0
  65. package/dist/{install-deps-NGSFDNUW.js → install-deps-XS2UUCUS.js} +6 -6
  66. package/dist/{issues-4HQKEUP7.js → issues-2IT7PSNZ.js} +4 -4
  67. package/dist/{lint-C5FOVRXY.js → lint-DKWJHET3.js} +6 -6
  68. package/dist/mcp/issue-management-server.js +8 -4
  69. package/dist/mcp/issue-management-server.js.map +1 -1
  70. package/dist/{open-2HL6GV5F.js → open-6PXNIPXS.js} +13 -12
  71. package/dist/open-6PXNIPXS.js.map +1 -0
  72. package/dist/{plan-GC3HF73T.js → plan-NJVQBBT3.js} +18 -18
  73. package/dist/prompts/epic-report-prompt.txt +145 -0
  74. package/dist/prompts/init-prompt.txt +32 -9
  75. package/dist/prompts/issue-prompt.txt +1 -1
  76. package/dist/prompts/swarm-orchestrator-prompt.txt +50 -6
  77. package/dist/{rebase-MLIN572O.js → rebase-6AXN45AE.js} +5 -5
  78. package/dist/{recap-CKGKFDJL.js → recap-XDKI3MTA.js} +6 -6
  79. package/dist/{run-CUNRQNZS.js → run-RHE5NPDT.js} +16 -16
  80. package/dist/run-RHE5NPDT.js.map +1 -0
  81. package/dist/schema/settings.schema.json +14 -0
  82. package/dist/{shell-M2YYPNGV.js → shell-XOILFEZW.js} +5 -5
  83. package/dist/{summary-XR4CBJEG.js → summary-BVYOM63C.js} +10 -8
  84. package/dist/{summary-XR4CBJEG.js.map → summary-BVYOM63C.js.map} +1 -1
  85. package/dist/{test-ESDAHEVE.js → test-6T2UMQ7T.js} +6 -6
  86. package/dist/{test-git-KWPLHYSI.js → test-git-CQ65OL45.js} +3 -3
  87. package/dist/{test-jira-6NK7UHSV.js → test-jira-CQQHGZ3S.js} +3 -3
  88. package/dist/{test-prefix-VVODGHXP.js → test-prefix-HMTZSS67.js} +3 -3
  89. package/dist/{test-webserver-AHXKC6H4.js → test-webserver-ZN73CM2T.js} +5 -5
  90. package/dist/{vscode-OY7HOVRO.js → vscode-ABQ5ZSH7.js} +5 -5
  91. package/package.json +1 -1
  92. package/dist/chunk-55NTREIU.js.map +0 -1
  93. package/dist/chunk-7TN5VW4I.js.map +0 -1
  94. package/dist/chunk-F5NKWLMQ.js.map +0 -1
  95. package/dist/chunk-G2DGDCDP.js.map +0 -1
  96. package/dist/chunk-MRPIDNZU.js.map +0 -1
  97. package/dist/chunk-P5MNWBLH.js.map +0 -1
  98. package/dist/chunk-QNRXRSKC.js.map +0 -1
  99. package/dist/chunk-UXBVDD7U.js.map +0 -1
  100. package/dist/dev-server-UQKNKU2S.js.map +0 -1
  101. package/dist/init-7SDJUAEZ.js.map +0 -1
  102. package/dist/open-2HL6GV5F.js.map +0 -1
  103. package/dist/run-CUNRQNZS.js.map +0 -1
  104. /package/dist/{ClaudeContextManager-ZH6LEA5I.js.map → ClaudeContextManager-KJ4VEA2F.js.map} +0 -0
  105. /package/dist/{ClaudeService-YR66WXZN.js.map → ClaudeService-WTJO4UW6.js.map} +0 -0
  106. /package/dist/{IssueTrackerFactory-O2ZBA666.js.map → IssueTrackerFactory-UEJALI4X.js.map} +0 -0
  107. /package/dist/{LoomLauncher-V54ENBEF.js.map → LoomLauncher-KG2VBNQA.js.map} +0 -0
  108. /package/dist/{PromptTemplateManager-4RFELNYY.js.map → PromptTemplateManager-QIUVJP6S.js.map} +0 -0
  109. /package/dist/{SettingsManager-SLSYEYDZ.js.map → SettingsManager-PVHBSCMI.js.map} +0 -0
  110. /package/dist/{build-ZTGWDHWU.js.map → build-2FXDYEZQ.js.map} +0 -0
  111. /package/dist/{chunk-LNY2Y32V.js.map → chunk-2WRD6Y5E.js.map} +0 -0
  112. /package/dist/{chunk-WYDLOQYO.js.map → chunk-32D4CWWH.js.map} +0 -0
  113. /package/dist/{chunk-KGOBNC5A.js.map → chunk-5W44AI63.js.map} +0 -0
  114. /package/dist/{chunk-PPQ5LV7U.js.map → chunk-D6FU4DLN.js.map} +0 -0
  115. /package/dist/{chunk-PS6K2AOV.js.map → chunk-DMNI225H.js.map} +0 -0
  116. /package/dist/{chunk-T4KFKKEB.js.map → chunk-H4TSDALC.js.map} +0 -0
  117. /package/dist/{chunk-J5JOJPK3.js.map → chunk-L3P3YJCE.js.map} +0 -0
  118. /package/dist/{chunk-EHAITKLS.js.map → chunk-MPHSR6GA.js.map} +0 -0
  119. /package/dist/{chunk-HWDQRW3O.js.map → chunk-OHX3PSAY.js.map} +0 -0
  120. /package/dist/{chunk-C2BVNJW5.js.map → chunk-OIJNBFMP.js.map} +0 -0
  121. /package/dist/{chunk-TJDKGKQV.js.map → chunk-OMV47LLA.js.map} +0 -0
  122. /package/dist/{chunk-KCAWSZUO.js.map → chunk-R2EFSRKR.js.map} +0 -0
  123. /package/dist/{chunk-T4NESGYB.js.map → chunk-UMAOVKQX.js.map} +0 -0
  124. /package/dist/{chunk-E5OM25WK.js.map → chunk-UQWMPQ2Q.js.map} +0 -0
  125. /package/dist/{chunk-ZEFTWM5Z.js.map → chunk-VUIPDX3T.js.map} +0 -0
  126. /package/dist/{chunk-GQDVH6FA.js.map → chunk-XC5JKRSH.js.map} +0 -0
  127. /package/dist/{chunk-ERMEYFT6.js.map → chunk-YVNG35OW.js.map} +0 -0
  128. /package/dist/{chunk-GPBX2BY2.js.map → chunk-ZWXJ7G2C.js.map} +0 -0
  129. /package/dist/{cleanup-BCVY7PEF.js.map → cleanup-I62RA5TZ.js.map} +0 -0
  130. /package/dist/{commit-L5JNBU4U.js.map → commit-7RI2JFFW.js.map} +0 -0
  131. /package/dist/{compile-GPJOHXH4.js.map → compile-NWTMKAGL.js.map} +0 -0
  132. /package/dist/{contribute-QEGCI4PS.js.map → contribute-QWPOT4QR.js.map} +0 -0
  133. /package/dist/{feedback-2LWXKLQZ.js.map → feedback-G63MODT2.js.map} +0 -0
  134. /package/dist/{git-IS7AV3ED.js.map → git-ZTMT6OAI.js.map} +0 -0
  135. /package/dist/{ignite-VQDJQ37S.js.map → ignite-GUYKYC5G.js.map} +0 -0
  136. /package/dist/{install-deps-NGSFDNUW.js.map → install-deps-XS2UUCUS.js.map} +0 -0
  137. /package/dist/{issues-4HQKEUP7.js.map → issues-2IT7PSNZ.js.map} +0 -0
  138. /package/dist/{lint-C5FOVRXY.js.map → lint-DKWJHET3.js.map} +0 -0
  139. /package/dist/{plan-GC3HF73T.js.map → plan-NJVQBBT3.js.map} +0 -0
  140. /package/dist/{rebase-MLIN572O.js.map → rebase-6AXN45AE.js.map} +0 -0
  141. /package/dist/{recap-CKGKFDJL.js.map → recap-XDKI3MTA.js.map} +0 -0
  142. /package/dist/{shell-M2YYPNGV.js.map → shell-XOILFEZW.js.map} +0 -0
  143. /package/dist/{test-ESDAHEVE.js.map → test-6T2UMQ7T.js.map} +0 -0
  144. /package/dist/{test-git-KWPLHYSI.js.map → test-git-CQ65OL45.js.map} +0 -0
  145. /package/dist/{test-jira-6NK7UHSV.js.map → test-jira-CQQHGZ3S.js.map} +0 -0
  146. /package/dist/{test-prefix-VVODGHXP.js.map → test-prefix-HMTZSS67.js.map} +0 -0
  147. /package/dist/{test-webserver-AHXKC6H4.js.map → test-webserver-ZN73CM2T.js.map} +0 -0
  148. /package/dist/{vscode-OY7HOVRO.js.map → vscode-ABQ5ZSH7.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/AgentManager.ts","../src/utils/MarkdownAgentParser.ts"],"sourcesContent":["import { readFile } from 'fs/promises'\nimport { accessSync } from 'fs'\nimport path from 'path'\nimport { fileURLToPath } from 'url'\nimport fg from 'fast-glob'\nimport fs from 'fs-extra'\nimport { MarkdownAgentParser } from '../utils/MarkdownAgentParser.js'\nimport { logger } from '../utils/logger.js'\nimport { VALID_CLAUDE_MODELS, type IloomSettings } from './SettingsManager.js'\nimport { PromptTemplateManager, TemplateVariables, buildReviewTemplateVariables } from './PromptTemplateManager.js'\n\n// Agent schema interface\nexport interface AgentConfig {\n\tdescription: string\n\tprompt: string\n\ttools?: string[] // Optional - when omitted, agent inherits all tools from parent\n\tmodel: string\n\tcolor?: string\n}\n\n// Container for all loaded agents (keyed by agent name without extension)\nexport interface AgentConfigs {\n\t[agentName: string]: AgentConfig\n}\n\nexport class AgentManager {\n\tprivate agentDir: string\n\tprivate templateManager: PromptTemplateManager\n\n\tconstructor(agentDir?: string, templateManager?: PromptTemplateManager) {\n\t\tthis.templateManager = templateManager ?? new PromptTemplateManager()\n\t\tif (agentDir) {\n\t\t\tthis.agentDir = agentDir\n\t\t} else {\n\t\t\t// Find agents relative to package installation\n\t\t\t// Same pattern as PromptTemplateManager\n\t\t\t// When running from dist/, agents are copied to dist/agents/\n\t\t\tconst currentFileUrl = import.meta.url\n\t\t\tconst currentFilePath = fileURLToPath(currentFileUrl)\n\t\t\tconst distDir = path.dirname(currentFilePath)\n\n\t\t\t// Walk up to find the agents directory\n\t\t\tlet agentDirPath = path.join(distDir, 'agents')\n\t\t\tlet currentDir = distDir\n\n\t\t\twhile (currentDir !== path.dirname(currentDir)) {\n\t\t\t\tconst candidatePath = path.join(currentDir, 'agents')\n\t\t\t\ttry {\n\t\t\t\t\taccessSync(candidatePath)\n\t\t\t\t\tagentDirPath = candidatePath\n\t\t\t\t\tbreak\n\t\t\t\t} catch {\n\t\t\t\t\tcurrentDir = path.dirname(currentDir)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.agentDir = agentDirPath\n\t\t\tlogger.debug('AgentManager initialized', { agentDir: this.agentDir })\n\t\t}\n\t}\n\n\t/**\n\t * Load agent configuration files from markdown (.md) format\n\t * Optionally apply model overrides from settings and template variable substitution\n\t * Throws error if agents directory doesn't exist or files are malformed\n\t * @param settings - Optional project settings with per-agent model overrides\n\t * @param templateVariables - Optional variables for template substitution in agent prompts\n\t * @param patterns - Optional glob patterns to filter which agents to load (default: ['*.md'])\n\t * Supports negation patterns like ['*.md', '!iloom-framework-detector.md']\n\t */\n\tasync loadAgents(\n\t\tsettings?: IloomSettings,\n\t\ttemplateVariables?: TemplateVariables,\n\t\tpatterns: string[] = ['*.md']\n\t): Promise<AgentConfigs> {\n\t\t// Always exclude non-agent markdown files (e.g. CLAUDE.md documentation)\n\t\tconst excludes = ['!CLAUDE.md', '!agents.md']\n\t\tconst effectivePatterns = [...patterns, ...excludes.filter(p => !patterns.includes(p))]\n\n\t\t// Use fast-glob to filter agent files based on patterns\n\t\t// caseSensitiveMatch: false so exclusions like !CLAUDE.md also catch claude.md, Claude.md, etc.\n\t\tconst agentFiles = await fg(effectivePatterns, {\n\t\t\tcwd: this.agentDir,\n\t\t\tonlyFiles: true,\n\t\t\tcaseSensitiveMatch: false,\n\t\t})\n\n\t\tconst agents: AgentConfigs = {}\n\n\t\tfor (const filename of agentFiles) {\n\t\t\tconst agentPath = path.join(this.agentDir, filename)\n\n\t\t\ttry {\n\t\t\t\tconst content = await readFile(agentPath, 'utf-8')\n\n\t\t\t\t// Parse markdown with frontmatter\n\t\t\t\tconst parsed = this.parseMarkdownAgent(content, filename)\n\t\t\t\tconst agentConfig = parsed.config\n\t\t\t\tconst agentName = parsed.name\n\n\t\t\t\t// Validate required fields\n\t\t\t\tthis.validateAgentConfig(agentConfig, agentName)\n\n\t\t\t\tagents[agentName] = agentConfig\n\t\t\t\tlogger.debug(`Loaded agent: ${agentName}`)\n\t\t\t} catch (error) {\n\t\t\t\tlogger.warn(`Skipping ${filename}: ${error instanceof Error ? error.message : 'Unknown error'}`)\n\t\t\t}\n\t\t}\n\n\t\t// Apply template variable substitution to agent prompts if variables provided\n\t\tif (templateVariables) {\n\t\t\t// Extract review config from settings and add to template variables\n\t\t\tObject.assign(templateVariables, buildReviewTemplateVariables(!!templateVariables.SWARM_MODE, settings?.agents))\n\n\t\t\tfor (const [agentName, agentConfig] of Object.entries(agents)) {\n\t\t\t\tagents[agentName] = {\n\t\t\t\t\t...agentConfig,\n\t\t\t\t\tprompt: this.templateManager.substituteVariables(agentConfig.prompt, templateVariables),\n\t\t\t\t}\n\t\t\t\tlogger.debug(`Applied template substitution to agent: ${agentName}`)\n\t\t\t}\n\t\t}\n\n\t\t// Apply settings overrides if provided\n\t\tif (settings?.agents) {\n\t\t\tfor (const [agentName, agentSettings] of Object.entries(settings.agents)) {\n\t\t\t\tif (agents[agentName] && agentSettings.model) {\n\t\t\t\t\tlogger.debug(`Overriding model for ${agentName}: ${agents[agentName].model} -> ${agentSettings.model}`)\n\t\t\t\t\tagents[agentName] = {\n\t\t\t\t\t\t...agents[agentName],\n\t\t\t\t\t\tmodel: agentSettings.model,\n\t\t\t\t\t}\n\t\t\t\t} else if (!agents[agentName]) {\n\t\t\t\t\t// Skip warning for runtime-generated agents (e.g., swarm worker)\n\t\t\t\t\tconst RUNTIME_GENERATED_AGENTS = ['iloom-swarm-worker']\n\t\t\t\t\tif (!RUNTIME_GENERATED_AGENTS.includes(agentName)) {\n\t\t\t\t\t\t// Only warn if the agent file doesn't exist at all (typo in settings)\n\t\t\t\t\t\t// Skip warning if the agent exists but wasn't loaded due to pattern filtering\n\t\t\t\t\t\tconst agentFile = path.join(this.agentDir, `${agentName}.md`)\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\taccessSync(agentFile)\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\tlogger.warn(`Settings reference unknown agent: ${agentName}`)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn agents\n\t}\n\n\t/**\n\t * Validate agent configuration has required fields\n\t * Note: tools is optional - when omitted, agent inherits all tools from parent\n\t */\n\tprivate validateAgentConfig(config: AgentConfig, agentName: string): void {\n\t\tconst requiredFields: (keyof AgentConfig)[] = ['description', 'prompt', 'model']\n\n\t\tfor (const field of requiredFields) {\n\t\t\tif (!config[field]) {\n\t\t\t\tthrow new Error(`Agent ${agentName} missing required field: ${field}`)\n\t\t\t}\n\t\t}\n\n\t\t// Tools is optional, but if present must be an array\n\t\tif (config.tools !== undefined && !Array.isArray(config.tools)) {\n\t\t\tthrow new Error(`Agent ${agentName} tools must be an array`)\n\t\t}\n\t}\n\n\t/**\n\t * Parse markdown agent file with YAML frontmatter\n\t * @param content - Raw markdown file content\n\t * @param filename - Original filename for error messages\n\t * @returns Parsed agent config and name\n\t */\n\tprivate parseMarkdownAgent(content: string, filename: string): { config: AgentConfig; name: string } {\n\t\ttry {\n\t\t\t// Parse frontmatter using custom parser\n\t\t\tconst { data, content: markdownBody } = MarkdownAgentParser.parse(content)\n\n\t\t\t// Validate frontmatter has required fields\n\t\t\tif (!data.name) {\n\t\t\t\tthrow new Error('Missing required field: name')\n\t\t\t}\n\t\t\tif (!data.description) {\n\t\t\t\tthrow new Error('Missing required field: description')\n\t\t\t}\n\t\t\t// Note: tools is now optional - when omitted, agent inherits all tools from parent\n\t\t\tif (!data.model) {\n\t\t\t\tthrow new Error('Missing required field: model')\n\t\t\t}\n\n\t\t\t// Parse tools from comma-separated string to array (only if tools field is present)\n\t\t\tlet tools: string[] | undefined\n\t\t\tif (data.tools) {\n\t\t\t\ttools = data.tools\n\t\t\t\t\t.split(',')\n\t\t\t\t\t.map((tool: string) => tool.trim())\n\t\t\t\t\t.filter((tool: string) => tool.length > 0)\n\t\t\t}\n\n\t\t\t// Validate model and warn if non-standard\n\t\t\tconst validModels: readonly string[] = VALID_CLAUDE_MODELS\n\t\t\tif (!validModels.includes(data.model)) {\n\t\t\t\tlogger.warn(\n\t\t\t\t\t`Agent ${data.name} uses model \"${data.model}\" which may not be recognized by Claude CLI, and your workflow may fail or produce unexpected results. ` +\n\t\t\t\t\t\t`Valid values are: ${validModels.join(', ')}`\n\t\t\t\t)\n\t\t\t}\n\n\t\t\t// Construct AgentConfig\n\t\t\tconst config: AgentConfig = {\n\t\t\t\tdescription: data.description,\n\t\t\t\tprompt: markdownBody.trim(),\n\t\t\t\tmodel: data.model,\n\t\t\t\t...(tools && { tools }),\n\t\t\t\t...(data.color && { color: data.color }),\n\t\t\t}\n\n\t\t\treturn { config, name: data.name }\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to parse markdown agent ${filename}: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Format loaded agents for Claude CLI --agents flag\n\t * Returns object suitable for JSON.stringify\n\t */\n\tformatForCli(agents: AgentConfigs): Record<string, unknown> {\n\t\t// The agents object is already in the correct format\n\t\t// Just return it - launchClaude will JSON.stringify it\n\t\treturn agents as Record<string, unknown>\n\t}\n\n\t/**\n\t * Load agents and prepare them for the current platform.\n\t * On macOS, returns agents formatted for the --agents CLI flag.\n\t * On Linux/Windows, renders agents to disk for auto-discovery and returns undefined.\n\t *\n\t * @param settings - Project settings for model overrides\n\t * @param templateVariables - Variables to substitute in agent prompts\n\t * @param patterns - Glob patterns for which agent files to load\n\t * @param targetDir - Directory for disk rendering (Linux/Windows). Defaults to cwd/.claude/agents/\n\t * @returns Agents object for CLI flag (macOS) or undefined (Linux/Windows, agents on disk)\n\t */\n\tasync loadAndPrepare(\n\t\tsettings: IloomSettings | undefined,\n\t\ttemplateVariables: TemplateVariables,\n\t\tpatterns: string[],\n\t\ttargetDir?: string\n\t): Promise<Record<string, unknown> | undefined> {\n\t\tconst loadedAgents = await this.loadAgents(settings, templateVariables, patterns)\n\n\t\tif (process.platform === 'darwin') {\n\t\t\tconst agents = this.formatForCli(loadedAgents)\n\t\t\tlogger.debug('Loaded agent configurations for CLI', {\n\t\t\t\tagentCount: Object.keys(agents).length,\n\t\t\t\tagentNames: Object.keys(agents),\n\t\t\t})\n\t\t\treturn agents\n\t\t}\n\n\t\tconst dir = targetDir ?? path.join(process.cwd(), '.claude', 'agents')\n\t\tconst rendered = await this.renderAgentsToDisk(loadedAgents, dir)\n\t\tlogger.debug('Rendered agent files to disk for auto-discovery', {\n\t\t\tagentCount: rendered.length,\n\t\t\tagentNames: rendered,\n\t\t\ttargetDir: dir,\n\t\t})\n\t\treturn undefined\n\t}\n\n\t/**\n\t * Render loaded agents to disk as markdown files with YAML frontmatter.\n\t * Claude Code auto-discovers agents from .claude/agents/ directory.\n\t *\n\t * @param agents - Loaded agent configs (from loadAgents())\n\t * @param targetDir - Absolute path to target directory (e.g., <worktree>/.claude/agents/)\n\t * @returns Array of rendered filenames\n\t */\n\tasync renderAgentsToDisk(agents: AgentConfigs, targetDir: string): Promise<string[]> {\n\t\tawait fs.ensureDir(targetDir)\n\n\t\t// Clean existing iloom agent files to avoid stale agents from previous runs\n\t\tconst existingFiles = await fg('iloom-*.md', { cwd: targetDir, onlyFiles: true })\n\t\tfor (const file of existingFiles) {\n\t\t\tawait fs.remove(path.join(targetDir, file))\n\t\t}\n\n\t\tconst renderedFiles: string[] = []\n\t\tfor (const [agentName, config] of Object.entries(agents)) {\n\t\t\tconst safeName = path.basename(agentName)\n\t\t\tconst filename = `${safeName}.md`\n\t\t\t// Build YAML frontmatter\n\t\t\tconst frontmatterLines = ['---', `name: ${agentName}`, `description: ${config.description}`]\n\t\t\tif (config.tools) frontmatterLines.push(`tools: ${config.tools.join(', ')}`)\n\t\t\tfrontmatterLines.push(`model: ${config.model}`)\n\t\t\tif (config.color) frontmatterLines.push(`color: ${config.color}`)\n\t\t\tfrontmatterLines.push('---')\n\n\t\t\tconst content = frontmatterLines.join('\\n') + '\\n\\n' + config.prompt + '\\n'\n\t\t\tawait fs.writeFile(path.join(targetDir, filename), content, 'utf-8')\n\t\t\trenderedFiles.push(filename)\n\t\t}\n\t\treturn renderedFiles\n\t}\n}\n","/**\n * Custom YAML frontmatter parser for agent markdown files\n * Replaces gray-matter dependency with lightweight custom implementation\n */\n\ninterface ParseResult {\n\tdata: Record<string, string>\n\tcontent: string\n}\n\nexport class MarkdownAgentParser {\n\t/**\n\t * Parse markdown content with YAML frontmatter\n\t * @param content - Raw markdown file content\n\t * @returns Object with parsed frontmatter data and markdown body content\n\t * @throws Error if frontmatter is malformed or missing\n\t */\n\tstatic parse(content: string): ParseResult {\n\t\tconst lines = content.split('\\n')\n\n\t\t// Check for opening frontmatter delimiter\n\t\tif (lines[0]?.trim() !== '---') {\n\t\t\tthrow new Error('Missing opening frontmatter delimiter (---)')\n\t\t}\n\n\t\t// Find closing frontmatter delimiter\n\t\tlet closingDelimiterIndex = -1\n\t\tfor (let i = 1; i < lines.length; i++) {\n\t\t\tif (lines[i]?.trim() === '---') {\n\t\t\t\tclosingDelimiterIndex = i\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif (closingDelimiterIndex === -1) {\n\t\t\tthrow new Error('Missing closing frontmatter delimiter (---)')\n\t\t}\n\n\t\t// Extract frontmatter lines (between the delimiters)\n\t\tconst frontmatterLines = lines.slice(1, closingDelimiterIndex)\n\n\t\t// Extract markdown body (after closing delimiter)\n\t\tconst bodyLines = lines.slice(closingDelimiterIndex + 1)\n\t\tconst markdownBody = bodyLines.join('\\n')\n\n\t\t// Parse YAML frontmatter into key-value pairs\n\t\tconst data = this.parseYaml(frontmatterLines.join('\\n'))\n\n\t\treturn {\n\t\t\tdata,\n\t\t\tcontent: markdownBody,\n\t\t}\n\t}\n\n\t/**\n\t * Parse simplified YAML into key-value object\n\t * Supports:\n\t * - Simple key: value pairs\n\t * - Multiline values with | indicator\n\t * - Values with special characters and newlines\n\t *\n\t * @param yaml - YAML string to parse\n\t * @returns Object with parsed key-value pairs\n\t */\n\tprivate static parseYaml(yaml: string): Record<string, string> {\n\t\tconst result: Record<string, string> = {}\n\t\tconst lines = yaml.split('\\n')\n\t\tlet currentKey: string | null = null\n\t\tlet currentValue: string[] = []\n\t\tlet isMultiline = false\n\n\t\tconst finalizeCurrent = (): void => {\n\t\t\tif (currentKey && currentValue.length > 0) {\n\t\t\t\tresult[currentKey] = currentValue.join('\\n').trim()\n\t\t\t\tcurrentKey = null\n\t\t\t\tcurrentValue = []\n\t\t\t}\n\t\t}\n\n\t\tfor (const line of lines) {\n\t\t\t// Skip empty lines when not in multiline mode\n\t\t\tif (!isMultiline && line.trim() === '') {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Check if this is a new key-value pair\n\t\t\tconst keyValueMatch = line.match(/^([a-zA-Z_][a-zA-Z0-9_-]*)\\s*:\\s*(.*)$/)\n\n\t\t\tif (keyValueMatch && !isMultiline) {\n\t\t\t\t// Finalize previous key if exists\n\t\t\t\tfinalizeCurrent()\n\n\t\t\t\tconst [, key, value] = keyValueMatch\n\t\t\t\tif (!key || value === undefined) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tcurrentKey = key\n\n\t\t\t\t// Check for multiline indicator\n\t\t\t\tif (value.trim() === '|') {\n\t\t\t\t\tisMultiline = true\n\t\t\t\t\tcurrentValue = []\n\t\t\t\t} else {\n\t\t\t\t\t// Single line value\n\t\t\t\t\tcurrentValue = [value]\n\t\t\t\t\tfinalizeCurrent()\n\t\t\t\t\tisMultiline = false\n\t\t\t\t}\n\t\t\t} else if (isMultiline && currentKey) {\n\t\t\t\t// Continuation of multiline value\n\t\t\t\t// Check if we've returned to normal indentation (new key)\n\t\t\t\tif (line.match(/^[a-zA-Z_][a-zA-Z0-9_-]*\\s*:/) && !line.startsWith(' ')) {\n\t\t\t\t\t// End of multiline, this is a new key\n\t\t\t\t\tfinalizeCurrent()\n\t\t\t\t\tisMultiline = false\n\n\t\t\t\t\t// Process this line as a new key\n\t\t\t\t\tconst match = line.match(/^([a-zA-Z_][a-zA-Z0-9_-]*)\\s*:\\s*(.*)$/)\n\t\t\t\t\tif (match) {\n\t\t\t\t\t\tconst [, key, value] = match\n\t\t\t\t\t\tif (key && value !== undefined) {\n\t\t\t\t\t\t\tcurrentKey = key\n\t\t\t\t\t\t\tcurrentValue = [value]\n\t\t\t\t\t\t\tfinalizeCurrent()\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Remove leading spaces (common indentation) from multiline values\n\t\t\t\t\tconst trimmedLine = line.replace(/^ {2}/, '')\n\t\t\t\t\tcurrentValue.push(trimmedLine)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Finalize last key\n\t\tfinalizeCurrent()\n\n\t\treturn result\n\t}\n}\n"],"mappings":"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","names":[]}
@@ -4,13 +4,19 @@ import {
4
4
  } from "./chunk-NXMDEL3F.js";
5
5
  import {
6
6
  VCSProviderFactory
7
- } from "./chunk-J5JOJPK3.js";
7
+ } from "./chunk-L3P3YJCE.js";
8
+ import {
9
+ TelemetryService
10
+ } from "./chunk-MY2Q3FJ3.js";
11
+ import {
12
+ resolveRecapFilePath
13
+ } from "./chunk-CQHHEW2M.js";
8
14
  import {
9
15
  IssueManagementProviderFactory
10
- } from "./chunk-ZEFTWM5Z.js";
16
+ } from "./chunk-VUIPDX3T.js";
11
17
  import {
12
18
  PromptTemplateManager
13
- } from "./chunk-MRPIDNZU.js";
19
+ } from "./chunk-LDE6VNG5.js";
14
20
  import {
15
21
  hasMultipleRemotes
16
22
  } from "./chunk-BZ7KTXPB.js";
@@ -20,19 +26,17 @@ import {
20
26
  } from "./chunk-DDHWZNGL.js";
21
27
  import {
22
28
  SettingsManager
23
- } from "./chunk-F5NKWLMQ.js";
29
+ } from "./chunk-MNPKEWBQ.js";
24
30
  import {
25
31
  MetadataManager
26
32
  } from "./chunk-XIVLGWUX.js";
33
+ import {
34
+ getLogger
35
+ } from "./chunk-FTYWGQFM.js";
27
36
  import {
28
37
  logger
29
38
  } from "./chunk-VRPPI6GU.js";
30
39
 
31
- // src/lib/SessionSummaryService.ts
32
- import path from "path";
33
- import os from "os";
34
- import fs from "fs-extra";
35
-
36
40
  // src/utils/claude-transcript.ts
37
41
  import { readFile } from "fs/promises";
38
42
  import { homedir } from "os";
@@ -106,46 +110,183 @@ async function readSessionContext(worktreePath, sessionId, maxSummaries = 3) {
106
110
  return formattedSummaries;
107
111
  }
108
112
 
109
- // src/lib/SessionSummaryService.ts
110
- var RECAPS_DIR = path.join(os.homedir(), ".config", "iloom-ai", "recaps");
111
- function slugifyPath(loomPath) {
112
- let slug = loomPath.replace(/[/\\]+$/, "");
113
- slug = slug.replace(/[/\\]/g, "___");
114
- slug = slug.replace(/[^a-zA-Z0-9_-]/g, "-");
115
- return `${slug}.json`;
116
- }
117
- async function readRecapFile(worktreePath) {
113
+ // src/lib/SwarmReportCollector.ts
114
+ import fs from "fs-extra";
115
+ var CONCURRENCY_LIMIT = 5;
116
+ async function readRecapForWorktree(worktreePath) {
118
117
  try {
119
- const filePath = path.join(RECAPS_DIR, slugifyPath(worktreePath));
120
- if (await fs.pathExists(filePath)) {
121
- const content = await fs.readFile(filePath, "utf8");
122
- const recap = JSON.parse(content);
123
- const hasGoal = recap.goal !== null && recap.goal !== void 0;
124
- const hasComplexity = recap.complexity !== null && recap.complexity !== void 0;
125
- const hasEntries = Array.isArray(recap.entries) && recap.entries.length > 0;
126
- const hasArtifacts = Array.isArray(recap.artifacts) && recap.artifacts.length > 0;
127
- const hasContent = hasGoal || hasComplexity || hasEntries || hasArtifacts;
128
- if (hasContent) {
129
- const recapOutput = {
130
- filePath,
131
- goal: recap.goal ?? null,
132
- complexity: recap.complexity ?? null,
133
- entries: recap.entries ?? [],
134
- artifacts: recap.artifacts ?? []
135
- };
136
- return formatRecapMarkdown(recapOutput);
137
- }
138
- }
139
- return null;
118
+ const filePath = resolveRecapFilePath(worktreePath);
119
+ if (!await fs.pathExists(filePath)) return null;
120
+ const content = await fs.readFile(filePath, "utf8");
121
+ const recap = JSON.parse(content);
122
+ const hasGoal = recap.goal !== null && recap.goal !== void 0;
123
+ const hasComplexity = recap.complexity !== null && recap.complexity !== void 0;
124
+ const hasEntries = Array.isArray(recap.entries) && recap.entries.length > 0;
125
+ const hasArtifacts = Array.isArray(recap.artifacts) && recap.artifacts.length > 0;
126
+ const hasContent = hasGoal || hasComplexity || hasEntries || hasArtifacts;
127
+ if (!hasContent) return null;
128
+ const recapOutput = {
129
+ filePath,
130
+ goal: recap.goal ?? null,
131
+ complexity: recap.complexity ?? null,
132
+ entries: recap.entries ?? [],
133
+ artifacts: recap.artifacts ?? []
134
+ };
135
+ return formatRecapMarkdown(recapOutput);
140
136
  } catch {
141
137
  return null;
142
138
  }
143
139
  }
140
+ var SwarmReportCollector = class {
141
+ constructor(metadataManager) {
142
+ this.metadataManager = metadataManager ?? new MetadataManager();
143
+ }
144
+ /**
145
+ * Collect implementation data from child issues and their recap files.
146
+ *
147
+ * @param childIssueNumbers - Array of child issue identifiers to collect data for
148
+ * @param epicWorktreePath - Worktree path of the parent epic loom
149
+ * @param settings - IloomSettings for configuring the issue management provider
150
+ * @returns Array of ChildImplementationData, one per child
151
+ */
152
+ async collectChildData(childIssueNumbers, epicWorktreePath, settings) {
153
+ var _a;
154
+ if (childIssueNumbers.length === 0) return [];
155
+ const providerType = ((_a = settings.issueManagement) == null ? void 0 : _a.provider) ?? "github";
156
+ const provider = IssueManagementProviderFactory.create(providerType, settings);
157
+ const worktreeMap = await this.buildChildWorktreeMap(childIssueNumbers, epicWorktreePath);
158
+ const results = [];
159
+ for (let i = 0; i < childIssueNumbers.length; i += CONCURRENCY_LIMIT) {
160
+ const batch = childIssueNumbers.slice(i, i + CONCURRENCY_LIMIT);
161
+ const batchResults = await Promise.allSettled(
162
+ batch.map((num) => this.fetchChildData(num, provider, worktreeMap))
163
+ );
164
+ for (let j = 0; j < batchResults.length; j++) {
165
+ const result = batchResults[j];
166
+ const issueNumber = batch[j] ?? "?";
167
+ if (!result) continue;
168
+ if (result.status === "fulfilled") {
169
+ results.push(result.value);
170
+ } else {
171
+ getLogger().error(
172
+ `SwarmReportCollector: unexpected rejection for issue ${issueNumber}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`
173
+ );
174
+ results.push({
175
+ issueNumber,
176
+ title: "Unknown",
177
+ status: "failure",
178
+ implementationComment: null,
179
+ recapMarkdown: null
180
+ });
181
+ }
182
+ }
183
+ }
184
+ return results;
185
+ }
186
+ /**
187
+ * Build a map of issueNumber -> worktreePath by scanning all loom metadata
188
+ * for children whose parentLoom.worktreePath matches the epic worktree.
189
+ */
190
+ async buildChildWorktreeMap(childIssueNumbers, epicWorktreePath) {
191
+ var _a;
192
+ const map = /* @__PURE__ */ new Map();
193
+ try {
194
+ const allMetadata = await this.metadataManager.listAllMetadata();
195
+ for (const metadata of allMetadata) {
196
+ if (((_a = metadata.parentLoom) == null ? void 0 : _a.worktreePath) !== epicWorktreePath) continue;
197
+ if (!metadata.worktreePath) continue;
198
+ for (const issueNum of metadata.issue_numbers) {
199
+ if (childIssueNumbers.includes(issueNum)) {
200
+ map.set(issueNum, metadata.worktreePath);
201
+ }
202
+ }
203
+ }
204
+ } catch (error) {
205
+ getLogger().debug(
206
+ `SwarmReportCollector: failed to build worktree map: ${error instanceof Error ? error.message : String(error)}`
207
+ );
208
+ }
209
+ return map;
210
+ }
211
+ /**
212
+ * Fetch issue data and recap for a single child.
213
+ * Returns status='failure' on API errors, status='missing' when issue has no comments.
214
+ */
215
+ async fetchChildData(issueNumber, provider, worktreeMap) {
216
+ try {
217
+ const issue = await provider.getIssue({ number: issueNumber, includeComments: true });
218
+ const comments = issue.comments ?? [];
219
+ const implementationComment = this.extractImplementationComment(comments);
220
+ const worktreePath = worktreeMap.get(issueNumber) ?? null;
221
+ const recapMarkdown = worktreePath ? await this.readChildRecap(worktreePath) : null;
222
+ let status;
223
+ if (comments.length === 0) {
224
+ status = "missing";
225
+ } else {
226
+ const issueState = issue.state.toLowerCase();
227
+ status = issueState === "closed" || issueState === "done" ? "success" : "failure";
228
+ }
229
+ return {
230
+ issueNumber,
231
+ title: issue.title,
232
+ status,
233
+ implementationComment,
234
+ recapMarkdown
235
+ };
236
+ } catch (error) {
237
+ getLogger().debug(
238
+ `SwarmReportCollector: failed to fetch data for issue ${issueNumber}: ${error instanceof Error ? error.message : String(error)}`
239
+ );
240
+ return {
241
+ issueNumber,
242
+ title: "Unknown",
243
+ status: "failure",
244
+ implementationComment: null,
245
+ recapMarkdown: null
246
+ };
247
+ }
248
+ }
249
+ /**
250
+ * Extract the implementation comment from an issue's comment list.
251
+ * Prefers comments that contain implementation markers; falls back to the last comment.
252
+ */
253
+ extractImplementationComment(comments) {
254
+ if (comments.length === 0) return null;
255
+ const implementationMarkers = [
256
+ "implementation complete",
257
+ "# implementation",
258
+ "## summary",
259
+ "changes made",
260
+ "validation results"
261
+ ];
262
+ for (let i = comments.length - 1; i >= 0; i--) {
263
+ const comment = comments[i];
264
+ if (!comment) continue;
265
+ const body = comment.body.toLowerCase();
266
+ if (implementationMarkers.some((marker) => body.includes(marker))) {
267
+ return comment.body;
268
+ }
269
+ }
270
+ const lastComment = comments[comments.length - 1];
271
+ return lastComment ? lastComment.body : null;
272
+ }
273
+ /**
274
+ * Read and format the recap file for a child worktree path.
275
+ * Returns null if the recap file is missing or cannot be read.
276
+ */
277
+ async readChildRecap(worktreePath) {
278
+ return readRecapForWorktree(worktreePath);
279
+ }
280
+ };
281
+
282
+ // src/lib/SessionSummaryService.ts
283
+ var GITHUB_COMMENT_MAX_LENGTH = 65536;
144
284
  var SessionSummaryService = class {
145
- constructor(templateManager, metadataManager, settingsManager) {
285
+ constructor(templateManager, metadataManager, settingsManager, swarmReportCollector) {
146
286
  this.templateManager = templateManager ?? new PromptTemplateManager();
147
287
  this.metadataManager = metadataManager ?? new MetadataManager();
148
288
  this.settingsManager = settingsManager ?? new SettingsManager();
289
+ this.swarmReportCollector = swarmReportCollector ?? new SwarmReportCollector();
149
290
  }
150
291
  /**
151
292
  * Generate and post a session summary to the issue
@@ -174,7 +315,7 @@ var SessionSummaryService = class {
174
315
  } else {
175
316
  logger.debug("No compact summaries found in session transcript");
176
317
  }
177
- const recapData = await readRecapFile(input.worktreePath);
318
+ const recapData = await readRecapForWorktree(input.worktreePath);
178
319
  if (recapData) {
179
320
  logger.debug(`Found recap data (${recapData.length} chars)`);
180
321
  } else {
@@ -215,6 +356,102 @@ var SessionSummaryService = class {
215
356
  logger.debug("Session summary generation error details:", { error });
216
357
  }
217
358
  }
359
+ /**
360
+ * Format child implementation data as readable markdown for the epic report template.
361
+ * This replaces raw JSON.stringify so the AI gets structured, readable input.
362
+ */
363
+ formatChildDataAsMarkdown(childData) {
364
+ return childData.map((child) => {
365
+ const sections = [
366
+ `### Child #${child.issueNumber}: ${child.title}`,
367
+ `**Status:** ${child.status}`
368
+ ];
369
+ if (child.implementationComment) {
370
+ sections.push("", "**Implementation Summary:**", child.implementationComment);
371
+ }
372
+ if (child.recapMarkdown) {
373
+ sections.push("", "**Recap (decisions, risks, insights):**", child.recapMarkdown);
374
+ }
375
+ if (!child.implementationComment && !child.recapMarkdown) {
376
+ sections.push("", "*No implementation data available for this child.*");
377
+ }
378
+ return sections.join("\n");
379
+ }).join("\n\n---\n\n");
380
+ }
381
+ /**
382
+ * Truncate a report to GitHub's comment size limit, appending a notice if truncated
383
+ */
384
+ truncateReport(report) {
385
+ if (report.length <= GITHUB_COMMENT_MAX_LENGTH) return report;
386
+ const truncationNotice = "\n\n---\n*Report truncated due to GitHub comment size limit.*";
387
+ const maxContentLength = GITHUB_COMMENT_MAX_LENGTH - truncationNotice.length;
388
+ return report.slice(0, maxContentLength) + truncationNotice;
389
+ }
390
+ /**
391
+ * Generate and post an epic implementation report to the issue or PR
392
+ *
393
+ * Non-blocking: Catches all errors and logs warnings instead of throwing
394
+ * This ensures the finish workflow continues even if report generation fails
395
+ */
396
+ async generateAndPostEpicReport(input) {
397
+ const log = getLogger();
398
+ try {
399
+ const settings = await this.settingsManager.loadSettings(input.worktreePath);
400
+ if (!this.shouldGenerateSummary("epic", settings)) {
401
+ log.debug("Skipping epic report: generateSummary is disabled");
402
+ return;
403
+ }
404
+ log.info("Generating epic implementation report...");
405
+ const childData = await this.swarmReportCollector.collectChildData(
406
+ input.childIssueNumbers,
407
+ input.worktreePath,
408
+ settings
409
+ );
410
+ const totalSucceeded = childData.filter((c) => c.status === "success").length;
411
+ const totalFailed = childData.filter((c) => c.status === "failure").length;
412
+ const prompt = await this.templateManager.getPrompt("epic-report", {
413
+ EPIC_NUMBER: String(input.epicIssueNumber),
414
+ EPIC_TITLE: input.epicTitle,
415
+ CHILD_DATA: this.formatChildDataAsMarkdown(childData),
416
+ TOTAL_CHILDREN: String(childData.length),
417
+ TOTAL_SUCCEEDED: String(totalSucceeded),
418
+ TOTAL_FAILED: String(totalFailed)
419
+ });
420
+ log.debug("Epic report prompt:\n" + prompt);
421
+ const summaryModel = this.settingsManager.getSummaryModel(settings);
422
+ const reportResult = await launchClaude(prompt, {
423
+ headless: true,
424
+ model: summaryModel
425
+ });
426
+ if (!reportResult || typeof reportResult !== "string" || reportResult.trim() === "") {
427
+ log.warn("Epic report generation returned empty result");
428
+ return;
429
+ }
430
+ const report = this.truncateReport(reportResult.trim());
431
+ await this.postSummaryToIssue(
432
+ input.epicIssueNumber,
433
+ report,
434
+ settings,
435
+ input.worktreePath,
436
+ input.prNumber
437
+ );
438
+ const target = input.prNumber ? `PR #${input.prNumber}` : "issue";
439
+ log.success(`Epic implementation report posted to ${target}`);
440
+ try {
441
+ TelemetryService.getInstance().track("epic.report_generated", {
442
+ total_children: childData.length,
443
+ succeeded: totalSucceeded,
444
+ failed: totalFailed
445
+ });
446
+ } catch (telemetryError) {
447
+ log.debug(`Telemetry tracking failed: ${telemetryError instanceof Error ? telemetryError.message : String(telemetryError)}`);
448
+ }
449
+ } catch (error) {
450
+ const errorMessage = error instanceof Error ? error.message : String(error);
451
+ log.warn(`Failed to generate epic report: ${errorMessage}`);
452
+ log.debug("Epic report generation error details:", { error });
453
+ }
454
+ }
218
455
  /**
219
456
  * Generate a session summary without posting it
220
457
  *
@@ -240,7 +477,7 @@ var SessionSummaryService = class {
240
477
  } else {
241
478
  logger.debug("No compact summaries found in session transcript");
242
479
  }
243
- const recapData = await readRecapFile(worktreePath);
480
+ const recapData = await readRecapForWorktree(worktreePath);
244
481
  if (recapData) {
245
482
  logger.debug(`Found recap data (${recapData.length} chars)`);
246
483
  } else {
@@ -299,7 +536,8 @@ var SessionSummaryService = class {
299
536
  if (loomType === "branch") {
300
537
  return false;
301
538
  }
302
- const workflowConfig = loomType === "issue" ? (_a = settings.workflows) == null ? void 0 : _a.issue : (_b = settings.workflows) == null ? void 0 : _b.pr;
539
+ const effectiveType = loomType === "epic" ? "issue" : loomType;
540
+ const workflowConfig = effectiveType === "issue" ? (_a = settings.workflows) == null ? void 0 : _a.issue : (_b = settings.workflows) == null ? void 0 : _b.pr;
303
541
  return (workflowConfig == null ? void 0 : workflowConfig.generateSummary) ?? true;
304
542
  }
305
543
  /**
@@ -379,4 +617,4 @@ var SessionSummaryService = class {
379
617
  export {
380
618
  SessionSummaryService
381
619
  };
382
- //# sourceMappingURL=chunk-UXBVDD7U.js.map
620
+ //# sourceMappingURL=chunk-U2OPXZ6E.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/claude-transcript.ts","../src/lib/SwarmReportCollector.ts","../src/lib/SessionSummaryService.ts"],"sourcesContent":["/**\n * Claude Transcript Utilities\n *\n * Provides functions to read and parse Claude Code session transcript files\n * stored in ~/.claude/projects/. These transcripts contain the full conversation\n * history including compact summaries from when conversations were compacted.\n */\n\nimport { readFile } from 'fs/promises'\nimport { homedir } from 'os'\nimport { join } from 'path'\nimport { logger } from './logger.js'\n\n/**\n * Entry in a Claude Code JSONL transcript file\n */\nexport interface TranscriptEntry {\n\ttype: 'user' | 'assistant' | 'system' | 'file-history-snapshot' | 'queue-operation'\n\tsessionId?: string\n\tmessage?: { role: string; content: string | Array<{ type: string; text?: string }> }\n\tisCompactSummary?: boolean\n\tisVisibleInTranscriptOnly?: boolean\n\tsubtype?: string // 'compact_boundary' for compaction markers\n\tcontent?: string\n\ttimestamp?: string\n\tuuid?: string\n\tparentUuid?: string\n}\n\n/**\n * Get the Claude projects directory path encoding for a worktree path\n * Encoding: /Users/adam/Projects/foo_bar -> -Users-adam-Projects-foo-bar\n *\n * Claude Code encodes paths by replacing both '/' and '_' with '-'\n *\n * @param worktreePath - Absolute path to the worktree\n * @returns Encoded directory name for Claude projects\n */\nexport function getClaudeProjectPath(worktreePath: string): string {\n\t// Replace all '/' and '_' with '-' (matching Claude Code's encoding)\n\treturn worktreePath.replace(/[/_]/g, '-')\n}\n\n/**\n * Get the full path to the Claude projects directory\n * @returns Path to ~/.claude/projects/\n */\nexport function getClaudeProjectsDir(): string {\n\treturn join(homedir(), '.claude', 'projects')\n}\n\n/**\n * Find the session transcript file for a given worktree and session ID\n *\n * @param worktreePath - Absolute path to the worktree\n * @param sessionId - Session ID to find transcript for\n * @returns Full path to the transcript file, or null if not found\n */\nexport function findSessionTranscript(worktreePath: string, sessionId: string): string | null {\n\tconst projectsDir = getClaudeProjectsDir()\n\tconst projectDirName = getClaudeProjectPath(worktreePath)\n\tconst transcriptPath = join(projectsDir, projectDirName, `${sessionId}.jsonl`)\n\treturn transcriptPath\n}\n\n/**\n * Extract the content from a compact summary message\n * Handles both string content and array content formats\n */\nfunction extractMessageContent(message: TranscriptEntry['message']): string | null {\n\tif (!message) return null\n\n\tif (typeof message.content === 'string') {\n\t\treturn message.content\n\t}\n\n\tif (Array.isArray(message.content)) {\n\t\t// Concatenate all text elements\n\t\treturn message.content\n\t\t\t.filter((item) => item.type === 'text' && item.text)\n\t\t\t.map((item) => item.text)\n\t\t\t.join('\\n')\n\t}\n\n\treturn null\n}\n\n/**\n * Extract compact summaries from a session transcript file\n *\n * Returns empty array if file doesn't exist or no summaries found.\n * Each compact summary contains structured history of pre-compaction conversation.\n *\n * @param transcriptPath - Full path to the transcript JSONL file\n * @param maxSummaries - Maximum number of summaries to return (default 3)\n * @returns Array of compact summary content strings, newest first\n */\nexport async function extractCompactSummaries(\n\ttranscriptPath: string,\n\tmaxSummaries = 3\n): Promise<string[]> {\n\ttry {\n\t\tconst content = await readFile(transcriptPath, 'utf-8')\n\t\tconst lines = content.split('\\n').filter((line) => line.trim())\n\n\t\tconst summaries: string[] = []\n\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line) as TranscriptEntry\n\n\t\t\t\t// Look for compact summary entries\n\t\t\t\tif (entry.isCompactSummary === true && entry.message) {\n\t\t\t\t\tconst summaryContent = extractMessageContent(entry.message)\n\t\t\t\t\tif (summaryContent) {\n\t\t\t\t\t\tsummaries.push(summaryContent)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip malformed JSON lines\n\t\t\t\tlogger.debug('Skipping malformed JSONL line in transcript')\n\t\t\t}\n\t\t}\n\n\t\t// Return most recent summaries (they appear in order in the file)\n\t\t// Limit to maxSummaries\n\t\treturn summaries.slice(-maxSummaries)\n\t} catch (error) {\n\t\t// File not found or permission error - return empty array (graceful degradation)\n\t\tif (error instanceof Error && 'code' in error && error.code === 'ENOENT') {\n\t\t\tlogger.debug('Transcript file not found:', transcriptPath)\n\t\t} else {\n\t\t\tlogger.debug('Error reading transcript file:', error)\n\t\t}\n\t\treturn []\n\t}\n}\n\n/**\n * Read session transcript and extract compact summaries for summary generation\n *\n * This is the main entry point for SessionSummaryService to get pre-compaction\n * conversation context. It gracefully handles all error cases.\n *\n * @param worktreePath - Absolute path to the worktree\n * @param sessionId - Session ID to find transcript for\n * @param maxSummaries - Maximum number of summaries to return (default 3)\n * @returns Formatted string of compact summaries, or null if none found\n */\nexport async function readSessionContext(\n\tworktreePath: string,\n\tsessionId: string,\n\tmaxSummaries = 3\n): Promise<string | null> {\n\tconst transcriptPath = findSessionTranscript(worktreePath, sessionId)\n\tif (!transcriptPath) {\n\t\treturn null\n\t}\n\n\tlogger.debug(`Checking transcript at: ${transcriptPath}`)\n\n\tconst summaries = await extractCompactSummaries(transcriptPath, maxSummaries)\n\n\tif (summaries.length === 0) {\n\t\treturn null\n\t}\n\n\t// Format summaries with separators\n\t// Newest summaries are at the end, so we reverse to show newest first\n\tconst formattedSummaries = summaries\n\t\t.reverse()\n\t\t.map((summary, index) => {\n\t\t\tconst header =\n\t\t\t\tsummaries.length > 1\n\t\t\t\t\t? `### Compact Summary ${index + 1} of ${summaries.length}\\n\\n`\n\t\t\t\t\t: ''\n\t\t\treturn `${header}${summary}`\n\t\t})\n\t\t.join('\\n\\n---\\n\\n')\n\n\treturn formattedSummaries\n}\n","/**\n * SwarmReportCollector: Aggregates implementation data from child issues and recap files\n *\n * This service collects structured per-child implementation data for use in\n * epic swarm implementation reports. It gathers:\n * 1. Issue title and implementation comments from the issue tracker\n * 2. Recap file data (decisions, risks, assumptions, insights) from disk\n */\n\nimport fs from 'fs-extra'\nimport { IssueManagementProviderFactory } from '../mcp/IssueManagementProviderFactory.js'\nimport type { IssueManagementProvider, IssueProvider } from '../mcp/types.js'\nimport type { IloomSettings } from './SettingsManager.js'\nimport { MetadataManager } from './MetadataManager.js'\nimport { resolveRecapFilePath } from '../utils/mcp.js'\nimport type { RecapFile, RecapOutput } from '../mcp/recap-types.js'\nimport { formatRecapMarkdown } from '../utils/recap-formatter.js'\nimport { getLogger } from '../utils/logger-context.js'\n\nexport interface ChildImplementationData {\n\tissueNumber: string\n\ttitle: string\n\tstatus: 'success' | 'failure' | 'missing'\n\timplementationComment: string | null\n\trecapMarkdown: string | null\n}\n\n// Concurrency limit for API calls\nconst CONCURRENCY_LIMIT = 5\n\n/**\n * Read and format the recap file for a worktree path.\n * Returns formatted recap markdown or null if not found/empty/error.\n * Shared between SwarmReportCollector and SessionSummaryService.\n */\nexport async function readRecapForWorktree(worktreePath: string): Promise<string | null> {\n\ttry {\n\t\tconst filePath = resolveRecapFilePath(worktreePath)\n\t\tif (!(await fs.pathExists(filePath))) return null\n\n\t\tconst content = await fs.readFile(filePath, 'utf8')\n\t\tconst recap = JSON.parse(content) as RecapFile\n\n\t\tconst hasGoal = recap.goal !== null && recap.goal !== undefined\n\t\tconst hasComplexity = recap.complexity !== null && recap.complexity !== undefined\n\t\tconst hasEntries = Array.isArray(recap.entries) && recap.entries.length > 0\n\t\tconst hasArtifacts = Array.isArray(recap.artifacts) && recap.artifacts.length > 0\n\t\tconst hasContent = hasGoal || hasComplexity || hasEntries || hasArtifacts\n\n\t\tif (!hasContent) return null\n\n\t\tconst recapOutput: RecapOutput = {\n\t\t\tfilePath,\n\t\t\tgoal: recap.goal ?? null,\n\t\t\tcomplexity: recap.complexity ?? null,\n\t\t\tentries: recap.entries ?? [],\n\t\t\tartifacts: recap.artifacts ?? [],\n\t\t}\n\t\treturn formatRecapMarkdown(recapOutput)\n\t} catch {\n\t\t// Graceful degradation - return null on any error\n\t\treturn null\n\t}\n}\n\nexport class SwarmReportCollector {\n\tprivate metadataManager: MetadataManager\n\n\tconstructor(metadataManager?: MetadataManager) {\n\t\tthis.metadataManager = metadataManager ?? new MetadataManager()\n\t}\n\n\t/**\n\t * Collect implementation data from child issues and their recap files.\n\t *\n\t * @param childIssueNumbers - Array of child issue identifiers to collect data for\n\t * @param epicWorktreePath - Worktree path of the parent epic loom\n\t * @param settings - IloomSettings for configuring the issue management provider\n\t * @returns Array of ChildImplementationData, one per child\n\t */\n\tasync collectChildData(\n\t\tchildIssueNumbers: string[],\n\t\tepicWorktreePath: string,\n\t\tsettings: IloomSettings\n\t): Promise<ChildImplementationData[]> {\n\t\tif (childIssueNumbers.length === 0) return []\n\n\t\t// Create issue management provider from settings\n\t\tconst providerType = (settings.issueManagement?.provider ?? 'github') as IssueProvider\n\t\tconst provider = IssueManagementProviderFactory.create(providerType, settings)\n\n\t\t// Build map of issueNumber -> worktreePath from loom metadata\n\t\tconst worktreeMap = await this.buildChildWorktreeMap(childIssueNumbers, epicWorktreePath)\n\n\t\t// Process children in batches using Promise.allSettled for bounded concurrency\n\t\tconst results: ChildImplementationData[] = []\n\t\tfor (let i = 0; i < childIssueNumbers.length; i += CONCURRENCY_LIMIT) {\n\t\t\tconst batch = childIssueNumbers.slice(i, i + CONCURRENCY_LIMIT)\n\t\t\tconst batchResults = await Promise.allSettled(\n\t\t\t\tbatch.map(num => this.fetchChildData(num, provider, worktreeMap))\n\t\t\t)\n\t\t\tfor (let j = 0; j < batchResults.length; j++) {\n\t\t\t\tconst result = batchResults[j]\n\t\t\t\tconst issueNumber = batch[j] ?? '?'\n\t\t\t\tif (!result) continue\n\t\t\t\tif (result.status === 'fulfilled') {\n\t\t\t\t\tresults.push(result.value)\n\t\t\t\t} else {\n\t\t\t\t\t// fetchChildData catches internally; this branch is a defensive fallback\n\t\t\t\t\tgetLogger().error(\n\t\t\t\t\t\t`SwarmReportCollector: unexpected rejection for issue ${issueNumber}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`\n\t\t\t\t\t)\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\tissueNumber,\n\t\t\t\t\t\ttitle: 'Unknown',\n\t\t\t\t\t\tstatus: 'failure',\n\t\t\t\t\t\timplementationComment: null,\n\t\t\t\t\t\trecapMarkdown: null,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn results\n\t}\n\n\t/**\n\t * Build a map of issueNumber -> worktreePath by scanning all loom metadata\n\t * for children whose parentLoom.worktreePath matches the epic worktree.\n\t */\n\tprivate async buildChildWorktreeMap(\n\t\tchildIssueNumbers: string[],\n\t\tepicWorktreePath: string\n\t): Promise<Map<string, string>> {\n\t\tconst map = new Map<string, string>()\n\t\ttry {\n\t\t\tconst allMetadata = await this.metadataManager.listAllMetadata()\n\t\t\tfor (const metadata of allMetadata) {\n\t\t\t\t// Only consider child looms that belong to this epic\n\t\t\t\tif (metadata.parentLoom?.worktreePath !== epicWorktreePath) continue\n\t\t\t\tif (!metadata.worktreePath) continue\n\n\t\t\t\t// Map each issue number associated with this child loom\n\t\t\t\tfor (const issueNum of metadata.issue_numbers) {\n\t\t\t\t\tif (childIssueNumbers.includes(issueNum)) {\n\t\t\t\t\t\tmap.set(issueNum, metadata.worktreePath)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tgetLogger().debug(\n\t\t\t\t`SwarmReportCollector: failed to build worktree map: ${error instanceof Error ? error.message : String(error)}`\n\t\t\t)\n\t\t}\n\t\treturn map\n\t}\n\n\t/**\n\t * Fetch issue data and recap for a single child.\n\t * Returns status='failure' on API errors, status='missing' when issue has no comments.\n\t */\n\tprivate async fetchChildData(\n\t\tissueNumber: string,\n\t\tprovider: IssueManagementProvider,\n\t\tworktreeMap: Map<string, string>\n\t): Promise<ChildImplementationData> {\n\t\ttry {\n\t\t\tconst issue = await provider.getIssue({ number: issueNumber, includeComments: true })\n\n\t\t\tconst comments = issue.comments ?? []\n\t\t\tconst implementationComment = this.extractImplementationComment(comments)\n\n\t\t\tconst worktreePath = worktreeMap.get(issueNumber) ?? null\n\t\t\tconst recapMarkdown = worktreePath ? await this.readChildRecap(worktreePath) : null\n\n\t\t\t// Determine status based on issue state and comments:\n\t\t\t// - No comments at all: 'missing' (child may not have started)\n\t\t\t// - Issue closed (state includes 'closed' or 'done'): 'success' (completed normally)\n\t\t\t// - Issue still open with comments: 'failure' (child started but didn't finish)\n\t\t\tlet status: 'success' | 'failure' | 'missing'\n\t\t\tif (comments.length === 0) {\n\t\t\t\tstatus = 'missing'\n\t\t\t} else {\n\t\t\t\tconst issueState = issue.state.toLowerCase()\n\t\t\t\tstatus = (issueState === 'closed' || issueState === 'done') ? 'success' : 'failure'\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tissueNumber,\n\t\t\t\ttitle: issue.title,\n\t\t\t\tstatus,\n\t\t\t\timplementationComment,\n\t\t\t\trecapMarkdown,\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tgetLogger().debug(\n\t\t\t\t`SwarmReportCollector: failed to fetch data for issue ${issueNumber}: ${error instanceof Error ? error.message : String(error)}`\n\t\t\t)\n\t\t\treturn {\n\t\t\t\tissueNumber,\n\t\t\t\ttitle: 'Unknown',\n\t\t\t\tstatus: 'failure',\n\t\t\t\timplementationComment: null,\n\t\t\t\trecapMarkdown: null,\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Extract the implementation comment from an issue's comment list.\n\t * Prefers comments that contain implementation markers; falls back to the last comment.\n\t */\n\tprivate extractImplementationComment(\n\t\tcomments: Array<{ id: string; body: string; author: unknown; createdAt: string; [key: string]: unknown }>\n\t): string | null {\n\t\tif (comments.length === 0) return null\n\n\t\t// Prefer a comment with clear implementation markers\n\t\tconst implementationMarkers = [\n\t\t\t'implementation complete',\n\t\t\t'# implementation',\n\t\t\t'## summary',\n\t\t\t'changes made',\n\t\t\t'validation results',\n\t\t]\n\n\t\tfor (let i = comments.length - 1; i >= 0; i--) {\n\t\t\tconst comment = comments[i]\n\t\t\tif (!comment) continue\n\t\t\tconst body = comment.body.toLowerCase()\n\t\t\tif (implementationMarkers.some(marker => body.includes(marker))) {\n\t\t\t\treturn comment.body\n\t\t\t}\n\t\t}\n\n\t\t// Fall back to the last comment\n\t\tconst lastComment = comments[comments.length - 1]\n\t\treturn lastComment ? lastComment.body : null\n\t}\n\n\t/**\n\t * Read and format the recap file for a child worktree path.\n\t * Returns null if the recap file is missing or cannot be read.\n\t */\n\tprivate async readChildRecap(worktreePath: string): Promise<string | null> {\n\t\treturn readRecapForWorktree(worktreePath)\n\t}\n}\n","/**\n * SessionSummaryService: Generates and posts Claude session summaries\n *\n * This service orchestrates:\n * 1. Reading session metadata to get session ID\n * 2. Loading and processing the session-summary prompt template\n * 3. Invoking Claude headless to generate the summary\n * 4. Posting the summary as a comment to the issue/PR\n */\n\nimport { logger } from '../utils/logger.js'\nimport { getLogger } from '../utils/logger-context.js'\nimport { launchClaude, generateDeterministicSessionId } from '../utils/claude.js'\nimport { readSessionContext } from '../utils/claude-transcript.js'\nimport { PromptTemplateManager } from './PromptTemplateManager.js'\nimport { MetadataManager } from './MetadataManager.js'\nimport { SettingsManager, type IloomSettings } from './SettingsManager.js'\nimport { IssueManagementProviderFactory } from '../mcp/IssueManagementProviderFactory.js'\nimport type { IssueProvider } from '../mcp/types.js'\nimport { VCSProviderFactory } from './VCSProviderFactory.js'\nimport { hasMultipleRemotes } from '../utils/remote.js'\nimport { SwarmReportCollector, readRecapForWorktree, type ChildImplementationData } from './SwarmReportCollector.js'\nimport { TelemetryService } from './TelemetryService.js'\n\nconst GITHUB_COMMENT_MAX_LENGTH = 65_536\n\n/**\n * Input for generating and posting a session summary\n */\nexport interface SessionSummaryInput {\n\tworktreePath: string\n\tissueNumber: string | number\n\tbranchName: string\n\tloomType: 'issue' | 'pr' | 'branch' | 'epic'\n\t/** Optional PR number - when provided, summary is posted to the PR instead of the issue */\n\tprNumber?: number\n}\n\n/**\n * Result from generating a session summary\n */\nexport interface SessionSummaryResult {\n\tsummary: string\n\tsessionId: string\n}\n\n/**\n * Input for generating and posting an epic implementation report\n */\nexport interface EpicReportInput {\n\tworktreePath: string\n\tepicIssueNumber: string | number\n\tchildIssueNumbers: string[]\n\tepicTitle: string\n\t/** Optional PR number - when provided, report is posted to the PR instead of the issue */\n\tprNumber?: number\n}\n\n/**\n * Service that generates and posts Claude session summaries to issues\n */\nexport class SessionSummaryService {\n\tprivate templateManager: PromptTemplateManager\n\treadonly metadataManager: MetadataManager\n\tprivate settingsManager: SettingsManager\n\tprivate swarmReportCollector: SwarmReportCollector\n\n\tconstructor(\n\t\ttemplateManager?: PromptTemplateManager,\n\t\tmetadataManager?: MetadataManager,\n\t\tsettingsManager?: SettingsManager,\n\t\tswarmReportCollector?: SwarmReportCollector\n\t) {\n\t\tthis.templateManager = templateManager ?? new PromptTemplateManager()\n\t\tthis.metadataManager = metadataManager ?? new MetadataManager()\n\t\tthis.settingsManager = settingsManager ?? new SettingsManager()\n\t\tthis.swarmReportCollector = swarmReportCollector ?? new SwarmReportCollector()\n\t}\n\n\t/**\n\t * Generate and post a session summary to the issue\n\t *\n\t * Non-blocking: Catches all errors and logs warnings instead of throwing\n\t * This ensures the finish workflow continues even if summary generation fails\n\t */\n\tasync generateAndPostSummary(input: SessionSummaryInput): Promise<void> {\n\t\ttry {\n\t\t\t// 1. Skip for branch type (no issue to comment on)\n\t\t\tif (input.loomType === 'branch') {\n\t\t\t\tlogger.debug('Skipping session summary: branch type has no associated issue')\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// 2. Read metadata to get sessionId, or generate deterministically\n\t\t\tconst metadata = await this.metadataManager.readMetadata(input.worktreePath)\n\t\t\tconst sessionId = metadata?.sessionId ?? generateDeterministicSessionId(input.worktreePath)\n\n\t\t\t// 3. Load settings to check generateSummary config\n\t\t\tconst settings = await this.settingsManager.loadSettings(input.worktreePath)\n\t\t\tif (!this.shouldGenerateSummary(input.loomType, settings)) {\n\t\t\t\tlogger.debug(`Skipping session summary: generateSummary is disabled for ${input.loomType} workflow`)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tlogger.info('Generating session summary...')\n\n\t\t\t// 4. Try to read compact summaries from session transcript for additional context\n\t\t\tlogger.debug(`Looking for session transcript with sessionId: ${sessionId}`)\n\t\t\tconst compactSummaries = await readSessionContext(input.worktreePath, sessionId)\n\t\t\tif (compactSummaries) {\n\t\t\t\tlogger.debug(`Found compact summaries (${compactSummaries.length} chars)`)\n\t\t\t} else {\n\t\t\t\tlogger.debug('No compact summaries found in session transcript')\n\t\t\t}\n\n\t\t\t// 5. Try to read recap data for high-signal context\n\t\t\tconst recapData = await readRecapForWorktree(input.worktreePath)\n\t\t\tif (recapData) {\n\t\t\t\tlogger.debug(`Found recap data (${recapData.length} chars)`)\n\t\t\t} else {\n\t\t\t\tlogger.debug('No recap data found')\n\t\t\t}\n\n\t\t\t// 6. Load and process the session-summary template\n\t\t\tconst prompt = await this.templateManager.getPrompt('session-summary', {\n\t\t\t\tISSUE_NUMBER: String(input.issueNumber),\n\t\t\t\tBRANCH_NAME: input.branchName,\n\t\t\t\tLOOM_TYPE: input.loomType,\n\t\t\t\tCOMPACT_SUMMARIES: compactSummaries ?? '',\n\t\t\t\tRECAP_DATA: recapData ?? '',\n\t\t\t})\n\n\t\t\tlogger.debug('Session summary prompt:\\n' + prompt)\n\n\t\t\t// 7. Invoke Claude headless to generate summary\n\t\t\t// Use --resume with session ID so Claude knows which conversation to summarize\n\t\t\tconst summaryModel = this.settingsManager.getSummaryModel(settings)\n\t\t\tconst summaryResult = await launchClaude(prompt, {\n\t\t\t\theadless: true,\n\t\t\t\tmodel: summaryModel,\n\t\t\t\tsessionId: sessionId, // Resume this session so Claude has conversation context\n\t\t\t\tnoSessionPersistence: true, // Don't persist new data after generating summary\n\t\t\t})\n\n\t\t\tif (!summaryResult || typeof summaryResult !== 'string' || summaryResult.trim() === '') {\n\t\t\t\tlogger.warn('Session summary generation returned empty result')\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst summary = summaryResult.trim()\n\n\t\t\t// 8. Skip posting if summary is too short (likely failed generation)\n\t\t\tif (summary.length < 100) {\n\t\t\t\tlogger.warn('Session summary too short, skipping post')\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// 9. Post summary to issue or PR (PR takes priority when prNumber is provided)\n\t\t\tawait this.postSummaryToIssue(input.issueNumber, summary, settings, input.worktreePath, input.prNumber)\n\n\t\t\tconst targetDescription = input.prNumber ? `PR #${input.prNumber}` : 'issue'\n\t\t\tlogger.success(`Session summary posted to ${targetDescription}`)\n\t\t} catch (error) {\n\t\t\t// Non-blocking: Log warning but don't throw\n\t\t\tconst errorMessage = error instanceof Error ? error.message : String(error)\n\t\t\tlogger.warn(`Failed to generate session summary: ${errorMessage}`)\n\t\t\tlogger.debug('Session summary generation error details:', { error })\n\t\t}\n\t}\n\n\t/**\n\t * Format child implementation data as readable markdown for the epic report template.\n\t * This replaces raw JSON.stringify so the AI gets structured, readable input.\n\t */\n\tprivate formatChildDataAsMarkdown(childData: ChildImplementationData[]): string {\n\t\treturn childData.map(child => {\n\t\t\tconst sections: string[] = [\n\t\t\t\t`### Child #${child.issueNumber}: ${child.title}`,\n\t\t\t\t`**Status:** ${child.status}`,\n\t\t\t]\n\n\t\t\tif (child.implementationComment) {\n\t\t\t\tsections.push('', '**Implementation Summary:**', child.implementationComment)\n\t\t\t}\n\n\t\t\tif (child.recapMarkdown) {\n\t\t\t\tsections.push('', '**Recap (decisions, risks, insights):**', child.recapMarkdown)\n\t\t\t}\n\n\t\t\tif (!child.implementationComment && !child.recapMarkdown) {\n\t\t\t\tsections.push('', '*No implementation data available for this child.*')\n\t\t\t}\n\n\t\t\treturn sections.join('\\n')\n\t\t}).join('\\n\\n---\\n\\n')\n\t}\n\n\t/**\n\t * Truncate a report to GitHub's comment size limit, appending a notice if truncated\n\t */\n\tprivate truncateReport(report: string): string {\n\t\tif (report.length <= GITHUB_COMMENT_MAX_LENGTH) return report\n\t\tconst truncationNotice = '\\n\\n---\\n*Report truncated due to GitHub comment size limit.*'\n\t\tconst maxContentLength = GITHUB_COMMENT_MAX_LENGTH - truncationNotice.length\n\t\treturn report.slice(0, maxContentLength) + truncationNotice\n\t}\n\n\t/**\n\t * Generate and post an epic implementation report to the issue or PR\n\t *\n\t * Non-blocking: Catches all errors and logs warnings instead of throwing\n\t * This ensures the finish workflow continues even if report generation fails\n\t */\n\tasync generateAndPostEpicReport(input: EpicReportInput): Promise<void> {\n\t\tconst log = getLogger()\n\t\ttry {\n\t\t\t// 1. Load settings, check generateSummary\n\t\t\tconst settings = await this.settingsManager.loadSettings(input.worktreePath)\n\t\t\tif (!this.shouldGenerateSummary('epic', settings)) {\n\t\t\t\tlog.debug('Skipping epic report: generateSummary is disabled')\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tlog.info('Generating epic implementation report...')\n\n\t\t\t// 2. Collect child data via SwarmReportCollector\n\t\t\tconst childData = await this.swarmReportCollector.collectChildData(\n\t\t\t\tinput.childIssueNumbers, input.worktreePath, settings\n\t\t\t)\n\n\t\t\t// 3. Load epic-report template with variables\n\t\t\tconst totalSucceeded = childData.filter(c => c.status === 'success').length\n\t\t\tconst totalFailed = childData.filter(c => c.status === 'failure').length\n\t\t\tconst prompt = await this.templateManager.getPrompt('epic-report', {\n\t\t\t\tEPIC_NUMBER: String(input.epicIssueNumber),\n\t\t\t\tEPIC_TITLE: input.epicTitle,\n\t\t\t\tCHILD_DATA: this.formatChildDataAsMarkdown(childData),\n\t\t\t\tTOTAL_CHILDREN: String(childData.length),\n\t\t\t\tTOTAL_SUCCEEDED: String(totalSucceeded),\n\t\t\t\tTOTAL_FAILED: String(totalFailed),\n\t\t\t})\n\n\t\t\tlog.debug('Epic report prompt:\\n' + prompt)\n\n\t\t\t// 4. Invoke Claude headless to generate report\n\t\t\tconst summaryModel = this.settingsManager.getSummaryModel(settings)\n\t\t\tconst reportResult = await launchClaude(prompt, {\n\t\t\t\theadless: true,\n\t\t\t\tmodel: summaryModel,\n\t\t\t})\n\n\t\t\tif (!reportResult || typeof reportResult !== 'string' || reportResult.trim() === '') {\n\t\t\t\tlog.warn('Epic report generation returned empty result')\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// 5. Truncate if needed, then post\n\t\t\tconst report = this.truncateReport(reportResult.trim())\n\t\t\tawait this.postSummaryToIssue(\n\t\t\t\tinput.epicIssueNumber, report, settings, input.worktreePath, input.prNumber\n\t\t\t)\n\n\t\t\tconst target = input.prNumber ? `PR #${input.prNumber}` : 'issue'\n\t\t\tlog.success(`Epic implementation report posted to ${target}`)\n\n\t\t\t// Track telemetry for epic report generation\n\t\t\ttry {\n\t\t\t\tTelemetryService.getInstance().track('epic.report_generated', {\n\t\t\t\t\ttotal_children: childData.length,\n\t\t\t\t\tsucceeded: totalSucceeded,\n\t\t\t\t\tfailed: totalFailed,\n\t\t\t\t})\n\t\t\t} catch (telemetryError) {\n\t\t\t\tlog.debug(`Telemetry tracking failed: ${telemetryError instanceof Error ? telemetryError.message : String(telemetryError)}`)\n\t\t\t}\n\t\t} catch (error) {\n\t\t\t// Non-blocking: log warning but don't throw\n\t\t\tconst errorMessage = error instanceof Error ? error.message : String(error)\n\t\t\tlog.warn(`Failed to generate epic report: ${errorMessage}`)\n\t\t\tlog.debug('Epic report generation error details:', { error })\n\t\t}\n\t}\n\n\t/**\n\t * Generate a session summary without posting it\n\t *\n\t * This method is useful for previewing the summary or for use by CLI commands\n\t * that want to display the summary before optionally posting it.\n\t *\n\t * @param worktreePath - Path to the worktree\n\t * @param branchName - Name of the branch\n\t * @param loomType - Type of loom ('issue' | 'pr' | 'branch')\n\t * @param issueNumber - Issue or PR number (optional, for template variables)\n\t * @returns The generated summary and session ID\n\t * @throws Error if Claude invocation fails\n\t */\n\tasync generateSummary(\n\t\tworktreePath: string,\n\t\tbranchName: string,\n\t\tloomType: 'issue' | 'pr' | 'branch' | 'epic',\n\t\tissueNumber?: string | number\n\t): Promise<SessionSummaryResult> {\n\t\t// 1. Read metadata or generate deterministic session ID\n\t\tconst metadata = await this.metadataManager.readMetadata(worktreePath)\n\t\tconst sessionId = metadata?.sessionId ?? generateDeterministicSessionId(worktreePath)\n\n\t\t// 2. Load settings for model configuration\n\t\tconst settings = await this.settingsManager.loadSettings(worktreePath)\n\n\t\tlogger.info('Generating session summary...')\n\n\t\t// 3. Try to read compact summaries from session transcript for additional context\n\t\tlogger.debug(`Looking for session transcript with sessionId: ${sessionId}`)\n\t\tconst compactSummaries = await readSessionContext(worktreePath, sessionId)\n\t\tif (compactSummaries) {\n\t\t\tlogger.debug(`Found compact summaries (${compactSummaries.length} chars)`)\n\t\t} else {\n\t\t\tlogger.debug('No compact summaries found in session transcript')\n\t\t}\n\n\t\t// 4. Try to read recap data for high-signal context\n\t\tconst recapData = await readRecapForWorktree(worktreePath)\n\t\tif (recapData) {\n\t\t\tlogger.debug(`Found recap data (${recapData.length} chars)`)\n\t\t} else {\n\t\t\tlogger.debug('No recap data found')\n\t\t}\n\n\t\t// 5. Load and process the session-summary template\n\t\tconst prompt = await this.templateManager.getPrompt('session-summary', {\n\t\t\tISSUE_NUMBER: issueNumber !== undefined ? String(issueNumber) : '',\n\t\t\tBRANCH_NAME: branchName,\n\t\t\tLOOM_TYPE: loomType,\n\t\t\tCOMPACT_SUMMARIES: compactSummaries ?? '',\n\t\t\tRECAP_DATA: recapData ?? '',\n\t\t})\n\n\t\tlogger.debug('Session summary prompt:\\n' + prompt)\n\n\t\t// 6. Invoke Claude headless to generate summary\n\t\tconst summaryModel = this.settingsManager.getSummaryModel(settings)\n\t\tconst summaryResult = await launchClaude(prompt, {\n\t\t\theadless: true,\n\t\t\tmodel: summaryModel,\n\t\t\tsessionId: sessionId,\n\t\t\tnoSessionPersistence: true, // Don't persist new data after generating summary\n\t\t})\n\n\t\tif (!summaryResult || typeof summaryResult !== 'string' || summaryResult.trim() === '') {\n\t\t\tthrow new Error('Session summary generation returned empty result')\n\t\t}\n\n\t\tconst summary = summaryResult.trim()\n\n\t\t// 7. Check if summary is too short (likely failed generation)\n\t\tif (summary.length < 100) {\n\t\t\tthrow new Error('Session summary too short - generation may have failed')\n\t\t}\n\n\t\treturn {\n\t\t\tsummary,\n\t\t\tsessionId: sessionId,\n\t\t}\n\t}\n\n\t/**\n\t * Post a summary to an issue (used by both generateAndPostSummary and CLI commands)\n\t *\n\t * @param issueNumber - Issue or PR number to post to\n\t * @param summary - The summary text to post\n\t * @param worktreePath - Path to worktree for loading settings (optional)\n\t */\n\tasync postSummary(\n\t\tissueNumber: string | number,\n\t\tsummary: string,\n\t\tworktreePath?: string,\n\t\tprNumber?: number\n\t): Promise<void> {\n\t\tconst settings = await this.settingsManager.loadSettings(worktreePath)\n\t\tawait this.postSummaryToIssue(issueNumber, summary, settings, worktreePath ?? process.cwd(), prNumber)\n\t\tconst target = prNumber ? `PR #${prNumber}` : 'issue'\n\t\tlogger.success(`Session summary posted to ${target}`)\n\t}\n\n\t/**\n\t * Determine if summary should be generated based on loom type and settings\n\t *\n\t * @param loomType - The type of loom being finished\n\t * @param settings - The loaded iloom settings\n\t * @returns true if summary should be generated\n\t */\n\tshouldGenerateSummary(\n\t\tloomType: 'issue' | 'pr' | 'branch' | 'epic',\n\t\tsettings: IloomSettings\n\t): boolean {\n\t\t// Branch type never generates summaries (no issue to comment on)\n\t\tif (loomType === 'branch') {\n\t\t\treturn false\n\t\t}\n\n\t\t// Epic uses issue workflow config (epics are issue-based)\n\t\tconst effectiveType = loomType === 'epic' ? 'issue' : loomType\n\n\t\t// Get workflow-specific config\n\t\tconst workflowConfig =\n\t\t\teffectiveType === 'issue'\n\t\t\t\t? settings.workflows?.issue\n\t\t\t\t: settings.workflows?.pr\n\n\t\t// Default to true if not explicitly set (for issue and pr types)\n\t\treturn workflowConfig?.generateSummary ?? true\n\t}\n\n\t/**\n\t * Apply attribution footer to summary based on settings\n\t *\n\t * @param summary - The summary text\n\t * @param worktreePath - Path to worktree for loading settings and detecting remotes\n\t * @returns Summary with attribution footer if applicable\n\t */\n\tasync applyAttribution(summary: string, worktreePath: string): Promise<string> {\n\t\tconst settings = await this.settingsManager.loadSettings(worktreePath)\n\t\treturn this.applyAttributionWithSettings(summary, settings, worktreePath)\n\t}\n\n\t/**\n\t * Apply attribution footer to summary based on provided settings\n\t *\n\t * @param summary - The summary text\n\t * @param settings - The loaded iloom settings\n\t * @param worktreePath - Path to worktree for detecting remotes\n\t * @returns Summary with attribution footer if applicable\n\t */\n\tasync applyAttributionWithSettings(\n\t\tsummary: string,\n\t\tsettings: IloomSettings,\n\t\tworktreePath: string\n\t): Promise<string> {\n\t\tconst attributionSetting = settings.attribution ?? 'upstreamOnly'\n\t\tlogger.debug(`Attribution setting from config: ${settings.attribution}`)\n\t\tlogger.debug(`Attribution setting (with default): ${attributionSetting}`)\n\n\t\tlet shouldShowAttribution = false\n\t\tif (attributionSetting === 'on') {\n\t\t\tshouldShowAttribution = true\n\t\t\tlogger.debug('Attribution: always on')\n\t\t} else if (attributionSetting === 'upstreamOnly') {\n\t\t\t// Only show attribution when contributing to external repos (multiple remotes)\n\t\t\tshouldShowAttribution = await hasMultipleRemotes(worktreePath)\n\t\t\tlogger.debug(`Attribution: upstreamOnly, hasMultipleRemotes=${shouldShowAttribution}`)\n\t\t} else {\n\t\t\tlogger.debug('Attribution: off')\n\t\t}\n\t\t// 'off' keeps shouldShowAttribution = false\n\n\t\tlogger.debug(`Should show attribution: ${shouldShowAttribution}`)\n\t\tif (shouldShowAttribution) {\n\t\t\tlogger.debug('Attribution footer appended to summary')\n\t\t\treturn `${summary}\\n\\n---\\n*Generated with 🤖❤️ by [iloom.ai](https://iloom.ai)*`\n\t\t}\n\n\t\treturn summary\n\t}\n\n\t/**\n\t * Post the summary as a comment to the issue or PR\n\t *\n\t * @param issueNumber - The issue number (used when prNumber is not provided)\n\t * @param summary - The summary text to post\n\t * @param settings - The loaded iloom settings\n\t * @param worktreePath - Path to worktree for attribution detection\n\t * @param prNumber - Optional PR number - when provided, posts to the PR instead\n\t */\n\tprivate async postSummaryToIssue(\n\t\tissueNumber: string | number,\n\t\tsummary: string,\n\t\tsettings: IloomSettings,\n\t\tworktreePath: string,\n\t\tprNumber?: number\n\t): Promise<void> {\n\t\t// Apply attribution if configured\n\t\tconst finalSummary = await this.applyAttributionWithSettings(summary, settings, worktreePath)\n\n\t\t// When prNumber is provided, post to the PR instead of the issue\n\t\tconst targetNumber = prNumber ?? issueNumber\n\t\tconst targetType = prNumber !== undefined ? 'pr' : 'issue'\n\n\t\tif (prNumber !== undefined) {\n\t\t\t// Try VCS provider first for PR comments (e.g., BitBucket)\n\t\t\tconst vcsProvider = VCSProviderFactory.create(settings)\n\t\t\tif (vcsProvider) {\n\t\t\t\tawait vcsProvider.createPRComment(prNumber, finalSummary, worktreePath)\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Fall through to issue management provider (GitHub)\n\t\t}\n\n\t\t// Use issue management provider (GitHub, Linear, Jira)\n\t\tconst providerType = prNumber !== undefined\n\t\t\t? 'github'\n\t\t\t: (settings.issueManagement?.provider ?? 'github') as IssueProvider\n\t\tconst provider = IssueManagementProviderFactory.create(providerType, settings)\n\n\t\t// Create the comment\n\t\tawait provider.createComment({\n\t\t\tnumber: String(targetNumber),\n\t\t\tbody: finalSummary,\n\t\t\ttype: targetType,\n\t\t})\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAQA,SAAS,gBAAgB;AACzB,SAAS,eAAe;AACxB,SAAS,YAAY;AA4Bd,SAAS,qBAAqB,cAA8B;AAElE,SAAO,aAAa,QAAQ,SAAS,GAAG;AACzC;AAMO,SAAS,uBAA+B;AAC9C,SAAO,KAAK,QAAQ,GAAG,WAAW,UAAU;AAC7C;AASO,SAAS,sBAAsB,cAAsB,WAAkC;AAC7F,QAAM,cAAc,qBAAqB;AACzC,QAAM,iBAAiB,qBAAqB,YAAY;AACxD,QAAM,iBAAiB,KAAK,aAAa,gBAAgB,GAAG,SAAS,QAAQ;AAC7E,SAAO;AACR;AAMA,SAAS,sBAAsB,SAAoD;AAClF,MAAI,CAAC,QAAS,QAAO;AAErB,MAAI,OAAO,QAAQ,YAAY,UAAU;AACxC,WAAO,QAAQ;AAAA,EAChB;AAEA,MAAI,MAAM,QAAQ,QAAQ,OAAO,GAAG;AAEnC,WAAO,QAAQ,QACb,OAAO,CAAC,SAAS,KAAK,SAAS,UAAU,KAAK,IAAI,EAClD,IAAI,CAAC,SAAS,KAAK,IAAI,EACvB,KAAK,IAAI;AAAA,EACZ;AAEA,SAAO;AACR;AAYA,eAAsB,wBACrB,gBACA,eAAe,GACK;AACpB,MAAI;AACH,UAAM,UAAU,MAAM,SAAS,gBAAgB,OAAO;AACtD,UAAM,QAAQ,QAAQ,MAAM,IAAI,EAAE,OAAO,CAAC,SAAS,KAAK,KAAK,CAAC;AAE9D,UAAM,YAAsB,CAAC;AAE7B,eAAW,QAAQ,OAAO;AACzB,UAAI;AACH,cAAM,QAAQ,KAAK,MAAM,IAAI;AAG7B,YAAI,MAAM,qBAAqB,QAAQ,MAAM,SAAS;AACrD,gBAAM,iBAAiB,sBAAsB,MAAM,OAAO;AAC1D,cAAI,gBAAgB;AACnB,sBAAU,KAAK,cAAc;AAAA,UAC9B;AAAA,QACD;AAAA,MACD,QAAQ;AAEP,eAAO,MAAM,6CAA6C;AAAA,MAC3D;AAAA,IACD;AAIA,WAAO,UAAU,MAAM,CAAC,YAAY;AAAA,EACrC,SAAS,OAAO;AAEf,QAAI,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS,UAAU;AACzE,aAAO,MAAM,8BAA8B,cAAc;AAAA,IAC1D,OAAO;AACN,aAAO,MAAM,kCAAkC,KAAK;AAAA,IACrD;AACA,WAAO,CAAC;AAAA,EACT;AACD;AAaA,eAAsB,mBACrB,cACA,WACA,eAAe,GACU;AACzB,QAAM,iBAAiB,sBAAsB,cAAc,SAAS;AACpE,MAAI,CAAC,gBAAgB;AACpB,WAAO;AAAA,EACR;AAEA,SAAO,MAAM,2BAA2B,cAAc,EAAE;AAExD,QAAM,YAAY,MAAM,wBAAwB,gBAAgB,YAAY;AAE5E,MAAI,UAAU,WAAW,GAAG;AAC3B,WAAO;AAAA,EACR;AAIA,QAAM,qBAAqB,UACzB,QAAQ,EACR,IAAI,CAAC,SAAS,UAAU;AACxB,UAAM,SACL,UAAU,SAAS,IAChB,uBAAuB,QAAQ,CAAC,OAAO,UAAU,MAAM;AAAA;AAAA,IACvD;AACJ,WAAO,GAAG,MAAM,GAAG,OAAO;AAAA,EAC3B,CAAC,EACA,KAAK,aAAa;AAEpB,SAAO;AACR;;;AC5KA,OAAO,QAAQ;AAmBf,IAAM,oBAAoB;AAO1B,eAAsB,qBAAqB,cAA8C;AACxF,MAAI;AACH,UAAM,WAAW,qBAAqB,YAAY;AAClD,QAAI,CAAE,MAAM,GAAG,WAAW,QAAQ,EAAI,QAAO;AAE7C,UAAM,UAAU,MAAM,GAAG,SAAS,UAAU,MAAM;AAClD,UAAM,QAAQ,KAAK,MAAM,OAAO;AAEhC,UAAM,UAAU,MAAM,SAAS,QAAQ,MAAM,SAAS;AACtD,UAAM,gBAAgB,MAAM,eAAe,QAAQ,MAAM,eAAe;AACxE,UAAM,aAAa,MAAM,QAAQ,MAAM,OAAO,KAAK,MAAM,QAAQ,SAAS;AAC1E,UAAM,eAAe,MAAM,QAAQ,MAAM,SAAS,KAAK,MAAM,UAAU,SAAS;AAChF,UAAM,aAAa,WAAW,iBAAiB,cAAc;AAE7D,QAAI,CAAC,WAAY,QAAO;AAExB,UAAM,cAA2B;AAAA,MAChC;AAAA,MACA,MAAM,MAAM,QAAQ;AAAA,MACpB,YAAY,MAAM,cAAc;AAAA,MAChC,SAAS,MAAM,WAAW,CAAC;AAAA,MAC3B,WAAW,MAAM,aAAa,CAAC;AAAA,IAChC;AACA,WAAO,oBAAoB,WAAW;AAAA,EACvC,QAAQ;AAEP,WAAO;AAAA,EACR;AACD;AAEO,IAAM,uBAAN,MAA2B;AAAA,EAGjC,YAAY,iBAAmC;AAC9C,SAAK,kBAAkB,mBAAmB,IAAI,gBAAgB;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,iBACL,mBACA,kBACA,UACqC;AApFvC;AAqFE,QAAI,kBAAkB,WAAW,EAAG,QAAO,CAAC;AAG5C,UAAM,iBAAgB,cAAS,oBAAT,mBAA0B,aAAY;AAC5D,UAAM,WAAW,+BAA+B,OAAO,cAAc,QAAQ;AAG7E,UAAM,cAAc,MAAM,KAAK,sBAAsB,mBAAmB,gBAAgB;AAGxF,UAAM,UAAqC,CAAC;AAC5C,aAAS,IAAI,GAAG,IAAI,kBAAkB,QAAQ,KAAK,mBAAmB;AACrE,YAAM,QAAQ,kBAAkB,MAAM,GAAG,IAAI,iBAAiB;AAC9D,YAAM,eAAe,MAAM,QAAQ;AAAA,QAClC,MAAM,IAAI,SAAO,KAAK,eAAe,KAAK,UAAU,WAAW,CAAC;AAAA,MACjE;AACA,eAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC7C,cAAM,SAAS,aAAa,CAAC;AAC7B,cAAM,cAAc,MAAM,CAAC,KAAK;AAChC,YAAI,CAAC,OAAQ;AACb,YAAI,OAAO,WAAW,aAAa;AAClC,kBAAQ,KAAK,OAAO,KAAK;AAAA,QAC1B,OAAO;AAEN,oBAAU,EAAE;AAAA,YACX,wDAAwD,WAAW,KAAK,OAAO,kBAAkB,QAAQ,OAAO,OAAO,UAAU,OAAO,OAAO,MAAM,CAAC;AAAA,UACvJ;AACA,kBAAQ,KAAK;AAAA,YACZ;AAAA,YACA,OAAO;AAAA,YACP,QAAQ;AAAA,YACR,uBAAuB;AAAA,YACvB,eAAe;AAAA,UAChB,CAAC;AAAA,QACF;AAAA,MACD;AAAA,IACD;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,sBACb,mBACA,kBAC+B;AApIjC;AAqIE,UAAM,MAAM,oBAAI,IAAoB;AACpC,QAAI;AACH,YAAM,cAAc,MAAM,KAAK,gBAAgB,gBAAgB;AAC/D,iBAAW,YAAY,aAAa;AAEnC,cAAI,cAAS,eAAT,mBAAqB,kBAAiB,iBAAkB;AAC5D,YAAI,CAAC,SAAS,aAAc;AAG5B,mBAAW,YAAY,SAAS,eAAe;AAC9C,cAAI,kBAAkB,SAAS,QAAQ,GAAG;AACzC,gBAAI,IAAI,UAAU,SAAS,YAAY;AAAA,UACxC;AAAA,QACD;AAAA,MACD;AAAA,IACD,SAAS,OAAO;AACf,gBAAU,EAAE;AAAA,QACX,uDAAuD,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,MAC9G;AAAA,IACD;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,eACb,aACA,UACA,aACmC;AACnC,QAAI;AACH,YAAM,QAAQ,MAAM,SAAS,SAAS,EAAE,QAAQ,aAAa,iBAAiB,KAAK,CAAC;AAEpF,YAAM,WAAW,MAAM,YAAY,CAAC;AACpC,YAAM,wBAAwB,KAAK,6BAA6B,QAAQ;AAExE,YAAM,eAAe,YAAY,IAAI,WAAW,KAAK;AACrD,YAAM,gBAAgB,eAAe,MAAM,KAAK,eAAe,YAAY,IAAI;AAM/E,UAAI;AACJ,UAAI,SAAS,WAAW,GAAG;AAC1B,iBAAS;AAAA,MACV,OAAO;AACN,cAAM,aAAa,MAAM,MAAM,YAAY;AAC3C,iBAAU,eAAe,YAAY,eAAe,SAAU,YAAY;AAAA,MAC3E;AAEA,aAAO;AAAA,QACN;AAAA,QACA,OAAO,MAAM;AAAA,QACb;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAAA,IACD,SAAS,OAAO;AACf,gBAAU,EAAE;AAAA,QACX,wDAAwD,WAAW,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,MAC/H;AACA,aAAO;AAAA,QACN;AAAA,QACA,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,uBAAuB;AAAA,QACvB,eAAe;AAAA,MAChB;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,6BACP,UACgB;AAChB,QAAI,SAAS,WAAW,EAAG,QAAO;AAGlC,UAAM,wBAAwB;AAAA,MAC7B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAEA,aAAS,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;AAC9C,YAAM,UAAU,SAAS,CAAC;AAC1B,UAAI,CAAC,QAAS;AACd,YAAM,OAAO,QAAQ,KAAK,YAAY;AACtC,UAAI,sBAAsB,KAAK,YAAU,KAAK,SAAS,MAAM,CAAC,GAAG;AAChE,eAAO,QAAQ;AAAA,MAChB;AAAA,IACD;AAGA,UAAM,cAAc,SAAS,SAAS,SAAS,CAAC;AAChD,WAAO,cAAc,YAAY,OAAO;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,eAAe,cAA8C;AAC1E,WAAO,qBAAqB,YAAY;AAAA,EACzC;AACD;;;AC9NA,IAAM,4BAA4B;AAqC3B,IAAM,wBAAN,MAA4B;AAAA,EAMlC,YACC,iBACA,iBACA,iBACA,sBACC;AACD,SAAK,kBAAkB,mBAAmB,IAAI,sBAAsB;AACpE,SAAK,kBAAkB,mBAAmB,IAAI,gBAAgB;AAC9D,SAAK,kBAAkB,mBAAmB,IAAI,gBAAgB;AAC9D,SAAK,uBAAuB,wBAAwB,IAAI,qBAAqB;AAAA,EAC9E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,uBAAuB,OAA2C;AACvE,QAAI;AAEH,UAAI,MAAM,aAAa,UAAU;AAChC,eAAO,MAAM,+DAA+D;AAC5E;AAAA,MACD;AAGA,YAAM,WAAW,MAAM,KAAK,gBAAgB,aAAa,MAAM,YAAY;AAC3E,YAAM,aAAY,qCAAU,cAAa,+BAA+B,MAAM,YAAY;AAG1F,YAAM,WAAW,MAAM,KAAK,gBAAgB,aAAa,MAAM,YAAY;AAC3E,UAAI,CAAC,KAAK,sBAAsB,MAAM,UAAU,QAAQ,GAAG;AAC1D,eAAO,MAAM,6DAA6D,MAAM,QAAQ,WAAW;AACnG;AAAA,MACD;AAEA,aAAO,KAAK,+BAA+B;AAG3C,aAAO,MAAM,kDAAkD,SAAS,EAAE;AAC1E,YAAM,mBAAmB,MAAM,mBAAmB,MAAM,cAAc,SAAS;AAC/E,UAAI,kBAAkB;AACrB,eAAO,MAAM,4BAA4B,iBAAiB,MAAM,SAAS;AAAA,MAC1E,OAAO;AACN,eAAO,MAAM,kDAAkD;AAAA,MAChE;AAGA,YAAM,YAAY,MAAM,qBAAqB,MAAM,YAAY;AAC/D,UAAI,WAAW;AACd,eAAO,MAAM,qBAAqB,UAAU,MAAM,SAAS;AAAA,MAC5D,OAAO;AACN,eAAO,MAAM,qBAAqB;AAAA,MACnC;AAGA,YAAM,SAAS,MAAM,KAAK,gBAAgB,UAAU,mBAAmB;AAAA,QACtE,cAAc,OAAO,MAAM,WAAW;AAAA,QACtC,aAAa,MAAM;AAAA,QACnB,WAAW,MAAM;AAAA,QACjB,mBAAmB,oBAAoB;AAAA,QACvC,YAAY,aAAa;AAAA,MAC1B,CAAC;AAED,aAAO,MAAM,8BAA8B,MAAM;AAIjD,YAAM,eAAe,KAAK,gBAAgB,gBAAgB,QAAQ;AAClE,YAAM,gBAAgB,MAAM,aAAa,QAAQ;AAAA,QAChD,UAAU;AAAA,QACV,OAAO;AAAA,QACP;AAAA;AAAA,QACA,sBAAsB;AAAA;AAAA,MACvB,CAAC;AAED,UAAI,CAAC,iBAAiB,OAAO,kBAAkB,YAAY,cAAc,KAAK,MAAM,IAAI;AACvF,eAAO,KAAK,kDAAkD;AAC9D;AAAA,MACD;AAEA,YAAM,UAAU,cAAc,KAAK;AAGnC,UAAI,QAAQ,SAAS,KAAK;AACzB,eAAO,KAAK,0CAA0C;AACtD;AAAA,MACD;AAGA,YAAM,KAAK,mBAAmB,MAAM,aAAa,SAAS,UAAU,MAAM,cAAc,MAAM,QAAQ;AAEtG,YAAM,oBAAoB,MAAM,WAAW,OAAO,MAAM,QAAQ,KAAK;AACrE,aAAO,QAAQ,6BAA6B,iBAAiB,EAAE;AAAA,IAChE,SAAS,OAAO;AAEf,YAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC1E,aAAO,KAAK,uCAAuC,YAAY,EAAE;AACjE,aAAO,MAAM,6CAA6C,EAAE,MAAM,CAAC;AAAA,IACpE;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,0BAA0B,WAA8C;AAC/E,WAAO,UAAU,IAAI,WAAS;AAC7B,YAAM,WAAqB;AAAA,QAC1B,cAAc,MAAM,WAAW,KAAK,MAAM,KAAK;AAAA,QAC/C,eAAe,MAAM,MAAM;AAAA,MAC5B;AAEA,UAAI,MAAM,uBAAuB;AAChC,iBAAS,KAAK,IAAI,+BAA+B,MAAM,qBAAqB;AAAA,MAC7E;AAEA,UAAI,MAAM,eAAe;AACxB,iBAAS,KAAK,IAAI,2CAA2C,MAAM,aAAa;AAAA,MACjF;AAEA,UAAI,CAAC,MAAM,yBAAyB,CAAC,MAAM,eAAe;AACzD,iBAAS,KAAK,IAAI,oDAAoD;AAAA,MACvE;AAEA,aAAO,SAAS,KAAK,IAAI;AAAA,IAC1B,CAAC,EAAE,KAAK,aAAa;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,QAAwB;AAC9C,QAAI,OAAO,UAAU,0BAA2B,QAAO;AACvD,UAAM,mBAAmB;AACzB,UAAM,mBAAmB,4BAA4B,iBAAiB;AACtE,WAAO,OAAO,MAAM,GAAG,gBAAgB,IAAI;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,0BAA0B,OAAuC;AACtE,UAAM,MAAM,UAAU;AACtB,QAAI;AAEH,YAAM,WAAW,MAAM,KAAK,gBAAgB,aAAa,MAAM,YAAY;AAC3E,UAAI,CAAC,KAAK,sBAAsB,QAAQ,QAAQ,GAAG;AAClD,YAAI,MAAM,mDAAmD;AAC7D;AAAA,MACD;AAEA,UAAI,KAAK,0CAA0C;AAGnD,YAAM,YAAY,MAAM,KAAK,qBAAqB;AAAA,QACjD,MAAM;AAAA,QAAmB,MAAM;AAAA,QAAc;AAAA,MAC9C;AAGA,YAAM,iBAAiB,UAAU,OAAO,OAAK,EAAE,WAAW,SAAS,EAAE;AACrE,YAAM,cAAc,UAAU,OAAO,OAAK,EAAE,WAAW,SAAS,EAAE;AAClE,YAAM,SAAS,MAAM,KAAK,gBAAgB,UAAU,eAAe;AAAA,QAClE,aAAa,OAAO,MAAM,eAAe;AAAA,QACzC,YAAY,MAAM;AAAA,QAClB,YAAY,KAAK,0BAA0B,SAAS;AAAA,QACpD,gBAAgB,OAAO,UAAU,MAAM;AAAA,QACvC,iBAAiB,OAAO,cAAc;AAAA,QACtC,cAAc,OAAO,WAAW;AAAA,MACjC,CAAC;AAED,UAAI,MAAM,0BAA0B,MAAM;AAG1C,YAAM,eAAe,KAAK,gBAAgB,gBAAgB,QAAQ;AAClE,YAAM,eAAe,MAAM,aAAa,QAAQ;AAAA,QAC/C,UAAU;AAAA,QACV,OAAO;AAAA,MACR,CAAC;AAED,UAAI,CAAC,gBAAgB,OAAO,iBAAiB,YAAY,aAAa,KAAK,MAAM,IAAI;AACpF,YAAI,KAAK,8CAA8C;AACvD;AAAA,MACD;AAGA,YAAM,SAAS,KAAK,eAAe,aAAa,KAAK,CAAC;AACtD,YAAM,KAAK;AAAA,QACV,MAAM;AAAA,QAAiB;AAAA,QAAQ;AAAA,QAAU,MAAM;AAAA,QAAc,MAAM;AAAA,MACpE;AAEA,YAAM,SAAS,MAAM,WAAW,OAAO,MAAM,QAAQ,KAAK;AAC1D,UAAI,QAAQ,wCAAwC,MAAM,EAAE;AAG5D,UAAI;AACH,yBAAiB,YAAY,EAAE,MAAM,yBAAyB;AAAA,UAC7D,gBAAgB,UAAU;AAAA,UAC1B,WAAW;AAAA,UACX,QAAQ;AAAA,QACT,CAAC;AAAA,MACF,SAAS,gBAAgB;AACxB,YAAI,MAAM,8BAA8B,0BAA0B,QAAQ,eAAe,UAAU,OAAO,cAAc,CAAC,EAAE;AAAA,MAC5H;AAAA,IACD,SAAS,OAAO;AAEf,YAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC1E,UAAI,KAAK,mCAAmC,YAAY,EAAE;AAC1D,UAAI,MAAM,yCAAyC,EAAE,MAAM,CAAC;AAAA,IAC7D;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAM,gBACL,cACA,YACA,UACA,aACgC;AAEhC,UAAM,WAAW,MAAM,KAAK,gBAAgB,aAAa,YAAY;AACrE,UAAM,aAAY,qCAAU,cAAa,+BAA+B,YAAY;AAGpF,UAAM,WAAW,MAAM,KAAK,gBAAgB,aAAa,YAAY;AAErE,WAAO,KAAK,+BAA+B;AAG3C,WAAO,MAAM,kDAAkD,SAAS,EAAE;AAC1E,UAAM,mBAAmB,MAAM,mBAAmB,cAAc,SAAS;AACzE,QAAI,kBAAkB;AACrB,aAAO,MAAM,4BAA4B,iBAAiB,MAAM,SAAS;AAAA,IAC1E,OAAO;AACN,aAAO,MAAM,kDAAkD;AAAA,IAChE;AAGA,UAAM,YAAY,MAAM,qBAAqB,YAAY;AACzD,QAAI,WAAW;AACd,aAAO,MAAM,qBAAqB,UAAU,MAAM,SAAS;AAAA,IAC5D,OAAO;AACN,aAAO,MAAM,qBAAqB;AAAA,IACnC;AAGA,UAAM,SAAS,MAAM,KAAK,gBAAgB,UAAU,mBAAmB;AAAA,MACtE,cAAc,gBAAgB,SAAY,OAAO,WAAW,IAAI;AAAA,MAChE,aAAa;AAAA,MACb,WAAW;AAAA,MACX,mBAAmB,oBAAoB;AAAA,MACvC,YAAY,aAAa;AAAA,IAC1B,CAAC;AAED,WAAO,MAAM,8BAA8B,MAAM;AAGjD,UAAM,eAAe,KAAK,gBAAgB,gBAAgB,QAAQ;AAClE,UAAM,gBAAgB,MAAM,aAAa,QAAQ;AAAA,MAChD,UAAU;AAAA,MACV,OAAO;AAAA,MACP;AAAA,MACA,sBAAsB;AAAA;AAAA,IACvB,CAAC;AAED,QAAI,CAAC,iBAAiB,OAAO,kBAAkB,YAAY,cAAc,KAAK,MAAM,IAAI;AACvF,YAAM,IAAI,MAAM,kDAAkD;AAAA,IACnE;AAEA,UAAM,UAAU,cAAc,KAAK;AAGnC,QAAI,QAAQ,SAAS,KAAK;AACzB,YAAM,IAAI,MAAM,wDAAwD;AAAA,IACzE;AAEA,WAAO;AAAA,MACN;AAAA,MACA;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,YACL,aACA,SACA,cACA,UACgB;AAChB,UAAM,WAAW,MAAM,KAAK,gBAAgB,aAAa,YAAY;AACrE,UAAM,KAAK,mBAAmB,aAAa,SAAS,UAAU,gBAAgB,QAAQ,IAAI,GAAG,QAAQ;AACrG,UAAM,SAAS,WAAW,OAAO,QAAQ,KAAK;AAC9C,WAAO,QAAQ,6BAA6B,MAAM,EAAE;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,sBACC,UACA,UACU;AA1YZ;AA4YE,QAAI,aAAa,UAAU;AAC1B,aAAO;AAAA,IACR;AAGA,UAAM,gBAAgB,aAAa,SAAS,UAAU;AAGtD,UAAM,iBACL,kBAAkB,WACf,cAAS,cAAT,mBAAoB,SACpB,cAAS,cAAT,mBAAoB;AAGxB,YAAO,iDAAgB,oBAAmB;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,iBAAiB,SAAiB,cAAuC;AAC9E,UAAM,WAAW,MAAM,KAAK,gBAAgB,aAAa,YAAY;AACrE,WAAO,KAAK,6BAA6B,SAAS,UAAU,YAAY;AAAA,EACzE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,6BACL,SACA,UACA,cACkB;AAClB,UAAM,qBAAqB,SAAS,eAAe;AACnD,WAAO,MAAM,oCAAoC,SAAS,WAAW,EAAE;AACvE,WAAO,MAAM,uCAAuC,kBAAkB,EAAE;AAExE,QAAI,wBAAwB;AAC5B,QAAI,uBAAuB,MAAM;AAChC,8BAAwB;AACxB,aAAO,MAAM,wBAAwB;AAAA,IACtC,WAAW,uBAAuB,gBAAgB;AAEjD,8BAAwB,MAAM,mBAAmB,YAAY;AAC7D,aAAO,MAAM,iDAAiD,qBAAqB,EAAE;AAAA,IACtF,OAAO;AACN,aAAO,MAAM,kBAAkB;AAAA,IAChC;AAGA,WAAO,MAAM,4BAA4B,qBAAqB,EAAE;AAChE,QAAI,uBAAuB;AAC1B,aAAO,MAAM,wCAAwC;AACrD,aAAO,GAAG,OAAO;AAAA;AAAA;AAAA;AAAA,IAClB;AAEA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAc,mBACb,aACA,SACA,UACA,cACA,UACgB;AA/dlB;AAieE,UAAM,eAAe,MAAM,KAAK,6BAA6B,SAAS,UAAU,YAAY;AAG5F,UAAM,eAAe,YAAY;AACjC,UAAM,aAAa,aAAa,SAAY,OAAO;AAEnD,QAAI,aAAa,QAAW;AAE3B,YAAM,cAAc,mBAAmB,OAAO,QAAQ;AACtD,UAAI,aAAa;AAChB,cAAM,YAAY,gBAAgB,UAAU,cAAc,YAAY;AACtE;AAAA,MACD;AAAA,IAED;AAGA,UAAM,eAAe,aAAa,SAC/B,aACC,cAAS,oBAAT,mBAA0B,aAAY;AAC1C,UAAM,WAAW,+BAA+B,OAAO,cAAc,QAAQ;AAG7E,UAAM,SAAS,cAAc;AAAA,MAC5B,QAAQ,OAAO,YAAY;AAAA,MAC3B,MAAM;AAAA,MACN,MAAM;AAAA,IACP,CAAC;AAAA,EACF;AACD;","names":[]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  PromptTemplateManager
4
- } from "./chunk-MRPIDNZU.js";
4
+ } from "./chunk-LDE6VNG5.js";
5
5
  import {
6
6
  detectClaudeCli,
7
7
  launchClaude,
@@ -9,7 +9,7 @@ import {
9
9
  } from "./chunk-DDHWZNGL.js";
10
10
  import {
11
11
  SettingsManager
12
- } from "./chunk-F5NKWLMQ.js";
12
+ } from "./chunk-MNPKEWBQ.js";
13
13
  import {
14
14
  logger
15
15
  } from "./chunk-VRPPI6GU.js";
@@ -121,4 +121,4 @@ var ClaudeService = class {
121
121
  export {
122
122
  ClaudeService
123
123
  };
124
- //# sourceMappingURL=chunk-T4NESGYB.js.map
124
+ //# sourceMappingURL=chunk-UMAOVKQX.js.map
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  findMainWorktreePathWithSettings
4
- } from "./chunk-KGOBNC5A.js";
4
+ } from "./chunk-5W44AI63.js";
5
5
  import {
6
6
  MetadataManager
7
7
  } from "./chunk-XIVLGWUX.js";
@@ -70,4 +70,4 @@ export {
70
70
  archiveRecap,
71
71
  findArchivedRecap
72
72
  };
73
- //# sourceMappingURL=chunk-E5OM25WK.js.map
73
+ //# sourceMappingURL=chunk-UQWMPQ2Q.js.map
@@ -38,7 +38,7 @@ import {
38
38
  } from "./chunk-KV4NU3RP.js";
39
39
  import {
40
40
  SettingsManager
41
- } from "./chunk-F5NKWLMQ.js";
41
+ } from "./chunk-MNPKEWBQ.js";
42
42
  import {
43
43
  logger
44
44
  } from "./chunk-VRPPI6GU.js";
@@ -1436,4 +1436,4 @@ var IssueManagementProviderFactory = class {
1436
1436
  export {
1437
1437
  IssueManagementProviderFactory
1438
1438
  };
1439
- //# sourceMappingURL=chunk-ZEFTWM5Z.js.map
1439
+ //# sourceMappingURL=chunk-VUIPDX3T.js.map
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  extractIssueNumber,
4
4
  extractPRNumber
5
- } from "./chunk-KGOBNC5A.js";
5
+ } from "./chunk-5W44AI63.js";
6
6
 
7
7
  // src/utils/IdentifierParser.ts
8
8
  function matchIssueIdentifier(input) {
@@ -108,4 +108,4 @@ export {
108
108
  matchIssueIdentifier,
109
109
  IdentifierParser
110
110
  };
111
- //# sourceMappingURL=chunk-GQDVH6FA.js.map
111
+ //# sourceMappingURL=chunk-XC5JKRSH.js.map