issue-flow 0.17.0 → 0.19.1

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 (239) hide show
  1. package/README.md +34 -100
  2. package/dist/agent-EWKA7357.js +32 -0
  3. package/dist/{analyze-6M3KEOX7.js → analyze-Z7ODC27F.js} +42 -41
  4. package/dist/analyze-Z7ODC27F.js.map +1 -0
  5. package/dist/{apply-ADB3ZK7W.js → apply-VNSWQS6O.js} +22 -24
  6. package/dist/apply-VNSWQS6O.js.map +1 -0
  7. package/dist/artifacts-AMJQJFFV.js +82 -0
  8. package/dist/artifacts-AMJQJFFV.js.map +1 -0
  9. package/dist/{bench-FM7I7EIV.js → bench-ZJEIACUK.js} +42 -42
  10. package/dist/{chunk-RYOUZ6EK.js → chunk-23VHVVKW.js} +3 -3
  11. package/dist/chunk-2EYLOS5I.js +99 -0
  12. package/dist/chunk-2EYLOS5I.js.map +1 -0
  13. package/dist/{chunk-ANI2NTR4.js → chunk-2EZW4X57.js} +28 -25
  14. package/dist/chunk-2EZW4X57.js.map +1 -0
  15. package/dist/{chunk-E6KXCV3T.js → chunk-2Q6PG3MT.js} +15 -15
  16. package/dist/chunk-2Q6PG3MT.js.map +1 -0
  17. package/dist/{chunk-C5TL7T4V.js → chunk-2YM2VH6R.js} +2 -2
  18. package/dist/{chunk-LPWPFIIB.js → chunk-2YYP7AE6.js} +14 -16
  19. package/dist/{chunk-LPWPFIIB.js.map → chunk-2YYP7AE6.js.map} +1 -1
  20. package/dist/chunk-3JD25HHJ.js +278 -0
  21. package/dist/chunk-3JD25HHJ.js.map +1 -0
  22. package/dist/{chunk-ISZ54SVE.js → chunk-46S7APLQ.js} +41 -17
  23. package/dist/chunk-46S7APLQ.js.map +1 -0
  24. package/dist/chunk-6AOZCWN2.js +21 -0
  25. package/dist/chunk-6AOZCWN2.js.map +1 -0
  26. package/dist/{chunk-QPGEFLMY.js → chunk-7CMVL4X6.js} +7 -7
  27. package/dist/chunk-7MJGYPHF.js +1571 -0
  28. package/dist/chunk-7MJGYPHF.js.map +1 -0
  29. package/dist/chunk-7ZZUSMBJ.js +35 -0
  30. package/dist/chunk-7ZZUSMBJ.js.map +1 -0
  31. package/dist/{chunk-DUS5ZNKL.js → chunk-DIMDAYZS.js} +7 -6
  32. package/dist/{chunk-DGRTJD5R.js → chunk-DXH4TQRB.js} +7 -7
  33. package/dist/{chunk-CRGIQFUQ.js → chunk-E2H3JV2E.js} +3 -1
  34. package/dist/chunk-E2H3JV2E.js.map +1 -0
  35. package/dist/{chunk-YUNBOUQH.js → chunk-ERAG5IQB.js} +8 -8
  36. package/dist/{chunk-D6ZBGJ3L.js → chunk-F2NFJ27Z.js} +130 -36
  37. package/dist/chunk-F2NFJ27Z.js.map +1 -0
  38. package/dist/{chunk-SOM4QA6C.js → chunk-GD7JZMSJ.js} +93 -18
  39. package/dist/chunk-GD7JZMSJ.js.map +1 -0
  40. package/dist/{chunk-YII4U7ZD.js → chunk-HEHHQU4G.js} +70 -81
  41. package/dist/chunk-HEHHQU4G.js.map +1 -0
  42. package/dist/{chunk-KZ5EY5XX.js → chunk-HHXWAWU6.js} +159 -114
  43. package/dist/chunk-HHXWAWU6.js.map +1 -0
  44. package/dist/{chunk-SGTTR4XW.js → chunk-HWWB22HC.js} +152 -100
  45. package/dist/chunk-HWWB22HC.js.map +1 -0
  46. package/dist/chunk-IMB6FA2F.js +673 -0
  47. package/dist/chunk-IMB6FA2F.js.map +1 -0
  48. package/dist/{chunk-6YQT3SSM.js → chunk-IQYXXFRC.js} +22 -6
  49. package/dist/chunk-IQYXXFRC.js.map +1 -0
  50. package/dist/chunk-J6VJ5M2U.js +1201 -0
  51. package/dist/chunk-J6VJ5M2U.js.map +1 -0
  52. package/dist/{chunk-B3TSS7BT.js → chunk-KDQRNVBZ.js} +3 -3
  53. package/dist/{chunk-A4H7P7T7.js → chunk-LRWIP7QS.js} +41 -26
  54. package/dist/chunk-LRWIP7QS.js.map +1 -0
  55. package/dist/{chunk-DV7HUC4Z.js → chunk-LYC57OXF.js} +20 -117
  56. package/dist/chunk-LYC57OXF.js.map +1 -0
  57. package/dist/{chunk-HAKDLR6N.js → chunk-MAJ6QDRL.js} +65 -78
  58. package/dist/chunk-MAJ6QDRL.js.map +1 -0
  59. package/dist/{chunk-HJBF4WD2.js → chunk-MHA7Q3KU.js} +288 -108
  60. package/dist/chunk-MHA7Q3KU.js.map +1 -0
  61. package/dist/{chunk-UK4E2FUA.js → chunk-MQTVMB7W.js} +103 -76
  62. package/dist/chunk-MQTVMB7W.js.map +1 -0
  63. package/dist/chunk-NP47HRX2.js +136 -0
  64. package/dist/chunk-NP47HRX2.js.map +1 -0
  65. package/dist/{chunk-MI5TREN3.js → chunk-NYDAHE3Y.js} +52 -46
  66. package/dist/chunk-NYDAHE3Y.js.map +1 -0
  67. package/dist/{chunk-UWDCIFTP.js → chunk-P7ZYOC23.js} +41 -57
  68. package/dist/chunk-P7ZYOC23.js.map +1 -0
  69. package/dist/{chunk-3R27MJ5R.js → chunk-PFWHHYEO.js} +3 -3
  70. package/dist/{chunk-2SUFAE6X.js → chunk-PHPAK2L6.js} +44 -33
  71. package/dist/{chunk-2SUFAE6X.js.map → chunk-PHPAK2L6.js.map} +1 -1
  72. package/dist/{chunk-PPZ3K4YM.js → chunk-PRGPSNGA.js} +2 -2
  73. package/dist/{chunk-32JSYXEK.js → chunk-RGFMQFX7.js} +9 -23
  74. package/dist/{chunk-32JSYXEK.js.map → chunk-RGFMQFX7.js.map} +1 -1
  75. package/dist/{chunk-YVPZUN6X.js → chunk-TJVX42KY.js} +335 -38
  76. package/dist/chunk-TJVX42KY.js.map +1 -0
  77. package/dist/{chunk-RCZJQUTC.js → chunk-UDRAD7MF.js} +60 -27
  78. package/dist/chunk-UDRAD7MF.js.map +1 -0
  79. package/dist/{chunk-HY5HLFDU.js → chunk-UHDXXLYH.js} +148 -135
  80. package/dist/chunk-UHDXXLYH.js.map +1 -0
  81. package/dist/{chunk-RNFUVKC3.js → chunk-UPAAZU4W.js} +30 -13
  82. package/dist/chunk-UPAAZU4W.js.map +1 -0
  83. package/dist/{chunk-L6HSKTQE.js → chunk-W7LZLJAF.js} +4 -4
  84. package/dist/{chunk-HLAPOUEM.js → chunk-WB2FVIUS.js} +44 -12
  85. package/dist/chunk-WB2FVIUS.js.map +1 -0
  86. package/dist/{chunk-VE3IYNDN.js → chunk-WPHC6JBR.js} +9 -9
  87. package/dist/chunk-WPHC6JBR.js.map +1 -0
  88. package/dist/{chunk-KZXQWC7U.js → chunk-XHCP6ZNO.js} +201 -64
  89. package/dist/chunk-XHCP6ZNO.js.map +1 -0
  90. package/dist/{chunk-7F7LGMVO.js → chunk-XKR3VD2Z.js} +12 -12
  91. package/dist/{chunk-7F7LGMVO.js.map → chunk-XKR3VD2Z.js.map} +1 -1
  92. package/dist/{chunk-ICM463EO.js → chunk-XNK5UN2D.js} +5 -649
  93. package/dist/chunk-XNK5UN2D.js.map +1 -0
  94. package/dist/{chunk-Z5O2XBWA.js → chunk-XQLNZW7I.js} +40 -30
  95. package/dist/chunk-XQLNZW7I.js.map +1 -0
  96. package/dist/{chunk-QMIQR7EE.js → chunk-ZAYATXJD.js} +32 -316
  97. package/dist/chunk-ZAYATXJD.js.map +1 -0
  98. package/dist/cli.js +128 -60
  99. package/dist/cli.js.map +1 -1
  100. package/dist/compat-YPDZB34U.js +24 -0
  101. package/dist/{config-KGZUPAUP.js → config-56B4OHZE.js} +20 -17
  102. package/dist/{contract-JH2WBIO6.js → contract-HWLFFL3F.js} +2 -2
  103. package/dist/{conventions-RYN7OXJN.js → conventions-DW2CM5I3.js} +20 -16
  104. package/dist/{conventions-RYN7OXJN.js.map → conventions-DW2CM5I3.js.map} +1 -1
  105. package/dist/db-5NU77PJA.js +264 -0
  106. package/dist/db-5NU77PJA.js.map +1 -0
  107. package/dist/diagnostics-UCTS2VUF.js +25 -0
  108. package/dist/execute-R7AVOLNG.js +38 -0
  109. package/dist/{generate-CX6KNHZS.js → generate-O7GUXLN4.js} +46 -43
  110. package/dist/generate-O7GUXLN4.js.map +1 -0
  111. package/dist/{git-A2IP32PQ.js → git-LCG45YXD.js} +2 -3
  112. package/dist/history-6GSURU3H.js +101 -0
  113. package/dist/history-6GSURU3H.js.map +1 -0
  114. package/dist/init-DD6DBRC5.js +28 -0
  115. package/dist/{operations-CAKIT2BO.js → operations-HU5ZFEBA.js} +78 -52
  116. package/dist/operations-HU5ZFEBA.js.map +1 -0
  117. package/dist/{permissions-G4XCAESF.js → permissions-NNNSRC4P.js} +5 -6
  118. package/dist/plan-XR6B7EM3.js +38 -0
  119. package/dist/{policy-WX6O2JID.js → policy-RFJOJQL6.js} +17 -15
  120. package/dist/{policy-WX6O2JID.js.map → policy-RFJOJQL6.js.map} +1 -1
  121. package/dist/pr-OSCNJCOO.js +42 -0
  122. package/dist/pr-review-IKIYHFBO.js +33 -0
  123. package/dist/prd-HFDZJJQR.js +37 -0
  124. package/dist/{ps-76L6ZAZL.js → ps-AKRHGPSR.js} +26 -15
  125. package/dist/ps-AKRHGPSR.js.map +1 -0
  126. package/dist/{recorder-N7U66HCO.js → recorder-T7426CKV.js} +11 -7
  127. package/dist/registry-DWRKD65A.js +17 -0
  128. package/dist/repository-TABBRW2C.js +80 -0
  129. package/dist/resume-GDXKK5XP.js +278 -0
  130. package/dist/resume-GDXKK5XP.js.map +1 -0
  131. package/dist/review-JJHMQA2O.js +38 -0
  132. package/dist/routing-FPNUVE7T.js +35 -0
  133. package/dist/run-RLCURLLL.js +61 -0
  134. package/dist/{session-publisher-KAJQRHNZ.js → session-publisher-Q5TNEZPX.js} +3 -3
  135. package/dist/session-publisher-Q5TNEZPX.js.map +1 -0
  136. package/dist/{state-manager-UPHO2EA2.js → state-manager-IGNTD4AB.js} +9 -4
  137. package/dist/state-manager-IGNTD4AB.js.map +1 -0
  138. package/dist/{usage-U7QNGCPK.js → usage-XW27AL5K.js} +69 -63
  139. package/dist/usage-XW27AL5K.js.map +1 -0
  140. package/dist/web-NCDBVZ5Y.js +205 -0
  141. package/dist/web-NCDBVZ5Y.js.map +1 -0
  142. package/package.json +20 -7
  143. package/prompts/analyze.md +16 -19
  144. package/prompts/execute.md +73 -144
  145. package/prompts/generate.md +23 -10
  146. package/prompts/plan.md +32 -60
  147. package/prompts/pr-review.md +58 -40
  148. package/prompts/pr.md +50 -16
  149. package/prompts/prd.md +26 -19
  150. package/prompts/review.md +41 -18
  151. package/web/AGENTS.md +115 -7
  152. package/web/public/app.css +393 -245
  153. package/web/public/app.js +212 -143
  154. package/web/public/index.html +106 -78
  155. package/dist/agent-TB6W7PT7.js +0 -30
  156. package/dist/analyze-6M3KEOX7.js.map +0 -1
  157. package/dist/apply-ADB3ZK7W.js.map +0 -1
  158. package/dist/chunk-6YQT3SSM.js.map +0 -1
  159. package/dist/chunk-A4H7P7T7.js.map +0 -1
  160. package/dist/chunk-ANI2NTR4.js.map +0 -1
  161. package/dist/chunk-CRGIQFUQ.js.map +0 -1
  162. package/dist/chunk-D6ZBGJ3L.js.map +0 -1
  163. package/dist/chunk-DV7HUC4Z.js.map +0 -1
  164. package/dist/chunk-E6KXCV3T.js.map +0 -1
  165. package/dist/chunk-HAKDLR6N.js.map +0 -1
  166. package/dist/chunk-HJBF4WD2.js.map +0 -1
  167. package/dist/chunk-HLAPOUEM.js.map +0 -1
  168. package/dist/chunk-HY5HLFDU.js.map +0 -1
  169. package/dist/chunk-ICM463EO.js.map +0 -1
  170. package/dist/chunk-ISZ54SVE.js.map +0 -1
  171. package/dist/chunk-KZ5EY5XX.js.map +0 -1
  172. package/dist/chunk-KZXQWC7U.js.map +0 -1
  173. package/dist/chunk-MI5TREN3.js.map +0 -1
  174. package/dist/chunk-QMIQR7EE.js.map +0 -1
  175. package/dist/chunk-RCZJQUTC.js.map +0 -1
  176. package/dist/chunk-RNFUVKC3.js.map +0 -1
  177. package/dist/chunk-SGTTR4XW.js.map +0 -1
  178. package/dist/chunk-SOM4QA6C.js.map +0 -1
  179. package/dist/chunk-UK4E2FUA.js.map +0 -1
  180. package/dist/chunk-UWDCIFTP.js.map +0 -1
  181. package/dist/chunk-VE3IYNDN.js.map +0 -1
  182. package/dist/chunk-WCCZJL7W.js +0 -141
  183. package/dist/chunk-WCCZJL7W.js.map +0 -1
  184. package/dist/chunk-XEYH6ZZI.js +0 -289
  185. package/dist/chunk-XEYH6ZZI.js.map +0 -1
  186. package/dist/chunk-XZUEFTUP.js +0 -243
  187. package/dist/chunk-XZUEFTUP.js.map +0 -1
  188. package/dist/chunk-YII4U7ZD.js.map +0 -1
  189. package/dist/chunk-YVPZUN6X.js.map +0 -1
  190. package/dist/chunk-Z5O2XBWA.js.map +0 -1
  191. package/dist/diagnostics-HTCWF7F4.js +0 -23
  192. package/dist/execute-QL3ZQIAP.js +0 -37
  193. package/dist/generate-CX6KNHZS.js.map +0 -1
  194. package/dist/init-PDVQDRS6.js +0 -24
  195. package/dist/operations-CAKIT2BO.js.map +0 -1
  196. package/dist/plan-PRORGS3L.js +0 -34
  197. package/dist/pr-27RMQI5F.js +0 -39
  198. package/dist/pr-review-VNESMAMT.js +0 -32
  199. package/dist/prd-ZNC4T5BS.js +0 -33
  200. package/dist/ps-76L6ZAZL.js.map +0 -1
  201. package/dist/registry-XLHHYLTR.js +0 -15
  202. package/dist/resume-VLWIE264.js +0 -244
  203. package/dist/resume-VLWIE264.js.map +0 -1
  204. package/dist/review-7CREJDKB.js +0 -35
  205. package/dist/routing-VCDYHSXA.js +0 -33
  206. package/dist/run-3MPORLF5.js +0 -56
  207. package/dist/usage-U7QNGCPK.js.map +0 -1
  208. package/dist/web-HUNDHCJ4.js +0 -172
  209. package/dist/web-HUNDHCJ4.js.map +0 -1
  210. /package/dist/{agent-TB6W7PT7.js.map → agent-EWKA7357.js.map} +0 -0
  211. /package/dist/{bench-FM7I7EIV.js.map → bench-ZJEIACUK.js.map} +0 -0
  212. /package/dist/{chunk-RYOUZ6EK.js.map → chunk-23VHVVKW.js.map} +0 -0
  213. /package/dist/{chunk-C5TL7T4V.js.map → chunk-2YM2VH6R.js.map} +0 -0
  214. /package/dist/{chunk-QPGEFLMY.js.map → chunk-7CMVL4X6.js.map} +0 -0
  215. /package/dist/{chunk-DUS5ZNKL.js.map → chunk-DIMDAYZS.js.map} +0 -0
  216. /package/dist/{chunk-DGRTJD5R.js.map → chunk-DXH4TQRB.js.map} +0 -0
  217. /package/dist/{chunk-YUNBOUQH.js.map → chunk-ERAG5IQB.js.map} +0 -0
  218. /package/dist/{chunk-B3TSS7BT.js.map → chunk-KDQRNVBZ.js.map} +0 -0
  219. /package/dist/{chunk-3R27MJ5R.js.map → chunk-PFWHHYEO.js.map} +0 -0
  220. /package/dist/{chunk-PPZ3K4YM.js.map → chunk-PRGPSNGA.js.map} +0 -0
  221. /package/dist/{chunk-L6HSKTQE.js.map → chunk-W7LZLJAF.js.map} +0 -0
  222. /package/dist/{config-KGZUPAUP.js.map → compat-YPDZB34U.js.map} +0 -0
  223. /package/dist/{contract-JH2WBIO6.js.map → config-56B4OHZE.js.map} +0 -0
  224. /package/dist/{diagnostics-HTCWF7F4.js.map → contract-HWLFFL3F.js.map} +0 -0
  225. /package/dist/{execute-QL3ZQIAP.js.map → diagnostics-UCTS2VUF.js.map} +0 -0
  226. /package/dist/{git-A2IP32PQ.js.map → execute-R7AVOLNG.js.map} +0 -0
  227. /package/dist/{init-PDVQDRS6.js.map → git-LCG45YXD.js.map} +0 -0
  228. /package/dist/{permissions-G4XCAESF.js.map → init-DD6DBRC5.js.map} +0 -0
  229. /package/dist/{plan-PRORGS3L.js.map → permissions-NNNSRC4P.js.map} +0 -0
  230. /package/dist/{pr-27RMQI5F.js.map → plan-XR6B7EM3.js.map} +0 -0
  231. /package/dist/{pr-review-VNESMAMT.js.map → pr-OSCNJCOO.js.map} +0 -0
  232. /package/dist/{prd-ZNC4T5BS.js.map → pr-review-IKIYHFBO.js.map} +0 -0
  233. /package/dist/{recorder-N7U66HCO.js.map → prd-HFDZJJQR.js.map} +0 -0
  234. /package/dist/{registry-XLHHYLTR.js.map → recorder-T7426CKV.js.map} +0 -0
  235. /package/dist/{review-7CREJDKB.js.map → registry-DWRKD65A.js.map} +0 -0
  236. /package/dist/{routing-VCDYHSXA.js.map → repository-TABBRW2C.js.map} +0 -0
  237. /package/dist/{run-3MPORLF5.js.map → review-JJHMQA2O.js.map} +0 -0
  238. /package/dist/{session-publisher-KAJQRHNZ.js.map → routing-FPNUVE7T.js.map} +0 -0
  239. /package/dist/{state-manager-UPHO2EA2.js.map → run-RLCURLLL.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/verbose.ts","../src/ui/logger.ts","../src/config/sources.ts","../src/core/journal.ts","../src/storage/db/import.ts","../src/storage/resolve.ts","../src/storage/artifact-storage.ts"],"sourcesContent":["let _verbose = false;\n\nexport function setVerbose(enabled: boolean): void {\n _verbose = enabled;\n}\n\nexport function isVerbose(): boolean {\n return _verbose;\n}\n\nlet _onOutput: ((line: string) => void) | undefined;\n\n/**\n * Set a global output callback for routing verbose output (e.g., through listr2 task.output).\n * Pass undefined to clear and fall back to direct stderr writes.\n */\nexport function setOutputCallback(callback: ((line: string) => void) | undefined): void {\n _onOutput = callback;\n}\n\nexport function getOutputCallback(): ((line: string) => void) | undefined {\n return _onOutput;\n}\n\nlet _onStoryUpdate: ((stories: import('../types.js').UserStory[]) => void) | undefined;\n\n/**\n * Set a global callback for story progress updates during engine execution.\n * Called after each iteration with the latest user stories state.\n * Pass undefined to clear.\n */\nexport function setStoryUpdateCallback(\n callback: ((stories: import('../types.js').UserStory[]) => void) | undefined,\n): void {\n _onStoryUpdate = callback;\n}\n\nexport function getStoryUpdateCallback():\n | ((stories: import('../types.js').UserStory[]) => void)\n | undefined {\n return _onStoryUpdate;\n}\n\nlet _onStoryStage: ((storyId: string | undefined) => void) | undefined;\n\n/**\n * Set a global callback for the active story of the current `execute`\n * iteration. Called by `core/engine.ts` alongside every `iteration:start`\n * publication, with the same `storyId` (or `undefined` once every story\n * already passes) — the terminal renderer's only source for \"who is\n * executing right now\", never a second, independently computed heuristic.\n * Pass `undefined` to clear.\n */\nexport function setStoryStageCallback(\n callback: ((storyId: string | undefined) => void) | undefined,\n): void {\n _onStoryStage = callback;\n}\n\nexport function getStoryStageCallback(): ((storyId: string | undefined) => void) | undefined {\n return _onStoryStage;\n}\n\nexport interface ActivityNotice {\n tool: string;\n detail?: string;\n}\n\nlet _onActivity: ((notice: ActivityNotice) => void) | undefined;\n\n/**\n * Live tool activity for the clean terminal view. Published in every mode,\n * including non-verbose — the snapshot's `currentActivity` and the dashboard\n * both read the same event.\n */\nexport function setActivityCallback(\n callback: ((notice: ActivityNotice) => void) | undefined,\n): void {\n _onActivity = callback;\n}\n\nexport function getActivityCallback(): ((notice: ActivityNotice) => void) | undefined {\n return _onActivity;\n}\n\nlet _globalTimeout: number | undefined;\n\nexport function setGlobalTimeout(ms: number): void {\n _globalTimeout = ms;\n}\n\nexport function getGlobalTimeout(): number | undefined {\n return _globalTimeout;\n}\n\n/**\n * Silence tolerated before an agent is considered stuck, in milliseconds.\n *\n * Global for the same reason the timeout is: every phase invokes the CLI\n * through the same two functions, and threading it through each one of them\n * would be a parameter with a single value per process. `undefined` leaves the\n * default; `0` turns the watchdog off.\n */\nlet _inactivityTimeout: number | undefined;\n\nexport function setInactivityTimeout(ms: number): void {\n _inactivityTimeout = ms;\n}\n\nexport function getInactivityTimeout(): number | undefined {\n return _inactivityTimeout;\n}\n","import chalk from 'chalk';\nimport ora, { type Ora } from 'ora';\nimport { getSessionPublisher } from '../core/session-publisher.js';\nimport type { SessionLogLevel } from '../core/session-state.js';\nimport { isoNow } from '../core/state-manager.js';\nimport { getOutputCallback } from '../core/verbose.js';\nimport { writeDiagnostic } from '../storage/diagnostics.js';\n\n/**\n * Detect if unicode output is supported.\n */\nexport function useUnicode(): boolean {\n if (process.env.NO_COLOR != null && process.env.NO_COLOR !== '') return false;\n if (!process.stdout.isTTY) return false;\n return true;\n}\n\n/**\n * Detect if color output is supported.\n */\nexport function useColor(): boolean {\n if (process.env.NO_COLOR != null && process.env.NO_COLOR !== '') return false;\n if (!process.stdout.isTTY) return false;\n return true;\n}\n\nexport interface Icons {\n success: string;\n fail: string;\n pending: string;\n retry: string;\n warn: string;\n start: string;\n end: string;\n notReached: string;\n tool: string;\n connector: string;\n info: string;\n}\n\nexport function getIcons(): Icons {\n if (useUnicode()) {\n return {\n success: '\\u2713',\n fail: '\\u2717',\n pending: '\\u23F3',\n retry: '\\u21BB',\n warn: '\\u26A0',\n start: '\\u25B6',\n end: '\\u25A0',\n notReached: '\\u25CB',\n tool: '\\u25B8',\n connector: '\\u2502',\n info: '\\u00B7',\n };\n }\n return {\n success: '[OK]',\n fail: '[FAIL]',\n pending: '[...]',\n retry: '[RETRY]',\n warn: '[WARN]',\n start: '[START]',\n end: '[END]',\n notReached: '[ ]',\n tool: '>',\n connector: '|',\n info: '-',\n };\n}\n\n/**\n * Get the terminal width.\n */\nexport function getTermWidth(): number {\n return process.stdout.columns ?? 80;\n}\n\n/**\n * Emit a line of output. Routes through the global output callback when\n * running inside a listr2 task context, otherwise falls back to console.log.\n *\n * Also forwards the line to the session publisher with its structured level;\n * ANSI codes are stripped by the reducer, so terminal output stays untouched.\n */\nfunction emit(line: string, level: SessionLogLevel = 'info'): void {\n getSessionPublisher().publish({ type: 'log', at: isoNow(), level, message: line });\n writeDiagnostic({\n level: level === 'warn' ? 'warning' : level,\n message: line,\n });\n const cb = getOutputCallback();\n if (cb) {\n cb(line);\n } else {\n console.log(line);\n }\n}\n\nexport function printSuccess(message: string): void {\n const icons = getIcons();\n if (useColor()) {\n emit(chalk.green(`${icons.success} ${message}`));\n } else {\n emit(`${icons.success} ${message}`);\n }\n}\n\nexport function printError(message: string): void {\n const icons = getIcons();\n if (useColor()) {\n emit(chalk.red(`${icons.fail} ${message}`), 'error');\n } else {\n emit(`${icons.fail} ${message}`, 'error');\n }\n}\n\nexport function printWarning(message: string): void {\n const icons = getIcons();\n if (useColor()) {\n emit(chalk.yellow(`${icons.warn} ${message}`), 'warn');\n } else {\n emit(`${icons.warn} ${message}`, 'warn');\n }\n}\n\nexport function printRetry(message: string): void {\n const icons = getIcons();\n if (useColor()) {\n emit(chalk.yellow(`${icons.retry} ${message}`), 'warn');\n } else {\n emit(`${icons.retry} ${message}`, 'warn');\n }\n}\n\nexport function printInfo(message: string): void {\n const icons = getIcons();\n if (useColor()) {\n emit(chalk.blue(`${icons.info} ${message}`));\n } else {\n emit(`${icons.info} ${message}`);\n }\n}\n\n/**\n * Create an ora spinner with consistent styling.\n */\nexport function createSpinner(message: string): Ora {\n return ora({\n text: message,\n color: 'blue',\n spinner: 'dots',\n });\n}\n\n/**\n * Format a duration in seconds to a human-readable string.\n */\nexport function formatDuration(totalSeconds: number): string {\n const hours = Math.floor(totalSeconds / 3600);\n const mins = Math.floor((totalSeconds % 3600) / 60);\n const secs = totalSeconds % 60;\n\n if (hours > 0) {\n return `${hours}h ${mins}m ${secs}s`;\n }\n if (mins > 0) {\n return `${mins}m ${secs}s`;\n }\n return `${secs}s`;\n}\n\n/**\n * Timer that calls a callback every second with the formatted elapsed time.\n */\nexport class ElapsedTimer {\n private startTime: number;\n private intervalId: ReturnType<typeof setInterval> | null = null;\n private cleanup = () => this.stop();\n\n constructor(private onTick: (elapsed: string) => void) {\n this.startTime = Date.now();\n }\n\n start(): this {\n this.startTime = Date.now();\n this.intervalId = setInterval(() => {\n const seconds = Math.floor((Date.now() - this.startTime) / 1000);\n this.onTick(formatDuration(seconds));\n }, 1_000);\n // Prevent the timer from keeping the process alive\n if (this.intervalId && typeof this.intervalId === 'object' && 'unref' in this.intervalId) {\n this.intervalId.unref();\n }\n process.on('exit', this.cleanup);\n return this;\n }\n\n getElapsedSeconds(): number {\n return Math.floor((Date.now() - this.startTime) / 1000);\n }\n\n stop(): number {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n this.intervalId = null;\n }\n process.removeListener('exit', this.cleanup);\n return this.getElapsedSeconds();\n }\n}\n","import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { getGlobalRoot } from '../storage/paths.js';\nimport { type GlobalConfig, globalConfigSchema } from '../storage/schemas.js';\nimport { printWarning } from '../ui/logger.js';\n\n/** Optional per-project configuration file, read from the project root. */\nexport const PROJECT_CONFIG_FILENAME = '.issue-flow.json';\n\n/**\n * Locate the project root without spawning `git`. Tests that mock `execa`\n * wholesale (execute-regression, executor) treat every spawn as the agent;\n * a `git rev-parse` here would steal that first call.\n */\nexport function findProjectRootFromCwd(start: string = process.cwd()): string | undefined {\n let dir = start;\n while (true) {\n if (existsSync(join(dir, PROJECT_CONFIG_FILENAME)) || existsSync(join(dir, '.git'))) {\n return dir;\n }\n const parent = dirname(dir);\n if (parent === dir) return undefined;\n dir = parent;\n }\n}\n\nexport async function readProjectConfigFile(\n projectRoot: string | undefined,\n warn: (message: string) => void,\n): Promise<Record<string, unknown> | null> {\n const root = projectRoot ?? findProjectRootFromCwd();\n if (root === undefined) {\n return null;\n }\n\n const filePath = join(root, PROJECT_CONFIG_FILENAME);\n let raw: string;\n try {\n raw = await readFile(filePath, 'utf-8');\n } catch {\n // The file is entirely optional — absence is the common case.\n return null;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n warn(`Ignoring ${PROJECT_CONFIG_FILENAME}: invalid JSON.`);\n return null;\n }\n\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n warn(`Ignoring ${PROJECT_CONFIG_FILENAME}: expected a JSON object.`);\n return null;\n }\n\n return parsed as Record<string, unknown>;\n}\n\n/** Machine-wide configuration file, read from the global storage root. */\nexport const GLOBAL_CONFIG_FILENAME = 'config.json';\n\nexport interface LoadGlobalConfigOptions {\n /** Environment source, forwarded to getGlobalRoot(). Defaults to process.env. */\n env?: NodeJS.ProcessEnv;\n /** Directory holding config.json. Defaults to getGlobalRoot(). */\n globalRoot?: string;\n /** Warning sink. Defaults to printWarning. */\n warn?: (message: string) => void;\n}\n\n/**\n * Read and parse `~/.issue-flow/config.json`, the preferences a user sets once\n * for every project.\n *\n * Never throws, exactly like readProjectConfigFile(): an absent file, an\n * unreadable path, invalid JSON, a non-object root or an invalid key all\n * degrade to \"no global preference\" so the caller falls back to the layers\n * below. Absence is silent — it is the common case; every other failure warns,\n * because a preference the user did write is being dropped.\n *\n * Validation happens key by key on purpose: a typo in `retry` must not cost the\n * user their `web` settings. Unknown keys are dropped without a warning, which\n * is what keeps a file written by a newer release readable here.\n */\nexport async function loadGlobalConfig(\n options: LoadGlobalConfigOptions = {},\n): Promise<GlobalConfig> {\n const warn = options.warn ?? printWarning;\n\n let root: string;\n try {\n root = options.globalRoot ?? getGlobalRoot({ env: options.env ?? process.env });\n } catch (err: unknown) {\n warn(`Ignoring ${GLOBAL_CONFIG_FILENAME}: ${(err as Error).message}`);\n return {};\n }\n\n const filePath = join(root, GLOBAL_CONFIG_FILENAME);\n let raw: string;\n try {\n raw = await readFile(filePath, 'utf-8');\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n warn(`Ignoring ${GLOBAL_CONFIG_FILENAME}: ${(err as Error).message}`);\n }\n return {};\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n warn(`Ignoring ${GLOBAL_CONFIG_FILENAME}: invalid JSON.`);\n return {};\n }\n\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n warn(`Ignoring ${GLOBAL_CONFIG_FILENAME}: expected a JSON object.`);\n return {};\n }\n\n const config: GlobalConfig = {};\n for (const [key, value] of Object.entries(parsed)) {\n const result = globalConfigSchema.safeParse({ [key]: value });\n if (!result.success) {\n warn(\n `Ignoring \"${key}\" key of ${GLOBAL_CONFIG_FILENAME}: ${result.error.issues[0]?.message ?? 'invalid value'}.`,\n );\n continue;\n }\n // An unknown key parses successfully into an empty object and disappears\n // here, which is the retro-compatible behaviour we want.\n Object.assign(config, result.data);\n }\n\n return config;\n}\n","import { appendFile, mkdir, rename, stat, unlink } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport {\n createInitialSnapshot,\n MemoryPublisher,\n type MemoryPublisherOptions,\n reduceSessionEvent,\n type SessionEvent,\n type SessionPublisher,\n type SessionReducerOptions,\n type SessionSnapshot,\n} from './session-state.js';\n\n/**\n * Append-only journal of `SessionEvent`s.\n *\n * `session.json` is a *projection*: the reducer folds every event into one\n * snapshot and the events themselves are discarded. That is the right shape for\n * a dashboard and the wrong one for an audit — after a six-hour run, \"what\n * happened at 3am\" has no answer, because the retries, the failures and their\n * order were consumed to produce a single final state.\n *\n * This publisher writes the events instead of the state: one JSON line each, in\n * order, with a monotonic `seq`. It sits **beside** `FilePublisher`, never in\n * its place. The snapshot stays the projection; the journal is the history, and\n * replaying it through `reduceSessionEvent` reproduces the snapshot — which is\n * the property `replayJournal()` and its test exist to keep true.\n *\n * The invariant of `session-state.ts` holds here unchanged: `publish()` is\n * synchronous, never throws, and every I/O failure is swallowed after a single\n * warning. Monitoring never affects the pipeline.\n */\n\n/** One line of `events.jsonl`. */\nexport interface JournalEntry {\n /** 1-based, monotonic within a file *and* across a rotation. */\n seq: number;\n event: SessionEvent;\n}\n\n/**\n * Event history is durable by default. Rotation is opt-in because retaining a\n * single previous file silently discarded older audit events.\n */\nexport const DEFAULT_JOURNAL_MAX_BYTES = 0;\n\nexport interface JournalPublisherOptions extends MemoryPublisherOptions {\n /**\n * Rotate once the file would grow past this. `0` (the default) disables\n * rotation; an explicit retention policy must own any deletion.\n */\n maxFileBytes?: number;\n}\n\n/**\n * Publisher that appends every event to `events.jsonl`.\n *\n * Writes are serialized on a promise chain — the same discipline as\n * `FilePublisher` — so lines never interleave and the order on disk is the\n * order of `publish()`. Nothing is throttled: dropping or coalescing events is\n * exactly what the snapshot already does, and the point of the journal is that\n * it does not.\n */\nexport class JournalPublisher extends MemoryPublisher {\n private readonly filePath: string;\n private readonly rotatedPath: string;\n private readonly maxFileBytes: number;\n private seq = 0;\n private writeChain: Promise<void> = Promise.resolve();\n private closed = false;\n /** Bytes in the current file. `null` until the first write measures it. */\n private bytes: number | null = null;\n\n constructor(filePath: string, rotatedPath: string, options: JournalPublisherOptions = {}) {\n super(options);\n this.filePath = filePath;\n this.rotatedPath = rotatedPath;\n this.maxFileBytes = options.maxFileBytes ?? DEFAULT_JOURNAL_MAX_BYTES;\n }\n\n protected override afterPublish(event: SessionEvent): void {\n if (this.closed) return;\n this.seq++;\n const line = `${JSON.stringify({ seq: this.seq, event } satisfies JournalEntry)}\\n`;\n this.enqueue(line);\n }\n\n private enqueue(line: string): void {\n this.writeChain = this.writeChain.then(async () => {\n try {\n await this.appendLine(line);\n } catch (err) {\n this.warnOnce(err);\n }\n });\n }\n\n private async appendLine(line: string): Promise<void> {\n const size = Buffer.byteLength(line);\n\n if (this.bytes === null) {\n await mkdir(dirname(this.filePath), { recursive: true });\n this.bytes = await currentSize(this.filePath);\n }\n\n // Rotate *before* the write that would cross the ceiling, so a line is\n // never split across two files: an entry that cannot be parsed whole is\n // worse than a file slightly under its limit.\n if (this.maxFileBytes > 0 && this.bytes > 0 && this.bytes + size > this.maxFileBytes) {\n await this.rotate();\n }\n\n await appendFile(this.filePath, line, 'utf-8');\n this.bytes += size;\n }\n\n /**\n * Keep exactly one previous generation only when a caller explicitly asks\n * for rotation. The default never enters this path, so history is never\n * discarded without an explicit policy.\n */\n private async rotate(): Promise<void> {\n try {\n await unlink(this.rotatedPath);\n } catch {\n // No previous generation. Nothing to remove.\n }\n await rename(this.filePath, this.rotatedPath);\n this.bytes = 0;\n }\n\n override async flush(): Promise<void> {\n await this.writeChain;\n }\n\n override async close(): Promise<void> {\n await this.flush();\n this.closed = true;\n }\n}\n\n/** Size of `path` in bytes, or `0` when it does not exist yet. */\nasync function currentSize(path: string): Promise<number> {\n try {\n return (await stat(path)).size;\n } catch {\n return 0;\n }\n}\n\n/**\n * Parse the lines of a journal, skipping what cannot be read.\n *\n * A journal is appended to by a live process and read by another, so the last\n * line may well be a partial write. Tolerating it — and any line a newer\n * release wrote in a shape this one does not know — is what makes the file\n * usable at all; the alternative is a reader that throws on a healthy run.\n */\nexport function parseJournal(content: string): JournalEntry[] {\n const entries: JournalEntry[] = [];\n\n for (const line of content.split('\\n')) {\n const trimmed = line.trim();\n if (trimmed === '') continue;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch {\n continue;\n }\n\n if (!isEntry(parsed)) continue;\n entries.push(parsed);\n }\n\n return entries;\n}\n\nfunction isEntry(value: unknown): value is JournalEntry {\n if (typeof value !== 'object' || value === null) return false;\n const candidate = value as { seq?: unknown; event?: unknown };\n if (typeof candidate.seq !== 'number') return false;\n const event = candidate.event;\n return (\n typeof event === 'object' && event !== null && typeof (event as SessionEvent).type === 'string'\n );\n}\n\n/**\n * Rebuild the snapshot from a journal.\n *\n * This is the reproducibility contract of the file: the same reducer that\n * produced `session.json` live, run over the events in the order they were\n * written, must land on the same state. A journal that cannot do that is a log,\n * not a journal.\n */\nexport function replayJournal(\n content: string,\n options: SessionReducerOptions = {},\n): SessionSnapshot {\n let snapshot = createInitialSnapshot();\n for (const entry of parseJournal(content)) {\n snapshot = reduceSessionEvent(snapshot, entry.event, options);\n }\n return snapshot;\n}\n\n/**\n * Fan one event stream out to several publishers.\n *\n * The journal sits **beside** the snapshot writer, so something has to hold\n * both. It is the composite rather than the journal that does it, because the\n * relationship is symmetric: neither surface owns the other, and dropping\n * either one has to leave the remaining one untouched.\n *\n * `snapshot()` and `version()` answer from the first member, which is the\n * primary surface. A member that fails has already swallowed its own error —\n * that is the contract of every publisher here — so the loop needs no guard of\n * its own beyond `publish()` never throwing.\n */\nexport class MultiPublisher implements SessionPublisher {\n private readonly members: readonly SessionPublisher[];\n private readonly primary: SessionPublisher;\n\n constructor(members: readonly SessionPublisher[]) {\n if (members.length === 0) {\n throw new Error('MultiPublisher needs at least one member');\n }\n this.members = members;\n this.primary = members[0] as SessionPublisher;\n }\n\n publish(event: SessionEvent): void {\n for (const member of this.members) {\n member.publish(event);\n }\n }\n\n snapshot(): SessionSnapshot {\n return this.primary.snapshot();\n }\n\n version(): number {\n return this.primary.version();\n }\n\n async flush(): Promise<void> {\n await Promise.all(this.members.map((member) => member.flush()));\n }\n\n async close(): Promise<void> {\n await Promise.all(this.members.map((member) => member.close()));\n }\n}\n","import { createHash } from 'node:crypto';\nimport { existsSync } from 'node:fs';\nimport { readdir, readFile, rename } from 'node:fs/promises';\nimport { basename, join, relative } from 'node:path';\nimport { parseJournal } from '../../core/journal.js';\nimport { taskPlanSchema } from '../../schemas.js';\nimport { METADATA_FILENAME } from '../compat.js';\nimport {\n EVENTS_FILENAME,\n ISSUES_DIR_NAME,\n PROVIDERS_HEALTH_FILENAME,\n QUEUES_DIR_NAME,\n ROTATED_EVENTS_FILENAME,\n VERIFY_FILENAME,\n} from '../paths.js';\nimport { projectMetadataSchema, providersHealthSchema } from '../schemas.js';\nimport { openDatabase } from './driver.js';\nimport {\n getDatabasePath,\n type OpenIssueFlowDatabaseOptions,\n openIssueFlowDatabase,\n} from './index.js';\nimport { CURRENT_SCHEMA_VERSION } from './migrations.js';\nimport { writePlanRows } from './repository.js';\n\n/** A source file that was imported into one or more relational tables. */\ninterface Artifact {\n path: string;\n relativePath: string;\n sha256: string;\n kind: 'metadata' | 'tasks' | 'health' | 'queue' | 'events' | 'verify';\n value: unknown;\n issueId?: string;\n}\n\nexport interface ImportProjectOptions extends OpenIssueFlowDatabaseOptions {\n projectId: string;\n projectDir: string;\n projectRoot: string;\n remoteUrl: string | null;\n /** Number of pre-migration backups to retain. Defaults to five. */\n backupRetention?: number;\n /** Explicit database history retention. Omitted values retain every row. */\n retention?: {\n executions?: number;\n events?: number;\n snapshots?: number;\n };\n /** Import the potentially large legacy JSONL journal. Disabled by default. */\n withEvents?: boolean;\n onWarning?: (message: string) => void;\n}\n\nexport interface ImportProjectResult {\n imported: number;\n skipped: number;\n tableCounts: Record<string, number>;\n failed: boolean;\n}\n\nconst EMPTY_COUNTS: Record<string, number> = {\n projects: 0,\n issues: 0,\n pipelines: 0,\n stories: 0,\n executions: 0,\n events: 0,\n queues: 0,\n provider_health: 0,\n verifications: 0,\n pull_requests: 0,\n user_story_numbering: 0,\n};\n\nfunction digest(content: string): string {\n return createHash('sha256').update(content).digest('hex');\n}\n\nfunction stableId(prefix: string, value: string): string {\n return `${prefix}-${digest(value).slice(0, 24)}`;\n}\n\nasync function fileIfPresent(path: string): Promise<string | null> {\n try {\n return await readFile(path, 'utf-8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw error;\n }\n}\n\nasync function collectArtifacts(projectDir: string, withEvents: boolean): Promise<Artifact[]> {\n const artifacts: Artifact[] = [];\n const addJson = async (path: string, kind: Artifact['kind'], issueId?: string): Promise<void> => {\n const content = await fileIfPresent(path);\n if (content === null) return;\n artifacts.push({\n path,\n relativePath: relative(projectDir, path),\n sha256: digest(content),\n kind,\n issueId,\n value: JSON.parse(content),\n });\n };\n\n await addJson(join(projectDir, METADATA_FILENAME), 'metadata');\n await addJson(join(projectDir, PROVIDERS_HEALTH_FILENAME), 'health');\n\n const issuesDir = join(projectDir, ISSUES_DIR_NAME);\n let issueEntries: string[] = [];\n try {\n issueEntries = await readdir(issuesDir);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n }\n for (const issueId of issueEntries) {\n const issueDir = join(issuesDir, issueId);\n await addJson(join(issueDir, 'tasks.json'), 'tasks', issueId);\n await addJson(join(issueDir, VERIFY_FILENAME), 'verify', issueId);\n if (withEvents) {\n for (const name of [ROTATED_EVENTS_FILENAME, EVENTS_FILENAME]) {\n const path = join(issueDir, name);\n const content = await fileIfPresent(path);\n if (content !== null) {\n artifacts.push({\n path,\n relativePath: relative(projectDir, path),\n sha256: digest(content),\n kind: 'events',\n issueId,\n value: parseJournal(content),\n });\n }\n }\n }\n }\n\n const queuesDir = join(projectDir, QUEUES_DIR_NAME);\n let queueEntries: string[] = [];\n try {\n queueEntries = await readdir(queuesDir);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n }\n for (const queueId of queueEntries) {\n await addJson(join(queuesDir, queueId, 'execution-plan.json'), 'queue');\n }\n return artifacts;\n}\n\nfunction increment(counts: Record<string, number>, table: string): void {\n counts[table] = (counts[table] ?? 0) + 1;\n}\n\nfunction insertIssue(\n database: Awaited<ReturnType<typeof openIssueFlowDatabase>>,\n projectId: string,\n id: string,\n status: string,\n title: string | null,\n branchName: string | null,\n timestamp: string,\n counts: Record<string, number>,\n): void {\n database\n .prepare(\n `INSERT INTO issues (project_id, id, title, status, branch_name, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(project_id, id) DO UPDATE SET\n title = excluded.title, status = excluded.status, branch_name = excluded.branch_name,\n updated_at = excluded.updated_at`,\n )\n .run(projectId, id, title, status, branchName, timestamp, timestamp);\n increment(counts, 'issues');\n}\n\nfunction importArtifact(\n database: Awaited<ReturnType<typeof openIssueFlowDatabase>>,\n artifact: Artifact,\n projectId: string,\n fallback: { root: string; remoteUrl: string | null; timestamp: string },\n counts: Record<string, number>,\n): void {\n const value = artifact.value as Record<string, unknown>;\n if (artifact.kind === 'metadata') {\n const metadata = projectMetadataSchema.parse(value);\n database\n .prepare(\n `INSERT INTO projects (id, root, remote_url, created_at, updated_at) VALUES (?, ?, ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET root = excluded.root, remote_url = excluded.remote_url,\n updated_at = excluded.updated_at`,\n )\n .run(projectId, metadata.root, metadata.remoteUrl, metadata.createdAt, metadata.updatedAt);\n increment(counts, 'projects');\n if (metadata.userStoryNumbering !== undefined) {\n const decision = metadata.userStoryNumbering;\n database\n .prepare(\n `INSERT INTO user_story_numbering (project_id, next_number, source, issue_id, decided_at, detail)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(project_id) DO UPDATE SET next_number = excluded.next_number,\n source = excluded.source, issue_id = excluded.issue_id, decided_at = excluded.decided_at,\n detail = excluded.detail`,\n )\n .run(\n projectId,\n decision.nextNumber,\n decision.source,\n decision.issueNumber,\n decision.decidedAt,\n decision.detail ?? null,\n );\n increment(counts, 'user_story_numbering');\n }\n return;\n }\n if (artifact.kind === 'tasks') {\n // Some pre-plan legacy trees use `tasks.json` as a tiny placeholder. It\n // is not structured pipeline state yet, so leave it available for the\n // legacy reader instead of failing the project-wide SQLite import.\n const parsed = taskPlanSchema.safeParse(value);\n if (!parsed.success) return;\n const plan = parsed.data;\n const issueId = artifact.issueId ?? String(plan.issueNumber);\n writePlanRows(\n database,\n {\n tasksPath: artifact.path,\n projectId,\n issueId,\n projectRoot: fallback.root,\n },\n plan,\n );\n increment(counts, 'issues');\n increment(counts, 'pipelines');\n counts.stories += plan.userStories.length;\n for (const execution of plan.executions ?? []) {\n const cost = execution.cost.status === 'unknown' ? null : execution.cost.amount;\n database\n .prepare(\n `INSERT INTO executions\n (id, project_id, issue_id, status, started_at, finished_at, duration_ms, cost_status, cost_amount, payload_json)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET status = excluded.status, finished_at = excluded.finished_at,\n duration_ms = excluded.duration_ms, cost_status = excluded.cost_status,\n cost_amount = excluded.cost_amount, payload_json = excluded.payload_json`,\n )\n .run(\n execution.id,\n projectId,\n issueId,\n execution.status,\n execution.startedAt,\n execution.finishedAt,\n execution.durationMs,\n execution.cost.status,\n cost,\n JSON.stringify(execution),\n );\n increment(counts, 'executions');\n }\n if (plan.pullRequest !== undefined) {\n const id = stableId('pr', `${projectId}:${issueId}:${plan.pullRequest.number}`);\n database\n .prepare(\n `INSERT INTO pull_requests (id, project_id, issue_id, number, url, status, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET url = excluded.url, status = excluded.status`,\n )\n .run(\n id,\n projectId,\n issueId,\n plan.pullRequest.number,\n plan.pullRequest.url,\n 'created',\n plan.pullRequest.createdAt,\n );\n increment(counts, 'pull_requests');\n }\n return;\n }\n if (artifact.kind === 'health') {\n const health = providersHealthSchema.parse(value);\n for (const [provider, record] of Object.entries(health.providers)) {\n database\n .prepare(\n `INSERT INTO provider_health (project_id, provider, payload_json, updated_at) VALUES (?, ?, ?, ?)\n ON CONFLICT(project_id, provider) DO UPDATE SET payload_json = excluded.payload_json,\n updated_at = excluded.updated_at`,\n )\n .run(projectId, provider, JSON.stringify(record), fallback.timestamp);\n increment(counts, 'provider_health');\n }\n return;\n }\n if (artifact.kind === 'events') {\n const issueId = artifact.issueId ?? 'unknown';\n const runId = `legacy:${projectId}:${issueId}`;\n insertIssue(database, projectId, issueId, 'imported', null, null, fallback.timestamp, counts);\n database\n .prepare(\n `INSERT INTO runs (id, project_id, issue_id, status, started_at, finished_at, session_id)\n VALUES (?, ?, ?, 'imported', ?, ?, ?)\n ON CONFLICT(id) DO NOTHING`,\n )\n .run(runId, projectId, issueId, fallback.timestamp, fallback.timestamp, runId);\n let nextSequence =\n (database\n .prepare('SELECT COALESCE(MAX(sequence), 0) AS sequence FROM events WHERE run_id = ?')\n .get<{ sequence: number }>(runId)?.sequence ?? 0) + 1;\n for (const [index, entry] of (artifact.value as ReturnType<typeof parseJournal>).entries()) {\n const id = stableId('event', `${artifact.relativePath}:${index}`);\n const event = entry.event as { at?: string; type: string };\n database\n .prepare(\n `INSERT INTO events\n (id, project_id, run_id, occurred_at, kind, payload_json, session_id, sequence)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET occurred_at = excluded.occurred_at, kind = excluded.kind,\n payload_json = excluded.payload_json`,\n )\n .run(\n id,\n projectId,\n runId,\n event.at ?? fallback.timestamp,\n event.type,\n JSON.stringify(entry),\n runId,\n nextSequence++,\n );\n increment(counts, 'events');\n }\n return;\n }\n if (artifact.kind === 'verify') {\n insertIssue(\n database,\n projectId,\n artifact.issueId ?? 'unknown',\n 'imported',\n null,\n null,\n fallback.timestamp,\n counts,\n );\n const id = stableId('verify', artifact.relativePath);\n database\n .prepare(\n `INSERT INTO verifications (id, project_id, issue_id, status, created_at, payload_json)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET status = excluded.status, payload_json = excluded.payload_json`,\n )\n .run(\n id,\n projectId,\n artifact.issueId ?? null,\n String(value.verdict ?? 'unknown'),\n fallback.timestamp,\n JSON.stringify(value),\n );\n increment(counts, 'verifications');\n return;\n }\n\n const queue = value as {\n id: string;\n status: string;\n createdAt: string;\n updatedAt: string;\n issues: Array<{ id: string; position: number; status: string }>;\n pullRequest?: { number: number; url: string; createdAt: string };\n };\n database\n .prepare(\n `INSERT INTO queues (id, project_id, status, payload_json, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET status = excluded.status, payload_json = excluded.payload_json,\n updated_at = excluded.updated_at`,\n )\n .run(\n queue.id,\n projectId,\n queue.status,\n JSON.stringify(queue),\n queue.createdAt,\n queue.updatedAt,\n );\n increment(counts, 'queues');\n for (const entry of queue.issues) {\n insertIssue(database, projectId, entry.id, entry.status, null, null, queue.updatedAt, counts);\n database\n .prepare(\n `INSERT INTO queue_issues (queue_id, project_id, issue_id, position, status) VALUES (?, ?, ?, ?, ?)\n ON CONFLICT(queue_id, project_id, issue_id) DO UPDATE SET position = excluded.position,\n status = excluded.status`,\n )\n .run(queue.id, projectId, entry.id, entry.position, entry.status);\n }\n if (queue.pullRequest !== undefined) {\n const id = stableId('pr', `${projectId}:queue:${queue.id}:${queue.pullRequest.number}`);\n database\n .prepare(\n `INSERT INTO pull_requests (id, project_id, number, url, status, created_at) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET url = excluded.url, status = excluded.status`,\n )\n .run(\n id,\n projectId,\n queue.pullRequest.number,\n queue.pullRequest.url,\n 'created',\n queue.pullRequest.createdAt,\n );\n increment(counts, 'pull_requests');\n }\n}\n\nasync function retainBackups(directory: string, retention: number): Promise<void> {\n let names: string[] = [];\n try {\n names = (await readdir(directory)).filter(\n (name) => name.startsWith('issue-flow-') && name.endsWith('.db'),\n );\n } catch {\n return;\n }\n for (const name of names.sort().slice(0, Math.max(0, names.length - retention))) {\n // A retention cleanup is deliberately best-effort; losing an old backup\n // must never make a migration fail.\n await import('node:fs/promises').then(({ unlink }) =>\n unlink(join(directory, name)).catch(() => undefined),\n );\n }\n}\n\n/** Trim only explicitly limited project history; zero means retain all rows. */\nfunction applyRetention(\n database: Awaited<ReturnType<typeof openIssueFlowDatabase>>,\n projectId: string,\n retention: ImportProjectOptions['retention'],\n): void {\n const trim = (table: 'executions' | 'events' | 'snapshots', limit: number | undefined) => {\n if (limit === undefined || limit === 0) return;\n const timestamp =\n table === 'executions' ? 'started_at' : table === 'events' ? 'occurred_at' : 'updated_at';\n database\n .prepare(\n `DELETE FROM ${table} WHERE id IN (\n SELECT id FROM ${table} WHERE project_id = ?\n ORDER BY ${timestamp} DESC, rowid DESC LIMIT -1 OFFSET ?\n )`,\n )\n .run(projectId, limit);\n };\n trim('executions', retention?.executions);\n trim('events', retention?.events);\n trim('snapshots', retention?.snapshots);\n}\n\nexport const DEFAULT_BACKUP_RETENTION = 5;\n\nasync function prepareDatabase(options: ImportProjectOptions): Promise<void> {\n const path = getDatabasePath(options);\n if (!existsSync(path)) return;\n const raw = await openDatabase(path, options);\n try {\n const integrity = raw.integrityCheck();\n if (integrity !== 'ok') {\n raw.close();\n const quarantine = `${path}.corrupt-${Date.now()}`;\n await rename(path, quarantine);\n options.onWarning?.(\n `SQLite integrity check failed; isolated ${path} as ${quarantine} and will re-import preserved JSON artifacts.`,\n );\n return;\n }\n const version = Number(\n raw.prepare('PRAGMA user_version').get<{ user_version: number }>()?.user_version ?? 0,\n );\n // A database upgrade is destructive enough to deserve a recovery point\n // even when there are no JSON artifacts to import. An empty project may\n // still contain the only copy of its SQLite state.\n const backupDirectory = join(getDatabasePath(options).replace(/[/\\\\][^/\\\\]+$/, ''), 'backups');\n if (version < CURRENT_SCHEMA_VERSION) {\n const backup = join(backupDirectory, `issue-flow-${Date.now()}.db`);\n raw.backup(backup);\n }\n await retainBackups(backupDirectory, options.backupRetention ?? DEFAULT_BACKUP_RETENTION);\n } finally {\n try {\n raw.close();\n } catch {\n /* closed after quarantine */\n }\n }\n}\n\n/**\n * Import the structured JSON state already present in one resolved project tree.\n * Sources are read first and never written. A complete project is then applied\n * in one SQLite transaction, so a repeat after interruption is safe.\n */\nexport async function importProjectArtifacts(\n options: ImportProjectOptions,\n): Promise<ImportProjectResult> {\n const counts = { ...EMPTY_COUNTS };\n try {\n await prepareDatabase(options);\n const database = await openIssueFlowDatabase(options);\n try {\n const adopted = database\n .prepare('SELECT project_id FROM project_imports WHERE project_id = ?')\n .get<{ project_id: string }>(options.projectId);\n const artifacts = (\n await collectArtifacts(options.projectDir, options.withEvents === true)\n ).filter((artifact) => {\n if (adopted === undefined) return true;\n if (options.withEvents === true) return artifact.kind === 'events';\n // tasks.json is the portable interchange boundary. A Skill may update\n // it between CLI processes; the stored projection hash below makes an\n // unchanged CLI materialization free while importing real external drift.\n return artifact.kind === 'tasks';\n });\n let imported = 0;\n let skipped = 0;\n database.transaction(() => {\n database\n .prepare(\n `INSERT INTO projects (id, root, remote_url, created_at, updated_at) VALUES (?, ?, ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET root = excluded.root, remote_url = excluded.remote_url,\n updated_at = excluded.updated_at`,\n )\n .run(\n options.projectId,\n options.projectRoot,\n options.remoteUrl,\n new Date().toISOString(),\n new Date().toISOString(),\n );\n increment(counts, 'projects');\n for (const artifact of artifacts) {\n const previous = database\n .prepare('SELECT sha256 FROM migrated_artifacts WHERE source_path = ?')\n .get<{ sha256: string }>(artifact.path);\n // Reprocess an otherwise unchanged legacy tasks file exactly once\n // when it predates the relational plan columns, so upgrading does\n // not require touching the source artifact.\n const requiresPlanProjection =\n artifact.kind === 'tasks' &&\n taskPlanSchema.safeParse(artifact.value).success &&\n database\n .prepare('SELECT project FROM pipelines WHERE project_id = ? AND issue_id = ?')\n .get<{ project: string | null }>(options.projectId, artifact.issueId ?? '')\n ?.project === null;\n if (previous?.sha256 === artifact.sha256 && !requiresPlanProjection) {\n skipped++;\n continue;\n }\n importArtifact(\n database,\n artifact,\n options.projectId,\n {\n root: options.projectRoot,\n remoteUrl: options.remoteUrl,\n timestamp: new Date().toISOString(),\n },\n counts,\n );\n database\n .prepare(\n `INSERT INTO migrated_artifacts (source_path, sha256, migrated_at, table_counts_json)\n VALUES (?, ?, ?, ?)\n ON CONFLICT(source_path) DO UPDATE SET sha256 = excluded.sha256,\n migrated_at = excluded.migrated_at, table_counts_json = excluded.table_counts_json`,\n )\n .run(artifact.path, artifact.sha256, new Date().toISOString(), JSON.stringify(counts));\n imported++;\n }\n applyRetention(database, options.projectId, options.retention);\n database\n .prepare(\n `INSERT INTO project_imports (project_id, completed_at) VALUES (?, ?)\n ON CONFLICT(project_id) DO NOTHING`,\n )\n .run(options.projectId, new Date().toISOString());\n });\n return { imported, skipped, tableCounts: counts, failed: false };\n } finally {\n database.close();\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n // These are database-opening compatibility failures, not failed imports.\n // Leave the database in place so the actionable driver/schema error is not\n // turned into a destructive-looking recovery event.\n if (\n message.includes('newer than this Issue Flow supports') ||\n message.includes('SQLite storage requires Node.js')\n ) {\n throw error;\n }\n const path = getDatabasePath(options);\n if (existsSync(path)) {\n const failed = `${path}.failed-${Date.now()}`;\n try {\n await rename(path, failed);\n options.onWarning?.(\n `SQLite import failed; preserved the database for diagnosis as ${basename(failed)}. Continuing with the intact JSON storage.`,\n );\n } catch {\n // The original import error remains the useful diagnostic.\n }\n }\n options.onWarning?.(`SQLite import skipped: ${message}`);\n return { imported: 0, skipped: 0, tableCounts: counts, failed: true };\n }\n}\n","import { stat } from 'node:fs/promises';\nimport { basename, join, resolve } from 'node:path';\nimport {\n loadGlobalConfig,\n PROJECT_CONFIG_FILENAME,\n readProjectConfigFile,\n} from '../config/sources.js';\nimport { bindTelemetry } from '../telemetry/recorder.js';\nimport { printInfo } from '../ui/logger.js';\nimport { getProjectRoot } from '../utils/git.js';\nimport {\n directoryExists,\n ensureWorkspaceStorageIgnored,\n selectArtifactStorage,\n WORKSPACE_STORAGE_DIR,\n} from './artifact-storage.js';\nimport { type MigrationResult, migrateLegacyStorage, resolveStorageMode } from './compat.js';\nimport { importProjectArtifacts } from './db/import.js';\nimport {\n getDatabasePath,\n registerProjectDatabaseOptions,\n resetProjectDatabaseOptions,\n} from './db/index.js';\nimport {\n registerPlanRepository,\n registerQueueRepository,\n registerStorageProjections,\n resetPlanRepositories,\n type StoredRetentionPolicy,\n} from './db/repository.js';\nimport {\n type GetGlobalRootOptions,\n GLOBAL_ROOT_ENV,\n getGlobalRoot,\n getIssuePathsAt,\n getQueuePathsAt,\n ISSUES_DIR_NAME,\n type IssuePaths,\n PROVIDERS_HEALTH_FILENAME,\n type QueuePaths,\n RUN_LOCK_FILENAME,\n} from './paths.js';\nimport { storageConfigInputSchema } from './schemas.js';\n\n/**\n * The single entry point every pipeline command uses to learn where an issue's\n * artifacts live.\n *\n * `paths.ts` and `compat.ts` stay pure and explicit — they take a `projectId` or\n * a `projectRoot` and never decide anything on their own. This module is the\n * opposite end: it knows the current repository, resolves the storage mode,\n * triggers the legacy migration when one is due, and caches all of that for the\n * lifetime of the process, so a call site is reduced to one line.\n *\n * It deliberately does **not** create the issue directory. When a repository\n * has explicitly opted into workspace storage by creating\n * `.issue-flow/issues/`, resolution only maintains a scoped nested `.gitignore`\n * so operational state cannot be staged accidentally.\n */\n\nexport interface ResolveIssuePathsOptions extends GetGlobalRootOptions {\n /**\n * Repository root. Defaults to {@link getProjectRoot}, i.e. the git toplevel\n * of the current working directory — which is what makes the result identical\n * from the repo root and from any subdirectory.\n */\n projectRoot?: string;\n /** Diagnostics are separate from structured command output. */\n notice?: (message: string) => void;\n}\n\n/** Everything the process needs to know about a project, resolved once. */\ninterface ProjectResolution {\n projectId: string;\n legacyDir: string;\n projectDir: string;\n storageMode: 'global' | 'workspace';\n databaseOptions: GetGlobalRootOptions;\n /** JSON remains active after a failed import or an explicit compatibility setting. */\n driver: 'sqlite' | 'json';\n retention?: StoredRetentionPolicy;\n}\n\ninterface StorageResolutionConfig {\n driver: 'sqlite' | 'json';\n backupRetention?: number;\n retention?: StoredRetentionPolicy & { backups?: number };\n}\n\n/** Resolve only the storage knobs; invalid project input safely uses defaults. */\nasync function loadStorageResolutionConfig(\n projectRoot: string,\n options: ResolveIssuePathsOptions,\n): Promise<StorageResolutionConfig> {\n const global = await loadGlobalConfig({ env: options.env, warn: options.notice ?? printInfo });\n const project = await readProjectConfigFile(projectRoot, options.notice ?? printInfo);\n const parsedProject = storageConfigInputSchema.safeParse(project?.storage);\n if (!parsedProject.success && project?.storage !== undefined) {\n (options.notice ?? printInfo)(\n `Ignoring \"storage\" key of ${PROJECT_CONFIG_FILENAME}: ${parsedProject.error.issues[0]?.message ?? 'invalid value'}.`,\n );\n }\n return {\n driver: parsedProject.success\n ? (parsedProject.data.driver ?? global.storage?.driver ?? 'sqlite')\n : (global.storage?.driver ?? 'sqlite'),\n backupRetention:\n (parsedProject.success ? parsedProject.data.backupRetention : undefined) ??\n global.storage?.backupRetention,\n retention:\n (parsedProject.success ? parsedProject.data.retention : undefined) ??\n global.storage?.retention,\n };\n}\n\n/**\n * Cached project resolutions, keyed by `<globalRoot>::<projectRoot>`.\n *\n * `projectRoot` is the identity of the project, but the global root takes part\n * in the key as well: `ISSUE_FLOW_HOME` decides *where* the resolution lands,\n * so two different roots must never share an entry (that would silently hand a\n * test the previous test's tree).\n *\n * The cached value is the in-flight promise rather than its result, so two\n * commands resolving concurrently share a single git call instead of racing\n * into two migrations.\n */\nconst projectCache = new Map<string, Promise<ProjectResolution>>();\n\n/**\n * Issues whose active directory has already been checked in this process, so\n * the per-issue fallback below stats each issue at most once.\n */\nconst checkedIssues = new Set<string>();\n\n/**\n * Drop every cached resolution.\n *\n * Exported for tests: the cache is keyed by project and global root, so a suite\n * that moves `ISSUE_FLOW_HOME` between cases must reset it in `beforeEach`.\n */\nexport function resetStorageResolutionCache(): void {\n projectCache.clear();\n checkedIssues.clear();\n // The path-to-repository registry has the same process lifetime as this\n // resolver cache. Clearing one without the other would leak a test's\n // temporary database into the next resolution.\n resetPlanRepositories();\n resetProjectDatabaseOptions();\n}\n\nasync function pathExists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction databaseOptionsFor(\n projectRoot: string,\n storageMode: 'global' | 'workspace',\n options: ResolveIssuePathsOptions,\n): GetGlobalRootOptions {\n if (storageMode === 'global') return options.env === undefined ? {} : { env: options.env };\n return {\n env: {\n ...(options.env ?? process.env),\n [GLOBAL_ROOT_ENV]: join(projectRoot, WORKSPACE_STORAGE_DIR),\n },\n };\n}\n\n/**\n * Tell the user that their artifacts moved, and that nothing was taken away.\n *\n * A migration that copied nothing stays silent: `migrateLegacyStorage` is\n * called speculatively (once per project, then once per issue seen for the\n * first time), and every one of those runs after the first copies zero files.\n * Printing on those would turn a one-time notice into noise on every command.\n *\n * The \"one notice per process\" guarantee falls out of the US-001 cache: the\n * project-level migration runs once, and the per-issue fallback only reaches a\n * second migration when an issue really is still legacy-only — in which case a\n * notice is the correct output, not a duplicate.\n */\nfunction announceMigration(result: MigrationResult, notice = printInfo): void {\n if (result.copied.length === 0) return;\n\n const count = result.copied.length;\n const target = join(result.globalDir, ISSUES_DIR_NAME);\n\n notice(`Migrated ${count} file${count === 1 ? '' : 's'} from ${result.legacyDir} to ${target}`);\n notice(\n `The legacy directory was not modified or removed — ${result.legacyDir} is kept as-is, read-only, for compatibility.`,\n );\n}\n\n/**\n * Resolve the project id, migrating the legacy tree when it is the only thing\n * that exists yet.\n *\n * A migration failure is not swallowed: `migrateLegacyStorage` already names the\n * issue and file that failed and states that the legacy directory was left\n * untouched, which is exactly what the user needs to fix it and retry.\n */\nasync function resolveProject(\n projectRoot: string,\n options: ResolveIssuePathsOptions,\n): Promise<ProjectResolution> {\n // resolveStorageMode returns the project id *and* the remote in a single git\n // call — calling getProjectId() afterwards would shell out to\n // `git remote get-url origin` a second time for the same answer.\n const status = await resolveStorageMode(projectRoot, options);\n const storage = await loadStorageResolutionConfig(projectRoot, options);\n const workspaceIssuesDir = join(projectRoot, WORKSPACE_STORAGE_DIR, ISSUES_DIR_NAME);\n const selected = selectArtifactStorage(\n projectRoot,\n getGlobalRoot(options),\n status.projectId,\n await directoryExists(workspaceIssuesDir),\n );\n const { storageMode, projectDir } = selected;\n const databaseOptions = databaseOptionsFor(projectRoot, storageMode, options);\n registerProjectDatabaseOptions(status.projectId, databaseOptions);\n\n if (storageMode === 'workspace') {\n await ensureWorkspaceStorageIgnored(projectRoot);\n } else if (status.mode === 'needs-migration') {\n announceMigration(\n await migrateLegacyStorage(projectRoot, options),\n options.notice ?? printInfo,\n );\n }\n\n const imported =\n storage.driver === 'sqlite' && !(await pathExists(join(projectDir, RUN_LOCK_FILENAME)))\n ? await importProjectArtifacts({\n ...databaseOptions,\n projectId: status.projectId,\n projectDir,\n projectRoot,\n remoteUrl: status.remoteUrl,\n ...((storage.retention?.backups ?? storage.backupRetention) === undefined\n ? {}\n : { backupRetention: storage.retention?.backups ?? storage.backupRetention }),\n ...(storage.retention === undefined ? {} : { retention: storage.retention }),\n onWarning: options.notice ?? printInfo,\n })\n : null;\n if (imported !== null && imported.imported > 0) {\n const counts = Object.entries(imported.tableCounts)\n .filter(([, count]) => count > 0)\n .map(([table, count]) => `${table}: ${count}`)\n .join(', ');\n (options.notice ?? printInfo)(\n `Imported ${imported.imported} structured artifact${imported.imported === 1 ? '' : 's'} from ${projectDir} into ${getDatabasePath(databaseOptions)} (${counts || 'no rows'}). No source artifacts were removed.`,\n );\n }\n\n return {\n projectId: status.projectId,\n legacyDir: status.legacyDir,\n projectDir,\n storageMode,\n databaseOptions,\n // An import failure is a successful *resolution* through the JSON\n // compatibility path. Do not subsequently register a SQLite repository,\n // or telemetry would create a fresh empty database and hide the fallback.\n driver: imported?.failed === true ? 'json' : storage.driver,\n retention: storage.retention,\n };\n}\n\n/** Cached half of {@link resolveIssuePaths}: one git call per project, per process. */\nfunction getProjectResolution(\n projectRoot: string,\n options: ResolveIssuePathsOptions,\n): Promise<ProjectResolution> {\n const key = `${getGlobalRoot(options)}::${projectRoot}`;\n\n const cached = projectCache.get(key);\n if (cached) return cached;\n\n // A rejected resolution is evicted so the next command retries instead of\n // inheriting a permanently failed promise.\n const pending = resolveProject(projectRoot, options).catch((err) => {\n projectCache.delete(key);\n throw err;\n });\n\n projectCache.set(key, pending);\n return pending;\n}\n\n/** Project-level directories of the current repository inside the active store. */\nexport interface ProjectStoragePaths {\n /** Deterministic id derived from the repository's remote (or its path). */\n projectId: string;\n /** Active structured-state driver after migration/recovery resolution. */\n storageDriver: 'sqlite' | 'json';\n /** Whether the explicit workspace override or the global fallback is active. */\n storageMode: 'global' | 'workspace';\n /** Database location selected with the artifact store. */\n databaseOptions: GetGlobalRootOptions;\n /** Global `<root>/projects/<projectId>` or opted-in `<workspace>/.issue-flow`. */\n projectDir: string;\n /** `<projectDir>/issues` — the parent of every issue directory. */\n issuesDir: string;\n /**\n * `<projectDir>/run.lock` — who is running in this project right now.\n *\n * Project-level rather than per issue on purpose: two runs in one repository\n * share a working tree and a branch, so \"a different issue\" is not a\n * different lock.\n */\n runLockFile: string;\n /** `<projectDir>/providers.json` — persisted health/cooldown per agent provider. */\n providersHealthFile: string;\n}\n\n/**\n * Resolve the project-level directories, without naming an issue.\n *\n * The issue-agnostic half of {@link resolveIssuePaths}, for the two questions\n * that are about the project rather than about one issue: \"can I write here?\"\n * (`LocalFileIssueProvider.isAvailable`) and \"which identifiers are taken?\"\n * (`highestLocalNumber`, which walks `issuesDir`). It shares the same cache, so\n * asking here and then resolving an issue still costs a single git call.\n *\n * Like the rest of this module it creates nothing — a caller that needs the\n * directory to exist does its own `mkdir(..., { recursive: true })`.\n */\nexport async function resolveProjectPaths(\n options: ResolveIssuePathsOptions = {},\n): Promise<ProjectStoragePaths> {\n const { projectRoot: projectRootOption, ...rootOptions } = options;\n const projectRoot = resolve(projectRootOption ?? (await getProjectRoot()));\n\n const project = await getProjectResolution(projectRoot, rootOptions);\n const projectDir = project.projectDir;\n const providersHealthFile = join(projectDir, PROVIDERS_HEALTH_FILENAME);\n if (project.driver === 'sqlite') {\n registerStorageProjections({\n context: {\n tasksPath: '',\n projectId: project.projectId,\n issueId: '',\n projectRoot,\n databaseOptions: project.databaseOptions,\n retention: project.retention,\n },\n providersHealthFile,\n });\n }\n\n return {\n projectId: project.projectId,\n storageDriver: project.driver,\n storageMode: project.storageMode,\n databaseOptions: project.databaseOptions,\n projectDir,\n issuesDir: join(projectDir, ISSUES_DIR_NAME),\n runLockFile: join(projectDir, RUN_LOCK_FILENAME),\n providersHealthFile,\n };\n}\n\n/**\n * Resolve the paths of a multi-issue execution queue in the current project.\n *\n * Shares the project cache with {@link resolveIssuePaths}, so a run that\n * resolves both still costs a single git call. There is no legacy tree for\n * queues — the concept did not exist before — so no migration is attempted.\n */\nexport async function resolveQueuePaths(\n queueId: string | number,\n options: ResolveIssuePathsOptions = {},\n): Promise<QueuePaths> {\n const { projectRoot: projectRootOption, ...rootOptions } = options;\n const projectRoot = resolve(projectRootOption ?? (await getProjectRoot()));\n\n const project = await getProjectResolution(projectRoot, rootOptions);\n const paths = getQueuePathsAt(project.projectDir, queueId);\n if (project.driver === 'sqlite') {\n registerQueueRepository({\n planFile: paths.planFile,\n projectId: project.projectId,\n projectRoot,\n databaseOptions: project.databaseOptions,\n retention: project.retention,\n });\n }\n return paths;\n}\n\n/**\n * Resolve every path of `issueNumber` under the selected store, migrating the\n * legacy `<projectRoot>/issues/` tree only while global storage is active.\n *\n * The migration is triggered in two situations:\n *\n * 1. project level — the global project directory does not exist at all and the\n * legacy one does (`mode === 'needs-migration'`);\n * 2. issue level — the project directory exists (so the mode is already\n * `global`) but *this* issue is only present in the legacy tree. That\n * happens whenever a project was migrated before an older issue was ever\n * read, or when a collaborator on a previous version added an issue to the\n * legacy directory afterwards. Re-running the migration is cheap and safe:\n * it skips every destination that already exists.\n */\nexport async function resolveIssuePaths(\n issueNumber: string | number,\n options: ResolveIssuePathsOptions = {},\n): Promise<IssuePaths> {\n const { projectRoot: projectRootOption, ...rootOptions } = options;\n const projectRoot = resolve(projectRootOption ?? (await getProjectRoot()));\n\n const project = await getProjectResolution(projectRoot, rootOptions);\n const paths = getIssuePathsAt(project.projectDir, issueNumber);\n\n if (!checkedIssues.has(paths.issueDir)) {\n // `getIssuePaths` already validated and normalized the identifier, so the\n // last segment of issueDir is the safe form to look for under the legacy\n // tree.\n const legacyIssueDir = join(project.legacyDir, basename(paths.issueDir));\n\n if (\n project.storageMode === 'global' &&\n !(await directoryExists(paths.issueDir)) &&\n (await directoryExists(legacyIssueDir))\n ) {\n announceMigration(await migrateLegacyStorage(projectRoot, rootOptions));\n }\n\n // Marked only once the check completed: a failed migration must be retried\n // by the next command, not remembered as done.\n checkedIssues.add(paths.issueDir);\n }\n\n if (project.driver === 'sqlite') {\n const context = {\n tasksPath: paths.tasksFile,\n projectId: project.projectId,\n issueId: basename(paths.issueDir),\n projectRoot,\n databaseOptions: project.databaseOptions,\n retention: project.retention,\n };\n registerPlanRepository(context);\n registerStorageProjections({\n context,\n verifyFile: paths.verifyFile,\n lastBranchFile: paths.lastBranchFile,\n providersHealthFile: join(project.projectDir, PROVIDERS_HEALTH_FILENAME),\n });\n bindTelemetry({ tasksPath: paths.tasksFile });\n } else {\n // The legacy state manager remains authoritative in JSON mode. Leaving a\n // telemetry binding here would lazily bootstrap a SQLite projection.\n bindTelemetry(null);\n }\n return paths;\n}\n","import { readFile, stat, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nexport const WORKSPACE_STORAGE_DIR = '.issue-flow';\n\nexport const WORKSPACE_IGNORE_BLOCK = [\n '# Issue Flow operational storage (managed)',\n '/issues/',\n '/queues/',\n '/issue-flow.db',\n '/issue-flow.db-*',\n '/run.lock',\n '/providers.json',\n '/metadata.json',\n '/backups/',\n '/.gitignore',\n].join('\\n');\n\nexport async function directoryExists(path: string): Promise<boolean> {\n try {\n return (await stat(path)).isDirectory();\n } catch {\n return false;\n }\n}\n\n/** Select one complete artifact store; individual files never mix roots. */\nexport function selectArtifactStorage(\n projectRoot: string,\n globalRoot: string,\n projectId: string,\n workspaceSelected: boolean,\n): { storageMode: 'global' | 'workspace'; projectDir: string } {\n return workspaceSelected\n ? { storageMode: 'workspace', projectDir: join(projectRoot, WORKSPACE_STORAGE_DIR) }\n : { storageMode: 'global', projectDir: join(globalRoot, 'projects', projectId) };\n}\n\n/** Protect an explicitly selected workspace store without hiding prompt overrides. */\nexport async function ensureWorkspaceStorageIgnored(projectRoot: string): Promise<void> {\n const path = join(projectRoot, WORKSPACE_STORAGE_DIR, '.gitignore');\n let current = '';\n try {\n current = await readFile(path, 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n }\n if (\n WORKSPACE_IGNORE_BLOCK.split('\\n')\n .slice(1)\n .every((line) => current.includes(line))\n )\n return;\n const prefix = current.length === 0 ? '' : `${current.trimEnd()}\\n\\n`;\n await writeFile(path, `${prefix}${WORKSPACE_IGNORE_BLOCK}\\n`, 'utf8');\n}\n"],"mappings":"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","names":["dirname","dirname","existsSync","readFile","rename","join","readFile","join","unlink","existsSync","rename","stat","basename","join","readFile","stat","join","stat","join","basename"]}
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  EXECUTION_PURPOSES,
4
4
  getTelemetryContext
5
- } from "./chunk-YII4U7ZD.js";
5
+ } from "./chunk-HEHHQU4G.js";
6
6
  import {
7
7
  loadTaskPlan
8
- } from "./chunk-SOM4QA6C.js";
8
+ } from "./chunk-GD7JZMSJ.js";
9
9
 
