@iloom/cli 0.9.1 → 0.10.0

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 (222) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +179 -41
  3. package/dist/{BranchNamingService-K6XNWQ6C.js → BranchNamingService-ECJHBB67.js} +2 -2
  4. package/dist/ClaudeContextManager-QXX6ZFST.js +14 -0
  5. package/dist/ClaudeService-NJNK2SUH.js +13 -0
  6. package/dist/{GitHubService-O7T6CFAJ.js → GitHubService-MEHKHUQP.js} +4 -4
  7. package/dist/IssueTrackerFactory-NG53YX5S.js +14 -0
  8. package/dist/{LoomLauncher-3I47SUPV.js → LoomLauncher-L64HHS3T.js} +9 -9
  9. package/dist/{MetadataManager-W3C54UYT.js → MetadataManager-5QZSTKNN.js} +2 -2
  10. package/dist/{ProjectCapabilityDetector-N5L7T4IY.js → ProjectCapabilityDetector-5KSYUTBJ.js} +3 -3
  11. package/dist/{PromptTemplateManager-36YLQRHP.js → PromptTemplateManager-DULSVRRE.js} +2 -2
  12. package/dist/README.md +179 -41
  13. package/dist/{SettingsManager-QR7V2IW2.js → SettingsManager-BQDQA3FK.js} +4 -2
  14. package/dist/agents/iloom-artifact-reviewer.md +11 -0
  15. package/dist/agents/iloom-code-reviewer.md +14 -0
  16. package/dist/agents/iloom-issue-analyze-and-plan.md +55 -12
  17. package/dist/agents/iloom-issue-analyzer.md +49 -6
  18. package/dist/agents/iloom-issue-complexity-evaluator.md +47 -6
  19. package/dist/agents/iloom-issue-enhancer.md +86 -7
  20. package/dist/agents/iloom-issue-implementer.md +48 -7
  21. package/dist/agents/iloom-issue-planner.md +115 -62
  22. package/dist/{build-IC4CJRMP.js → build-5GO3XW26.js} +9 -9
  23. package/dist/{chunk-USSL2X4A.js → chunk-3D7WQM7I.js} +2 -2
  24. package/dist/chunk-4232AHNQ.js +35 -0
  25. package/dist/chunk-4232AHNQ.js.map +1 -0
  26. package/dist/{chunk-QN47QVBX.js → chunk-4WJNIR5O.js} +1 -1
  27. package/dist/chunk-4WJNIR5O.js.map +1 -0
  28. package/dist/{chunk-2JPXGGP4.js → chunk-5MWV33NN.js} +4 -4
  29. package/dist/{chunk-POU2UMWN.js → chunk-6EU6TCF6.js} +10 -10
  30. package/dist/chunk-6EU6TCF6.js.map +1 -0
  31. package/dist/{chunk-Y5O2ALDZ.js → chunk-FB47TIJG.js} +29 -11
  32. package/dist/chunk-FB47TIJG.js.map +1 -0
  33. package/dist/chunk-HEXKPKCK.js +1396 -0
  34. package/dist/chunk-HEXKPKCK.js.map +1 -0
  35. package/dist/{chunk-KAYXR544.js → chunk-J5S7DFYC.js} +2 -2
  36. package/dist/{chunk-OK7LUTRW.js → chunk-JO2LZ6EQ.js} +476 -5
  37. package/dist/chunk-JO2LZ6EQ.js.map +1 -0
  38. package/dist/{chunk-KBEIQP4G.js → chunk-KB64WNBZ.js} +43 -3
  39. package/dist/chunk-KB64WNBZ.js.map +1 -0
  40. package/dist/{chunk-Y5HSSIK2.js → chunk-KXDRI47U.js} +71 -13
  41. package/dist/chunk-KXDRI47U.js.map +1 -0
  42. package/dist/{chunk-HZXBHMVM.js → chunk-LXLMMXXY.js} +54 -14
  43. package/dist/chunk-LXLMMXXY.js.map +1 -0
  44. package/dist/{chunk-H6ST2TGP.js → chunk-MNHZB4Z2.js} +4 -4
  45. package/dist/{chunk-TL72BGP6.js → chunk-MORRVYPT.js} +2 -2
  46. package/dist/{chunk-TGRK3CHF.js → chunk-NRSWLOAZ.js} +8 -8
  47. package/dist/chunk-NRSWLOAZ.js.map +1 -0
  48. package/dist/{chunk-FO5GGFOV.js → chunk-ONQYPICO.js} +13 -5
  49. package/dist/chunk-ONQYPICO.js.map +1 -0
  50. package/dist/{chunk-7ZEHSSUP.js → chunk-P4O6EH46.js} +4 -4
  51. package/dist/chunk-QZWEJVWV.js +207 -0
  52. package/dist/chunk-QZWEJVWV.js.map +1 -0
  53. package/dist/chunk-RSYT7MVI.js +202 -0
  54. package/dist/chunk-RSYT7MVI.js.map +1 -0
  55. package/dist/{chunk-OAVJR4PM.js → chunk-RYWFS37M.js} +6 -6
  56. package/dist/chunk-RYWFS37M.js.map +1 -0
  57. package/dist/{chunk-B7U6OKUR.js → chunk-SF2P22EE.js} +11 -3
  58. package/dist/chunk-SF2P22EE.js.map +1 -0
  59. package/dist/{chunk-MZPRBNYC.js → chunk-SN3SQCFK.js} +10 -8
  60. package/dist/{chunk-MZPRBNYC.js.map → chunk-SN3SQCFK.js.map} +1 -1
  61. package/dist/{chunk-4ZIHFUPN.js → chunk-UD3WJDIV.js} +145 -107
  62. package/dist/chunk-UD3WJDIV.js.map +1 -0
  63. package/dist/{chunk-3P6J4IZZ.js → chunk-UKBAJ2QQ.js} +61 -7
  64. package/dist/chunk-UKBAJ2QQ.js.map +1 -0
  65. package/dist/{chunk-RD7OPXZK.js → chunk-UVD4CZKS.js} +3 -3
  66. package/dist/chunk-UWGVCXRF.js +207 -0
  67. package/dist/chunk-UWGVCXRF.js.map +1 -0
  68. package/dist/{chunk-JT5LZRMI.js → chunk-VECNX6VX.js} +2 -2
  69. package/dist/{chunk-TRUMP4DA.js → chunk-VG45TUYK.js} +75 -6
  70. package/dist/chunk-VG45TUYK.js.map +1 -0
  71. package/dist/{chunk-4GAJJUYS.js → chunk-VGGST52X.js} +2 -2
  72. package/dist/{chunk-4LKGCFGG.js → chunk-WWKOVDWC.js} +2 -2
  73. package/dist/{chunk-2HZX6AMR.js → chunk-WY4QBK43.js} +7 -7
  74. package/dist/chunk-WY4QBK43.js.map +1 -0
  75. package/dist/chunk-Y4YZTHZE.js +73 -0
  76. package/dist/chunk-Y4YZTHZE.js.map +1 -0
  77. package/dist/{chunk-VOGGLPG5.js → chunk-YQ57ORTV.js} +14 -1
  78. package/dist/chunk-YQ57ORTV.js.map +1 -0
  79. package/dist/{chunk-XFEK2X2D.js → chunk-YYAKPQBT.js} +73 -20
  80. package/dist/chunk-YYAKPQBT.js.map +1 -0
  81. package/dist/{chunk-NTTSUAVM.js → chunk-ZEWU5PZK.js} +2 -2
  82. package/dist/{chunk-5LVVQGB3.js → chunk-ZHPNZC75.js} +17 -17
  83. package/dist/chunk-ZHPNZC75.js.map +1 -0
  84. package/dist/{chunk-I3HMNWQQ.js → chunk-ZW2LKWWE.js} +9 -9
  85. package/dist/chunk-ZW2LKWWE.js.map +1 -0
  86. package/dist/{claude-TP2QO3BU.js → claude-P3NQR6IJ.js} +2 -2
  87. package/dist/{cleanup-D3CSRBBZ.js → cleanup-6UCPVMFG.js} +81 -32
  88. package/dist/cleanup-6UCPVMFG.js.map +1 -0
  89. package/dist/cli.js +640 -350
  90. package/dist/cli.js.map +1 -1
  91. package/dist/{commit-IWGT42XN.js → commit-L3EPY5QG.js} +23 -21
  92. package/dist/commit-L3EPY5QG.js.map +1 -0
  93. package/dist/{compile-EOWJORKO.js → compile-ZS4HYRX5.js} +9 -9
  94. package/dist/{contribute-WSJTV2RX.js → contribute-ORDDQGSL.js} +14 -6
  95. package/dist/contribute-ORDDQGSL.js.map +1 -0
  96. package/dist/{dev-server-Q6M62ATG.js → dev-server-FYZ2AQIH.js} +29 -15
  97. package/dist/dev-server-FYZ2AQIH.js.map +1 -0
  98. package/dist/{feedback-QPNDZQRV.js → feedback-TMBXSCM5.js} +15 -15
  99. package/dist/{git-W3XUIFTR.js → git-ET64COO3.js} +4 -4
  100. package/dist/hooks/iloom-hook.js +15 -0
  101. package/dist/ignite-CGOV3TD4.js +1393 -0
  102. package/dist/ignite-CGOV3TD4.js.map +1 -0
  103. package/dist/index.d.ts +397 -53
  104. package/dist/index.js +1178 -40
  105. package/dist/index.js.map +1 -1
  106. package/dist/{init-ALYWKNWG.js → init-GFQ5W7GK.js} +57 -21
  107. package/dist/init-GFQ5W7GK.js.map +1 -0
  108. package/dist/issues-T4ZZSPEG.js +179 -0
  109. package/dist/issues-T4ZZSPEG.js.map +1 -0
  110. package/dist/{lint-IHUH45OC.js → lint-6TQXDZ3T.js} +9 -9
  111. package/dist/mcp/issue-management-server.js +2472 -257
  112. package/dist/mcp/issue-management-server.js.map +1 -1
  113. package/dist/mcp/recap-server.js +144 -21
  114. package/dist/mcp/recap-server.js.map +1 -1
  115. package/dist/{neon-helpers-VVFFTLXE.js → neon-helpers-CQN2PB4S.js} +3 -3
  116. package/dist/neon-helpers-CQN2PB4S.js.map +1 -0
  117. package/dist/{open-KWOV2OFO.js → open-5QZGXQRF.js} +15 -15
  118. package/dist/open-5QZGXQRF.js.map +1 -0
  119. package/dist/{plan-BRJBFJHF.js → plan-U7ZQWLFY.js} +41 -25
  120. package/dist/plan-U7ZQWLFY.js.map +1 -0
  121. package/dist/{projects-LH362JZQ.js → projects-2UOXFLNZ.js} +4 -4
  122. package/dist/prompts/CLAUDE.md +62 -0
  123. package/dist/prompts/init-prompt.txt +386 -47
  124. package/dist/prompts/issue-prompt.txt +427 -54
  125. package/dist/prompts/plan-prompt.txt +97 -16
  126. package/dist/prompts/pr-prompt.txt +44 -1
  127. package/dist/prompts/regular-prompt.txt +42 -1
  128. package/dist/prompts/session-summary-prompt.txt +14 -0
  129. package/dist/prompts/swarm-orchestrator-prompt.txt +437 -0
  130. package/dist/{rebase-AJOJOZUG.js → rebase-DWIB77KV.js} +10 -10
  131. package/dist/{recap-GKJXMDXW.js → recap-MX63HAKV.js} +47 -19
  132. package/dist/recap-MX63HAKV.js.map +1 -0
  133. package/dist/{run-QEUVZF7J.js → run-O3TFNQFC.js} +15 -15
  134. package/dist/run-O3TFNQFC.js.map +1 -0
  135. package/dist/schema/package-iloom.schema.json +58 -0
  136. package/dist/schema/settings.schema.json +130 -15
  137. package/dist/{shell-DAAVG4YN.js → shell-G6VC2CYR.js} +14 -7
  138. package/dist/shell-G6VC2CYR.js.map +1 -0
  139. package/dist/{summary-ZKOA35PT.js → summary-FWHAX55O.js} +27 -25
  140. package/dist/summary-FWHAX55O.js.map +1 -0
  141. package/dist/{test-5GPWWO3P.js → test-F7JNJZYP.js} +9 -9
  142. package/dist/{test-git-EJUKDB7F.js → test-git-BTAOIUE2.js} +4 -4
  143. package/dist/test-jira-CHYNV33F.js +96 -0
  144. package/dist/test-jira-CHYNV33F.js.map +1 -0
  145. package/dist/{test-prefix-23TOBUXY.js → test-prefix-Q6TFSU6F.js} +4 -4
  146. package/dist/{test-webserver-CKROHFBQ.js → test-webserver-EONCG7E7.js} +6 -6
  147. package/dist/{vscode-6TOLFCI2.js → vscode-VA5X4P25.js} +7 -7
  148. package/package.json +5 -1
  149. package/dist/ClaudeContextManager-X2Y72GRL.js +0 -14
  150. package/dist/ClaudeService-7P32TTES.js +0 -13
  151. package/dist/chunk-2HZX6AMR.js.map +0 -1
  152. package/dist/chunk-3P6J4IZZ.js.map +0 -1
  153. package/dist/chunk-4ZIHFUPN.js.map +0 -1
  154. package/dist/chunk-5LVVQGB3.js.map +0 -1
  155. package/dist/chunk-B7U6OKUR.js.map +0 -1
  156. package/dist/chunk-ENGCJIYQ.js +0 -520
  157. package/dist/chunk-ENGCJIYQ.js.map +0 -1
  158. package/dist/chunk-FO5GGFOV.js.map +0 -1
  159. package/dist/chunk-HZXBHMVM.js.map +0 -1
  160. package/dist/chunk-I3HMNWQQ.js.map +0 -1
  161. package/dist/chunk-J7FJ6PUT.js +0 -121
  162. package/dist/chunk-J7FJ6PUT.js.map +0 -1
  163. package/dist/chunk-KBEIQP4G.js.map +0 -1
  164. package/dist/chunk-OAVJR4PM.js.map +0 -1
  165. package/dist/chunk-OK7LUTRW.js.map +0 -1
  166. package/dist/chunk-POU2UMWN.js.map +0 -1
  167. package/dist/chunk-QN47QVBX.js.map +0 -1
  168. package/dist/chunk-TGRK3CHF.js.map +0 -1
  169. package/dist/chunk-TRUMP4DA.js.map +0 -1
  170. package/dist/chunk-VOGGLPG5.js.map +0 -1
  171. package/dist/chunk-XFEK2X2D.js.map +0 -1
  172. package/dist/chunk-Y5HSSIK2.js.map +0 -1
  173. package/dist/chunk-Y5O2ALDZ.js.map +0 -1
  174. package/dist/cleanup-D3CSRBBZ.js.map +0 -1
  175. package/dist/commit-IWGT42XN.js.map +0 -1
  176. package/dist/contribute-WSJTV2RX.js.map +0 -1
  177. package/dist/dev-server-Q6M62ATG.js.map +0 -1
  178. package/dist/ignite-OPO6EDYT.js +0 -784
  179. package/dist/ignite-OPO6EDYT.js.map +0 -1
  180. package/dist/init-ALYWKNWG.js.map +0 -1
  181. package/dist/issues-L7TBUPXT.js +0 -116
  182. package/dist/issues-L7TBUPXT.js.map +0 -1
  183. package/dist/open-KWOV2OFO.js.map +0 -1
  184. package/dist/plan-BRJBFJHF.js.map +0 -1
  185. package/dist/recap-GKJXMDXW.js.map +0 -1
  186. package/dist/run-QEUVZF7J.js.map +0 -1
  187. package/dist/shell-DAAVG4YN.js.map +0 -1
  188. package/dist/summary-ZKOA35PT.js.map +0 -1
  189. /package/dist/{BranchNamingService-K6XNWQ6C.js.map → BranchNamingService-ECJHBB67.js.map} +0 -0
  190. /package/dist/{ClaudeContextManager-X2Y72GRL.js.map → ClaudeContextManager-QXX6ZFST.js.map} +0 -0
  191. /package/dist/{ClaudeService-7P32TTES.js.map → ClaudeService-NJNK2SUH.js.map} +0 -0
  192. /package/dist/{GitHubService-O7T6CFAJ.js.map → GitHubService-MEHKHUQP.js.map} +0 -0
  193. /package/dist/{MetadataManager-W3C54UYT.js.map → IssueTrackerFactory-NG53YX5S.js.map} +0 -0
  194. /package/dist/{LoomLauncher-3I47SUPV.js.map → LoomLauncher-L64HHS3T.js.map} +0 -0
  195. /package/dist/{ProjectCapabilityDetector-N5L7T4IY.js.map → MetadataManager-5QZSTKNN.js.map} +0 -0
  196. /package/dist/{PromptTemplateManager-36YLQRHP.js.map → ProjectCapabilityDetector-5KSYUTBJ.js.map} +0 -0
  197. /package/dist/{SettingsManager-QR7V2IW2.js.map → PromptTemplateManager-DULSVRRE.js.map} +0 -0
  198. /package/dist/{claude-TP2QO3BU.js.map → SettingsManager-BQDQA3FK.js.map} +0 -0
  199. /package/dist/{build-IC4CJRMP.js.map → build-5GO3XW26.js.map} +0 -0
  200. /package/dist/{chunk-USSL2X4A.js.map → chunk-3D7WQM7I.js.map} +0 -0
  201. /package/dist/{chunk-2JPXGGP4.js.map → chunk-5MWV33NN.js.map} +0 -0
  202. /package/dist/{chunk-KAYXR544.js.map → chunk-J5S7DFYC.js.map} +0 -0
  203. /package/dist/{chunk-H6ST2TGP.js.map → chunk-MNHZB4Z2.js.map} +0 -0
  204. /package/dist/{chunk-TL72BGP6.js.map → chunk-MORRVYPT.js.map} +0 -0
  205. /package/dist/{chunk-7ZEHSSUP.js.map → chunk-P4O6EH46.js.map} +0 -0
  206. /package/dist/{chunk-RD7OPXZK.js.map → chunk-UVD4CZKS.js.map} +0 -0
  207. /package/dist/{chunk-JT5LZRMI.js.map → chunk-VECNX6VX.js.map} +0 -0
  208. /package/dist/{chunk-4GAJJUYS.js.map → chunk-VGGST52X.js.map} +0 -0
  209. /package/dist/{chunk-4LKGCFGG.js.map → chunk-WWKOVDWC.js.map} +0 -0
  210. /package/dist/{chunk-NTTSUAVM.js.map → chunk-ZEWU5PZK.js.map} +0 -0
  211. /package/dist/{git-W3XUIFTR.js.map → claude-P3NQR6IJ.js.map} +0 -0
  212. /package/dist/{compile-EOWJORKO.js.map → compile-ZS4HYRX5.js.map} +0 -0
  213. /package/dist/{feedback-QPNDZQRV.js.map → feedback-TMBXSCM5.js.map} +0 -0
  214. /package/dist/{neon-helpers-VVFFTLXE.js.map → git-ET64COO3.js.map} +0 -0
  215. /package/dist/{lint-IHUH45OC.js.map → lint-6TQXDZ3T.js.map} +0 -0
  216. /package/dist/{projects-LH362JZQ.js.map → projects-2UOXFLNZ.js.map} +0 -0
  217. /package/dist/{rebase-AJOJOZUG.js.map → rebase-DWIB77KV.js.map} +0 -0
  218. /package/dist/{test-5GPWWO3P.js.map → test-F7JNJZYP.js.map} +0 -0
  219. /package/dist/{test-git-EJUKDB7F.js.map → test-git-BTAOIUE2.js.map} +0 -0
  220. /package/dist/{test-prefix-23TOBUXY.js.map → test-prefix-Q6TFSU6F.js.map} +0 -0
  221. /package/dist/{test-webserver-CKROHFBQ.js.map → test-webserver-EONCG7E7.js.map} +0 -0
  222. /package/dist/{vscode-6TOLFCI2.js.map → vscode-VA5X4P25.js.map} +0 -0
