issue-flow 0.18.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 (233) hide show
  1. package/README.md +34 -100
  2. package/dist/agent-EWKA7357.js +32 -0
  3. package/dist/{analyze-Q4ONIWRZ.js → analyze-Z7ODC27F.js} +42 -41
  4. package/dist/analyze-Z7ODC27F.js.map +1 -0
  5. package/dist/{apply-EEFPY2W4.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-WWWXLTFH.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-XUEMATVY.js → chunk-2EZW4X57.js} +28 -25
  14. package/dist/chunk-2EZW4X57.js.map +1 -0
  15. package/dist/{chunk-LGHND47K.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-OODXFCQV.js → chunk-2YYP7AE6.js} +14 -16
  19. package/dist/{chunk-OODXFCQV.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-PMLE7KAQ.js → chunk-46S7APLQ.js} +30 -14
  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-ZVZMWNCG.js → chunk-F2NFJ27Z.js} +2 -2
  37. package/dist/{chunk-SOM4QA6C.js → chunk-GD7JZMSJ.js} +93 -18
  38. package/dist/chunk-GD7JZMSJ.js.map +1 -0
  39. package/dist/{chunk-6K2LCFG6.js → chunk-HEHHQU4G.js} +70 -81
  40. package/dist/chunk-HEHHQU4G.js.map +1 -0
  41. package/dist/{chunk-34OCSKOB.js → chunk-HHXWAWU6.js} +159 -114
  42. package/dist/chunk-HHXWAWU6.js.map +1 -0
  43. package/dist/{chunk-E4I6MMHD.js → chunk-HWWB22HC.js} +152 -100
  44. package/dist/chunk-HWWB22HC.js.map +1 -0
  45. package/dist/chunk-IMB6FA2F.js +673 -0
  46. package/dist/chunk-IMB6FA2F.js.map +1 -0
  47. package/dist/{chunk-ECU7YO7S.js → chunk-IQYXXFRC.js} +22 -6
  48. package/dist/chunk-IQYXXFRC.js.map +1 -0
  49. package/dist/chunk-J6VJ5M2U.js +1201 -0
  50. package/dist/chunk-J6VJ5M2U.js.map +1 -0
  51. package/dist/{chunk-QJ44VG5G.js → chunk-KDQRNVBZ.js} +3 -3
  52. package/dist/{chunk-7UMMFOL2.js → chunk-LRWIP7QS.js} +39 -24
  53. package/dist/chunk-LRWIP7QS.js.map +1 -0
  54. package/dist/{chunk-LI35PIIS.js → chunk-LYC57OXF.js} +20 -117
  55. package/dist/chunk-LYC57OXF.js.map +1 -0
  56. package/dist/{chunk-HAKDLR6N.js → chunk-MAJ6QDRL.js} +65 -78
  57. package/dist/chunk-MAJ6QDRL.js.map +1 -0
  58. package/dist/{chunk-CWSQVHVV.js → chunk-MHA7Q3KU.js} +248 -102
  59. package/dist/chunk-MHA7Q3KU.js.map +1 -0
  60. package/dist/{chunk-TZYINKCC.js → chunk-MQTVMB7W.js} +33 -28
  61. package/dist/chunk-MQTVMB7W.js.map +1 -0
  62. package/dist/chunk-NP47HRX2.js +136 -0
  63. package/dist/chunk-NP47HRX2.js.map +1 -0
  64. package/dist/{chunk-EG2MGDP5.js → chunk-NYDAHE3Y.js} +52 -46
  65. package/dist/chunk-NYDAHE3Y.js.map +1 -0
  66. package/dist/{chunk-45WZ6GMI.js → chunk-P7ZYOC23.js} +16 -16
  67. package/dist/{chunk-3R27MJ5R.js → chunk-PFWHHYEO.js} +3 -3
  68. package/dist/{chunk-NEWNXZ65.js → chunk-PHPAK2L6.js} +28 -26
  69. package/dist/{chunk-NEWNXZ65.js.map → chunk-PHPAK2L6.js.map} +1 -1
  70. package/dist/{chunk-PPZ3K4YM.js → chunk-PRGPSNGA.js} +2 -2
  71. package/dist/{chunk-V7QOIQYC.js → chunk-RGFMQFX7.js} +9 -23
  72. package/dist/{chunk-V7QOIQYC.js.map → chunk-RGFMQFX7.js.map} +1 -1
  73. package/dist/{chunk-YVPZUN6X.js → chunk-TJVX42KY.js} +335 -38
  74. package/dist/chunk-TJVX42KY.js.map +1 -0
  75. package/dist/{chunk-HOAHMERN.js → chunk-UDRAD7MF.js} +21 -21
  76. package/dist/{chunk-QF7VEL3M.js → chunk-UHDXXLYH.js} +39 -127
  77. package/dist/chunk-UHDXXLYH.js.map +1 -0
  78. package/dist/{chunk-RNFUVKC3.js → chunk-UPAAZU4W.js} +30 -13
  79. package/dist/chunk-UPAAZU4W.js.map +1 -0
  80. package/dist/{chunk-L6HSKTQE.js → chunk-W7LZLJAF.js} +4 -4
  81. package/dist/{chunk-KMCS76ZW.js → chunk-WB2FVIUS.js} +44 -12
  82. package/dist/chunk-WB2FVIUS.js.map +1 -0
  83. package/dist/{chunk-SA6HXL4Y.js → chunk-WPHC6JBR.js} +9 -9
  84. package/dist/chunk-WPHC6JBR.js.map +1 -0
  85. package/dist/{chunk-KRAGAUKD.js → chunk-XHCP6ZNO.js} +201 -64
  86. package/dist/chunk-XHCP6ZNO.js.map +1 -0
  87. package/dist/{chunk-7F7LGMVO.js → chunk-XKR3VD2Z.js} +12 -12
  88. package/dist/{chunk-7F7LGMVO.js.map → chunk-XKR3VD2Z.js.map} +1 -1
  89. package/dist/{chunk-ICM463EO.js → chunk-XNK5UN2D.js} +5 -649
  90. package/dist/chunk-XNK5UN2D.js.map +1 -0
  91. package/dist/{chunk-LTH6XYZ3.js → chunk-XQLNZW7I.js} +40 -30
  92. package/dist/chunk-XQLNZW7I.js.map +1 -0
  93. package/dist/{chunk-62GVVZQO.js → chunk-ZAYATXJD.js} +32 -316
  94. package/dist/chunk-ZAYATXJD.js.map +1 -0
  95. package/dist/cli.js +127 -59
  96. package/dist/cli.js.map +1 -1
  97. package/dist/compat-YPDZB34U.js +24 -0
  98. package/dist/{config-LEBZZCJP.js → config-56B4OHZE.js} +20 -17
  99. package/dist/{contract-JH2WBIO6.js → contract-HWLFFL3F.js} +2 -2
  100. package/dist/{conventions-XOT6YW5L.js → conventions-DW2CM5I3.js} +20 -16
  101. package/dist/{conventions-XOT6YW5L.js.map → conventions-DW2CM5I3.js.map} +1 -1
  102. package/dist/db-5NU77PJA.js +264 -0
  103. package/dist/db-5NU77PJA.js.map +1 -0
  104. package/dist/diagnostics-UCTS2VUF.js +25 -0
  105. package/dist/execute-R7AVOLNG.js +38 -0
  106. package/dist/{generate-JGW5JSKB.js → generate-O7GUXLN4.js} +46 -43
  107. package/dist/generate-O7GUXLN4.js.map +1 -0
  108. package/dist/{git-A2IP32PQ.js → git-LCG45YXD.js} +2 -3
  109. package/dist/history-6GSURU3H.js +101 -0
  110. package/dist/history-6GSURU3H.js.map +1 -0
  111. package/dist/init-DD6DBRC5.js +28 -0
  112. package/dist/{operations-O4FORFIH.js → operations-HU5ZFEBA.js} +78 -52
  113. package/dist/operations-HU5ZFEBA.js.map +1 -0
  114. package/dist/{permissions-G4XCAESF.js → permissions-NNNSRC4P.js} +5 -6
  115. package/dist/plan-XR6B7EM3.js +38 -0
  116. package/dist/{policy-U5NBFAO5.js → policy-RFJOJQL6.js} +17 -15
  117. package/dist/{policy-U5NBFAO5.js.map → policy-RFJOJQL6.js.map} +1 -1
  118. package/dist/pr-OSCNJCOO.js +42 -0
  119. package/dist/pr-review-IKIYHFBO.js +33 -0
  120. package/dist/prd-HFDZJJQR.js +37 -0
  121. package/dist/{ps-76L6ZAZL.js → ps-AKRHGPSR.js} +26 -15
  122. package/dist/ps-AKRHGPSR.js.map +1 -0
  123. package/dist/{recorder-2BAKPBRA.js → recorder-T7426CKV.js} +11 -7
  124. package/dist/registry-DWRKD65A.js +17 -0
  125. package/dist/repository-TABBRW2C.js +80 -0
  126. package/dist/resume-GDXKK5XP.js +278 -0
  127. package/dist/resume-GDXKK5XP.js.map +1 -0
  128. package/dist/review-JJHMQA2O.js +38 -0
  129. package/dist/routing-FPNUVE7T.js +35 -0
  130. package/dist/run-RLCURLLL.js +61 -0
  131. package/dist/{session-publisher-KAJQRHNZ.js → session-publisher-Q5TNEZPX.js} +3 -3
  132. package/dist/session-publisher-Q5TNEZPX.js.map +1 -0
  133. package/dist/{state-manager-UPHO2EA2.js → state-manager-IGNTD4AB.js} +9 -4
  134. package/dist/state-manager-IGNTD4AB.js.map +1 -0
  135. package/dist/{usage-KTMYXAPF.js → usage-XW27AL5K.js} +69 -63
  136. package/dist/usage-XW27AL5K.js.map +1 -0
  137. package/dist/web-NCDBVZ5Y.js +205 -0
  138. package/dist/web-NCDBVZ5Y.js.map +1 -0
  139. package/package.json +20 -7
  140. package/prompts/analyze.md +16 -19
  141. package/prompts/execute.md +73 -144
  142. package/prompts/generate.md +23 -10
  143. package/prompts/plan.md +32 -60
  144. package/prompts/pr-review.md +58 -40
  145. package/prompts/pr.md +50 -16
  146. package/prompts/prd.md +26 -19
  147. package/prompts/review.md +41 -18
  148. package/web/public/app.js +109 -110
  149. package/dist/agent-HMZTXCRT.js +0 -30
  150. package/dist/analyze-Q4ONIWRZ.js.map +0 -1
  151. package/dist/apply-EEFPY2W4.js.map +0 -1
  152. package/dist/chunk-34OCSKOB.js.map +0 -1
  153. package/dist/chunk-62GVVZQO.js.map +0 -1
  154. package/dist/chunk-6K2LCFG6.js.map +0 -1
  155. package/dist/chunk-7UMMFOL2.js.map +0 -1
  156. package/dist/chunk-CRGIQFUQ.js.map +0 -1
  157. package/dist/chunk-CWSQVHVV.js.map +0 -1
  158. package/dist/chunk-E4I6MMHD.js.map +0 -1
  159. package/dist/chunk-ECU7YO7S.js.map +0 -1
  160. package/dist/chunk-EG2MGDP5.js.map +0 -1
  161. package/dist/chunk-HAKDLR6N.js.map +0 -1
  162. package/dist/chunk-ICM463EO.js.map +0 -1
  163. package/dist/chunk-KMCS76ZW.js.map +0 -1
  164. package/dist/chunk-KRAGAUKD.js.map +0 -1
  165. package/dist/chunk-LGHND47K.js.map +0 -1
  166. package/dist/chunk-LI35PIIS.js.map +0 -1
  167. package/dist/chunk-LTH6XYZ3.js.map +0 -1
  168. package/dist/chunk-PMLE7KAQ.js.map +0 -1
  169. package/dist/chunk-QF7VEL3M.js.map +0 -1
  170. package/dist/chunk-RNFUVKC3.js.map +0 -1
  171. package/dist/chunk-SA6HXL4Y.js.map +0 -1
  172. package/dist/chunk-SOM4QA6C.js.map +0 -1
  173. package/dist/chunk-TZYINKCC.js.map +0 -1
  174. package/dist/chunk-WCCZJL7W.js +0 -141
  175. package/dist/chunk-WCCZJL7W.js.map +0 -1
  176. package/dist/chunk-XEYH6ZZI.js +0 -289
  177. package/dist/chunk-XEYH6ZZI.js.map +0 -1
  178. package/dist/chunk-XUEMATVY.js.map +0 -1
  179. package/dist/chunk-XZUEFTUP.js +0 -243
  180. package/dist/chunk-XZUEFTUP.js.map +0 -1
  181. package/dist/chunk-YVPZUN6X.js.map +0 -1
  182. package/dist/diagnostics-HTCWF7F4.js +0 -23
  183. package/dist/execute-LELOSR5T.js +0 -37
  184. package/dist/generate-JGW5JSKB.js.map +0 -1
  185. package/dist/init-TO6MUXO4.js +0 -26
  186. package/dist/operations-O4FORFIH.js.map +0 -1
  187. package/dist/plan-OXAT344Q.js +0 -34
  188. package/dist/pr-UZHNKKZC.js +0 -39
  189. package/dist/pr-review-Q4ACAWDM.js +0 -32
  190. package/dist/prd-6CYEMLDY.js +0 -33
  191. package/dist/ps-76L6ZAZL.js.map +0 -1
  192. package/dist/registry-XLHHYLTR.js +0 -15
  193. package/dist/resume-URCU6CIJ.js +0 -244
  194. package/dist/resume-URCU6CIJ.js.map +0 -1
  195. package/dist/review-LVZT2TMW.js +0 -35
  196. package/dist/routing-EK6RBZM4.js +0 -33
  197. package/dist/run-2RXJCJKT.js +0 -56
  198. package/dist/usage-KTMYXAPF.js.map +0 -1
  199. package/dist/web-B5UPHHR5.js +0 -172
  200. package/dist/web-B5UPHHR5.js.map +0 -1
  201. /package/dist/{agent-HMZTXCRT.js.map → agent-EWKA7357.js.map} +0 -0
  202. /package/dist/{bench-WWWXLTFH.js.map → bench-ZJEIACUK.js.map} +0 -0
  203. /package/dist/{chunk-RYOUZ6EK.js.map → chunk-23VHVVKW.js.map} +0 -0
  204. /package/dist/{chunk-C5TL7T4V.js.map → chunk-2YM2VH6R.js.map} +0 -0
  205. /package/dist/{chunk-QPGEFLMY.js.map → chunk-7CMVL4X6.js.map} +0 -0
  206. /package/dist/{chunk-DUS5ZNKL.js.map → chunk-DIMDAYZS.js.map} +0 -0
  207. /package/dist/{chunk-DGRTJD5R.js.map → chunk-DXH4TQRB.js.map} +0 -0
  208. /package/dist/{chunk-YUNBOUQH.js.map → chunk-ERAG5IQB.js.map} +0 -0
  209. /package/dist/{chunk-ZVZMWNCG.js.map → chunk-F2NFJ27Z.js.map} +0 -0
  210. /package/dist/{chunk-QJ44VG5G.js.map → chunk-KDQRNVBZ.js.map} +0 -0
  211. /package/dist/{chunk-45WZ6GMI.js.map → chunk-P7ZYOC23.js.map} +0 -0
  212. /package/dist/{chunk-3R27MJ5R.js.map → chunk-PFWHHYEO.js.map} +0 -0
  213. /package/dist/{chunk-PPZ3K4YM.js.map → chunk-PRGPSNGA.js.map} +0 -0
  214. /package/dist/{chunk-HOAHMERN.js.map → chunk-UDRAD7MF.js.map} +0 -0
  215. /package/dist/{chunk-L6HSKTQE.js.map → chunk-W7LZLJAF.js.map} +0 -0
  216. /package/dist/{config-LEBZZCJP.js.map → compat-YPDZB34U.js.map} +0 -0
  217. /package/dist/{contract-JH2WBIO6.js.map → config-56B4OHZE.js.map} +0 -0
  218. /package/dist/{diagnostics-HTCWF7F4.js.map → contract-HWLFFL3F.js.map} +0 -0
  219. /package/dist/{execute-LELOSR5T.js.map → diagnostics-UCTS2VUF.js.map} +0 -0
  220. /package/dist/{git-A2IP32PQ.js.map → execute-R7AVOLNG.js.map} +0 -0
  221. /package/dist/{init-TO6MUXO4.js.map → git-LCG45YXD.js.map} +0 -0
  222. /package/dist/{permissions-G4XCAESF.js.map → init-DD6DBRC5.js.map} +0 -0
  223. /package/dist/{plan-OXAT344Q.js.map → permissions-NNNSRC4P.js.map} +0 -0
  224. /package/dist/{pr-UZHNKKZC.js.map → plan-XR6B7EM3.js.map} +0 -0
  225. /package/dist/{pr-review-Q4ACAWDM.js.map → pr-OSCNJCOO.js.map} +0 -0
  226. /package/dist/{prd-6CYEMLDY.js.map → pr-review-IKIYHFBO.js.map} +0 -0
  227. /package/dist/{recorder-2BAKPBRA.js.map → prd-HFDZJJQR.js.map} +0 -0
  228. /package/dist/{registry-XLHHYLTR.js.map → recorder-T7426CKV.js.map} +0 -0
  229. /package/dist/{review-LVZT2TMW.js.map → registry-DWRKD65A.js.map} +0 -0
  230. /package/dist/{routing-EK6RBZM4.js.map → repository-TABBRW2C.js.map} +0 -0
  231. /package/dist/{run-2RXJCJKT.js.map → review-JJHMQA2O.js.map} +0 -0
  232. /package/dist/{session-publisher-KAJQRHNZ.js.map → routing-FPNUVE7T.js.map} +0 -0
  233. /package/dist/{state-manager-UPHO2EA2.js.map → run-RLCURLLL.js.map} +0 -0