10
10
  // src/telemetry/timing.ts
11
11
  var EMPTY_TIMING = {
@@ -117,4 +117,4 @@ export {
117
117
  formatPhaseLine,
118
118
  snapshotTimingFields
119
119
  };
120
- //# sourceMappingURL=chunk-B3TSS7BT.js.map
120
+ //# sourceMappingURL=chunk-KDQRNVBZ.js.map
@@ -4,20 +4,27 @@ import {
4
4
  uniqueIds
5
5
  } from "./chunk-KMMASOA6.js";
6
6
  import {
7
+ DEFAULT_PIPELINE_STATE,
8
+ WORKFLOW_PHASES,
7
9
  isoNow,
8
10
  loadTaskPlan,
9
11
  saveTaskPlan
10
- } from "./chunk-SOM4QA6C.js";
12
+ } from "./chunk-GD7JZMSJ.js";
11
13
  import {
12
14
  executionPlanSchema
13
- } from "./chunk-ICM463EO.js";
15
+ } from "./chunk-IMB6FA2F.js";
16
+ import {
17
+ getQueueRepository,
18
+ loadStoredQueue,
19
+ saveStoredQueue
20
+ } from "./chunk-7MJGYPHF.js";
14
21
  import {
15
22
  writeFileAtomic
16
- } from "./chunk-3R27MJ5R.js";
23
+ } from "./chunk-PFWHHYEO.js";
17
24
 