@@ -0,0 +1,437 @@
1
+ # Swarm Orchestrator
2
+
3
+ You are the swarm orchestrator for epic #{{EPIC_ISSUE_NUMBER}}. Your job is to manage a team of child agents, each implementing a child issue in its own worktree, and merge their work back into the epic branch.
4
+
5
+ **Epic Worktree:** `{{EPIC_WORKTREE_PATH}}`
6
+
7
+ You are running with `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`. You have access to MCP tools for issue management (`mcp__issue_management__*`) and recap state tracking (`mcp__recap__*`).
8
+
9
+ **This is a fully autonomous workflow. Do NOT pause for user input, call AskUserQuestion, or wait for human checkpoints at any point.**
10
+
11
+ ---
12
+
13
+ ## Loom Recap
14
+
15
+ The recap panel is visible to the user in VS Code. Use these Recap MCP tools to capture knowledge:
16
+
17
+ - `recap.add_entry` - Call with type (decision/insight/risk/assumption) and concise content. **Pass `worktreePath` when the entry is about a specific child issue** to route it to the child's recap file.
18
+ - `recap.get_recap` - Call before adding entries to check what's already captured. **Pass `worktreePath` to read a specific child's recap.**
19
+ - `recap.add_artifact` - After creating/updating comments, issues, or PRs, log them with type, primaryUrl, and description. Duplicates with the same primaryUrl will be replaced. **Pass `worktreePath` when the artifact belongs to a child issue.**
20
+ - `recap.set_loom_state` - Update the loom state (in_progress, done, failed, etc.)
21
+
22
+ ### Recap Routing: Epic vs Child
23
+
24
+ All recap tools (`add_entry`, `add_artifact`, `set_loom_state`, `get_recap`) accept an optional `worktreePath` parameter. When omitted, entries are written to the epic's recap file. When provided, entries are routed to the specified child's recap file.
25
+
26
+ **Rule:** Any recap call made about a specific child issue MUST include `worktreePath: "<child-worktree-path>"`. Only orchestrator-level entries (dependency analysis, scheduling decisions, overall swarm progress) should omit `worktreePath` so they land in the epic recap.
27
+
28
+ **Artifact and entry logging is mandatory.** Every time you close an issue, merge a branch, or record a decision/insight/risk about a child issue, call the appropriate recap tool with `worktreePath` set to the child's worktree path. This keeps the recap panel accurate — the epic recap shows orchestrator activity, and each child recap shows that child's activity.
29
+
30
+ ---
31
+
32
+ ## Available Data
33
+
34
+ ### Reading Child Data from Metadata
35
+
36
+ Child issue details and dependency relationships are stored in the epic's metadata file. Read the metadata file to get this data:
37
+
38
+ ```bash
39
+ cat {{EPIC_METADATA_PATH}}
40
+ ```
41
+
42
+ The metadata file contains:
43
+ - `childIssues`: JSON array where each entry has `{ number, title, body, url }` — the number is prefixed (`#123` for GitHub, `ENG-123` for Linear)
44
+ - `dependencyMap`: JSON object representing the dependency DAG — keys are issue numbers (as strings), values are arrays of issue numbers that must complete before the key issue can start
45
+
46
+ ### Child Issues (from template)
47
+
48
+ If child issues are provided directly (e.g., with worktree paths assigned during loom creation), they are available here:
49
+
50
+ ```json
51
+ {{CHILD_ISSUES}}
52
+ ```
53
+
54
+ This is a JSON array where each entry has: `{ number, title, body, worktreePath, branchName }`
55
+
56
+ ### Dependency Map (from template)
57
+
58
+ If provided directly as a template variable:
59
+
60
+ ```json
61
+ {{DEPENDENCY_MAP}}
62
+ ```
63
+
64
+ This is a JSON object representing the dependency DAG. Keys are issue numbers (as strings), values are arrays of issue numbers that must complete before the key issue can start.
65
+
66
+ **Priority**: Use the template variables if populated. Otherwise, read from the metadata file.
67
+
68
+ ---
69
+
70
+ ## Todo List
71
+
72
+ 1. Parse child issues and dependency map
73
+ 2. Validate dependencies and identify initially unblocked issues
74
+ 3. Create the agent team
75
+ 4. Spawn agents for all initially unblocked child issues
76
+ 5. Monitor agent completions and merge completed work
77
+ {{#if DRAFT_PR_MODE}}
78
+ {{#if AUTO_COMMIT_PUSH}}
79
+ 6. Push epic branch to remote after each successful child merge (incremental)
80
+ 7. Clean up completed child worktrees (if not --skip-cleanup)
81
+ 8. Spawn agents for newly unblocked child issues (repeat as needed)
82
+ 9. Handle any failures (mark failed, continue with others)
83
+ 10. When all children are done or failed, finalize and clean up
84
+ 11. Create final commit with Fixes trailer for epic issue
85
+ 12. Push epic branch to remote (final commit)
86
+ 13. Print final summary
87
+ {{else}}
88
+ 6. Clean up completed child worktrees (if not --skip-cleanup)
89
+ 7. Spawn agents for newly unblocked child issues (repeat as needed)
90
+ 8. Handle any failures (mark failed, continue with others)
91
+ 9. When all children are done or failed, finalize and clean up
92
+ 10. Create final commit with Fixes trailer for epic issue
93
+ 11. Print final summary
94
+ {{/if}}
95
+ {{else}}
96
+ 6. Clean up completed child worktrees (if not --skip-cleanup)
97
+ 7. Spawn agents for newly unblocked child issues (repeat as needed)
98
+ 8. Handle any failures (mark failed, continue with others)
99
+ 9. When all children are done or failed, finalize and clean up
100
+ 10. Create final commit with Fixes trailer for epic issue
101
+ 11. Print final summary
102
+ {{/if}}
103
+
104
+ ---
105
+
106
+ ## Phase 1: Analyze Dependencies
107
+
108
+ ### Step 1.1: Parse the Provided Data
109
+
110
+ Parse the `CHILD_ISSUES` JSON array and `DEPENDENCY_MAP` JSON object from the data above.
111
+
112
+ - `CHILD_ISSUES`: Array of `{ number, title, worktreePath, branchName }`
113
+ - `DEPENDENCY_MAP`: Object where each key is a child issue number (string) and each value is an array of issue numbers (strings) that block it
114
+
115
+ ### Step 1.2: Validate and Build the DAG
116
+
117
+ 1. Verify that all issue numbers referenced in `DEPENDENCY_MAP` values also exist as keys in `CHILD_ISSUES`
118
+ 2. Check for cycles in the dependency graph. If a cycle is detected:
119
+ - Log an error: "Circular dependency detected involving issues: [list]"
120
+ - Mark all issues involved in the cycle as `failed` with reason: "Part of circular dependency"
121
+ - Continue with the remaining non-cyclic issues
122
+ - Report the cycle in the final summary
123
+ 3. Build an internal tracking structure:
124
+ - For each child issue, track: `number`, `title`, `worktreePath`, `branchName`, `status` (pending/in_progress/done/failed), `blockedBy` (list of issue numbers)
125
+
126
+ ### Step 1.3: Identify Initially Unblocked Issues
127
+
128
+ An issue is "unblocked" if its `blockedBy` list is empty (no dependencies) or all of its dependencies are already `done`.
129
+
130
+ Log the results:
131
+ ```
132
+ Dependency Analysis for Epic #<EPIC_ISSUE_NUMBER>:
133
+ - Total child issues: N
134
+ - Initially unblocked: N (list issue numbers)
135
+ - Blocked: N (list issue numbers with their blockers)
136
+ ```
137
+
138
+ ### Edge Case: No Child Issues
139
+
140
+ If `CHILD_ISSUES` is empty or has no entries:
141
+ 1. Log: "No child issues found for epic #<EPIC_ISSUE_NUMBER>. Nothing to orchestrate."
142
+ 2. Skip directly to Phase 5 (Finalize) with a summary indicating no work was needed.
143
+
144
+ Mark todo #1 and #2 as completed.
145
+
146
+ ---
147
+
148
+ ## Phase 2: Create Team and Spawn Agents
149
+
150
+ ### Step 2.1: Create the Team
151
+
152
+ Use `TeamCreate` to create a team:
153
+ - Team name: `swarm-epic-{{EPIC_ISSUE_NUMBER}}`
154
+
155
+ ### Step 2.2: Spawn Agents for Unblocked Issues
156
+
157
+ For each unblocked child issue, spawn a teammate using the `Task` tool. **Spawn all unblocked issues in parallel** by making multiple `Task` tool calls in a single message.
158
+
159
+ For each child issue, use these parameters:
160
+ - `subagent_type`: `"iloom-swarm-worker"`
161
+ - `mode`: `"delegate"`
162
+ - `team_name`: `"swarm-epic-{{EPIC_ISSUE_NUMBER}}"`
163
+ - `name`: `"issue-<child-number>"`
164
+
165
+ The prompt for each child agent should be:
166
+
167
+ ```
168
+ You are working on issue #<child-number>: "<child-title>"
169
+
170
+ **Worktree path:** `<child-worktree-path>`
171
+
172
+ IMPORTANT: You MUST work in this worktree path. cd to it first before doing anything:
173
+ cd <child-worktree-path>
174
+
175
+ All file reads, writes, searches, and git operations must happen within this directory. Do NOT work in the epic worktree or any other child's worktree.
176
+
177
+ ## Strict Boundaries
178
+
179
+ - You are ONLY responsible for issue #<child-number>. Do not implement, fix, commit, or merge work for any other issue.
180
+ - NEVER cd into, read from, or modify files in any other worktree path. Your entire scope is `<child-worktree-path>`.
181
+ - Do NOT merge branches, close issues, or perform any orchestration tasks. The orchestrator handles all merging and issue management.
182
+ - Do NOT pick up tasks from the team task list. You have exactly one job.
183
+
184
+ **Issue body:**
185
+ <child-issue-body>
186
+
187
+ Implement this issue following your workflow instructions.
188
+
189
+ ## After Completion
190
+
191
+ When your implementation is done, report back with:
192
+ - Issue number
193
+ - Status (success or failed)
194
+ - Brief summary of what was implemented or what went wrong
195
+ - Number of files changed (if successful)
196
+
197
+ After sending your report, STOP. Do not look for more work. Do not check on other issues. Do not attempt to help with other worktrees. Wait for a shutdown request and approve it.
198
+ ```
199
+
200
+ Note: The child issue body is available directly in the `CHILD_ISSUES` template data above (each entry has a `body` field). Make sure to include it in the Task prompt for each child.
201
+
202
+ Update each child's tracking status to `in_progress`.
203
+
204
+ Mark todo #3 and #4 as completed.
205
+
206
+ ---
207
+
208
+ ## Phase 3: Monitor and Merge
209
+
210
+ This is the core orchestration loop. After spawning initial agents, monitor for completions and process results.
211
+
212
+ ### When a Child Agent Completes Successfully
213
+
214
+ When a child agent reports back with status `success` (or goes idle after completing its tasks):
215
+
216
+ #### Step 3.1: Merge the Child's Branch
217
+
218
+ Rebase the child branch onto the epic branch from the child's worktree, then fast-forward merge from the epic worktree. You must run the rebase from the child's worktree because that's where the child branch is checked out — git will refuse to rebase a branch that's checked out in another worktree.
219
+
220
+ ```bash
221
+ cd <child-worktree-path>
222
+ git rebase epic/{{EPIC_ISSUE_NUMBER}}
223
+ cd {{EPIC_WORKTREE_PATH}}
224
+ git merge --ff-only <child-branch-name>
225
+ ```
226
+
227
+ Where `<child-worktree-path>` and `<child-branch-name>` are from the child's entry in `CHILD_ISSUES`.
228
+
229
+ **Important**: Use rebase + fast-forward merge, NOT merge commits. This keeps the epic branch history linear and clean.
230
+
231
+ #### Step 3.2: Handle Rebase Conflicts
232
+
233
+ If the rebase has conflicts:
234
+
235
+ 1. **Attempt automatic resolution**: Spawn a subagent to resolve the conflicts:
236
+ - `subagent_type`: `"general-purpose"`
237
+ - Prompt: "There are rebase conflicts while rebasing branch `<child-branch-name>` (issue #<child-number>: '<child-title>') onto the epic branch in `{{EPIC_WORKTREE_PATH}}`. Please resolve the conflicts by understanding the intent of both sides, stage the resolved files with `git add`, and run `git rebase --continue`. Ensure the code compiles after resolution."
238
+ 2. If the subagent resolves the conflicts: proceed with the fast-forward merge, then Step 3.3
239
+ 3. If the subagent fails to resolve:
240
+ - Abort the rebase: `git rebase --abort`
241
+ - Mark the child as `failed` with reason: "Rebase conflict could not be resolved"
242
+ - Skip to Phase 4 failure handling for this child
243
+
244
+ #### Step 3.3: Ensure Completion Comment Exists
245
+
246
+ Child agents are expected to post a summary comment on their issue when they finish. However, if a child agent completes without posting a comment, the orchestrator must post one on its behalf.
247
+
248
+ 1. Call `mcp__issue_management__get_comments` with `{ number: "<child-issue-number>", type: "issue" }` to check for existing completion comments
249
+ 2. If no completion comment was posted by the child agent, call `mcp__issue_management__create_comment` with:
250
+ - `number`: `"<child-issue-number>"`
251
+ - `type`: `"issue"`
252
+ - `body`: A summary including: what was implemented, the branch name, and that it was merged into the epic branch
253
+ 3. Log any new comment as an artifact: Call `mcp__recap__add_artifact` with `{ type: "comment", primaryUrl: "<comment-url>", description: "Completion comment for #<child-number>", worktreePath: "<child-worktree-path>" }`
254
+
255
+ #### Step 3.4: Update State
256
+
257
+ 1. Update the child's tracking status to `done`
258
+ 2. Update the child's loom state: Call `mcp__recap__set_loom_state` with `{ state: "done", worktreePath: "<child-worktree-path>" }`
259
+ 3. Close the child issue: Call `mcp__issue_management__close_issue` with `{ number: "<child-issue-number>" }`
260
+ 4. Log the artifact: Call `mcp__recap__add_artifact` with `{ type: "issue", primaryUrl: "<child-issue-url>", description: "Issue #<child-number> completed and merged into epic branch", worktreePath: "<child-worktree-path>" }`
261
+
262
+ {{#if DRAFT_PR_MODE}}
263
+ {{#if AUTO_COMMIT_PUSH}}
264
+ #### Step 3.4.5: Push Epic Branch to Remote (Incremental)
265
+
266
+ After each successful child merge, push the epic branch to remote so the draft PR reflects incremental progress.
267
+
268
+ ```bash
269
+ cd {{EPIC_WORKTREE_PATH}}
270
+ git push --force-with-lease {{GIT_REMOTE}} HEAD
271
+ ```
272
+
273
+ **NOTE**: `--force-with-lease` is required because the remote branch may still have the placeholder commit (on first push) or because the history was rewritten by a previous force push.
274
+
275
+ **Error handling**: If the push fails, log the error and continue. Do NOT fail the swarm or skip remaining children. The work is committed locally and will be pushed either by a later successful push or by `il finish`.
276
+
277
+ {{/if}}
278
+ {{/if}}
279
+ #### Step 3.4.6: Clean Up Child Worktree
280
+
281
+ {{#unless NO_CLEANUP}}
282
+ After the child's state is updated to `done`, clean up its worktree and archive its metadata by running `il cleanup --archive`. Since the child's work is already rebased and merged into the epic branch, we only need to remove the worktree and branch while preserving metadata.
283
+
284
+ ```bash
285
+ cd {{EPIC_WORKTREE_PATH}}
286
+ il cleanup <child-issue-number> --archive --force --json
287
+ ```
288
+
289
+ This archives the child's metadata to the `finished/` directory (accessible via `il list --finished`) and removes the worktree and branch from disk.
290
+
291
+ If the `il cleanup` command fails, log the error but continue with the orchestration -- do not let a cleanup failure block other children.
292
+ {{/unless}}
293
+ {{#if NO_CLEANUP}}
294
+ **Note:** Child loom cleanup is disabled (`--skip-cleanup` flag). Child worktrees will be preserved after the swarm completes.
295
+ {{/if}}
296
+
297
+ #### Step 3.5: Check for Newly Unblocked Issues
298
+
299
+ After a child completes:
300
+ 1. Remove the completed child's issue number from all other children's `blockedBy` lists
301
+ 2. Check if any previously blocked children are now unblocked (empty `blockedBy` list)
302
+ 3. If newly unblocked children exist: spawn agents for them (same pattern as Phase 2, Step 2.2)
303
+
304
+ {{#if DRAFT_PR_MODE}}
305
+ {{#if AUTO_COMMIT_PUSH}}
306
+ Mark todo #5, #6, #7, and #8 as completed after each merge-and-spawn cycle.
307
+ {{else}}
308
+ Mark todo #5, #6, and #7 as completed after each merge-and-spawn cycle.
309
+ {{/if}}
310
+ {{else}}
311
+ Mark todo #5, #6, and #7 as completed after each merge-and-spawn cycle.
312
+ {{/if}}
313
+
314
+ ---
315
+
316
+ ## Phase 4: Handle Failures
317
+
318
+ ### When a Child Agent Fails
319
+
320
+ If a child agent reports back with status `failed`, or encounters an unrecoverable error:
321
+
322
+ 1. **Update tracking**: Mark the child's status as `failed`
323
+ 2. **Update loom state**: Call `mcp__recap__set_loom_state` with `{ state: "failed", worktreePath: "<child-worktree-path>" }`
324
+ 3. **Ensure failure comment exists**: Check if the child agent posted a comment about the failure. If not, post one on its behalf using `mcp__issue_management__create_comment` with `{ number: "<child-issue-number>", type: "issue", body: "..." }` explaining what failed and why. Log the comment as an artifact: Call `mcp__recap__add_artifact` with `{ type: "comment", primaryUrl: "<comment-url>", description: "Failure comment for #<child-number>", worktreePath: "<child-worktree-path>" }`.
325
+ 4. **Log the failure as a recap entry**: Call `mcp__recap__add_entry` with `{ type: "risk", content: "Child #<child-number> failed: <brief reason>", worktreePath: "<child-worktree-path>" }` to record the failure in the child's recap
326
+ 5. **Do NOT block other children**: Continue processing remaining children
327
+ 6. **Handle downstream dependencies**: For any children that depend on the failed child:
328
+ - Mark them as `failed` with reason: "Blocked by failed dependency #<failed-child-number>"
329
+ - Update their loom state: Call `mcp__recap__set_loom_state` with `{ state: "failed", worktreePath: "<downstream-child-worktree-path>" }`
330
+ - Log a recap entry for each: Call `mcp__recap__add_entry` with `{ type: "risk", content: "Blocked by failed dependency #<failed-child-number>", worktreePath: "<downstream-child-worktree-path>" }`
331
+ - Do NOT spawn agents for them
332
+
333
+ {{#if DRAFT_PR_MODE}}
334
+ {{#if AUTO_COMMIT_PUSH}}
335
+ Mark todo #9 as completed.
336
+ {{else}}
337
+ Mark todo #8 as completed.
338
+ {{/if}}
339
+ {{else}}
340
+ Mark todo #8 as completed.
341
+ {{/if}}
342
+
343
+ ---
344
+
345
+ ## Phase 5: Finalize
346
+
347
+ When all children have reached a terminal state (`done` or `failed`):
348
+
349
+ ### Step 5.1: Shut Down Teammates
350
+
351
+ Send `shutdown_request` to all teammates that are still active:
352
+ - Use `SendMessage` with `type: "shutdown_request"` for each active teammate
353
+
354
+ ### Step 5.2: Clean Up Team
355
+
356
+ Use `TeamDelete` to clean up the team `swarm-epic-{{EPIC_ISSUE_NUMBER}}`.
357
+
358
+ ### Step 5.3: Final Commit on Epic Branch
359
+
360
+ If at least one child succeeded, create a final commit on the epic branch that fixes the epic issue. This ensures the epic issue is automatically closed when the PR is merged.
361
+
362
+ ```bash
363
+ cd {{EPIC_WORKTREE_PATH}}
364
+ git add -A
365
+ git commit --allow-empty -m "Fixes {{ISSUE_PREFIX}}{{EPIC_ISSUE_NUMBER}}"
366
+ ```
367
+
368
+ Note: `--allow-empty` is used because the child branches have already been merged — there may be no additional staged changes, but we still need the commit message trailer to trigger issue closure.
369
+
370
+ {{#if DRAFT_PR_MODE}}
371
+ {{#if AUTO_COMMIT_PUSH}}
372
+ ### Step 5.3.5: Push Epic Branch to Remote (Final Commit)
373
+
374
+ After the final "Fixes" commit, push the epic branch to remote so the draft PR includes the issue-closing trailer.
375
+
376
+ **Note**: Incremental pushes in Step 3.4.5 should have already pushed merged child work. This final push adds the "Fixes" commit.
377
+
378
+ 1. **Check if push is needed:**
379
+ ```bash
380
+ cd {{EPIC_WORKTREE_PATH}}
381
+ git log -1 --format=%s
382
+ ```
383
+ - If the latest commit message starts with `[iloom-placeholder]` or `[iloom] Temporary`, no children succeeded. Skip the push.
384
+
385
+ 2. **Push to remote:**
386
+ ```bash
387
+ cd {{EPIC_WORKTREE_PATH}}
388
+ git push --force-with-lease {{GIT_REMOTE}} HEAD
389
+ ```
390
+ **NOTE**: `--force-with-lease` is required because the branch history includes rebased child commits.
391
+
392
+ 3. **Handle errors gracefully:**
393
+ - If push fails: Log the error but do NOT fail the swarm. The work is committed locally and `il finish` will handle the push.
394
+ - Do NOT retry automatically.
395
+
396
+ 4. **Notify completion:**
397
+ - Log: "Epic branch pushed to remote. Draft PR #{{DRAFT_PR_NUMBER}} updated with final commit."
398
+
399
+ {{/if}}
400
+ {{/if}}
401
+ ### Step 5.4: Print Summary
402
+
403
+ Print a comprehensive summary:
404
+
405
+ ```
406
+ ## Swarm Orchestration Summary for Epic #<EPIC_ISSUE_NUMBER>
407
+
408
+ ### Results
409
+ | Issue | Title | Status | Details |
410
+ |-------|-------|--------|---------|
411
+ | #<number> | <title> | <done/failed> | <brief detail> |
412
+ | ... | ... | ... | ... |
413
+
414
+ ### Statistics
415
+ - Total children: N
416
+ - Succeeded: N
417
+ - Failed: N
418
+
419
+ ### Epic Branch State
420
+ The epic branch at `{{EPIC_WORKTREE_PATH}}` contains merged work from all successful children.
421
+
422
+ ### Failed Children
423
+ <If any failed, list them with reasons>
424
+
425
+ ### Next Steps
426
+ The epic worktree is ready for review at: `{{EPIC_WORKTREE_PATH}}`
427
+ ```
428
+
429
+ {{#if DRAFT_PR_MODE}}
430
+ {{#if AUTO_COMMIT_PUSH}}
431
+ Mark todo #10, #11, #12, and #13 as completed.
432
+ {{else}}
433
+ Mark todo #9, #10, and #11 as completed.
434
+ {{/if}}
435
+ {{else}}
436
+ Mark todo #9, #10, and #11 as completed.
437
+ {{/if}}
@@ -2,24 +2,24 @@
2
2
  import {
3
3
  BuildRunner,
4
4
  MergeManager
5
- } from "./chunk-HZXBHMVM.js";
5
+ } from "./chunk-LXLMMXXY.js";
6
6
  import {
7
7
  installDependencies
8
- } from "./chunk-4LKGCFGG.js";
8
+ } from "./chunk-WWKOVDWC.js";
9
9
  import {
10
10
  GitWorktreeManager
11
- } from "./chunk-4GAJJUYS.js";
12
- import "./chunk-TL72BGP6.js";
13
- import "./chunk-VOGGLPG5.js";
11
+ } from "./chunk-VGGST52X.js";
12
+ import "./chunk-MORRVYPT.js";
13
+ import "./chunk-YQ57ORTV.js";
14
+ import "./chunk-ONQYPICO.js";
14
15
  import {
15
16
  getWorktreeRoot,
16
17
  isValidGitRepo
17
- } from "./chunk-H6ST2TGP.js";
18
+ } from "./chunk-MNHZB4Z2.js";
18
19
  import {
19
20
  SettingsManager
20
- } from "./chunk-XFEK2X2D.js";
21
- import "./chunk-KBEIQP4G.js";
22
- import "./chunk-FO5GGFOV.js";
21
+ } from "./chunk-YYAKPQBT.js";
22
+ import "./chunk-KB64WNBZ.js";
23
23
  import "./chunk-6MLEBAYZ.js";
24
24
  import {
25
25
  logger
@@ -139,4 +139,4 @@ export {
139
139
  RebaseCommand,
140
140
  WorktreeValidationError
141
141
  };
142
- //# sourceMappingURL=rebase-AJOJOZUG.js.map
142
+ //# sourceMappingURL=rebase-DWIB77KV.js.map
@@ -2,15 +2,18 @@
2
2
  import {
3
3
  formatRecapMarkdown
4
4
  } from "./chunk-NXMDEL3F.js";
5
+ import {
6
+ findArchivedRecap
7
+ } from "./chunk-Y4YZTHZE.js";
5
8
  import {
6
9
  IdentifierParser
7
- } from "./chunk-2HZX6AMR.js";
10
+ } from "./chunk-WY4QBK43.js";
8
11
  import {
9
12
  GitWorktreeManager
10
- } from "./chunk-4GAJJUYS.js";
11
- import "./chunk-H6ST2TGP.js";
12
- import "./chunk-XFEK2X2D.js";
13
- import "./chunk-KBEIQP4G.js";
13
+ } from "./chunk-VGGST52X.js";
14
+ import "./chunk-MNHZB4Z2.js";
15
+ import "./chunk-YYAKPQBT.js";
16
+ import "./chunk-KB64WNBZ.js";
14
17
  import "./chunk-6MLEBAYZ.js";
15
18
  import "./chunk-VT4PDUYT.js";
16
19
 
@@ -31,8 +34,7 @@ var RecapCommand = class {
31
34
  * Returns RecapOutput in JSON mode, void otherwise
32
35
  */
33
36
  async execute(input) {
34
- const loomPath = await this.resolveLoomPath(input.identifier);
35
- const filePath = path.join(RECAPS_DIR, slugifyPath(loomPath));
37
+ const filePath = await this.resolveRecapFilePath(input.identifier);
36
38
  let recap = {};
37
39
  try {
38
40
  if (await fs.pathExists(filePath)) {
@@ -52,12 +54,14 @@ var RecapCommand = class {
52
54
  console.log(formatRecapMarkdown(result));
53
55
  }
54
56
  /**
55
- * Resolve identifier to loom path
56
- * Falls back to cwd when no identifier is provided (backward compatible)
57
+ * Resolve identifier to a full recap file path.
58
+ * Returns the path to the active recap file, or falls back to an archived
59
+ * recap when the worktree no longer exists (e.g., after cleanup --archive).
60
+ * Falls back to cwd when no identifier is provided (backward compatible).
57
61
  */
58
- async resolveLoomPath(identifier) {
62
+ async resolveRecapFilePath(identifier) {
59
63
  if (!(identifier == null ? void 0 : identifier.trim())) {
60
- return process.cwd();
64
+ return path.join(RECAPS_DIR, slugifyPath(process.cwd()));
61
65
  }
62
66
  const trimmedId = identifier.trim();
63
67
  const gitWorktreeManager = new GitWorktreeManager();
@@ -68,34 +72,58 @@ var RecapCommand = class {
68
72
  const prNumber = parseInt(prMatch[1], 10);
69
73
  const worktree = await gitWorktreeManager.findWorktreeForPR(prNumber, "");
70
74
  if (worktree) {
71
- return worktree.path;
75
+ return path.join(RECAPS_DIR, slugifyPath(worktree.path));
72
76
  }
73
- throw new Error(`No worktree found for PR #${prNumber}`);
77
+ const archivedPath = await findArchivedRecap("pr", prNumber);
78
+ if (archivedPath) return archivedPath;
79
+ throw new Error(`No worktree or archived recap found for PR #${prNumber}`);
74
80
  }
75
81
  try {
76
82
  const parsed = await identifierParser.parseForPatternDetection(trimmedId);
77
83
  if (parsed.type === "pr" && typeof parsed.number === "number") {
78
84
  const worktree = await gitWorktreeManager.findWorktreeForPR(parsed.number, "");
79
85
  if (worktree) {
80
- return worktree.path;
86
+ return path.join(RECAPS_DIR, slugifyPath(worktree.path));
81
87
  }
82
- throw new Error(`No worktree found for PR #${parsed.number}`);
88
+ const archivedPath = await findArchivedRecap("pr", parsed.number);
89
+ if (archivedPath) return archivedPath;
90
+ throw new Error(`No worktree or archived recap found for PR #${parsed.number}`);
83
91
  }
84
92
  if (parsed.type === "issue" && parsed.number !== void 0) {
85
93
  const worktree = await gitWorktreeManager.findWorktreeForIssue(parsed.number);
86
94
  if (worktree) {
87
- return worktree.path;
95
+ return path.join(RECAPS_DIR, slugifyPath(worktree.path));
96
+ }
97
+ const issueNum = typeof parsed.number === "string" ? parseInt(parsed.number, 10) : parsed.number;
98
+ if (isNaN(issueNum)) {
99
+ throw new Error(`No worktree found for identifier: ${identifier}`);
88
100
  }
89
- throw new Error(`No worktree found for issue #${parsed.number}`);
101
+ const archivedPath = await findArchivedRecap("issue", issueNum);
102
+ if (archivedPath) return archivedPath;
103
+ throw new Error(`No worktree or archived recap found for issue #${parsed.number}`);
90
104
  }
91
105
  if (parsed.type === "branch" && parsed.branchName) {
92
106
  const worktree = await gitWorktreeManager.findWorktreeForBranch(parsed.branchName);
93
107
  if (worktree) {
94
- return worktree.path;
108
+ return path.join(RECAPS_DIR, slugifyPath(worktree.path));
95
109
  }
96
110
  throw new Error(`No worktree found for branch: ${parsed.branchName}`);
97
111
  }
98
112
  } catch (error) {
113
+ if (error instanceof Error && (error.message.startsWith("No worktree or archived recap found") || error.message.startsWith("No worktree found for branch:"))) {
114
+ throw error;
115
+ }
116
+ if (error instanceof Error && error.message === `No worktree found for identifier: ${trimmedId}`) {
117
+ const numericMatch = trimmedId.match(/^(\d+)$/);
118
+ if (numericMatch == null ? void 0 : numericMatch[1]) {
119
+ const num = parseInt(numericMatch[1], 10);
120
+ const archivedIssue = await findArchivedRecap("issue", num);
121
+ if (archivedIssue) return archivedIssue;
122
+ const archivedPr = await findArchivedRecap("pr", num);
123
+ if (archivedPr) return archivedPr;
124
+ throw new Error(`No worktree or archived recap found for #${num}`);
125
+ }
126
+ }
99
127
  if (error instanceof Error) {
100
128
  throw new Error(`Could not resolve identifier '${identifier}': ${error.message}`);
101
129
  }
@@ -107,4 +135,4 @@ var RecapCommand = class {
107
135
  export {
108
136
  RecapCommand
109
137
  };
110
- //# sourceMappingURL=recap-GKJXMDXW.js.map
138
+ //# sourceMappingURL=recap-MX63HAKV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/recap.ts"],"sourcesContent":["/**\n * RecapCommand - Fast read-only command for VS Code extension\n *\n * Reads ~/.config/iloom-ai/recaps/{current-loom}.json and outputs it.\n * Skips config validation for fast startup.\n * Includes filePath in output so extension can set up file watcher.\n */\nimport path from 'path'\nimport os from 'os'\nimport fs from 'fs-extra'\nimport type { RecapFile, RecapOutput } from '../mcp/recap-types.js'\nimport { GitWorktreeManager } from '../lib/GitWorktreeManager.js'\nimport { IdentifierParser } from '../utils/IdentifierParser.js'\nimport { formatRecapMarkdown } from '../utils/recap-formatter.js'\nimport { findArchivedRecap } from '../utils/recap-archiver.js'\n\nconst RECAPS_DIR = path.join(os.homedir(), '.config', 'iloom-ai', 'recaps')\n\n/**\n * Reuse MetadataManager.slugifyPath() algorithm\n *\n * Algorithm:\n * 1. Trim trailing slashes\n * 2. Replace all path separators (/ or \\) with ___ (triple underscore)\n * 3. Replace any other non-alphanumeric characters (except _ and -) with -\n * 4. Append .json\n */\nfunction slugifyPath(loomPath: string): string {\n\tlet slug = loomPath.replace(/[/\\\\]+$/, '')\n\tslug = slug.replace(/[/\\\\]/g, '___')\n\tslug = slug.replace(/[^a-zA-Z0-9_-]/g, '-')\n\treturn `${slug}.json`\n}\n\nexport interface RecapCommandInput {\n\tidentifier?: string | undefined // Optional identifier (issue number, PR number, branch name)\n\tjson?: boolean | undefined\n}\n\nexport class RecapCommand {\n\t/**\n\t * Execute the recap command\n\t * Returns RecapOutput in JSON mode, void otherwise\n\t */\n\tasync execute(input: RecapCommandInput): Promise<RecapOutput | void> {\n\t\t// Resolve recap file path from identifier or fall back to cwd\n\t\tconst filePath = await this.resolveRecapFilePath(input.identifier)\n\n\t\t// Read recap file (return empty object if not found)\n\t\tlet recap: RecapFile = {}\n\t\ttry {\n\t\t\tif (await fs.pathExists(filePath)) {\n\t\t\t\tconst content = await fs.readFile(filePath, 'utf8')\n\t\t\t\trecap = JSON.parse(content) as RecapFile\n\t\t\t}\n\t\t} catch {\n\t\t\t// Graceful degradation - return empty recap on read error\n\t\t\t// This is intentional for fast startup\n\t\t}\n\n\t\t// Build output with filePath for file watching (provide defaults for optional fields)\n\t\tconst goal = recap.goal ?? null\n\t\tconst complexity = recap.complexity ?? null\n\t\tconst entries = recap.entries ?? []\n\t\tconst artifacts = recap.artifacts ?? []\n\t\tconst result: RecapOutput = { filePath, goal, complexity, entries, artifacts }\n\n\t\tif (input.json) {\n\t\t\treturn result\n\t\t}\n\n\t\t// Non-JSON mode: print markdown format (intentionally using console.log for piping/redirection)\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log(formatRecapMarkdown(result))\n\t}\n\n\t/**\n\t * Resolve identifier to a full recap file path.\n\t * Returns the path to the active recap file, or falls back to an archived\n\t * recap when the worktree no longer exists (e.g., after cleanup --archive).\n\t * Falls back to cwd when no identifier is provided (backward compatible).\n\t */\n\tprivate async resolveRecapFilePath(identifier: string | undefined): Promise<string> {\n\t\t// Default: use current working directory\n\t\tif (!identifier?.trim()) {\n\t\t\treturn path.join(RECAPS_DIR, slugifyPath(process.cwd()))\n\t\t}\n\n\t\tconst trimmedId = identifier.trim()\n\t\tconst gitWorktreeManager = new GitWorktreeManager()\n\t\tconst identifierParser = new IdentifierParser(gitWorktreeManager)\n\n\t\t// Check for PR-specific formats: pr/123, PR-123, PR/123\n\t\tconst prPattern = /^(?:pr|PR)[/-](\\d+)$/\n\t\tconst prMatch = trimmedId.match(prPattern)\n\t\tif (prMatch?.[1]) {\n\t\t\tconst prNumber = parseInt(prMatch[1], 10)\n\t\t\tconst worktree = await gitWorktreeManager.findWorktreeForPR(prNumber, '')\n\t\t\tif (worktree) {\n\t\t\t\treturn path.join(RECAPS_DIR, slugifyPath(worktree.path))\n\t\t\t}\n\t\t\t// Try archived recap before throwing\n\t\t\tconst archivedPath = await findArchivedRecap('pr', prNumber)\n\t\t\tif (archivedPath) return archivedPath\n\t\t\tthrow new Error(`No worktree or archived recap found for PR #${prNumber}`)\n\t\t}\n\n\t\t// Use IdentifierParser for pattern-based detection\n\t\ttry {\n\t\t\tconst parsed = await identifierParser.parseForPatternDetection(trimmedId)\n\n\t\t\t// Find worktree based on parsed type\n\t\t\tif (parsed.type === 'pr' && typeof parsed.number === 'number') {\n\t\t\t\tconst worktree = await gitWorktreeManager.findWorktreeForPR(parsed.number, '')\n\t\t\t\tif (worktree) {\n\t\t\t\t\treturn path.join(RECAPS_DIR, slugifyPath(worktree.path))\n\t\t\t\t}\n\t\t\t\t// Try archived recap before throwing\n\t\t\t\tconst archivedPath = await findArchivedRecap('pr', parsed.number)\n\t\t\t\tif (archivedPath) return archivedPath\n\t\t\t\tthrow new Error(`No worktree or archived recap found for PR #${parsed.number}`)\n\t\t\t}\n\n\t\t\tif (parsed.type === 'issue' && parsed.number !== undefined) {\n\t\t\t\tconst worktree = await gitWorktreeManager.findWorktreeForIssue(parsed.number)\n\t\t\t\tif (worktree) {\n\t\t\t\t\treturn path.join(RECAPS_DIR, slugifyPath(worktree.path))\n\t\t\t\t}\n\t\t\t\t// Try archived recap before throwing\n\t\t\t\tconst issueNum = typeof parsed.number === 'string' ? parseInt(parsed.number, 10) : parsed.number\n\t\t\t\tif (isNaN(issueNum)) {\n\t\t\t\t\tthrow new Error(`No worktree found for identifier: ${identifier}`)\n\t\t\t\t}\n\t\t\t\tconst archivedPath = await findArchivedRecap('issue', issueNum)\n\t\t\t\tif (archivedPath) return archivedPath\n\t\t\t\tthrow new Error(`No worktree or archived recap found for issue #${parsed.number}`)\n\t\t\t}\n\n\t\t\tif (parsed.type === 'branch' && parsed.branchName) {\n\t\t\t\tconst worktree = await gitWorktreeManager.findWorktreeForBranch(parsed.branchName)\n\t\t\t\tif (worktree) {\n\t\t\t\t\treturn path.join(RECAPS_DIR, slugifyPath(worktree.path))\n\t\t\t\t}\n\t\t\t\t// Branch lookups cannot match archived metadata -- no fallback\n\t\t\t\tthrow new Error(`No worktree found for branch: ${parsed.branchName}`)\n\t\t\t}\n\t\t} catch (error) {\n\t\t\t// Let \"not found\" errors from our own lookups pass through without wrapping --\n\t\t\t// these are valid parse results where no worktree/archive was found\n\t\t\tif (error instanceof Error && (\n\t\t\t\terror.message.startsWith('No worktree or archived recap found') ||\n\t\t\t\terror.message.startsWith('No worktree found for branch:')\n\t\t\t)) {\n\t\t\t\tthrow error\n\t\t\t}\n\n\t\t\t// IdentifierParser throws \"No worktree found for identifier: N\" when no active\n\t\t\t// worktree exists for a numeric input. For plain numbers, still try archived recaps\n\t\t\t// before giving up -- this is the primary archived-lookup path.\n\t\t\tif (\n\t\t\t\terror instanceof Error &&\n\t\t\t\terror.message === `No worktree found for identifier: ${trimmedId}`\n\t\t\t) {\n\t\t\t\tconst numericMatch = trimmedId.match(/^(\\d+)$/)\n\t\t\t\tif (numericMatch?.[1]) {\n\t\t\t\t\tconst num = parseInt(numericMatch[1], 10)\n\t\t\t\t\t// Try issue first, then PR\n\t\t\t\t\tconst archivedIssue = await findArchivedRecap('issue', num)\n\t\t\t\t\tif (archivedIssue) return archivedIssue\n\t\t\t\t\tconst archivedPr = await findArchivedRecap('pr', num)\n\t\t\t\t\tif (archivedPr) return archivedPr\n\t\t\t\t\tthrow new Error(`No worktree or archived recap found for #${num}`)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Re-throw IdentifierParser errors with context\n\t\t\tif (error instanceof Error) {\n\t\t\t\tthrow new Error(`Could not resolve identifier '${identifier}': ${error.message}`)\n\t\t\t}\n\t\t\tthrow error\n\t\t}\n\n\t\t// Should not reach here, but provide a fallback error\n\t\tthrow new Error(`Could not resolve identifier: ${identifier}`)\n\t}\n}\n"],"mappings":"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","names":[]}