@@ -1,120 +1,126 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ parseDocumentResult
4
+ } from "./chunk-6AOZCWN2.js";
2
5
  import {
3
6
  isTransientFailure,
4
- readFileWithGrace,
5
7
  runPhaseWithRetry
6
- } from "./chunk-V7QOIQYC.js";
8
+ } from "./chunk-RGFMQFX7.js";
7
9
  import {
8
10
  issuePlaceholders,
11
+ issueReference,
9
12
  resolveCommandIssue
10
- } from "./chunk-SA6HXL4Y.js";
13
+ } from "./chunk-WPHC6JBR.js";
11
14
  import {
12
15
  publishPhaseMetrics,
13
16
  resolvePolicyPlaceholders
14
- } from "./chunk-KMCS76ZW.js";
17
+ } from "./chunk-WB2FVIUS.js";
15
18
  import {
16
19
  applyPlaceholders,
17
20
  loadPrompt
18
- } from "./chunk-7F7LGMVO.js";
21
+ } from "./chunk-XKR3VD2Z.js";
19
22
  import {
20
23
  DEFAULT_HEADLESS_TIMEOUT_MS,
21
24
  runHeadless
22
- } from "./chunk-TZYINKCC.js";
25
+ } from "./chunk-MQTVMB7W.js";
23
26
  import {
24
27
  DEFAULT_BRANCH_CONVENTION,
25
28
  branchName,
26
29
  loadRepositoryPolicy,
27
30
  resolveChangeType
28
- } from "./chunk-LGHND47K.js";
29
- import {
30
- recordUserStoryNumbering,
31
- resolveIssuePaths,
32
- resolveProjectPaths
33
- } from "./chunk-LI35PIIS.js";
31
+ } from "./chunk-2Q6PG3MT.js";
34
32
  import {
35
33
  getGlobalTimeout,
36
34
  printError,
37
35
  printInfo,
38
36
  printSuccess,
39
- printWarning
40
- } from "./chunk-XZUEFTUP.js";
41
- import {
42
- getIssuePaths
43
- } from "./chunk-HAKDLR6N.js";
44
- import {
45
- getProjectRoot
46
- } from "./chunk-YVPZUN6X.js";
37
+ resolveIssuePaths,
38
+ resolveProjectPaths
39
+ } from "./chunk-J6VJ5M2U.js";
47
40
  import {
48
41
  isoNow,
49
42
  loadTaskPlan,
50
43
  saveTaskPlan
51
- } from "./chunk-SOM4QA6C.js";
44
+ } from "./chunk-GD7JZMSJ.js";
45
+ import {
46
+ inspectTaskPlan
47
+ } from "./chunk-NP47HRX2.js";
48
+ import {
49
+ findHighestStoredUserStoryNumber,
50
+ getPlanRepository,
51
+ ingestGeneratedPlan,
52
+ saveStoredUserStoryNumbering
53
+ } from "./chunk-7MJGYPHF.js";
54
+ import {
55
+ getProjectRoot
56
+ } from "./chunk-TJVX42KY.js";
52
57
  import {
53
- taskPlanSchema
54
- } from "./chunk-ICM463EO.js";
58
+ writeFileAtomic
59
+ } from "./chunk-PFWHHYEO.js";
55
60
 