18
25
  // src/execution/plan.ts
19
- import { mkdir, readFile } from "fs/promises";
20
- import { dirname } from "path";
26
+ import { mkdir, readFile } from "node:fs/promises";
27
+ import { dirname } from "node:path";
21
28
  import { ZodError } from "zod";
22
29
 
23
30
  // src/execution/containers.ts
@@ -369,7 +376,7 @@ function buildExecutionPlan(input) {
369
376
  origin: requested.has(id) ? "requested" : "discovered",
370
377
  role,
371
378
  externalDependencies: (relations?.blockedBy ?? []).filter(
372
- (blocker) => !scheduled.has(blocker)
379
+ (blocker) => !scheduled.has(blocker) && input.graph.nodes.get(blocker)?.issue?.state !== "closed"
373
380
  ),
374
381
  // Only the constraints that survive inside the queue: a blocker left out
375
382
  // of it is reported in `excluded`, not enforced as a dependency.
@@ -394,7 +401,7 @@ function buildExecutionPlan(input) {
394
401
  number: described.number,
395
402
  title: described.title,
396
403
  url: described.url,
397
- reason: "Discovered in the hierarchy but not selected for this run"
404
+ reason: node.issue?.state === "closed" ? "Already closed before this run" : "Discovered in the hierarchy but not selected for this run"
398
405
  });
