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,1201 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ writeDiagnostic
4
+ } from "./chunk-7CMVL4X6.js";
5
+ import {
6
+ MemoryPublisher,
7
+ getSessionPublisher
8
+ } from "./chunk-DIMDAYZS.js";
9
+ import {
10
+ bindTelemetry
11
+ } from "./chunk-HEHHQU4G.js";
12
+ import {
13
+ METADATA_FILENAME,
14
+ migrateLegacyStorage,
15
+ resolveStorageMode
16
+ } from "./chunk-LYC57OXF.js";
17
+ import {
18
+ isoNow
19
+ } from "./chunk-GD7JZMSJ.js";
20
+ import {
21
+ globalConfigSchema,
22
+ projectMetadataSchema,
23
+ providersHealthSchema,
24
+ storageConfigInputSchema
25
+ } from "./chunk-IMB6FA2F.js";
26
+ import {
27
+ CURRENT_SCHEMA_VERSION,
28
+ getDatabasePath,
29
+ openDatabase,
30
+ openIssueFlowDatabase,
31
+ registerPlanRepository,
32
+ registerProjectDatabaseOptions,
33
+ registerQueueRepository,
34
+ registerStorageProjections,
35
+ resetPlanRepositories,
36
+ resetProjectDatabaseOptions,
37
+ writePlanRows
38
+ } from "./chunk-7MJGYPHF.js";
39
+ import {
40
+ taskPlanSchema
41
+ } from "./chunk-XNK5UN2D.js";
42
+ import {
43
+ EVENTS_FILENAME,
44
+ GLOBAL_ROOT_ENV,
45
+ ISSUES_DIR_NAME,
46
+ PROVIDERS_HEALTH_FILENAME,
47
+ QUEUES_DIR_NAME,
48
+ ROTATED_EVENTS_FILENAME,
49
+ RUN_LOCK_FILENAME,
50
+ VERIFY_FILENAME,
51
+ getGlobalRoot,
52
+ getIssuePathsAt,
53
+ getQueuePathsAt
54
+ } from "./chunk-MAJ6QDRL.js";
55
+ import {
56
+ getProjectRoot
57
+ } from "./chunk-TJVX42KY.js";
58
+
59
+ // src/core/verbose.ts
60
+ var _verbose = false;
61
+ function setVerbose(enabled) {
62
+ _verbose = enabled;
63
+ }
64
+ function isVerbose() {
65
+ return _verbose;
66
+ }
67
+ var _onOutput;
68
+ function setOutputCallback(callback) {
69
+ _onOutput = callback;
70
+ }
71
+ function getOutputCallback() {
72
+ return _onOutput;
73
+ }
74
+ var _onStoryUpdate;
75
+ function setStoryUpdateCallback(callback) {
76
+ _onStoryUpdate = callback;
77
+ }
78
+ function getStoryUpdateCallback() {
79
+ return _onStoryUpdate;
80
+ }
81
+ var _onStoryStage;
82
+ function setStoryStageCallback(callback) {
83
+ _onStoryStage = callback;
84
+ }
85
+ function getStoryStageCallback() {
86
+ return _onStoryStage;
87
+ }
88
+ var _onActivity;
89
+ function setActivityCallback(callback) {
90
+ _onActivity = callback;
91
+ }
92
+ function getActivityCallback() {
93
+ return _onActivity;
94
+ }
95
+ var _globalTimeout;
96
+ function setGlobalTimeout(ms) {
97
+ _globalTimeout = ms;
98
+ }
99
+ function getGlobalTimeout() {
100
+ return _globalTimeout;
101
+ }
102
+ var _inactivityTimeout;
103
+ function setInactivityTimeout(ms) {
104
+ _inactivityTimeout = ms;
105
+ }
106
+ function getInactivityTimeout() {
107
+ return _inactivityTimeout;
108
+ }
109
+
110
+ // src/ui/logger.ts
111
+ import chalk from "chalk";
112
+ import ora from "ora";
113
+ function useUnicode() {
114
+ if (process.env.NO_COLOR != null && process.env.NO_COLOR !== "") return false;
115
+ if (!process.stdout.isTTY) return false;
116
+ return true;
117
+ }
118
+ function useColor() {
119
+ if (process.env.NO_COLOR != null && process.env.NO_COLOR !== "") return false;
120
+ if (!process.stdout.isTTY) return false;
121
+ return true;
122
+ }
123
+ function getIcons() {
124
+ if (useUnicode()) {
125
+ return {
126
+ success: "\u2713",
127
+ fail: "\u2717",
128
+ pending: "\u23F3",
129
+ retry: "\u21BB",
130
+ warn: "\u26A0",
131
+ start: "\u25B6",
132
+ end: "\u25A0",
133
+ notReached: "\u25CB",
134
+ tool: "\u25B8",
135
+ connector: "\u2502",
136
+ info: "\xB7"
137
+ };
138
+ }
139
+ return {
140
+ success: "[OK]",
141
+ fail: "[FAIL]",
142
+ pending: "[...]",
143
+ retry: "[RETRY]",
144
+ warn: "[WARN]",
145
+ start: "[START]",
146
+ end: "[END]",
147
+ notReached: "[ ]",
148
+ tool: ">",
149
+ connector: "|",
150
+ info: "-"
151
+ };
152
+ }
153
+ function getTermWidth() {
154
+ return process.stdout.columns ?? 80;
155
+ }
156
+ function emit(line, level = "info") {
157
+ getSessionPublisher().publish({ type: "log", at: isoNow(), level, message: line });
158
+ writeDiagnostic({
159
+ level: level === "warn" ? "warning" : level,
160
+ message: line
161
+ });
162
+ const cb = getOutputCallback();
163
+ if (cb) {
164
+ cb(line);
165
+ } else {
166
+ console.log(line);
167
+ }
168
+ }
169
+ function printSuccess(message) {
170
+ const icons = getIcons();
171
+ if (useColor()) {
172
+ emit(chalk.green(`${icons.success} ${message}`));
173
+ } else {
174
+ emit(`${icons.success} ${message}`);
175
+ }
176
+ }
177
+ function printError(message) {
178
+ const icons = getIcons();
179
+ if (useColor()) {
180
+ emit(chalk.red(`${icons.fail} ${message}`), "error");
181
+ } else {
182
+ emit(`${icons.fail} ${message}`, "error");
183
+ }
184
+ }
185
+ function printWarning(message) {
186
+ const icons = getIcons();
187
+ if (useColor()) {
188
+ emit(chalk.yellow(`${icons.warn} ${message}`), "warn");
189
+ } else {
190
+ emit(`${icons.warn} ${message}`, "warn");
191
+ }
192
+ }
193
+ function printRetry(message) {
194
+ const icons = getIcons();
195
+ if (useColor()) {
196
+ emit(chalk.yellow(`${icons.retry} ${message}`), "warn");
197
+ } else {
198
+ emit(`${icons.retry} ${message}`, "warn");
199
+ }
200
+ }
201
+ function printInfo(message) {
202
+ const icons = getIcons();
203
+ if (useColor()) {
204
+ emit(chalk.blue(`${icons.info} ${message}`));
205
+ } else {
206
+ emit(`${icons.info} ${message}`);
207
+ }
208
+ }
209
+ function createSpinner(message) {
210
+ return ora({
211
+ text: message,
212
+ color: "blue",
213
+ spinner: "dots"
214
+ });
215
+ }
216
+ function formatDuration(totalSeconds) {
217
+ const hours = Math.floor(totalSeconds / 3600);
218
+ const mins = Math.floor(totalSeconds % 3600 / 60);
219
+ const secs = totalSeconds % 60;
220
+ if (hours > 0) {
221
+ return `${hours}h ${mins}m ${secs}s`;
222
+ }
223
+ if (mins > 0) {
224
+ return `${mins}m ${secs}s`;
225
+ }
226
+ return `${secs}s`;
227
+ }
228
+ var ElapsedTimer = class {
229
+ constructor(onTick) {
230
+ this.onTick = onTick;
231
+ this.startTime = Date.now();
232
+ }
233
+ onTick;
234
+ startTime;
235
+ intervalId = null;
236
+ cleanup = () => this.stop();
237
+ start() {
238
+ this.startTime = Date.now();
239
+ this.intervalId = setInterval(() => {
240
+ const seconds = Math.floor((Date.now() - this.startTime) / 1e3);
241
+ this.onTick(formatDuration(seconds));
242
+ }, 1e3);
243
+ if (this.intervalId && typeof this.intervalId === "object" && "unref" in this.intervalId) {
244
+ this.intervalId.unref();
245
+ }
246
+ process.on("exit", this.cleanup);
247
+ return this;
248
+ }
249
+ getElapsedSeconds() {
250
+ return Math.floor((Date.now() - this.startTime) / 1e3);
251
+ }
252
+ stop() {
253
+ if (this.intervalId) {
254
+ clearInterval(this.intervalId);
255
+ this.intervalId = null;
256
+ }
257
+ process.removeListener("exit", this.cleanup);
258
+ return this.getElapsedSeconds();
259
+ }
260
+ };
261
+
262
+ // src/config/sources.ts
263
+ import { existsSync } from "node:fs";
264
+ import { readFile } from "node:fs/promises";
265
+ import { dirname, join } from "node:path";
266
+ var PROJECT_CONFIG_FILENAME = ".issue-flow.json";
267
+ function findProjectRootFromCwd(start = process.cwd()) {
268
+ let dir = start;
269
+ while (true) {
270
+ if (existsSync(join(dir, PROJECT_CONFIG_FILENAME)) || existsSync(join(dir, ".git"))) {
271
+ return dir;
272
+ }
273
+ const parent = dirname(dir);
274
+ if (parent === dir) return void 0;
275
+ dir = parent;
276
+ }
277
+ }
278
+ async function readProjectConfigFile(projectRoot, warn) {
279
+ const root = projectRoot ?? findProjectRootFromCwd();
280
+ if (root === void 0) {
281
+ return null;
282
+ }
283
+ const filePath = join(root, PROJECT_CONFIG_FILENAME);
284
+ let raw;
285
+ try {
286
+ raw = await readFile(filePath, "utf-8");
287
+ } catch {
288
+ return null;
289
+ }
290
+ let parsed;
291
+ try {
292
+ parsed = JSON.parse(raw);
293
+ } catch {
294
+ warn(`Ignoring ${PROJECT_CONFIG_FILENAME}: invalid JSON.`);
295
+ return null;
296
+ }
297
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
298
+ warn(`Ignoring ${PROJECT_CONFIG_FILENAME}: expected a JSON object.`);
299
+ return null;
300
+ }
301
+ return parsed;
302
+ }
303
+ var GLOBAL_CONFIG_FILENAME = "config.json";
304
+ async function loadGlobalConfig(options = {}) {
305
+ const warn = options.warn ?? printWarning;
306
+ let root;
307
+ try {
308
+ root = options.globalRoot ?? getGlobalRoot({ env: options.env ?? process.env });
309
+ } catch (err) {
310
+ warn(`Ignoring ${GLOBAL_CONFIG_FILENAME}: ${err.message}`);
311
+ return {};
312
+ }
313
+ const filePath = join(root, GLOBAL_CONFIG_FILENAME);
314
+ let raw;
315
+ try {
316
+ raw = await readFile(filePath, "utf-8");
317
+ } catch (err) {
318
+ if (err.code !== "ENOENT") {
319
+ warn(`Ignoring ${GLOBAL_CONFIG_FILENAME}: ${err.message}`);
320
+ }
321
+ return {};
322
+ }
323
+ let parsed;
324
+ try {
325
+ parsed = JSON.parse(raw);
326
+ } catch {
327
+ warn(`Ignoring ${GLOBAL_CONFIG_FILENAME}: invalid JSON.`);
328
+ return {};
329
+ }
330
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
331
+ warn(`Ignoring ${GLOBAL_CONFIG_FILENAME}: expected a JSON object.`);
332
+ return {};
333
+ }
334
+ const config = {};
335
+ for (const [key, value] of Object.entries(parsed)) {
336
+ const result = globalConfigSchema.safeParse({ [key]: value });
337
+ if (!result.success) {
338
+ warn(
339
+ `Ignoring "${key}" key of ${GLOBAL_CONFIG_FILENAME}: ${result.error.issues[0]?.message ?? "invalid value"}.`
340
+ );
341
+ continue;
342
+ }
343
+ Object.assign(config, result.data);
344
+ }
345
+ return config;
346
+ }
347
+
348
+ // src/core/journal.ts
349
+ import { appendFile, mkdir, rename, stat, unlink } from "node:fs/promises";
350
+ import { dirname as dirname2 } from "node:path";
351
+ var DEFAULT_JOURNAL_MAX_BYTES = 0;
352
+ var JournalPublisher = class extends MemoryPublisher {
353
+ filePath;
354
+ rotatedPath;
355
+ maxFileBytes;
356
+ seq = 0;
357
+ writeChain = Promise.resolve();
358
+ closed = false;
359
+ /** Bytes in the current file. `null` until the first write measures it. */
360
+ bytes = null;
361
+ constructor(filePath, rotatedPath, options = {}) {
362
+ super(options);
363
+ this.filePath = filePath;
364
+ this.rotatedPath = rotatedPath;
365
+ this.maxFileBytes = options.maxFileBytes ?? DEFAULT_JOURNAL_MAX_BYTES;
366
+ }
367
+ afterPublish(event) {
368
+ if (this.closed) return;
369
+ this.seq++;
370
+ const line = `${JSON.stringify({ seq: this.seq, event })}
371
+ `;
372
+ this.enqueue(line);
373
+ }
374
+ enqueue(line) {
375
+ this.writeChain = this.writeChain.then(async () => {
376
+ try {
377
+ await this.appendLine(line);
378
+ } catch (err) {
379
+ this.warnOnce(err);
380
+ }
381
+ });
382
+ }
383
+ async appendLine(line) {
384
+ const size = Buffer.byteLength(line);
385
+ if (this.bytes === null) {
386
+ await mkdir(dirname2(this.filePath), { recursive: true });
387
+ this.bytes = await currentSize(this.filePath);
388
+ }
389
+ if (this.maxFileBytes > 0 && this.bytes > 0 && this.bytes + size > this.maxFileBytes) {
390
+ await this.rotate();
391
+ }
392
+ await appendFile(this.filePath, line, "utf-8");
393
+ this.bytes += size;
394
+ }
395
+ /**
396
+ * Keep exactly one previous generation only when a caller explicitly asks
397
+ * for rotation. The default never enters this path, so history is never
398
+ * discarded without an explicit policy.
399
+ */
400
+ async rotate() {
401
+ try {
402
+ await unlink(this.rotatedPath);
403
+ } catch {
404
+ }
405
+ await rename(this.filePath, this.rotatedPath);
406
+ this.bytes = 0;
407
+ }
408
+ async flush() {
409
+ await this.writeChain;
410
+ }
411
+ async close() {
412
+ await this.flush();
413
+ this.closed = true;
414
+ }
415
+ };
416
+ async function currentSize(path) {
417
+ try {
418
+ return (await stat(path)).size;
419
+ } catch {
420
+ return 0;
421
+ }
422
+ }
423
+ function parseJournal(content) {
424
+ const entries = [];
425
+ for (const line of content.split("\n")) {
426
+ const trimmed = line.trim();
427
+ if (trimmed === "") continue;
428
+ let parsed;
429
+ try {
430
+ parsed = JSON.parse(trimmed);
431
+ } catch {
432
+ continue;
433
+ }
434
+ if (!isEntry(parsed)) continue;
435
+ entries.push(parsed);
436
+ }
437
+ return entries;
438
+ }
439
+ function isEntry(value) {
440
+ if (typeof value !== "object" || value === null) return false;
441
+ const candidate = value;
442
+ if (typeof candidate.seq !== "number") return false;
443
+ const event = candidate.event;
444
+ return typeof event === "object" && event !== null && typeof event.type === "string";
445
+ }
446
+ var MultiPublisher = class {
447
+ members;
448
+ primary;
449
+ constructor(members) {
450
+ if (members.length === 0) {
451
+ throw new Error("MultiPublisher needs at least one member");
452
+ }
453
+ this.members = members;
454
+ this.primary = members[0];
455
+ }
456
+ publish(event) {
457
+ for (const member of this.members) {
458
+ member.publish(event);
459
+ }
460
+ }
461
+ snapshot() {
462
+ return this.primary.snapshot();
463
+ }
464
+ version() {
465
+ return this.primary.version();
466
+ }
467
+ async flush() {
468
+ await Promise.all(this.members.map((member) => member.flush()));
469
+ }
470
+ async close() {
471
+ await Promise.all(this.members.map((member) => member.close()));
472
+ }
473
+ };
474
+
475
+ // src/storage/db/import.ts
476
+ import { createHash } from "node:crypto";
477
+ import { existsSync as existsSync2 } from "node:fs";
478
+ import { readdir, readFile as readFile2, rename as rename2 } from "node:fs/promises";
479
+ import { basename, join as join2, relative } from "node:path";
480
+ var EMPTY_COUNTS = {
481
+ projects: 0,
482
+ issues: 0,
483
+ pipelines: 0,
484
+ stories: 0,
485
+ executions: 0,
486
+ events: 0,
487
+ queues: 0,
488
+ provider_health: 0,
489
+ verifications: 0,
490
+ pull_requests: 0,
491
+ user_story_numbering: 0
492
+ };
493
+ function digest(content) {
494
+ return createHash("sha256").update(content).digest("hex");
495
+ }
496
+ function stableId(prefix, value) {
497
+ return `${prefix}-${digest(value).slice(0, 24)}`;
498
+ }
499
+ async function fileIfPresent(path) {
500
+ try {
501
+ return await readFile2(path, "utf-8");
502
+ } catch (error) {
503
+ if (error.code === "ENOENT") return null;
504
+ throw error;
505
+ }
506
+ }
507
+ async function collectArtifacts(projectDir, withEvents) {
508
+ const artifacts = [];
509
+ const addJson = async (path, kind, issueId) => {
510
+ const content = await fileIfPresent(path);
511
+ if (content === null) return;
512
+ artifacts.push({
513
+ path,
514
+ relativePath: relative(projectDir, path),
515
+ sha256: digest(content),
516
+ kind,
517
+ issueId,
518
+ value: JSON.parse(content)
519
+ });
520
+ };
521
+ await addJson(join2(projectDir, METADATA_FILENAME), "metadata");
522
+ await addJson(join2(projectDir, PROVIDERS_HEALTH_FILENAME), "health");
523
+ const issuesDir = join2(projectDir, ISSUES_DIR_NAME);
524
+ let issueEntries = [];
525
+ try {
526
+ issueEntries = await readdir(issuesDir);
527
+ } catch (error) {
528
+ if (error.code !== "ENOENT") throw error;
529
+ }
530
+ for (const issueId of issueEntries) {
531
+ const issueDir = join2(issuesDir, issueId);
532
+ await addJson(join2(issueDir, "tasks.json"), "tasks", issueId);
533
+ await addJson(join2(issueDir, VERIFY_FILENAME), "verify", issueId);
534
+ if (withEvents) {
535
+ for (const name of [ROTATED_EVENTS_FILENAME, EVENTS_FILENAME]) {
536
+ const path = join2(issueDir, name);
537
+ const content = await fileIfPresent(path);
538
+ if (content !== null) {
539
+ artifacts.push({
540
+ path,
541
+ relativePath: relative(projectDir, path),
542
+ sha256: digest(content),
543
+ kind: "events",
544
+ issueId,
545
+ value: parseJournal(content)
546
+ });
547
+ }
548
+ }
549
+ }
550
+ }
551
+ const queuesDir = join2(projectDir, QUEUES_DIR_NAME);
552
+ let queueEntries = [];
553
+ try {
554
+ queueEntries = await readdir(queuesDir);
555
+ } catch (error) {
556
+ if (error.code !== "ENOENT") throw error;
557
+ }
558
+ for (const queueId of queueEntries) {
559
+ await addJson(join2(queuesDir, queueId, "execution-plan.json"), "queue");
560
+ }
561
+ return artifacts;
562
+ }
563
+ function increment(counts, table) {
564
+ counts[table] = (counts[table] ?? 0) + 1;
565
+ }
566
+ function insertIssue(database, projectId, id, status, title, branchName, timestamp, counts) {
567
+ database.prepare(
568
+ `INSERT INTO issues (project_id, id, title, status, branch_name, created_at, updated_at)
569
+ VALUES (?, ?, ?, ?, ?, ?, ?)
570
+ ON CONFLICT(project_id, id) DO UPDATE SET
571
+ title = excluded.title, status = excluded.status, branch_name = excluded.branch_name,
572
+ updated_at = excluded.updated_at`
573
+ ).run(projectId, id, title, status, branchName, timestamp, timestamp);
574
+ increment(counts, "issues");
575
+ }
576
+ function importArtifact(database, artifact, projectId, fallback, counts) {
577
+ const value = artifact.value;
578
+ if (artifact.kind === "metadata") {
579
+ const metadata = projectMetadataSchema.parse(value);
580
+ database.prepare(
581
+ `INSERT INTO projects (id, root, remote_url, created_at, updated_at) VALUES (?, ?, ?, ?, ?)
582
+ ON CONFLICT(id) DO UPDATE SET root = excluded.root, remote_url = excluded.remote_url,
583
+ updated_at = excluded.updated_at`
584
+ ).run(projectId, metadata.root, metadata.remoteUrl, metadata.createdAt, metadata.updatedAt);
585
+ increment(counts, "projects");
586
+ if (metadata.userStoryNumbering !== void 0) {
587
+ const decision = metadata.userStoryNumbering;
588
+ database.prepare(
589
+ `INSERT INTO user_story_numbering (project_id, next_number, source, issue_id, decided_at, detail)
590
+ VALUES (?, ?, ?, ?, ?, ?)
591
+ ON CONFLICT(project_id) DO UPDATE SET next_number = excluded.next_number,
592
+ source = excluded.source, issue_id = excluded.issue_id, decided_at = excluded.decided_at,
593
+ detail = excluded.detail`
594
+ ).run(
595
+ projectId,
596
+ decision.nextNumber,
597
+ decision.source,
598
+ decision.issueNumber,
599
+ decision.decidedAt,
600
+ decision.detail ?? null
601
+ );
602
+ increment(counts, "user_story_numbering");
603
+ }
604
+ return;
605
+ }
606
+ if (artifact.kind === "tasks") {
607
+ const parsed = taskPlanSchema.safeParse(value);
608
+ if (!parsed.success) return;
609
+ const plan = parsed.data;
610
+ const issueId = artifact.issueId ?? String(plan.issueNumber);
611
+ writePlanRows(
612
+ database,
613
+ {
614
+ tasksPath: artifact.path,
615
+ projectId,
616
+ issueId,
617
+ projectRoot: fallback.root
618
+ },
619
+ plan
620
+ );
621
+ increment(counts, "issues");
622
+ increment(counts, "pipelines");
623
+ counts.stories += plan.userStories.length;
624
+ for (const execution of plan.executions ?? []) {
625
+ const cost = execution.cost.status === "unknown" ? null : execution.cost.amount;
626
+ database.prepare(
627
+ `INSERT INTO executions
628
+ (id, project_id, issue_id, status, started_at, finished_at, duration_ms, cost_status, cost_amount, payload_json)
629
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
630
+ ON CONFLICT(id) DO UPDATE SET status = excluded.status, finished_at = excluded.finished_at,
631
+ duration_ms = excluded.duration_ms, cost_status = excluded.cost_status,
632
+ cost_amount = excluded.cost_amount, payload_json = excluded.payload_json`
633
+ ).run(
634
+ execution.id,
635
+ projectId,
636
+ issueId,
637
+ execution.status,
638
+ execution.startedAt,
639
+ execution.finishedAt,
640
+ execution.durationMs,
641
+ execution.cost.status,
642
+ cost,
643
+ JSON.stringify(execution)
644
+ );
645
+ increment(counts, "executions");
646
+ }
647
+ if (plan.pullRequest !== void 0) {
648
+ const id = stableId("pr", `${projectId}:${issueId}:${plan.pullRequest.number}`);
649
+ database.prepare(
650
+ `INSERT INTO pull_requests (id, project_id, issue_id, number, url, status, created_at)
651
+ VALUES (?, ?, ?, ?, ?, ?, ?)
652
+ ON CONFLICT(id) DO UPDATE SET url = excluded.url, status = excluded.status`
653
+ ).run(
654
+ id,
655
+ projectId,
656
+ issueId,
657
+ plan.pullRequest.number,
658
+ plan.pullRequest.url,
659
+ "created",
660
+ plan.pullRequest.createdAt
661
+ );
662
+ increment(counts, "pull_requests");
663
+ }
664
+ return;
665
+ }
666
+ if (artifact.kind === "health") {
667
+ const health = providersHealthSchema.parse(value);
668
+ for (const [provider, record] of Object.entries(health.providers)) {
669
+ database.prepare(
670
+ `INSERT INTO provider_health (project_id, provider, payload_json, updated_at) VALUES (?, ?, ?, ?)
671
+ ON CONFLICT(project_id, provider) DO UPDATE SET payload_json = excluded.payload_json,
672
+ updated_at = excluded.updated_at`
673
+ ).run(projectId, provider, JSON.stringify(record), fallback.timestamp);
674
+ increment(counts, "provider_health");
675
+ }
676
+ return;
677
+ }
678
+ if (artifact.kind === "events") {
679
+ const issueId = artifact.issueId ?? "unknown";
680
+ const runId = `legacy:${projectId}:${issueId}`;
681
+ insertIssue(database, projectId, issueId, "imported", null, null, fallback.timestamp, counts);
682
+ database.prepare(
683
+ `INSERT INTO runs (id, project_id, issue_id, status, started_at, finished_at, session_id)
684
+ VALUES (?, ?, ?, 'imported', ?, ?, ?)
685
+ ON CONFLICT(id) DO NOTHING`
686
+ ).run(runId, projectId, issueId, fallback.timestamp, fallback.timestamp, runId);
687
+ let nextSequence = (database.prepare("SELECT COALESCE(MAX(sequence), 0) AS sequence FROM events WHERE run_id = ?").get(runId)?.sequence ?? 0) + 1;
688
+ for (const [index, entry] of artifact.value.entries()) {
689
+ const id = stableId("event", `${artifact.relativePath}:${index}`);
690
+ const event = entry.event;
691
+ database.prepare(
692
+ `INSERT INTO events
693
+ (id, project_id, run_id, occurred_at, kind, payload_json, session_id, sequence)
694
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
695
+ ON CONFLICT(id) DO UPDATE SET occurred_at = excluded.occurred_at, kind = excluded.kind,
696
+ payload_json = excluded.payload_json`
697
+ ).run(
698
+ id,
699
+ projectId,
700
+ runId,
701
+ event.at ?? fallback.timestamp,
702
+ event.type,
703
+ JSON.stringify(entry),
704
+ runId,
705
+ nextSequence++
706
+ );
707
+ increment(counts, "events");
708
+ }
709
+ return;
710
+ }
711
+ if (artifact.kind === "verify") {
712
+ insertIssue(
713
+ database,
714
+ projectId,
715
+ artifact.issueId ?? "unknown",
716
+ "imported",
717
+ null,
718
+ null,
719
+ fallback.timestamp,
720
+ counts
721
+ );
722
+ const id = stableId("verify", artifact.relativePath);
723
+ database.prepare(
724
+ `INSERT INTO verifications (id, project_id, issue_id, status, created_at, payload_json)
725
+ VALUES (?, ?, ?, ?, ?, ?)
726
+ ON CONFLICT(id) DO UPDATE SET status = excluded.status, payload_json = excluded.payload_json`
727
+ ).run(
728
+ id,
729
+ projectId,
730
+ artifact.issueId ?? null,
731
+ String(value.verdict ?? "unknown"),
732
+ fallback.timestamp,
733
+ JSON.stringify(value)
734
+ );
735
+ increment(counts, "verifications");
736
+ return;
737
+ }
738
+ const queue = value;
739
+ database.prepare(
740
+ `INSERT INTO queues (id, project_id, status, payload_json, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)
741
+ ON CONFLICT(id) DO UPDATE SET status = excluded.status, payload_json = excluded.payload_json,
742
+ updated_at = excluded.updated_at`
743
+ ).run(
744
+ queue.id,
745
+ projectId,
746
+ queue.status,
747
+ JSON.stringify(queue),
748
+ queue.createdAt,
749
+ queue.updatedAt
750
+ );
751
+ increment(counts, "queues");
752
+ for (const entry of queue.issues) {
753
+ insertIssue(database, projectId, entry.id, entry.status, null, null, queue.updatedAt, counts);
754
+ database.prepare(
755
+ `INSERT INTO queue_issues (queue_id, project_id, issue_id, position, status) VALUES (?, ?, ?, ?, ?)
756
+ ON CONFLICT(queue_id, project_id, issue_id) DO UPDATE SET position = excluded.position,
757
+ status = excluded.status`
758
+ ).run(queue.id, projectId, entry.id, entry.position, entry.status);
759
+ }
760
+ if (queue.pullRequest !== void 0) {
761
+ const id = stableId("pr", `${projectId}:queue:${queue.id}:${queue.pullRequest.number}`);
762
+ database.prepare(
763
+ `INSERT INTO pull_requests (id, project_id, number, url, status, created_at) VALUES (?, ?, ?, ?, ?, ?)
764
+ ON CONFLICT(id) DO UPDATE SET url = excluded.url, status = excluded.status`
765
+ ).run(
766
+ id,
767
+ projectId,
768
+ queue.pullRequest.number,
769
+ queue.pullRequest.url,
770
+ "created",
771
+ queue.pullRequest.createdAt
772
+ );
773
+ increment(counts, "pull_requests");
774
+ }
775
+ }
776
+ async function retainBackups(directory, retention) {
777
+ let names = [];
778
+ try {
779
+ names = (await readdir(directory)).filter(
780
+ (name) => name.startsWith("issue-flow-") && name.endsWith(".db")
781
+ );
782
+ } catch {
783
+ return;
784
+ }
785
+ for (const name of names.sort().slice(0, Math.max(0, names.length - retention))) {
786
+ await import("node:fs/promises").then(
787
+ ({ unlink: unlink2 }) => unlink2(join2(directory, name)).catch(() => void 0)
788
+ );
789
+ }
790
+ }
791
+ function applyRetention(database, projectId, retention) {
792
+ const trim = (table, limit) => {
793
+ if (limit === void 0 || limit === 0) return;
794
+ const timestamp = table === "executions" ? "started_at" : table === "events" ? "occurred_at" : "updated_at";
795
+ database.prepare(
796
+ `DELETE FROM ${table} WHERE id IN (
797
+ SELECT id FROM ${table} WHERE project_id = ?
798
+ ORDER BY ${timestamp} DESC, rowid DESC LIMIT -1 OFFSET ?
799
+ )`
800
+ ).run(projectId, limit);
801
+ };
802
+ trim("executions", retention?.executions);
803
+ trim("events", retention?.events);
804
+ trim("snapshots", retention?.snapshots);
805
+ }
806
+ var DEFAULT_BACKUP_RETENTION = 5;
807
+ async function prepareDatabase(options) {
808
+ const path = getDatabasePath(options);
809
+ if (!existsSync2(path)) return;
810
+ const raw = await openDatabase(path, options);
811
+ try {
812
+ const integrity = raw.integrityCheck();
813
+ if (integrity !== "ok") {
814
+ raw.close();
815
+ const quarantine = `${path}.corrupt-${Date.now()}`;
816
+ await rename2(path, quarantine);
817
+ options.onWarning?.(
818
+ `SQLite integrity check failed; isolated ${path} as ${quarantine} and will re-import preserved JSON artifacts.`
819
+ );
820
+ return;
821
+ }
822
+ const version = Number(
823
+ raw.prepare("PRAGMA user_version").get()?.user_version ?? 0
824
+ );
825
+ const backupDirectory = join2(getDatabasePath(options).replace(/[/\\][^/\\]+$/, ""), "backups");
826
+ if (version < CURRENT_SCHEMA_VERSION) {
827
+ const backup = join2(backupDirectory, `issue-flow-${Date.now()}.db`);
828
+ raw.backup(backup);
829
+ }
830
+ await retainBackups(backupDirectory, options.backupRetention ?? DEFAULT_BACKUP_RETENTION);
831
+ } finally {
832
+ try {
833
+ raw.close();
834
+ } catch {
835
+ }
836
+ }
837
+ }
838
+ async function importProjectArtifacts(options) {
839
+ const counts = { ...EMPTY_COUNTS };
840
+ try {
841
+ await prepareDatabase(options);
842
+ const database = await openIssueFlowDatabase(options);
843
+ try {
844
+ const adopted = database.prepare("SELECT project_id FROM project_imports WHERE project_id = ?").get(options.projectId);
845
+ const artifacts = (await collectArtifacts(options.projectDir, options.withEvents === true)).filter((artifact) => {
846
+ if (adopted === void 0) return true;
847
+ if (options.withEvents === true) return artifact.kind === "events";
848
+ return artifact.kind === "tasks";
849
+ });
850
+ let imported = 0;
851
+ let skipped = 0;
852
+ database.transaction(() => {
853
+ database.prepare(
854
+ `INSERT INTO projects (id, root, remote_url, created_at, updated_at) VALUES (?, ?, ?, ?, ?)
855
+ ON CONFLICT(id) DO UPDATE SET root = excluded.root, remote_url = excluded.remote_url,
856
+ updated_at = excluded.updated_at`
857
+ ).run(
858
+ options.projectId,
859
+ options.projectRoot,
860
+ options.remoteUrl,
861
+ (/* @__PURE__ */ new Date()).toISOString(),
862
+ (/* @__PURE__ */ new Date()).toISOString()
863
+ );
864
+ increment(counts, "projects");
865
+ for (const artifact of artifacts) {
866
+ const previous = database.prepare("SELECT sha256 FROM migrated_artifacts WHERE source_path = ?").get(artifact.path);
867
+ const requiresPlanProjection = artifact.kind === "tasks" && taskPlanSchema.safeParse(artifact.value).success && database.prepare("SELECT project FROM pipelines WHERE project_id = ? AND issue_id = ?").get(options.projectId, artifact.issueId ?? "")?.project === null;
868
+ if (previous?.sha256 === artifact.sha256 && !requiresPlanProjection) {
869
+ skipped++;
870
+ continue;
871
+ }
872
+ importArtifact(
873
+ database,
874
+ artifact,
875
+ options.projectId,
876
+ {
877
+ root: options.projectRoot,
878
+ remoteUrl: options.remoteUrl,
879
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
880
+ },
881
+ counts
882
+ );
883
+ database.prepare(
884
+ `INSERT INTO migrated_artifacts (source_path, sha256, migrated_at, table_counts_json)
885
+ VALUES (?, ?, ?, ?)
886
+ ON CONFLICT(source_path) DO UPDATE SET sha256 = excluded.sha256,
887
+ migrated_at = excluded.migrated_at, table_counts_json = excluded.table_counts_json`
888
+ ).run(artifact.path, artifact.sha256, (/* @__PURE__ */ new Date()).toISOString(), JSON.stringify(counts));
889
+ imported++;
890
+ }
891
+ applyRetention(database, options.projectId, options.retention);
892
+ database.prepare(
893
+ `INSERT INTO project_imports (project_id, completed_at) VALUES (?, ?)
894
+ ON CONFLICT(project_id) DO NOTHING`
895
+ ).run(options.projectId, (/* @__PURE__ */ new Date()).toISOString());
896
+ });
897
+ return { imported, skipped, tableCounts: counts, failed: false };
898
+ } finally {
899
+ database.close();
900
+ }
901
+ } catch (error) {
902
+ const message = error instanceof Error ? error.message : String(error);
903
+ if (message.includes("newer than this Issue Flow supports") || message.includes("SQLite storage requires Node.js")) {
904
+ throw error;
905
+ }
906
+ const path = getDatabasePath(options);
907
+ if (existsSync2(path)) {
908
+ const failed = `${path}.failed-${Date.now()}`;
909
+ try {
910
+ await rename2(path, failed);
911
+ options.onWarning?.(
912
+ `SQLite import failed; preserved the database for diagnosis as ${basename(failed)}. Continuing with the intact JSON storage.`
913
+ );
914
+ } catch {
915
+ }
916
+ }
917
+ options.onWarning?.(`SQLite import skipped: ${message}`);
918
+ return { imported: 0, skipped: 0, tableCounts: counts, failed: true };
919
+ }
920
+ }
921
+
922
+ // src/storage/resolve.ts
923
+ import { stat as stat3 } from "node:fs/promises";
924
+ import { basename as basename2, join as join4, resolve } from "node:path";
925
+
926
+ // src/storage/artifact-storage.ts
927
+ import { readFile as readFile3, stat as stat2, writeFile } from "node:fs/promises";
928
+ import { join as join3 } from "node:path";
929
+ var WORKSPACE_STORAGE_DIR = ".issue-flow";
930
+ var WORKSPACE_IGNORE_BLOCK = [
931
+ "# Issue Flow operational storage (managed)",
932
+ "/issues/",
933
+ "/queues/",
934
+ "/issue-flow.db",
935
+ "/issue-flow.db-*",
936
+ "/run.lock",
937
+ "/providers.json",
938
+ "/metadata.json",
939
+ "/backups/",
940
+ "/.gitignore"
941
+ ].join("\n");
942
+ async function directoryExists(path) {
943
+ try {
944
+ return (await stat2(path)).isDirectory();
945
+ } catch {
946
+ return false;
947
+ }
948
+ }
949
+ function selectArtifactStorage(projectRoot, globalRoot, projectId, workspaceSelected) {
950
+ return workspaceSelected ? { storageMode: "workspace", projectDir: join3(projectRoot, WORKSPACE_STORAGE_DIR) } : { storageMode: "global", projectDir: join3(globalRoot, "projects", projectId) };
951
+ }
952
+ async function ensureWorkspaceStorageIgnored(projectRoot) {
953
+ const path = join3(projectRoot, WORKSPACE_STORAGE_DIR, ".gitignore");
954
+ let current = "";
955
+ try {
956
+ current = await readFile3(path, "utf8");
957
+ } catch (error) {
958
+ if (error.code !== "ENOENT") throw error;
959
+ }
960
+ if (WORKSPACE_IGNORE_BLOCK.split("\n").slice(1).every((line) => current.includes(line)))
961
+ return;
962
+ const prefix = current.length === 0 ? "" : `${current.trimEnd()}
963
+
964
+ `;
965
+ await writeFile(path, `${prefix}${WORKSPACE_IGNORE_BLOCK}
966
+ `, "utf8");
967
+ }
968
+
969
+ // src/storage/resolve.ts
970
+ async function loadStorageResolutionConfig(projectRoot, options) {
971
+ const global = await loadGlobalConfig({ env: options.env, warn: options.notice ?? printInfo });
972
+ const project = await readProjectConfigFile(projectRoot, options.notice ?? printInfo);
973
+ const parsedProject = storageConfigInputSchema.safeParse(project?.storage);
974
+ if (!parsedProject.success && project?.storage !== void 0) {
975
+ (options.notice ?? printInfo)(
976
+ `Ignoring "storage" key of ${PROJECT_CONFIG_FILENAME}: ${parsedProject.error.issues[0]?.message ?? "invalid value"}.`
977
+ );
978
+ }
979
+ return {
980
+ driver: parsedProject.success ? parsedProject.data.driver ?? global.storage?.driver ?? "sqlite" : global.storage?.driver ?? "sqlite",
981
+ backupRetention: (parsedProject.success ? parsedProject.data.backupRetention : void 0) ?? global.storage?.backupRetention,
982
+ retention: (parsedProject.success ? parsedProject.data.retention : void 0) ?? global.storage?.retention
983
+ };
984
+ }
985
+ var projectCache = /* @__PURE__ */ new Map();
986
+ var checkedIssues = /* @__PURE__ */ new Set();
987
+ function resetStorageResolutionCache() {
988
+ projectCache.clear();
989
+ checkedIssues.clear();
990
+ resetPlanRepositories();
991
+ resetProjectDatabaseOptions();
992
+ }
993
+ async function pathExists(path) {
994
+ try {
995
+ await stat3(path);
996
+ return true;
997
+ } catch {
998
+ return false;
999
+ }
1000
+ }
1001
+ function databaseOptionsFor(projectRoot, storageMode, options) {
1002
+ if (storageMode === "global") return options.env === void 0 ? {} : { env: options.env };
1003
+ return {
1004
+ env: {
1005
+ ...options.env ?? process.env,
1006
+ [GLOBAL_ROOT_ENV]: join4(projectRoot, WORKSPACE_STORAGE_DIR)
1007
+ }
1008
+ };
1009
+ }
1010
+ function announceMigration(result, notice = printInfo) {
1011
+ if (result.copied.length === 0) return;
1012
+ const count = result.copied.length;
1013
+ const target = join4(result.globalDir, ISSUES_DIR_NAME);
1014
+ notice(`Migrated ${count} file${count === 1 ? "" : "s"} from ${result.legacyDir} to ${target}`);
1015
+ notice(
1016
+ `The legacy directory was not modified or removed \u2014 ${result.legacyDir} is kept as-is, read-only, for compatibility.`
1017
+ );
1018
+ }
1019
+ async function resolveProject(projectRoot, options) {
1020
+ const status = await resolveStorageMode(projectRoot, options);
1021
+ const storage = await loadStorageResolutionConfig(projectRoot, options);
1022
+ const workspaceIssuesDir = join4(projectRoot, WORKSPACE_STORAGE_DIR, ISSUES_DIR_NAME);
1023
+ const selected = selectArtifactStorage(
1024
+ projectRoot,
1025
+ getGlobalRoot(options),
1026
+ status.projectId,
1027
+ await directoryExists(workspaceIssuesDir)
1028
+ );
1029
+ const { storageMode, projectDir } = selected;
1030
+ const databaseOptions = databaseOptionsFor(projectRoot, storageMode, options);
1031
+ registerProjectDatabaseOptions(status.projectId, databaseOptions);
1032
+ if (storageMode === "workspace") {
1033
+ await ensureWorkspaceStorageIgnored(projectRoot);
1034
+ } else if (status.mode === "needs-migration") {
1035
+ announceMigration(
1036
+ await migrateLegacyStorage(projectRoot, options),
1037
+ options.notice ?? printInfo
1038
+ );
1039
+ }
1040
+ const imported = storage.driver === "sqlite" && !await pathExists(join4(projectDir, RUN_LOCK_FILENAME)) ? await importProjectArtifacts({
1041
+ ...databaseOptions,
1042
+ projectId: status.projectId,
1043
+ projectDir,
1044
+ projectRoot,
1045
+ remoteUrl: status.remoteUrl,
1046
+ ...(storage.retention?.backups ?? storage.backupRetention) === void 0 ? {} : { backupRetention: storage.retention?.backups ?? storage.backupRetention },
1047
+ ...storage.retention === void 0 ? {} : { retention: storage.retention },
1048
+ onWarning: options.notice ?? printInfo
1049
+ }) : null;
1050
+ if (imported !== null && imported.imported > 0) {
1051
+ const counts = Object.entries(imported.tableCounts).filter(([, count]) => count > 0).map(([table, count]) => `${table}: ${count}`).join(", ");
1052
+ (options.notice ?? printInfo)(
1053
+ `Imported ${imported.imported} structured artifact${imported.imported === 1 ? "" : "s"} from ${projectDir} into ${getDatabasePath(databaseOptions)} (${counts || "no rows"}). No source artifacts were removed.`
1054
+ );
1055
+ }
1056
+ return {
1057
+ projectId: status.projectId,
1058
+ legacyDir: status.legacyDir,
1059
+ projectDir,
1060
+ storageMode,
1061
+ databaseOptions,
1062
+ // An import failure is a successful *resolution* through the JSON
1063
+ // compatibility path. Do not subsequently register a SQLite repository,
1064
+ // or telemetry would create a fresh empty database and hide the fallback.
1065
+ driver: imported?.failed === true ? "json" : storage.driver,
1066
+ retention: storage.retention
1067
+ };
1068
+ }
1069
+ function getProjectResolution(projectRoot, options) {
1070
+ const key = `${getGlobalRoot(options)}::${projectRoot}`;
1071
+ const cached = projectCache.get(key);
1072
+ if (cached) return cached;
1073
+ const pending = resolveProject(projectRoot, options).catch((err) => {
1074
+ projectCache.delete(key);
1075
+ throw err;
1076
+ });
1077
+ projectCache.set(key, pending);
1078
+ return pending;
1079
+ }
1080
+ async function resolveProjectPaths(options = {}) {
1081
+ const { projectRoot: projectRootOption, ...rootOptions } = options;
1082
+ const projectRoot = resolve(projectRootOption ?? await getProjectRoot());
1083
+ const project = await getProjectResolution(projectRoot, rootOptions);
1084
+ const projectDir = project.projectDir;
1085
+ const providersHealthFile = join4(projectDir, PROVIDERS_HEALTH_FILENAME);
1086
+ if (project.driver === "sqlite") {
1087
+ registerStorageProjections({
1088
+ context: {
1089
+ tasksPath: "",
1090
+ projectId: project.projectId,
1091
+ issueId: "",
1092
+ projectRoot,
1093
+ databaseOptions: project.databaseOptions,
1094
+ retention: project.retention
1095
+ },
1096
+ providersHealthFile
1097
+ });
1098
+ }
1099
+ return {
1100
+ projectId: project.projectId,
1101
+ storageDriver: project.driver,
1102
+ storageMode: project.storageMode,
1103
+ databaseOptions: project.databaseOptions,
1104
+ projectDir,
1105
+ issuesDir: join4(projectDir, ISSUES_DIR_NAME),
1106
+ runLockFile: join4(projectDir, RUN_LOCK_FILENAME),
1107
+ providersHealthFile
1108
+ };
1109
+ }
1110
+ async function resolveQueuePaths(queueId, options = {}) {
1111
+ const { projectRoot: projectRootOption, ...rootOptions } = options;
1112
+ const projectRoot = resolve(projectRootOption ?? await getProjectRoot());
1113
+ const project = await getProjectResolution(projectRoot, rootOptions);
1114
+ const paths = getQueuePathsAt(project.projectDir, queueId);
1115
+ if (project.driver === "sqlite") {
1116
+ registerQueueRepository({
1117
+ planFile: paths.planFile,
1118
+ projectId: project.projectId,
1119
+ projectRoot,
1120
+ databaseOptions: project.databaseOptions,
1121
+ retention: project.retention
1122
+ });
1123
+ }
1124
+ return paths;
1125
+ }
1126
+ async function resolveIssuePaths(issueNumber, options = {}) {
1127
+ const { projectRoot: projectRootOption, ...rootOptions } = options;
1128
+ const projectRoot = resolve(projectRootOption ?? await getProjectRoot());
1129
+ const project = await getProjectResolution(projectRoot, rootOptions);
1130
+ const paths = getIssuePathsAt(project.projectDir, issueNumber);
1131
+ if (!checkedIssues.has(paths.issueDir)) {
1132
+ const legacyIssueDir = join4(project.legacyDir, basename2(paths.issueDir));
1133
+ if (project.storageMode === "global" && !await directoryExists(paths.issueDir) && await directoryExists(legacyIssueDir)) {
1134
+ announceMigration(await migrateLegacyStorage(projectRoot, rootOptions));
1135
+ }
1136
+ checkedIssues.add(paths.issueDir);
1137
+ }
1138
+ if (project.driver === "sqlite") {
1139
+ const context = {
1140
+ tasksPath: paths.tasksFile,
1141
+ projectId: project.projectId,
1142
+ issueId: basename2(paths.issueDir),
1143
+ projectRoot,
1144
+ databaseOptions: project.databaseOptions,
1145
+ retention: project.retention
1146
+ };
1147
+ registerPlanRepository(context);
1148
+ registerStorageProjections({
1149
+ context,
1150
+ verifyFile: paths.verifyFile,
1151
+ lastBranchFile: paths.lastBranchFile,
1152
+ providersHealthFile: join4(project.projectDir, PROVIDERS_HEALTH_FILENAME)
1153
+ });
1154
+ bindTelemetry({ tasksPath: paths.tasksFile });
1155
+ } else {
1156
+ bindTelemetry(null);
1157
+ }
1158
+ return paths;
1159
+ }
1160
+
1161
+ export {
1162
+ setVerbose,
1163
+ isVerbose,
1164
+ setOutputCallback,
1165
+ getOutputCallback,
1166
+ setStoryUpdateCallback,
1167
+ getStoryUpdateCallback,
1168
+ setStoryStageCallback,
1169
+ getStoryStageCallback,
1170
+ setActivityCallback,
1171
+ getActivityCallback,
1172
+ setGlobalTimeout,
1173
+ getGlobalTimeout,
1174
+ setInactivityTimeout,
1175
+ getInactivityTimeout,
1176
+ useUnicode,
1177
+ useColor,
1178
+ getIcons,
1179
+ getTermWidth,
1180
+ printSuccess,
1181
+ printError,
1182
+ printWarning,
1183
+ printRetry,
1184
+ printInfo,
1185
+ createSpinner,
1186
+ formatDuration,
1187
+ ElapsedTimer,
1188
+ PROJECT_CONFIG_FILENAME,
1189
+ readProjectConfigFile,
1190
+ GLOBAL_CONFIG_FILENAME,
1191
+ loadGlobalConfig,
1192
+ JournalPublisher,
1193
+ parseJournal,
1194
+ MultiPublisher,
1195
+ importProjectArtifacts,
1196
+ resetStorageResolutionCache,
1197
+ resolveProjectPaths,
1198
+ resolveQueuePaths,
1199
+ resolveIssuePaths
1200
+ };
1201
+ //# sourceMappingURL=chunk-J6VJ5M2U.js.map