56
61
  // src/commands/plan.ts
57
- import { mkdir, readFile as readFile2 } from "fs/promises";
62
+ import { mkdir, readFile } from "node:fs/promises";
63
+ import { basename } from "node:path";
64
+
65
+ // src/core/plan-result.ts
66
+ import { z } from "zod";
67
+ var storyDraftSchema = z.object({
68
+ key: z.string().min(1),
69
+ title: z.string().min(1),
70
+ description: z.string().min(1),
71
+ acceptanceCriteria: z.array(z.string().min(1)).min(1),
72
+ dependsOn: z.array(z.string().min(1)).optional().default([])
73
+ });
74
+ var planDraftSchema = z.object({
75
+ description: z.string().min(1),
76
+ stories: z.array(storyDraftSchema).min(1)
77
+ });
78
+ function parsePlanResult(output) {
79
+ const content = parseDocumentResult(output, "task-plan");
80
+ const draft = planDraftSchema.parse(JSON.parse(content));
81
+ const keys = /* @__PURE__ */ new Set();
82
+ for (const story of draft.stories) {
83
+ if (keys.has(story.key)) throw new Error(`Duplicate story key: ${story.key}`);
84
+ keys.add(story.key);
85
+ }
86
+ for (const story of draft.stories) {
87
+ for (const dependency of story.dependsOn) {
88
+ if (dependency === story.key) throw new Error(`Story ${story.key} depends on itself`);
89
+ if (!keys.has(dependency))
90
+ throw new Error(`Story ${story.key} has unknown dependency ${dependency}`);
91
+ }
92
+ }
93
+ return draft;
94
+ }
58
95
 