399
406
  }
400
407
  return {
@@ -414,6 +421,11 @@ function buildExecutionPlan(input) {
414
421
  };
415
422
  }
416
423
  async function loadExecutionPlan(path) {
424
+ const repository = getQueueRepository(path);
425
+ if (repository !== void 0) {
426
+ const stored = await loadStoredQueue(repository);
427
+ if (stored !== null) return stored;
428
+ }
417
429
  const raw = JSON.parse(await readFile(path, "utf-8"));
418
430
  try {
419
431
  return executionPlanSchema.parse(raw);
@@ -427,8 +439,13 @@ ${issues}`);
427
439
  }
428
440
  }
429
441
  async function saveExecutionPlan(path, plan, now = isoNow) {
430
- await mkdir(dirname(path), { recursive: true });
431
442
  const stamped = { ...plan, updatedAt: now() };
443
+ const repository = getQueueRepository(path);
444
+ if (repository !== void 0) {
445
+ await saveStoredQueue(repository, stamped);
446
+ return;
447
+ }
448
+ await mkdir(dirname(path), { recursive: true });
432
449
  await writeFileAtomic(path, `${JSON.stringify(stamped, null, 2)}
433
450
  `);
434
451
  }
@@ -539,22 +556,25 @@ function setQueueBranch(plan, branchName) {
539
556
  function setQueuePullRequest(plan, pullRequest) {
540
557
  return { ...plan, pullRequest };
541
558
  }
559
+ function queueNeedsFinalization(plan) {
560
+ return !plan.noBranch && (!plan.pullRequest || plan.prReview && !plan.prReviewCompleted) || plan.closeIssue === true && plan.issues.some(
561
+ (entry) => entry.status === "completed" && !(plan.closedIssueIds ?? []).includes(entry.id)
562
+ );
563
+ }
542
564
 
543
565
  // src/core/pipeline.ts
544
- var PIPELINE_PHASES = ["init", "prd", "plan", "execute", "review", "pr"];
566
+ var PIPELINE_PHASES = [
567
+ "init",
568
+ ...WORKFLOW_PHASES.filter(({ phase }) => phase !== "pr-review").map(({ phase }) => phase)
569
+ ];
545
570
  var PIPELINE_PHASES_WITH_PR_REVIEW = [...PIPELINE_PHASES, "pr-review"];
546
571
  var PIPELINE_PHASES_NO_BRANCH = PIPELINE_PHASES.filter(
547
572
  (p) => p !== "pr"
548
573
  );
549
- var PHASE_TO_FIELD = {
550
- init: null,
551
- prd: "prdCompleted",
552
- plan: "jsonCompleted",
553
- execute: "executionCompleted",
554
- review: "reviewCompleted",
555
- pr: "prCreated",
556
- "pr-review": "prReviewCompleted"
557
- };
574
+ var PHASE_TO_FIELD = Object.fromEntries([
575
+ ["init", null],
576
+ ...WORKFLOW_PHASES.map(({ phase, field }) => [phase, field])
577
+ ]);
558
578
  var PipelineManager = class {
559
579
  tasksJsonPath;
560
580
  plan;
@@ -619,13 +639,7 @@ var PipelineManager = class {
619
639
  this.plan = {
620
640
  ...this.plan,
621
641
  pipeline: {
622
- ...this.plan.pipeline ?? {
623
- prdCompleted: false,
624
- jsonCompleted: false,
625
- executionCompleted: false,
626
- reviewCompleted: false,
627
- prCreated: false
628
- },
642
+ ...this.plan.pipeline ?? DEFAULT_PIPELINE_STATE,
629
643
  [field]: true
630
644
  }
631
645
  };
@@ -650,9 +664,10 @@ export {
650
664
  markQueueIssueBlocked,
651
665
  setQueueBranch,
652
666
  setQueuePullRequest,
667
+ queueNeedsFinalization,
653
668
  PIPELINE_PHASES,
654
669
  PIPELINE_PHASES_WITH_PR_REVIEW,
655
670
  PIPELINE_PHASES_NO_BRANCH,
656
671
  PipelineManager
657
672
  };
658
- //# sourceMappingURL=chunk-A4H7P7T7.js.map
673
+ //# sourceMappingURL=chunk-LRWIP7QS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/execution/plan.ts","../src/execution/containers.ts","../src/issues/graph.ts","../src/execution/order.ts","../src/core/pipeline.ts"],"sourcesContent":["import { mkdir, readFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { ZodError } from 'zod';\nimport { isoNow } from '../core/state-manager.js';\nimport type { DependencyGraph } from '../issues/graph.js';\nimport type { Issue } from '../issues/types.js';\nimport { getQueueRepository, loadStoredQueue, saveStoredQueue } from '../storage/db/repository.js';\nimport { executionPlanSchema } from '../storage/schemas.js';\nimport type { LastError, PullRequestRef } from '../types.js';\nimport { writeFileAtomic } from '../utils/fs.js';\nimport { isContainer } from './containers.js';\nimport { priorityOf } from './order.js';\nimport type {\n ExecutionPlan,\n ExecutionPlanExcluded,\n ExecutionPlanIssue,\n QueueIssueRole,\n QueueIssueStatus,\n QueueStatus,\n} from './types.js';\n\n/**\n * Building, reading, writing and advancing the execution plan of a queue.\n *\n * The plan is the only state a multi-issue run adds to the storage tree, and it\n * is written **only** for a queue with more than one Issue — a single-issue run\n * leaves the layout exactly as it was.\n */\n\nexport interface BuildExecutionPlanInput {\n /** Project id of the global storage tree. */\n projectId: string;\n /** Identifiers the user asked for, in order; the first one names the queue. */\n requested: readonly string[];\n graph: DependencyGraph;\n /** Ids to run, already ordered by `computeExecutionOrder`. */\n order: readonly string[];\n noBranch?: boolean;\n prReview?: boolean;\n /** Injectable clock, so tests can assert `createdAt` vs `updatedAt`. */\n now?: () => string;\n}\n\nfunction parentOf(graph: DependencyGraph, id: string): string | null {\n for (const node of graph.nodes.values()) {\n if (node.relations.children.includes(id)) return node.id;\n }\n return null;\n}\n\n/** Title/url/number of a node, falling back to what the id alone tells us. */\nfunction describeIssue(\n issue: Issue | null,\n id: string,\n): {\n number: number | null;\n title: string;\n url: string | null;\n source: string;\n} {\n const parsed = Number.parseInt(id, 10);\n return {\n number: issue?.number ?? (Number.isNaN(parsed) ? null : parsed),\n title: issue?.title ?? '',\n url: issue?.remoteRef ?? null,\n source: issue?.source ?? 'github',\n };\n}\n\n/**\n * Turn a graph plus an order into the persisted plan.\n *\n * Every Issue of the graph that is *not* in the order becomes an `excluded`\n * entry: the consolidated Pull Request reports them as known pending work, so a\n * hierarchy the user chose to trim never disappears silently.\n */\nexport function buildExecutionPlan(input: BuildExecutionPlanInput): ExecutionPlan {\n const now = input.now ?? isoNow;\n const at = now();\n const scheduled = new Set(input.order);\n const requested = new Set(input.requested);\n\n const issues: ExecutionPlanIssue[] = input.order.map((id, index) => {\n const node = input.graph.nodes.get(id);\n const relations = node?.relations;\n const described = describeIssue(node?.issue ?? null, id);\n const children = relations?.children ?? [];\n const role: QueueIssueRole = isContainer({\n issue: node?.issue ?? null,\n children,\n })\n ? 'container'\n : 'executable';\n\n return {\n id,\n number: described.number,\n title: described.title,\n url: described.url,\n source: described.source,\n position: index + 1,\n status: 'pending',\n origin: requested.has(id) ? 'requested' : 'discovered',\n role,\n externalDependencies: (relations?.blockedBy ?? []).filter(\n (blocker) =>\n !scheduled.has(blocker) && input.graph.nodes.get(blocker)?.issue?.state !== 'closed',\n ),\n // Only the constraints that survive inside the queue: a blocker left out\n // of it is reported in `excluded`, not enforced as a dependency.\n dependsOn: (relations?.blockedBy ?? []).filter((blocker) => scheduled.has(blocker)),\n parent: relations?.parent ?? parentOf(input.graph, id),\n priority: priorityOf(node?.issue?.labels ?? []),\n heuristic: (relations?.heuristic.length ?? 0) > 0,\n failedPhase: null,\n lastError: null,\n attempts: 0,\n blockedReason: null,\n startedAt: null,\n completedAt: null,\n };\n });\n\n const excluded: ExecutionPlanExcluded[] = [];\n for (const node of input.graph.nodes.values()) {\n if (scheduled.has(node.id)) continue;\n const described = describeIssue(node.issue, node.id);\n excluded.push({\n id: node.id,\n number: described.number,\n title: described.title,\n url: described.url,\n reason:\n node.issue?.state === 'closed'\n ? 'Already closed before this run'\n : 'Discovered in the hierarchy but not selected for this run',\n });\n }\n\n return {\n schemaVersion: 1,\n id: input.requested[0] ?? (input.order[0] as string),\n project: input.projectId,\n requested: [...input.requested],\n branchName: null,\n noBranch: input.noBranch ?? false,\n prReview: input.prReview ?? false,\n status: 'pending',\n createdAt: at,\n updatedAt: at,\n truncated: input.graph.truncated,\n issues,\n excluded,\n };\n}\n\n/** Read a persisted plan. Throws with the offending fields on invalid content. */\nexport async function loadExecutionPlan(path: string): Promise<ExecutionPlan> {\n const repository = getQueueRepository(path);\n if (repository !== undefined) {\n const stored = await loadStoredQueue(repository);\n if (stored !== null) return stored;\n }\n const raw: unknown = JSON.parse(await readFile(path, 'utf-8'));\n\n try {\n return executionPlanSchema.parse(raw) as ExecutionPlan;\n } catch (err) {\n if (err instanceof ZodError) {\n const issues = err.issues.map((i) => ` - ${i.path.join('.')}: ${i.message}`).join('\\n');\n throw new Error(`Invalid execution-plan.json at ${path}:\\n${issues}`);\n }\n throw err;\n }\n}\n\n/**\n * Persist a plan atomically, creating the queue directory on first write.\n *\n * `updatedAt` is stamped here so no call site can forget it — the file is the\n * only record of how far a queue got, and a stale timestamp on it is worse than\n * none.\n */\nexport async function saveExecutionPlan(\n path: string,\n plan: ExecutionPlan,\n now: () => string = isoNow,\n): Promise<void> {\n const stamped: ExecutionPlan = { ...plan, updatedAt: now() };\n const repository = getQueueRepository(path);\n if (repository !== undefined) {\n await saveStoredQueue(repository, stamped);\n return;\n }\n await mkdir(dirname(path), { recursive: true });\n await writeFileAtomic(path, `${JSON.stringify(stamped, null, 2)}\\n`);\n}\n\n/**\n * The Issue the queue should work on next, or `null` when every one of them is\n * completed.\n *\n * The order of the lookups *is* the resume policy: an interrupted issue\n * (`in_progress`) and a failed one are retried before the queue moves on, and\n * each of them picks up from its own `tasks.json` (phase-level resume) rather\n * than from the start. A `completed` issue is never revisited — that is the\n * whole point of persisting the queue.\n */\nexport interface NextQueueIssueOptions {\n /**\n * Ids this invocation is done with, however they ended. A queue that skips a\n * failing Issue and comes back to it needs a way to say \"not this one again\",\n * or \"come back later\" becomes \"come back forever\".\n */\n exclude?: ReadonlySet<string>;\n}\n\n/**\n * Whether every dependency this Issue declares *inside the queue* is finished.\n *\n * The execution order already places dependencies first, so this only bites\n * once an entry stops being `completed` — skipped, blocked or failed. Handing\n * out an Issue whose blocker was skipped is how a queue produces work that\n * cannot compile.\n */\nfunction dependenciesSatisfied(plan: ExecutionPlan, entry: ExecutionPlanIssue): boolean {\n return entry.dependsOn.every((id) => {\n const dependency = plan.issues.find((candidate) => candidate.id === id);\n if (dependency === undefined) return true;\n // A container is not work: waiting on it would deadlock the children\n // that complete it.\n if (dependency.role === 'container') return true;\n return dependency.status === 'completed';\n });\n}\n\n/**\n * The Issue to work on next.\n *\n * The order of the four lookups is the resumption policy:\n * an interrupted Issue first (it was mid-flight), then one that failed on a\n * previous invocation, then the untouched ones, and only at the end the ones\n * this queue deliberately set aside — which is what \"go on with the\n * independent work and come back to it\" means in one line.\n *\n * `blocked` is absent on purpose: it needs a human, and no ordering of the\n * queue changes that.\n */\nexport function nextQueueIssue(\n plan: ExecutionPlan,\n options: NextQueueIssueOptions = {},\n): ExecutionPlanIssue | null {\n const exclude = options.exclude ?? new Set<string>();\n const eligible = (status: QueueIssueStatus) => (entry: ExecutionPlanIssue) =>\n entry.role !== 'container' &&\n entry.status === status &&\n !exclude.has(entry.id) &&\n dependenciesSatisfied(plan, entry);\n\n return (\n plan.issues.find(eligible('in_progress')) ??\n plan.issues.find(eligible('failed')) ??\n plan.issues.find(eligible('pending')) ??\n plan.issues.find(eligible('skipped')) ??\n null\n );\n}\n\n/**\n * Status of the queue derived from its entries.\n *\n * `skipped` is *not* terminal — the queue means to come back to it — so a queue\n * holding one is still `in_progress`. `blocked` is: nothing the queue can do\n * moves it, so a queue that ends with one ends `failed`, which is what a person\n * needs to see.\n */\nexport function queueStatus(plan: ExecutionPlan): QueueStatus {\n if (plan.issues.some((entry) => entry.status === 'failed' || entry.status === 'blocked')) {\n return 'failed';\n }\n if (plan.issues.every((entry) => entry.status === 'completed')) return 'completed';\n if (plan.issues.some((entry) => entry.status !== 'pending')) return 'in_progress';\n return 'pending';\n}\n\n/** Whether every Issue of the queue has been resolved. */\nexport function isQueueComplete(plan: ExecutionPlan): boolean {\n return plan.issues.every((entry) => entry.status === 'completed');\n}\n\n/** Apply `patch` to one entry and recompute the queue status. Pure. */\nfunction updateIssue(\n plan: ExecutionPlan,\n id: string,\n patch: Partial<ExecutionPlanIssue>,\n): ExecutionPlan {\n const issues = plan.issues.map((entry) => (entry.id === id ? { ...entry, ...patch } : entry));\n const next: ExecutionPlan = { ...plan, issues };\n return { ...next, status: queueStatus(next) };\n}\n\nexport function markQueueIssueInProgress(\n plan: ExecutionPlan,\n id: string,\n now: () => string = isoNow,\n): ExecutionPlan {\n const current = plan.issues.find((entry) => entry.id === id);\n return updateIssue(plan, id, {\n status: 'in_progress',\n // Preserved across a resume: the queue reports when the work on this Issue\n // really began, not when it was last picked up.\n startedAt: current?.startedAt ?? now(),\n failedPhase: null,\n lastError: null,\n });\n}\n\nfunction completeResolvedContainers(plan: ExecutionPlan, now: () => string): ExecutionPlan {\n let next = plan;\n let changed = true;\n while (changed) {\n changed = false;\n for (const entry of next.issues) {\n if (entry.role !== 'container' || entry.status === 'completed') continue;\n const children = next.issues.filter((candidate) => candidate.parent === entry.id);\n if (children.length === 0) continue;\n if (children.every((child) => child.status === 'completed')) {\n next = updateIssue(next, entry.id, {\n status: 'completed',\n completedAt: now(),\n failedPhase: null,\n lastError: null,\n });\n changed = true;\n }\n }\n }\n return next;\n}\n\nexport function markQueueIssueCompleted(\n plan: ExecutionPlan,\n id: string,\n now: () => string = isoNow,\n): ExecutionPlan {\n return completeResolvedContainers(\n updateIssue(plan, id, {\n status: 'completed',\n completedAt: now(),\n failedPhase: null,\n lastError: null,\n }),\n now,\n );\n}\n\nexport function markQueueIssueFailed(\n plan: ExecutionPlan,\n id: string,\n failure: { phase: string | null; error: LastError | null },\n): ExecutionPlan {\n const current = plan.issues.find((entry) => entry.id === id);\n return updateIssue(plan, id, {\n status: 'failed',\n failedPhase: failure.phase,\n lastError: failure.error,\n attempts: (current?.attempts ?? 0) + 1,\n });\n}\n\n/**\n * Set an Issue aside and come back to it later.\n *\n * The difference from `failed` is the whole point of `--on-issue-failure skip`:\n * a failure that stopped this attempt is not a verdict on the eleven other\n * Issues in the queue, and `skipped` is the status that says \"not now\" rather\n * than \"not at all\". `attempts` is incremented here because the counter is what\n * keeps \"come back later\" from becoming \"come back forever\".\n */\nexport function markQueueIssueSkipped(\n plan: ExecutionPlan,\n id: string,\n failure: { phase: string | null; error: LastError | null },\n): ExecutionPlan {\n const current = plan.issues.find((entry) => entry.id === id);\n return updateIssue(plan, id, {\n status: 'skipped',\n failedPhase: failure.phase,\n lastError: failure.error,\n attempts: (current?.attempts ?? 0) + 1,\n });\n}\n\n/**\n * Stop working on an Issue until a person looks at it.\n *\n * `blocked` is never handed back out by `nextQueueIssue`: waiting cannot fix\n * a missing credential or a repository mid-rebase, and a queue that could\n * unblock itself would spin on the same cause forever.\n */\nexport function markQueueIssueBlocked(\n plan: ExecutionPlan,\n id: string,\n reason: string,\n failure: { phase: string | null; error: LastError | null },\n): ExecutionPlan {\n const current = plan.issues.find((entry) => entry.id === id);\n return updateIssue(plan, id, {\n status: 'blocked',\n blockedReason: reason,\n failedPhase: failure.phase,\n lastError: failure.error,\n attempts: (current?.attempts ?? 0) + 1,\n });\n}\n\n/** Record the branch every Issue of the queue shares. */\nexport function setQueueBranch(plan: ExecutionPlan, branchName: string): ExecutionPlan {\n return { ...plan, branchName };\n}\n\n/** Record the single Pull Request that consolidated the queue. */\nexport function setQueuePullRequest(\n plan: ExecutionPlan,\n pullRequest: PullRequestRef,\n): ExecutionPlan {\n return { ...plan, pullRequest };\n}\n\n/** Completed implementation can still owe PR delivery, review or explicit closure. */\nexport function queueNeedsFinalization(plan: ExecutionPlan): boolean {\n return (\n (!plan.noBranch && (!plan.pullRequest || (plan.prReview && !plan.prReviewCompleted))) ||\n (plan.closeIssue === true &&\n plan.issues.some(\n (entry) => entry.status === 'completed' && !(plan.closedIssueIds ?? []).includes(entry.id),\n ))\n );\n}\n","import type { DependencyGraph } from '../issues/graph.js';\nimport type { Issue } from '../issues/types.js';\n\nexport const CONTAINER_SIGNALS = ['children', 'issue-type', 'label', 'title-prefix'] as const;\n\nexport type ContainerSignal = (typeof CONTAINER_SIGNALS)[number];\n\nexport interface ContainerConfig {\n detect: readonly ContainerSignal[];\n issueTypes: readonly string[];\n labels: readonly string[];\n titlePrefixes: readonly string[];\n}\n\n/** Unconfigured repos: only the universal signal. */\nexport const DEFAULT_CONTAINER_CONFIG: ContainerConfig = {\n detect: ['children'],\n issueTypes: ['Epic'],\n labels: ['epic'],\n titlePrefixes: ['[Epic]'],\n};\n\nexport interface ContainerProbe {\n issue: Pick<Issue, 'title' | 'labels' | 'type'> | null;\n children: readonly string[];\n}\n\nfunction enabled(config: ContainerConfig, signal: ContainerSignal): boolean {\n return config.detect.includes(signal);\n}\n\nfunction hasTitlePrefix(title: string, prefixes: readonly string[]): boolean {\n const trimmed = title.trim();\n return prefixes.some((prefix) => trimmed.toLowerCase().startsWith(prefix.toLowerCase()));\n}\n\n/**\n * Precedence: children > issue type > label > title prefix.\n * Having children always wins when that signal is enabled.\n */\nexport function isContainer(\n probe: ContainerProbe,\n config: ContainerConfig = DEFAULT_CONTAINER_CONFIG,\n): boolean {\n if (enabled(config, 'children') && probe.children.length > 0) return true;\n\n const issue = probe.issue;\n if (issue === null) return false;\n\n if (enabled(config, 'issue-type')) {\n const type = issue.type?.trim();\n if (\n type !== undefined &&\n type !== '' &&\n config.issueTypes.some((name) => name.toLowerCase() === type.toLowerCase())\n ) {\n return true;\n }\n }\n\n if (enabled(config, 'label')) {\n const labels = new Set(issue.labels.map((label) => label.toLowerCase()));\n if (config.labels.some((label) => labels.has(label.toLowerCase()))) return true;\n }\n\n if (enabled(config, 'title-prefix') && hasTitlePrefix(issue.title, config.titlePrefixes)) {\n return true;\n }\n\n return false;\n}\n\n/** Root plus every descendant reached through `children`. */\nexport function collectCascadeIds(graph: DependencyGraph, roots: readonly string[]): string[] {\n const ids = new Set<string>();\n const walk = (id: string): void => {\n if (ids.has(id)) return;\n ids.add(id);\n for (const child of graph.nodes.get(id)?.relations.children ?? []) {\n walk(child);\n }\n };\n for (const root of roots) walk(root);\n return [...ids];\n}\n","import { emptyRelations, uniqueIds } from './relations.js';\nimport type { Issue, IssueRelations } from './types.js';\n\n/**\n * Dependency graph of a set of Issues, built by walking the relations every\n * provider exposes through `fetchRelations`.\n *\n * The traversal is deliberately *structural*: it follows hierarchy\n * (parent/children) and dependencies (blockedBy/blocking) and stops there. Plain\n * mentions are recorded on each node but never expanded — a body citing \"see\n * also #12\" would otherwise drag an unrelated Issue into an execution plan the\n * user is about to confirm.\n */\n\nexport interface DependencyGraphNode {\n id: string;\n relations: IssueRelations;\n /** Distance from the closest root, in traversal hops. */\n depth: number;\n /** The Issue itself, when the caller supplied a way to read it. */\n issue: Issue | null;\n /** True when the node was requested by the user rather than discovered. */\n root: boolean;\n}\n\nexport interface DependencyGraph {\n /** Ids the traversal started from, in the order they were requested. */\n roots: string[];\n /** Every discovered node, keyed by id, in discovery order. */\n nodes: Map<string, DependencyGraphNode>;\n /**\n * Dependency cycles found among the discovered nodes, each listed once,\n * starting at its smallest member. Reported rather than thrown: discovery\n * must still answer, and it is the ordering layer that refuses to run.\n */\n cycles: string[][];\n /** True when a limit stopped the traversal before it was exhausted. */\n truncated: boolean;\n limits: { maxNodes: number; maxDepth: number };\n}\n\n/**\n * Conservative defaults: a real hierarchy is a handful of Issues, and each node\n * costs several API calls. A malformed (or simply huge) graph must not turn a\n * `run` into a rate-limit incident.\n */\nexport const DEFAULT_MAX_NODES = 25;\nexport const DEFAULT_MAX_DEPTH = 3;\n\nexport interface BuildDependencyGraphOptions {\n maxNodes?: number;\n maxDepth?: number;\n /** Issues the caller already resolved, so the traversal does not re-read them. */\n known?: Iterable<Issue>;\n /** Reader for the Issues the traversal discovers. Optional. */\n fetchIssue?: (id: string) => Promise<Issue | null>;\n /** Warning sink for a source that failed. Silent by default. */\n warn?: (message: string) => void;\n}\n\n/** Edge `from` → `to`, meaning \"`from` must be resolved before `to`\". */\nexport interface DependencyEdge {\n from: string;\n to: string;\n}\n\n/**\n * Ordering constraints of a graph: only hierarchy-free, explicit dependencies.\n *\n * `blockedBy`/`blocking` are the two spellings of the same fact, so both are\n * normalized into a single direction here. Parent/child is **not** a constraint\n * — it is a presentation and tie-breaking concern (see `computeExecutionOrder`),\n * because a parent is usually an umbrella that neither blocks nor is blocked by\n * its children.\n *\n * Edges pointing outside the graph are dropped: an Issue the traversal never\n * reached cannot be scheduled, so a constraint against it is unenforceable.\n */\nexport function dependencyEdges(graph: DependencyGraph): DependencyEdge[] {\n const edges: DependencyEdge[] = [];\n const seen = new Set<string>();\n\n const add = (from: string, to: string): void => {\n if (from === to || !graph.nodes.has(from) || !graph.nodes.has(to)) return;\n const key = `${from}->${to}`;\n if (seen.has(key)) return;\n seen.add(key);\n edges.push({ from, to });\n };\n\n for (const node of graph.nodes.values()) {\n for (const blocker of node.relations.blockedBy) {\n add(blocker, node.id);\n }\n for (const blocked of node.relations.blocking) {\n add(node.id, blocked);\n }\n }\n\n return edges;\n}\n\n/** Every cycle of the dependency edges, each reported once. */\nexport function findCycles(graph: DependencyGraph): string[][] {\n const adjacency = new Map<string, string[]>();\n for (const { from, to } of dependencyEdges(graph)) {\n const list = adjacency.get(from);\n if (list === undefined) {\n adjacency.set(from, [to]);\n } else {\n list.push(to);\n }\n }\n\n const cycles: string[][] = [];\n const reported = new Set<string>();\n const state = new Map<string, 'visiting' | 'done'>();\n const stack: string[] = [];\n\n const record = (cycle: string[]): void => {\n // Rotate to the smallest member so the same cycle found from two entry\n // points is reported once.\n let pivot = 0;\n for (let i = 1; i < cycle.length; i++) {\n if (compareIds(cycle[i] as string, cycle[pivot] as string) < 0) pivot = i;\n }\n const normalized = [...cycle.slice(pivot), ...cycle.slice(0, pivot)];\n const key = normalized.join('->');\n if (reported.has(key)) return;\n reported.add(key);\n cycles.push(normalized);\n };\n\n const visit = (id: string): void => {\n state.set(id, 'visiting');\n stack.push(id);\n\n for (const next of adjacency.get(id) ?? []) {\n const status = state.get(next);\n if (status === 'visiting') {\n record(stack.slice(stack.indexOf(next)));\n continue;\n }\n if (status === undefined) {\n visit(next);\n }\n }\n\n stack.pop();\n state.set(id, 'done');\n };\n\n for (const id of graph.nodes.keys()) {\n if (!state.has(id)) visit(id);\n }\n\n return cycles;\n}\n\n/** Numeric when both ids are numbers, lexicographic otherwise. */\nexport function compareIds(a: string, b: string): number {\n const na = Number(a);\n const nb = Number(b);\n if (Number.isInteger(na) && Number.isInteger(nb)) {\n return na - nb;\n }\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\n/** Ids a node links to structurally, in traversal order. */\nfunction neighbours(relations: IssueRelations): string[] {\n return uniqueIds(\n [\n ...(relations.parent === null ? [] : [relations.parent]),\n ...relations.children,\n ...relations.blockedBy,\n ...relations.blocking,\n ],\n relations.id,\n );\n}\n\n/**\n * Walk the relations of `roots` breadth-first and return the whole structure.\n *\n * Failures never abort the walk: a node whose relations cannot be read enters\n * the graph with empty relations (and a warning), exactly like an Issue that\n * genuinely relates to nothing. That keeps a partially unavailable API from\n * costing the user the Issues that *were* discovered.\n *\n * Breadth-first is what makes `depth` meaningful under a limit: when the walk is\n * truncated, what is missing is the far edge of the graph, not a random subset.\n */\nexport async function buildDependencyGraph(\n roots: readonly string[],\n fetchRelations: (id: string) => Promise<IssueRelations | null>,\n options: BuildDependencyGraphOptions = {},\n): Promise<DependencyGraph> {\n const maxNodes = options.maxNodes ?? DEFAULT_MAX_NODES;\n const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;\n const warn = options.warn ?? ((): void => {});\n\n const known = new Map<string, Issue>();\n for (const issue of options.known ?? []) {\n known.set(issue.id, issue);\n }\n\n const rootIds = uniqueIds(roots);\n const nodes = new Map<string, DependencyGraphNode>();\n const queued = new Set<string>(rootIds);\n let truncated = false;\n\n let frontier: { id: string; depth: number }[] = rootIds.map((id) => ({ id, depth: 0 }));\n\n while (frontier.length > 0) {\n const next: { id: string; depth: number }[] = [];\n\n for (const { id, depth } of frontier) {\n if (nodes.size >= maxNodes) {\n truncated = true;\n break;\n }\n\n let relations: IssueRelations | null = null;\n try {\n relations = await fetchRelations(id);\n } catch (err) {\n warn(\n `Could not read the relations of issue '${id}': ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n let issue = known.get(id) ?? null;\n if (issue === null && options.fetchIssue !== undefined) {\n try {\n issue = await options.fetchIssue(id);\n } catch (err) {\n warn(`Could not read issue '${id}': ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n const resolved = relations ?? emptyRelations(id);\n nodes.set(id, { id, relations: resolved, depth, issue, root: rootIds.includes(id) });\n\n if (depth >= maxDepth) {\n // Only a node that actually has unexplored neighbours truncates the\n // graph; a leaf at max depth explored everything there was.\n if (neighbours(resolved).some((neighbour) => !queued.has(neighbour))) {\n truncated = true;\n }\n continue;\n }\n\n for (const neighbour of neighbours(resolved)) {\n if (queued.has(neighbour)) continue;\n queued.add(neighbour);\n next.push({ id: neighbour, depth: depth + 1 });\n }\n }\n\n if (nodes.size >= maxNodes && next.length > 0) {\n truncated = true;\n break;\n }\n frontier = next;\n }\n\n const graph: DependencyGraph = {\n roots: rootIds,\n nodes,\n cycles: [],\n truncated,\n limits: { maxNodes, maxDepth },\n };\n\n linkReferencedBy(graph);\n graph.cycles = findCycles(graph);\n\n return graph;\n}\n\n/**\n * Complete `referencedBy` with the inverse of `references` among the discovered\n * nodes.\n *\n * The origin only reports the citations it can see (GitHub's timeline covers\n * cross-references but not, for instance, a mention added by the textual\n * heuristic); inverting what the graph already holds costs no API call and\n * keeps the two directions consistent inside it.\n */\nfunction linkReferencedBy(graph: DependencyGraph): void {\n for (const node of graph.nodes.values()) {\n for (const cited of node.relations.references) {\n const target = graph.nodes.get(cited);\n if (target === undefined || target.id === node.id) continue;\n target.relations.referencedBy = uniqueIds(\n [...target.relations.referencedBy, node.id],\n target.id,\n );\n }\n }\n}\n","import { compareIds, type DependencyGraph, dependencyEdges, findCycles } from '../issues/graph.js';\nimport type { IssuePriority } from './types.js';\n\n/**\n * Turning a dependency graph into the order the queue runs in.\n *\n * The criteria are applied in the exact precedence the initiative specifies:\n *\n * 1. **dependencies and blocks** — hard constraints; an Issue never starts\n * before something it depends on has finished. This is the topological part\n * and the only one that can make the computation fail (a cycle);\n * 2. **hierarchy** — with two Issues both ready to run, the ancestor goes\n * first, so an umbrella Issue frames the work of its sub-issues;\n * 3. **priority** — the GitHub labels `high`, `medium` and `low`;\n * 4. **logical order** — the identifier, numerically when it is a number.\n *\n * Everything below the first item is a *tie-break among Issues that are already\n * free to run*, never a constraint: a `high` Issue that depends on a `low` one\n * still runs second, which is the whole point of the feature.\n */\n\nconst PRIORITY_LABELS: Record<string, IssuePriority> = {\n high: 'high',\n medium: 'medium',\n low: 'low',\n};\n\n/** Ranking of a priority; an Issue with no label sorts after every labelled one. */\nconst PRIORITY_RANK: Record<IssuePriority, number> = { high: 0, medium: 1, low: 2 };\nconst NO_PRIORITY_RANK = 3;\n\n/**\n * Priority carried by a label set, or `null` when there is none.\n *\n * Both the bare labels used by this repository (`high`) and the qualified\n * spellings other projects use (`priority: high`, `priority/high`, `P-high`)\n * are accepted; anything else is simply not a priority. The absence of a label\n * is never an error — most Issues have none.\n */\nexport function priorityOf(labels: readonly string[]): IssuePriority | null {\n for (const label of labels) {\n const normalized = label.trim().toLowerCase();\n const direct = PRIORITY_LABELS[normalized];\n if (direct !== undefined) return direct;\n\n const qualified = /^(?:priority|prio|p)\\s*[:/-]\\s*(high|medium|low)$/.exec(normalized);\n const matched = qualified?.[1];\n if (matched !== undefined) return PRIORITY_LABELS[matched] as IssuePriority;\n }\n return null;\n}\n\nexport interface ExecutionOrderOptions {\n /**\n * Subset of the graph to schedule. Defaults to every node — the \"run the\n * whole hierarchy\" answer; the \"only the ones I asked for\" answer passes the\n * requested ids.\n */\n include?: readonly string[];\n}\n\nexport type ExecutionOrderResult =\n | { ok: true; order: string[] }\n | { ok: false; cycles: string[][] };\n\n/** Number of ancestors of `id` inside the selection: 0 for a top-level Issue. */\nfunction hierarchyDepth(\n id: string,\n parentOf: Map<string, string | null>,\n selected: Set<string>,\n): number {\n let depth = 0;\n let current = parentOf.get(id) ?? null;\n const seen = new Set<string>([id]);\n\n while (current !== null && selected.has(current) && !seen.has(current)) {\n depth++;\n seen.add(current);\n current = parentOf.get(current) ?? null;\n }\n\n return depth;\n}\n\n/**\n * Order the selected Issues of a graph.\n *\n * Kahn's algorithm with a deterministic tie-break, so the same graph always\n * produces the same order — a plan the user confirmed must not reshuffle itself\n * on the next run.\n *\n * A cycle is reported, never worked around: an arbitrary order would silently\n * implement an Issue before its dependency, which is exactly the failure this\n * whole feature exists to prevent. Constraints pointing outside the selection\n * are dropped — an Issue that is not going to run cannot block anything.\n */\nexport function computeExecutionOrder(\n graph: DependencyGraph,\n options: ExecutionOrderOptions = {},\n): ExecutionOrderResult {\n const selected = new Set(\n (options.include ?? [...graph.nodes.keys()]).filter((id) => graph.nodes.has(id)),\n );\n\n const parentOf = new Map<string, string | null>();\n const priority = new Map<string, IssuePriority | null>();\n for (const id of selected) {\n const node = graph.nodes.get(id);\n parentOf.set(id, node?.relations.parent ?? null);\n priority.set(id, priorityOf(node?.issue?.labels ?? []));\n }\n\n const incoming = new Map<string, Set<string>>();\n const outgoing = new Map<string, string[]>();\n for (const id of selected) {\n incoming.set(id, new Set());\n outgoing.set(id, []);\n }\n for (const { from, to } of dependencyEdges(graph)) {\n if (!selected.has(from) || !selected.has(to) || from === to) continue;\n incoming.get(to)?.add(from);\n outgoing.get(from)?.push(to);\n }\n\n const depth = new Map<string, number>();\n for (const id of selected) {\n depth.set(id, hierarchyDepth(id, parentOf, selected));\n }\n\n const rank = (id: string): number => {\n const value = priority.get(id) ?? null;\n return value === null ? NO_PRIORITY_RANK : PRIORITY_RANK[value];\n };\n\n const better = (a: string, b: string): number => {\n const byHierarchy = (depth.get(a) ?? 0) - (depth.get(b) ?? 0);\n if (byHierarchy !== 0) return byHierarchy;\n const byPriority = rank(a) - rank(b);\n if (byPriority !== 0) return byPriority;\n return compareIds(a, b);\n };\n\n const ready = [...selected].filter((id) => (incoming.get(id)?.size ?? 0) === 0);\n const order: string[] = [];\n\n while (ready.length > 0) {\n ready.sort(better);\n const next = ready.shift() as string;\n order.push(next);\n\n for (const dependent of outgoing.get(next) ?? []) {\n const pending = incoming.get(dependent);\n if (pending === undefined) continue;\n pending.delete(next);\n if (pending.size === 0) {\n ready.push(dependent);\n }\n }\n }\n\n if (order.length !== selected.size) {\n // Prefer the cycles the graph already recorded; recompute only if the\n // selection is what closed the loop.\n const cycles = graph.cycles.length > 0 ? graph.cycles : findCycles(graph);\n const relevant = cycles.filter((cycle) => cycle.every((id) => selected.has(id)));\n return { ok: false, cycles: relevant.length > 0 ? relevant : cycles };\n }\n\n return { ok: true, order };\n}\n\n/** One-line description of the cycles, for an error message. */\nexport function describeCycles(cycles: readonly string[][]): string {\n return cycles.map((cycle) => [...cycle, cycle[0]].map((id) => `#${id}`).join(' → ')).join('; ');\n}\n","import type { PipelineState, TaskPlan } from '../types.js';\nimport { loadTaskPlan, saveTaskPlan } from './state-manager.js';\nimport { DEFAULT_PIPELINE_STATE, WORKFLOW_PHASES } from './workflow-contract.js';\n\n/**\n * Ordered pipeline phases. Each phase must complete before the next can start.\n *\n * `pr-review` is deliberately NOT here: it is opt-in via `--pr-review`. Adding\n * it to the default set would make every existing `tasks.json` (which has no\n * `prReviewCompleted`) look unfinished, so `getNextPhase()` would resume into a\n * phase the user never asked for.\n */\nexport type PipelinePhase = 'init' | (typeof WORKFLOW_PHASES)[number]['phase'];\nexport const PIPELINE_PHASES: readonly PipelinePhase[] = [\n 'init',\n ...WORKFLOW_PHASES.filter(({ phase }) => phase !== 'pr-review').map(({ phase }) => phase),\n];\n\n/**\n * The default set plus the opt-in `pr-review` phase, used when the user passes\n * `--pr-review` (or the flag was persisted in `plan.prReview.enabled`).\n */\nexport const PIPELINE_PHASES_WITH_PR_REVIEW = [...PIPELINE_PHASES, 'pr-review'] as const;\n\n/**\n * Pipeline phases excluding the 'pr' phase, for --no-branch mode.\n * `pr-review` is excluded too: without a branch there is no PR to review.\n */\nexport const PIPELINE_PHASES_NO_BRANCH = PIPELINE_PHASES.filter(\n (p): p is Exclude<PipelinePhase, 'pr' | 'pr-review'> => p !== 'pr',\n);\n\n/**\n * Map pipeline phases to their corresponding PipelineState field.\n * 'init' has no persisted state — it's a runtime-only check.\n */\nconst PHASE_TO_FIELD = Object.fromEntries([\n ['init', null],\n ...WORKFLOW_PHASES.map(({ phase, field }) => [phase, field]),\n]) as Record<PipelinePhase, keyof PipelineState | null>;\n\nexport class PipelineManager {\n private tasksJsonPath: string;\n private plan: TaskPlan;\n private activePhases: readonly PipelinePhase[];\n\n constructor(plan: TaskPlan, tasksJsonPath: string, activePhases?: readonly PipelinePhase[]) {\n this.plan = plan;\n this.tasksJsonPath = tasksJsonPath;\n this.activePhases = activePhases ?? PIPELINE_PHASES;\n }\n\n /**\n * Reload state from disk.\n */\n async reload(): Promise<void> {\n this.plan = await loadTaskPlan(this.tasksJsonPath);\n }\n\n /**\n * Check if a phase is complete.\n */\n isPhaseComplete(phase: PipelinePhase): boolean {\n const field = PHASE_TO_FIELD[phase];\n if (field === null) return true; // init is always \"complete\" after running\n return this.plan.pipeline?.[field] ?? false;\n }\n\n /**\n * Get the first incomplete phase in the pipeline.\n * Returns null if all phases are complete.\n */\n getNextPhase(): PipelinePhase | null {\n for (const phase of this.activePhases) {\n if (!this.isPhaseComplete(phase)) {\n return phase;\n }\n }\n return null;\n }\n\n /**\n * Get the current phase (alias for getNextPhase).\n */\n getCurrentPhase(): PipelinePhase | null {\n return this.getNextPhase();\n }\n\n /**\n * Check whether we can resume from a specific phase.\n * All prerequisite phases must be complete.\n */\n canResume(fromPhase: PipelinePhase): boolean {\n const idx = this.activePhases.indexOf(fromPhase);\n if (idx < 0) return false;\n\n // All active phases before fromPhase must be complete\n for (let i = 0; i < idx; i++) {\n if (!this.isPhaseComplete(this.activePhases[i])) {\n return false;\n }\n }\n return true;\n }\n\n /**\n * Mark a phase as complete and persist to disk.\n */\n async markPhaseComplete(phase: PipelinePhase): Promise<void> {\n const field = PHASE_TO_FIELD[phase];\n if (field === null) return; // init has no persisted state\n\n this.plan = {\n ...this.plan,\n pipeline: {\n ...(this.plan.pipeline ?? DEFAULT_PIPELINE_STATE),\n [field]: true,\n },\n };\n\n await saveTaskPlan(this.tasksJsonPath, this.plan);\n }\n}\n"],"mappings":"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","names":["parentOf"]}