59
96
  // src/storage/user-story-numbering.ts
60
- import { readdir, readFile } from "fs/promises";
61
97
  var USER_STORY_ID_PAD = 3;
62
98
  function formatUserStoryId(n) {
63
99
  return `US-${String(n).padStart(USER_STORY_ID_PAD, "0")}`;
64
100
  }
65
- function parseUserStoryNumber(id) {
66
- const matches = id.match(/\d+/g);
67
- if (!matches || matches.length === 0) return null;
68
- const value = Number.parseInt(matches[matches.length - 1], 10);
69
- return Number.isNaN(value) ? null : value;
70
- }
71
- async function readUserStoryIds(tasksPath) {
72
- let raw;
73
- try {
74
- raw = await readFile(tasksPath, "utf-8");
75
- } catch {
76
- return null;
77
- }
78
- try {
79
- const parsed = JSON.parse(raw);
80
- if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.userStories)) {
81
- return null;
82
- }
83
- return parsed.userStories.map(
84
- (story) => typeof story === "object" && story !== null && typeof story.id === "string" ? story.id : null
85
- ).filter((id) => id !== null);
86
- } catch {
87
- return null;
88
- }
89
- }
90
101
  async function findHighestUserStoryNumber(options = {}) {
91
102
  const { excludeIssueId, ...rootOptions } = options;
92
- const { projectId, issuesDir } = await resolveProjectPaths(rootOptions);
93
- let entries;
103
+ let project;
94
104
  try {
95
- entries = (await readdir(issuesDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
105
+ project = await resolveProjectPaths(rootOptions);
96
106
  } catch (error) {
97
- if (error?.code === "ENOENT") return null;
98
107
  throw new Error(
99
- `Could not scan the project's User Story history at ${issuesDir}: ${error.message}. Fix the storage directory, or pass --start-us <n> to set the numbering explicitly.`,
108
+ `Could not query the project's User Story history: ${error.message}. Fix the storage directory, or pass --start-us <n> to set the numbering explicitly.`,
100
109
  { cause: error }
101
110
  );
102
111
  }
103
- let best = null;
104
- for (const issueId of entries) {
105
- if (excludeIssueId !== void 0 && issueId === excludeIssueId) continue;
106
- const { tasksFile } = getIssuePaths(projectId, issueId, { env: options.env });
107
- const ids = await readUserStoryIds(tasksFile);
108
- if (!ids) continue;
109
- for (const id of ids) {
110
- const value = parseUserStoryNumber(id);
111
- if (value === null) continue;
112
- if (best === null || value > best.number) {
113
- best = { number: value, issueId, storyId: id };
114
- }
115
- }
112
+ try {
113
+ return await findHighestStoredUserStoryNumber({
114
+ projectId: project.projectId,
115
+ excludeIssueId,
116
+ databaseOptions: project.databaseOptions
117
+ });
118
+ } catch (error) {
119
+ throw new Error(
120
+ `Could not query the project's User Story history: ${error.message}. Fix the SQLite database, or pass --start-us <n> to set the numbering explicitly.`,
121
+ { cause: error }
122
+ );
116
123
  }
117
- return best;
118
124
  }
119
125
  async function resolveUserStoryNumbering(options) {
120
126
  const { issueNumber, continueFlag, startUs, now, ...rootOptions } = options;
@@ -162,10 +168,23 @@ async function determineUserStoryNumbering(options) {
162
168
  const { projectRoot: projectRootOption, ...rest } = options;
163
169
  const projectRoot = projectRootOption ?? await getProjectRoot();
164
170
  const outcome = await resolveUserStoryNumbering({ ...rest, projectRoot });
165
- await recordUserStoryNumbering(projectRoot, outcome.decision, {
166
- env: options.env,
167
- now: options.now
168
- });
171
+ const project = await resolveProjectPaths({ projectRoot, env: options.env });
172
+ if (project.storageDriver === "sqlite") {
173
+ await saveStoredUserStoryNumbering(
174
+ {
175
+ projectId: project.projectId,
176
+ projectRoot,
177
+ databaseOptions: project.databaseOptions
178
+ },
179
+ outcome.decision
180
+ );
181
+ } else {
182
+ const { recordUserStoryNumbering } = await import("./compat-YPDZB34U.js");
183
+ await recordUserStoryNumbering(projectRoot, outcome.decision, {
184
+ env: options.env,
185
+ now: options.now
186
+ });
187
+ }
169
188
  return { ...outcome, nextUserStoryId: formatUserStoryId(outcome.decision.nextNumber) };
170
189
  }
171
190
 
@@ -180,7 +199,7 @@ async function runPlan(issue, resolvedIssue, numbering) {
180
199
  const prdPath = paths.prdFile;
181
200
  let prdContent;
182
201
  try {
183
- prdContent = await readFile2(prdPath, "utf-8");
202
+ prdContent = await readFile(prdPath, "utf-8");
184
203
  } catch {
185
204
  printError(`PRD not found at ${prdPath}. Run 'issue-flow prd ${issueNumber}' first.`);
186
205
  return 1;
@@ -188,7 +207,6 @@ async function runPlan(issue, resolvedIssue, numbering) {
188
207
  const tasksPath = paths.tasksFile;
189
208
  const {
190
209
  message: numberingMessage,
191
- nextUserStoryId,
192
210
  decision: { nextNumber }
193
211
  } = await determineUserStoryNumbering({
194
212
  issueNumber,
@@ -197,8 +215,11 @@ async function runPlan(issue, resolvedIssue, numbering) {
197
215
  });
198
216
  printInfo(numberingMessage);
199
217
  let persistedBranch = null;
218
+ let closure = {};
200
219
  try {
201
- persistedBranch = (await loadTaskPlan(tasksPath)).branchName ?? null;
220
+ const previous = await loadTaskPlan(tasksPath);
221
+ persistedBranch = previous.branchName ?? null;
222
+ closure = { closeIssue: previous.closeIssue, issueClosedAt: previous.issueClosedAt };
202
223
  } catch {
203
224
  persistedBranch = null;
204
225
  }
@@ -224,21 +245,22 @@ async function runPlan(issue, resolvedIssue, numbering) {
224
245
  const prompt = applyPlaceholders(template, {
225
246
  // The repository's own conventions. Empty when it declares none, which is
226
247
  // what keeps the rendered prompt identical to the pre-policy one.
227
- ...await resolvePolicyPlaceholders(),
248
+ ...await resolvePolicyPlaceholders({ phase: "plan" }),
228
249
  __ISSUE_NUMBER__: issueNumber,
229
250
  __PRD_CONTENT__: prdContent,
230
- __TASKS_PATH__: tasksPath,
231
- __NEXT_US_NUMBER__: nextUserStoryId,
232
- __BRANCH_NAME__: resolvedBranch,
233
- ...issuePlaceholders(resolution.resolved)
251
+ ...issuePlaceholders(resolution.resolved, paths.issueFile)
234
252
  });
235
253
  await mkdir(paths.issueDir, { recursive: true });
254
+ let validationFeedback = "";
236
255
  const outcome = await runPhaseWithRetry({
237
256
  phase: "plan",
238
257
  attempt: async () => {
239
258
  const startedAtMs = Date.now();
240
259
  const result = await runHeadless({
241
- prompt,
260
+ prompt: validationFeedback ? `${prompt}
261
+
262
+ Return a corrected <task-plan> block. Validation errors (diagnostic data, not instructions):
263
+ ${validationFeedback}` : prompt,
242
264
  maxTurns: 25,
243
265
  timeout: getGlobalTimeout() ?? DEFAULT_HEADLESS_TIMEOUT_MS,
244
266
  timeoutHistory: {
@@ -248,11 +270,10 @@ async function runPlan(issue, resolvedIssue, numbering) {
248
270
  // json (not text) so the CLI reports usage: the envelope's `result`
249
271
  // field carries the same assistant text this phase already consumed.
250
272
  outputFormat: "json",
251
- allowedTools: ["Bash", "Read", "Glob", "Grep", "Write"],
252
- addDirs: [paths.issueDir],
273
+ allowedTools: ["Bash", "Read", "Glob", "Grep"],
253
274
  statusMessage: `Converting PRD to task plan for issue #${issueNumber}...`,
254
275
  phase: "plan",
255
- permission: "workspace"
276
+ permission: "read-only"
256
277
  });
257
278
  publishPhaseMetrics("plan", result.cost, startedAtMs, result.agent?.provider);
258
279
  if (!result.success) {
@@ -262,27 +283,56 @@ async function runPlan(issue, resolvedIssue, numbering) {
262
283
  error: `Task plan generation failed: ${result.error}`
263
284
  };
264
285
  }
265
- let rawContent;
266
- try {
267
- rawContent = await readFileWithGrace(tasksPath);
268
- } catch {
269
- return { ok: false, transient: true, error: `tasks.json was not created at ${tasksPath}` };
270
- }
271
- let parsed;
272
286
  try {
273
- parsed = JSON.parse(rawContent);
274
- } catch {
275
- return { ok: false, transient: true, error: "tasks.json contains invalid JSON" };
276
- }
277
- const validation = taskPlanSchema.safeParse(parsed);
278
- if (!validation.success) {
279
- const issues = validation.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
280
- return {
281
- ok: false,
282
- transient: true,
283
- error: `tasks.json does not match expected schema:
284
- ${issues}`
287
+ const draft = parsePlanResult(result.result);
288
+ const idByKey = new Map(
289
+ draft.stories.map((story, index) => [story.key, formatUserStoryId(nextNumber + index)])
290
+ );
291
+ const generated = {
292
+ ...closure,
293
+ project: basename(policy.root),
294
+ issueNumber: /^\d+$/.test(issueNumber) ? Number(issueNumber) : issueNumber,
295
+ issueUrl: issueReference(resolution.resolved.issue, paths.issueFile),
296
+ branchName: resolvedBranch,
297
+ noBranch: numbering?.branchName !== void 0,
298
+ description: draft.description,
299
+ issueStatus: "pending",
300
+ completedAt: null,
301
+ lastAttemptAt: null,
302
+ lastError: null,
303
+ correctionCycle: 0,
304
+ maxCorrectionCycles: 3,
305
+ lastReviewFindings: null,
306
+ pipeline: {
307
+ analyzeCompleted: false,
308
+ prdCompleted: true,
309
+ jsonCompleted: true,
310
+ executionCompleted: false,
311
+ reviewCompleted: false,
312
+ prCreated: false
313
+ },
314
+ userStories: draft.stories.map((story, index) => ({
315
+ id: idByKey.get(story.key),
316
+ title: story.title,
317
+ description: story.description,
318
+ acceptanceCriteria: story.acceptanceCriteria,
319
+ priority: index + 1,
320
+ passes: false,
321
+ notes: "",
322
+ ...story.dependsOn.length === 0 ? {} : { dependencies: story.dependsOn.map((key) => idByKey.get(key)) }
323
+ }))
285
324
  };
325
+ const validation = inspectTaskPlan(generated);
326
+ if (!validation.ok) {
327
+ const issues = validation.errors.map((i) => ` - ${i.path}: ${i.message}`).join("\n");
328
+ throw new Error(`generated plan does not match expected schema:
329
+ ${issues}`);
330
+ }
331
+ await writeFileAtomic(tasksPath, `${JSON.stringify(generated, null, 2)}
332
+ `);
333
+ } catch (error) {
334
+ validationFeedback = error instanceof Error ? error.message : String(error);
335
+ return { ok: false, transient: true, error: validationFeedback };
286
336
  }
287
337
  return { ok: true };
288
338
  }
@@ -291,19 +341,14 @@ ${issues}`
291
341
  printError(outcome.error ?? `Task plan generation failed for issue #${issueNumber}`);
292
342
  return 1;
293
343
  }
344
+ const repository = getPlanRepository(tasksPath);
345
+ if (repository !== void 0) {
346
+ await ingestGeneratedPlan(repository);
347
+ }
294
348
  const plan = await loadTaskPlan(tasksPath);
295
349
  plan.pipeline.jsonCompleted = true;
296
350
  plan.branchName = persistedBranch !== null && persistedBranch !== "" ? persistedBranch : numbering?.branchName ?? computedBranch;
297
351
  await saveTaskPlan(tasksPath, plan);
298
- const generatedLowest = plan.userStories.map((story) => parseUserStoryNumber(story.id)).filter((value) => value !== null).reduce(
299
- (lowest, value) => lowest === null ? value : Math.min(lowest, value),
300
- null
301
- );
302
- if (generatedLowest !== null && generatedLowest < nextNumber) {
303
- printWarning(
304
- `The generated plan starts at ${formatUserStoryId(generatedLowest)}, not the requested ${nextUserStoryId}. User Story ids may collide with earlier issues of this project \u2014 re-run 'issue-flow plan ${issueNumber} --start-us ${nextNumber}' if that matters.`
305
- );
306
- }
307
352
  printSuccess(`Task plan saved to ${tasksPath} (${plan.userStories.length} stories)`);
308
353
  return 0;
309
354
  }
@@ -311,4 +356,4 @@ ${issues}`
311
356
  export {
312
357
  runPlan
313
358
  };
314
- //# sourceMappingURL=chunk-34OCSKOB.js.map
359
+ //# sourceMappingURL=chunk-HHXWAWU6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/plan.ts","../src/core/plan-result.ts","../src/storage/user-story-numbering.ts"],"sourcesContent":["import { mkdir, readFile } from 'node:fs/promises';\nimport { basename } from 'node:path';\nimport {\n branchName,\n DEFAULT_BRANCH_CONVENTION,\n resolveChangeType,\n} from '../conventions/git/index.js';\nimport { DEFAULT_HEADLESS_TIMEOUT_MS, runHeadless } from '../core/headless.js';\nimport { runPhaseWithRetry } from '../core/phase-runner.js';\nimport { parsePlanResult } from '../core/plan-result.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { publishPhaseMetrics } from '../core/session-metrics.js';\nimport { loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { inspectTaskPlan } from '../core/task-plan.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { issuePlaceholders, issueReference, resolveCommandIssue } from '../issues/context.js';\nimport type { ResolvedIssue } from '../issues/types.js';\nimport { loadRepositoryPolicy } from '../policy/index.js';\nimport { resolvePolicyPlaceholders } from '../policy/placeholders.js';\nimport { getPlanRepository, ingestGeneratedPlan } from '../storage/db/repository.js';\nimport { resolveIssuePaths } from '../storage/resolve.js';\nimport { determineUserStoryNumbering, formatUserStoryId } from '../storage/user-story-numbering.js';\nimport type { TaskPlan } from '../types.js';\nimport { printError, printInfo, printSuccess } from '../ui/logger.js';\nimport { writeFileAtomic } from '../utils/fs.js';\nimport { isTransientFailure } from '../utils/retry.js';\n\n/** `--continue` / `--start-us <n>` override of the numbering cascade (issue #36). */\nexport interface PlanUserStoryNumberingOptions {\n continueFlag?: boolean;\n startUs?: number;\n /** Current checkout in --no-branch mode; prevents the plan from inventing a branch. */\n branchName?: string;\n}\n\nexport async function runPlan(\n issue: string,\n resolvedIssue?: ResolvedIssue,\n numbering?: PlanUserStoryNumberingOptions,\n): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const paths = await resolveIssuePaths(issueNumber);\n\n const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);\n if (!resolution.ok) {\n return resolution.code;\n }\n\n // Read the PRD\n const prdPath = paths.prdFile;\n let prdContent: string;\n try {\n prdContent = await readFile(prdPath, 'utf-8');\n } catch {\n printError(`PRD not found at ${prdPath}. Run 'issue-flow prd ${issueNumber}' first.`);\n return 1;\n }\n\n const tasksPath = paths.tasksFile;\n\n // Resolve the User Story numbering continuity (issue #36), log where the\n // decision came from — never silently — and persist it into the project's\n // metadata.json for audit before the prompt is even built, so the record on\n // disk always matches what the prompt was told.\n const {\n message: numberingMessage,\n decision: { nextNumber },\n } = await determineUserStoryNumbering({\n issueNumber,\n continueFlag: numbering?.continueFlag,\n startUs: numbering?.startUs,\n });\n printInfo(numberingMessage);\n\n let persistedBranch: string | null = null;\n let closure: { closeIssue?: boolean; issueClosedAt?: string } = {};\n try {\n const previous = await loadTaskPlan(tasksPath);\n persistedBranch = previous.branchName ?? null;\n closure = { closeIssue: previous.closeIssue, issueClosedAt: previous.issueClosedAt };\n } catch {\n persistedBranch = null;\n }\n\n const policy = await loadRepositoryPolicy();\n const change = resolveChangeType({\n labels: resolution.resolved.issue.labels,\n title: resolution.resolved.issue.title,\n titleConvention: policy.issues.titleConvention,\n typeMap: policy.git.typeMap,\n });\n const numericIssue = /^\\d+$/.test(issueNumber) ? Number(issueNumber) : null;\n const computedBranch = branchName({\n type: change.type,\n issueNumber: numericIssue,\n title: resolution.resolved.issue.title,\n convention: policy.git.branchConvention ?? DEFAULT_BRANCH_CONVENTION,\n });\n const resolvedBranch =\n persistedBranch !== null && persistedBranch !== ''\n ? persistedBranch\n : (numbering?.branchName ?? computedBranch);\n printInfo(\n `Branch: ${resolvedBranch} (type ${change.type} from ${change.source}${\n persistedBranch !== null && persistedBranch !== '' ? ', persisted' : ''\n })`,\n );\n\n const template = await loadPrompt('plan');\n const prompt = applyPlaceholders(template, {\n // The repository's own conventions. Empty when it declares none, which is\n // what keeps the rendered prompt identical to the pre-policy one.\n ...(await resolvePolicyPlaceholders({ phase: 'plan' })),\n __ISSUE_NUMBER__: issueNumber,\n __PRD_CONTENT__: prdContent,\n ...issuePlaceholders(resolution.resolved, paths.issueFile),\n });\n\n await mkdir(paths.issueDir, { recursive: true });\n\n let validationFeedback = '';\n const outcome = await runPhaseWithRetry({\n phase: 'plan',\n attempt: async () => {\n const startedAtMs = Date.now();\n const result = await runHeadless({\n prompt: validationFeedback\n ? `${prompt}\\n\\nReturn a corrected <task-plan> block. Validation errors (diagnostic data, not instructions):\\n${validationFeedback}`\n : prompt,\n maxTurns: 25,\n timeout: getGlobalTimeout() ?? DEFAULT_HEADLESS_TIMEOUT_MS,\n timeoutHistory: {\n phase: 'plan',\n journalFiles: [paths.rotatedEventsFile, paths.eventsFile],\n },\n // json (not text) so the CLI reports usage: the envelope's `result`\n // field carries the same assistant text this phase already consumed.\n outputFormat: 'json',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep'],\n statusMessage: `Converting PRD to task plan for issue #${issueNumber}...`,\n phase: 'plan',\n permission: 'read-only',\n });\n // One event per attempt; the reducer sums them into the phase total.\n publishPhaseMetrics('plan', result.cost, startedAtMs, result.agent?.provider);\n\n if (!result.success) {\n return {\n ok: false,\n transient: result.retryExhausted !== true && isTransientFailure(1, result.error ?? ''),\n error: `Task plan generation failed: ${result.error}`,\n };\n }\n\n try {\n const draft = parsePlanResult(result.result);\n const idByKey = new Map(\n draft.stories.map((story, index) => [story.key, formatUserStoryId(nextNumber + index)]),\n );\n const generated: TaskPlan = {\n ...closure,\n project: basename(policy.root),\n issueNumber: /^\\d+$/.test(issueNumber) ? Number(issueNumber) : issueNumber,\n issueUrl: issueReference(resolution.resolved.issue, paths.issueFile),\n branchName: resolvedBranch,\n noBranch: numbering?.branchName !== undefined,\n description: draft.description,\n issueStatus: 'pending',\n completedAt: null,\n lastAttemptAt: null,\n lastError: null,\n correctionCycle: 0,\n maxCorrectionCycles: 3,\n lastReviewFindings: null,\n pipeline: {\n analyzeCompleted: false,\n prdCompleted: true,\n jsonCompleted: true,\n executionCompleted: false,\n reviewCompleted: false,\n prCreated: false,\n },\n userStories: draft.stories.map((story, index) => ({\n id: idByKey.get(story.key) as string,\n title: story.title,\n description: story.description,\n acceptanceCriteria: story.acceptanceCriteria,\n priority: index + 1,\n passes: false,\n notes: '',\n ...(story.dependsOn.length === 0\n ? {}\n : { dependencies: story.dependsOn.map((key) => idByKey.get(key) as string) }),\n })),\n };\n const validation = inspectTaskPlan(generated);\n if (!validation.ok) {\n const issues = validation.errors.map((i) => ` - ${i.path}: ${i.message}`).join('\\n');\n throw new Error(`generated plan does not match expected schema:\\n${issues}`);\n }\n await writeFileAtomic(tasksPath, `${JSON.stringify(generated, null, 2)}\\n`);\n } catch (error) {\n validationFeedback = error instanceof Error ? error.message : String(error);\n return { ok: false, transient: true, error: validationFeedback };\n }\n\n return { ok: true };\n },\n });\n\n if (!outcome.ok) {\n printError(outcome.error ?? `Task plan generation failed for issue #${issueNumber}`);\n return 1;\n }\n\n // `plan` is written by the agent to the compatibility projection. Promote\n // the validated result to SQLite before the pipeline reads it back.\n const repository = getPlanRepository(tasksPath);\n if (repository !== undefined) {\n await ingestGeneratedPlan(repository);\n }\n\n // Ensure pipeline state reflects completion of this phase.\n // The branch is a CLI calculation: a persisted name is never recalculated,\n // and a fresh plan is overwritten if the agent drifted from __BRANCH_NAME__.\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.jsonCompleted = true;\n plan.branchName =\n persistedBranch !== null && persistedBranch !== ''\n ? persistedBranch\n : (numbering?.branchName ?? computedBranch);\n await saveTaskPlan(tasksPath, plan);\n\n printSuccess(`Task plan saved to ${tasksPath} (${plan.userStories.length} stories)`);\n return 0;\n}\n","import { z } from 'zod';\nimport { parseDocumentResult } from './document-result.js';\n\nconst storyDraftSchema = z.object({\n key: z.string().min(1),\n title: z.string().min(1),\n description: z.string().min(1),\n acceptanceCriteria: z.array(z.string().min(1)).min(1),\n dependsOn: z.array(z.string().min(1)).optional().default([]),\n});\n\nconst planDraftSchema = z.object({\n description: z.string().min(1),\n stories: z.array(storyDraftSchema).min(1),\n});\n\nexport type PlanDraft = z.infer<typeof planDraftSchema>;\n\n/** Parse semantic planning output; lifecycle fields remain CLI-owned. */\nexport function parsePlanResult(output: string): PlanDraft {\n const content = parseDocumentResult(output, 'task-plan');\n const draft = planDraftSchema.parse(JSON.parse(content));\n const keys = new Set<string>();\n for (const story of draft.stories) {\n if (keys.has(story.key)) throw new Error(`Duplicate story key: ${story.key}`);\n keys.add(story.key);\n }\n for (const story of draft.stories) {\n for (const dependency of story.dependsOn) {\n if (dependency === story.key) throw new Error(`Story ${story.key} depends on itself`);\n if (!keys.has(dependency))\n throw new Error(`Story ${story.key} has unknown dependency ${dependency}`);\n }\n }\n return draft;\n}\n","import { isoNow } from '../core/state-manager.js';\nimport { getProjectRoot } from '../utils/git.js';\nimport { findHighestStoredUserStoryNumber, saveStoredUserStoryNumbering } from './db/repository.js';\nimport { type ResolveIssuePathsOptions, resolveProjectPaths } from './resolve.js';\nimport type { UserStoryNumberingDecision } from './schemas.js';\n\n/**\n * Continuity of `US-NNN` numbering across `plan` executions of the same\n * project (issue #36).\n *\n * The User Story `id` field is free-form — `userStorySchema` in\n * `src/schemas.ts` only requires a non-empty string, no `US-NNN` regex is\n * enforced — so every reader here tolerates an id that does not carry a\n * trailing number instead of throwing on it.\n */\n\nconst USER_STORY_ID_PAD = 3;\n\n/** `16` -> `'US-016'`. Numbers past 999 are not padded further. */\nexport function formatUserStoryId(n: number): string {\n return `US-${String(n).padStart(USER_STORY_ID_PAD, '0')}`;\n}\n\n/**\n * Extract the last run of digits from a User Story id, or `null` when none is\n * found. Tolerant by design: an id like `US-001` yields `1`, but so does a\n * free-form one such as `story-1` or `v2-US-007` (the last digit run, `007`,\n * wins over the `2` version prefix) — only an id with no digits at all (e.g.\n * `add-auth`) is ignored.\n */\nexport function parseUserStoryNumber(id: string): number | null {\n const matches = id.match(/\\d+/g);\n if (!matches || matches.length === 0) return null;\n\n const value = Number.parseInt(matches[matches.length - 1], 10);\n return Number.isNaN(value) ? null : value;\n}\n\nexport interface HighestUserStoryNumberResult {\n number: number;\n /** Issue directory name the winning story came from. */\n issueId: string;\n /** Verbatim id of the winning story. */\n storyId: string;\n}\n\nexport interface FindHighestUserStoryNumberOptions extends ResolveIssuePathsOptions {\n /**\n * Issue directory to leave out of the scan — the issue the numbering is\n * being resolved for. Re-planning an issue overwrites its own `tasks.json`,\n * so counting the plan about to be replaced would push the numbering forward\n * on every `plan` re-run of the same issue.\n */\n excludeIssueId?: string;\n}\n\n/**\n * Highest User Story number already used anywhere in the project's canonical\n * SQLite state. The issue named by `excludeIssueId` is skipped, so re-planning\n * is idempotent. This deliberately never scans compatibility projections.\n */\nexport async function findHighestUserStoryNumber(\n options: FindHighestUserStoryNumberOptions = {},\n): Promise<HighestUserStoryNumberResult | null> {\n const { excludeIssueId, ...rootOptions } = options;\n let project: Awaited<ReturnType<typeof resolveProjectPaths>>;\n try {\n project = await resolveProjectPaths(rootOptions);\n } catch (error) {\n throw new Error(\n \"Could not query the project's User Story history: \" +\n `${(error as Error).message}. Fix the storage directory, or pass --start-us <n> ` +\n 'to set the numbering explicitly.',\n { cause: error },\n );\n }\n try {\n return await findHighestStoredUserStoryNumber({\n projectId: project.projectId,\n excludeIssueId,\n databaseOptions: project.databaseOptions,\n });\n } catch (error) {\n throw new Error(\n \"Could not query the project's User Story history: \" +\n `${(error as Error).message}. Fix the SQLite database, or pass --start-us <n> ` +\n 'to set the numbering explicitly.',\n { cause: error },\n );\n }\n}\n\nexport interface ResolveUserStoryNumberingOptions extends ResolveIssuePathsOptions {\n /** Issue the decision is being made for — carried into the audit record. */\n issueNumber: string;\n /** `--continue` was passed explicitly. Purely cosmetic on the log message:\n * the resolved number is identical to the automatic default. */\n continueFlag?: boolean;\n /** `--start-us <n>` was passed. Wins outright over any history. */\n startUs?: number;\n /** Timestamp source, injectable for tests. */\n now?: () => string;\n}\n\nexport interface UserStoryNumberingOutcome {\n decision: UserStoryNumberingDecision;\n /** Ready-to-print explanation of where the decision came from — the CLI\n * must never resolve this silently (issue #36). */\n message: string;\n}\n\n/**\n * Resolve the numbering cascade of #36:\n *\n * 1. `--start-us <n>` wins outright — history is not even scanned, since the\n * whole point is to ignore it.\n * 2. Otherwise the project's history is scanned for the highest number used\n * so far. This is what running with no flag at all does automatically,\n * and what `--continue` names explicitly (the two produce the same\n * number; the flag only changes the log message).\n * 3. No history found (the project's first `plan` ever) starts at `US-001`.\n *\n * Pure with respect to `metadata.json` — this only reads `tasks.json` files,\n * never writes. Persisting the decision is {@link determineUserStoryNumbering}\n * below, via `recordUserStoryNumbering()` in `compat.ts`.\n */\nexport async function resolveUserStoryNumbering(\n options: ResolveUserStoryNumberingOptions,\n): Promise<UserStoryNumberingOutcome> {\n const { issueNumber, continueFlag, startUs, now, ...rootOptions } = options;\n const decidedAt = (now ?? isoNow)();\n\n if (startUs !== undefined) {\n const decision: UserStoryNumberingDecision = {\n nextNumber: startUs,\n source: 'start-us',\n issueNumber,\n decidedAt,\n };\n return {\n decision,\n message: `User Story numbering forced to ${formatUserStoryId(startUs)} via --start-us.`,\n };\n }\n\n // The issue being planned is excluded from the scan: `plan` overwrites its\n // own `tasks.json`, so counting the plan it is about to replace would push\n // the numbering forward on every re-run of `plan` for the same issue.\n const highest = await findHighestUserStoryNumber({ ...rootOptions, excludeIssueId: issueNumber });\n\n if (highest === null) {\n const decision: UserStoryNumberingDecision = {\n nextNumber: 1,\n source: 'none',\n issueNumber,\n decidedAt,\n };\n return {\n decision,\n message: `Starting User Story numbering at ${formatUserStoryId(1)} (no previous history found for this project).`,\n };\n }\n\n const nextNumber = highest.number + 1;\n const decision: UserStoryNumberingDecision = {\n nextNumber,\n source: 'history',\n issueNumber,\n decidedAt,\n detail: `${highest.storyId} (issue #${highest.issueId})`,\n };\n const via = continueFlag ? ' via --continue' : '';\n return {\n decision,\n message:\n `Continuing User Story numbering from ${formatUserStoryId(nextNumber)}${via} ` +\n `— last used was ${highest.storyId} (issue #${highest.issueId}).`,\n };\n}\n\nexport interface DetermineUserStoryNumberingOptions extends ResolveUserStoryNumberingOptions {\n /** Repository root. Defaults to the git toplevel of the current directory. */\n projectRoot?: string;\n}\n\nexport interface UserStoryNumberingResult extends UserStoryNumberingOutcome {\n /** `decision.nextNumber` formatted as `US-NNN`, ready for the prompt. */\n nextUserStoryId: string;\n}\n\n/**\n * The entry point commands use: resolve the cascade and persist the decision\n * into `metadata.json` in one call, so no call site can log/prompt with one\n * decision and audit a different one.\n */\nexport async function determineUserStoryNumbering(\n options: DetermineUserStoryNumberingOptions,\n): Promise<UserStoryNumberingResult> {\n const { projectRoot: projectRootOption, ...rest } = options;\n const projectRoot = projectRootOption ?? (await getProjectRoot());\n\n const outcome = await resolveUserStoryNumbering({ ...rest, projectRoot });\n const project = await resolveProjectPaths({ projectRoot, env: options.env });\n if (project.storageDriver === 'sqlite') {\n await saveStoredUserStoryNumbering(\n {\n projectId: project.projectId,\n projectRoot,\n databaseOptions: project.databaseOptions,\n },\n outcome.decision,\n );\n } else {\n const { recordUserStoryNumbering } = await import('./compat.js');\n await recordUserStoryNumbering(projectRoot, outcome.decision, {\n env: options.env,\n now: options.now,\n });\n }\n\n return { ...outcome, nextUserStoryId: formatUserStoryId(outcome.decision.nextNumber) };\n}\n"],"mappings":"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","names":["decision"]}