issue-flow 0.17.0 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/README.md +34 -100
  2. package/dist/agent-EWKA7357.js +32 -0
  3. package/dist/{analyze-6M3KEOX7.js → analyze-Z7ODC27F.js} +42 -41
  4. package/dist/analyze-Z7ODC27F.js.map +1 -0
  5. package/dist/{apply-ADB3ZK7W.js → apply-VNSWQS6O.js} +22 -24
  6. package/dist/apply-VNSWQS6O.js.map +1 -0
  7. package/dist/artifacts-AMJQJFFV.js +82 -0
  8. package/dist/artifacts-AMJQJFFV.js.map +1 -0
  9. package/dist/{bench-FM7I7EIV.js → bench-ZJEIACUK.js} +42 -42
  10. package/dist/{chunk-RYOUZ6EK.js → chunk-23VHVVKW.js} +3 -3
  11. package/dist/chunk-2EYLOS5I.js +99 -0
  12. package/dist/chunk-2EYLOS5I.js.map +1 -0
  13. package/dist/{chunk-ANI2NTR4.js → chunk-2EZW4X57.js} +28 -25
  14. package/dist/chunk-2EZW4X57.js.map +1 -0
  15. package/dist/{chunk-E6KXCV3T.js → chunk-2Q6PG3MT.js} +15 -15
  16. package/dist/chunk-2Q6PG3MT.js.map +1 -0
  17. package/dist/{chunk-C5TL7T4V.js → chunk-2YM2VH6R.js} +2 -2
  18. package/dist/{chunk-LPWPFIIB.js → chunk-2YYP7AE6.js} +14 -16
  19. package/dist/{chunk-LPWPFIIB.js.map → chunk-2YYP7AE6.js.map} +1 -1
  20. package/dist/chunk-3JD25HHJ.js +278 -0
  21. package/dist/chunk-3JD25HHJ.js.map +1 -0
  22. package/dist/{chunk-ISZ54SVE.js → chunk-46S7APLQ.js} +41 -17
  23. package/dist/chunk-46S7APLQ.js.map +1 -0
  24. package/dist/chunk-6AOZCWN2.js +21 -0
  25. package/dist/chunk-6AOZCWN2.js.map +1 -0
  26. package/dist/{chunk-QPGEFLMY.js → chunk-7CMVL4X6.js} +7 -7
  27. package/dist/chunk-7MJGYPHF.js +1571 -0
  28. package/dist/chunk-7MJGYPHF.js.map +1 -0
  29. package/dist/chunk-7ZZUSMBJ.js +35 -0
  30. package/dist/chunk-7ZZUSMBJ.js.map +1 -0
  31. package/dist/{chunk-DUS5ZNKL.js → chunk-DIMDAYZS.js} +7 -6
  32. package/dist/{chunk-DGRTJD5R.js → chunk-DXH4TQRB.js} +7 -7
  33. package/dist/{chunk-CRGIQFUQ.js → chunk-E2H3JV2E.js} +3 -1
  34. package/dist/chunk-E2H3JV2E.js.map +1 -0
  35. package/dist/{chunk-YUNBOUQH.js → chunk-ERAG5IQB.js} +8 -8
  36. package/dist/{chunk-D6ZBGJ3L.js → chunk-F2NFJ27Z.js} +130 -36
  37. package/dist/chunk-F2NFJ27Z.js.map +1 -0
  38. package/dist/{chunk-SOM4QA6C.js → chunk-GD7JZMSJ.js} +93 -18
  39. package/dist/chunk-GD7JZMSJ.js.map +1 -0
  40. package/dist/{chunk-YII4U7ZD.js → chunk-HEHHQU4G.js} +70 -81
  41. package/dist/chunk-HEHHQU4G.js.map +1 -0
  42. package/dist/{chunk-KZ5EY5XX.js → chunk-HHXWAWU6.js} +159 -114
  43. package/dist/chunk-HHXWAWU6.js.map +1 -0
  44. package/dist/{chunk-SGTTR4XW.js → chunk-HWWB22HC.js} +152 -100
  45. package/dist/chunk-HWWB22HC.js.map +1 -0
  46. package/dist/chunk-IMB6FA2F.js +673 -0
  47. package/dist/chunk-IMB6FA2F.js.map +1 -0
  48. package/dist/{chunk-6YQT3SSM.js → chunk-IQYXXFRC.js} +22 -6
  49. package/dist/chunk-IQYXXFRC.js.map +1 -0
  50. package/dist/chunk-J6VJ5M2U.js +1201 -0
  51. package/dist/chunk-J6VJ5M2U.js.map +1 -0
  52. package/dist/{chunk-B3TSS7BT.js → chunk-KDQRNVBZ.js} +3 -3
  53. package/dist/{chunk-A4H7P7T7.js → chunk-LRWIP7QS.js} +41 -26
  54. package/dist/chunk-LRWIP7QS.js.map +1 -0
  55. package/dist/{chunk-DV7HUC4Z.js → chunk-LYC57OXF.js} +20 -117
  56. package/dist/chunk-LYC57OXF.js.map +1 -0
  57. package/dist/{chunk-HAKDLR6N.js → chunk-MAJ6QDRL.js} +65 -78
  58. package/dist/chunk-MAJ6QDRL.js.map +1 -0
  59. package/dist/{chunk-HJBF4WD2.js → chunk-MHA7Q3KU.js} +288 -108
  60. package/dist/chunk-MHA7Q3KU.js.map +1 -0
  61. package/dist/{chunk-UK4E2FUA.js → chunk-MQTVMB7W.js} +103 -76
  62. package/dist/chunk-MQTVMB7W.js.map +1 -0
  63. package/dist/chunk-NP47HRX2.js +136 -0
  64. package/dist/chunk-NP47HRX2.js.map +1 -0
  65. package/dist/{chunk-MI5TREN3.js → chunk-NYDAHE3Y.js} +52 -46
  66. package/dist/chunk-NYDAHE3Y.js.map +1 -0
  67. package/dist/{chunk-UWDCIFTP.js → chunk-P7ZYOC23.js} +41 -57
  68. package/dist/chunk-P7ZYOC23.js.map +1 -0
  69. package/dist/{chunk-3R27MJ5R.js → chunk-PFWHHYEO.js} +3 -3
  70. package/dist/{chunk-2SUFAE6X.js → chunk-PHPAK2L6.js} +44 -33
  71. package/dist/{chunk-2SUFAE6X.js.map → chunk-PHPAK2L6.js.map} +1 -1
  72. package/dist/{chunk-PPZ3K4YM.js → chunk-PRGPSNGA.js} +2 -2
  73. package/dist/{chunk-32JSYXEK.js → chunk-RGFMQFX7.js} +9 -23
  74. package/dist/{chunk-32JSYXEK.js.map → chunk-RGFMQFX7.js.map} +1 -1
  75. package/dist/{chunk-YVPZUN6X.js → chunk-TJVX42KY.js} +335 -38
  76. package/dist/chunk-TJVX42KY.js.map +1 -0
  77. package/dist/{chunk-RCZJQUTC.js → chunk-UDRAD7MF.js} +60 -27
  78. package/dist/chunk-UDRAD7MF.js.map +1 -0
  79. package/dist/{chunk-HY5HLFDU.js → chunk-UHDXXLYH.js} +148 -135
  80. package/dist/chunk-UHDXXLYH.js.map +1 -0
  81. package/dist/{chunk-RNFUVKC3.js → chunk-UPAAZU4W.js} +30 -13
  82. package/dist/chunk-UPAAZU4W.js.map +1 -0
  83. package/dist/{chunk-L6HSKTQE.js → chunk-W7LZLJAF.js} +4 -4
  84. package/dist/{chunk-HLAPOUEM.js → chunk-WB2FVIUS.js} +44 -12
  85. package/dist/chunk-WB2FVIUS.js.map +1 -0
  86. package/dist/{chunk-VE3IYNDN.js → chunk-WPHC6JBR.js} +9 -9
  87. package/dist/chunk-WPHC6JBR.js.map +1 -0
  88. package/dist/{chunk-KZXQWC7U.js → chunk-XHCP6ZNO.js} +201 -64
  89. package/dist/chunk-XHCP6ZNO.js.map +1 -0
  90. package/dist/{chunk-7F7LGMVO.js → chunk-XKR3VD2Z.js} +12 -12
  91. package/dist/{chunk-7F7LGMVO.js.map → chunk-XKR3VD2Z.js.map} +1 -1
  92. package/dist/{chunk-ICM463EO.js → chunk-XNK5UN2D.js} +5 -649
  93. package/dist/chunk-XNK5UN2D.js.map +1 -0
  94. package/dist/{chunk-Z5O2XBWA.js → chunk-XQLNZW7I.js} +40 -30
  95. package/dist/chunk-XQLNZW7I.js.map +1 -0
  96. package/dist/{chunk-QMIQR7EE.js → chunk-ZAYATXJD.js} +32 -316
  97. package/dist/chunk-ZAYATXJD.js.map +1 -0
  98. package/dist/cli.js +128 -60
  99. package/dist/cli.js.map +1 -1
  100. package/dist/compat-YPDZB34U.js +24 -0
  101. package/dist/{config-KGZUPAUP.js → config-56B4OHZE.js} +20 -17
  102. package/dist/{contract-JH2WBIO6.js → contract-HWLFFL3F.js} +2 -2
  103. package/dist/{conventions-RYN7OXJN.js → conventions-DW2CM5I3.js} +20 -16
  104. package/dist/{conventions-RYN7OXJN.js.map → conventions-DW2CM5I3.js.map} +1 -1
  105. package/dist/db-5NU77PJA.js +264 -0
  106. package/dist/db-5NU77PJA.js.map +1 -0
  107. package/dist/diagnostics-UCTS2VUF.js +25 -0
  108. package/dist/execute-R7AVOLNG.js +38 -0
  109. package/dist/{generate-CX6KNHZS.js → generate-O7GUXLN4.js} +46 -43
  110. package/dist/generate-O7GUXLN4.js.map +1 -0
  111. package/dist/{git-A2IP32PQ.js → git-LCG45YXD.js} +2 -3
  112. package/dist/history-6GSURU3H.js +101 -0
  113. package/dist/history-6GSURU3H.js.map +1 -0
  114. package/dist/init-DD6DBRC5.js +28 -0
  115. package/dist/{operations-CAKIT2BO.js → operations-HU5ZFEBA.js} +78 -52
  116. package/dist/operations-HU5ZFEBA.js.map +1 -0
  117. package/dist/{permissions-G4XCAESF.js → permissions-NNNSRC4P.js} +5 -6
  118. package/dist/plan-XR6B7EM3.js +38 -0
  119. package/dist/{policy-WX6O2JID.js → policy-RFJOJQL6.js} +17 -15
  120. package/dist/{policy-WX6O2JID.js.map → policy-RFJOJQL6.js.map} +1 -1
  121. package/dist/pr-OSCNJCOO.js +42 -0
  122. package/dist/pr-review-IKIYHFBO.js +33 -0
  123. package/dist/prd-HFDZJJQR.js +37 -0
  124. package/dist/{ps-76L6ZAZL.js → ps-AKRHGPSR.js} +26 -15
  125. package/dist/ps-AKRHGPSR.js.map +1 -0
  126. package/dist/{recorder-N7U66HCO.js → recorder-T7426CKV.js} +11 -7
  127. package/dist/registry-DWRKD65A.js +17 -0
  128. package/dist/repository-TABBRW2C.js +80 -0
  129. package/dist/resume-GDXKK5XP.js +278 -0
  130. package/dist/resume-GDXKK5XP.js.map +1 -0
  131. package/dist/review-JJHMQA2O.js +38 -0
  132. package/dist/routing-FPNUVE7T.js +35 -0
  133. package/dist/run-RLCURLLL.js +61 -0
  134. package/dist/{session-publisher-KAJQRHNZ.js → session-publisher-Q5TNEZPX.js} +3 -3
  135. package/dist/session-publisher-Q5TNEZPX.js.map +1 -0
  136. package/dist/{state-manager-UPHO2EA2.js → state-manager-IGNTD4AB.js} +9 -4
  137. package/dist/state-manager-IGNTD4AB.js.map +1 -0
  138. package/dist/{usage-U7QNGCPK.js → usage-XW27AL5K.js} +69 -63
  139. package/dist/usage-XW27AL5K.js.map +1 -0
  140. package/dist/web-NCDBVZ5Y.js +205 -0
  141. package/dist/web-NCDBVZ5Y.js.map +1 -0
  142. package/package.json +20 -7
  143. package/prompts/analyze.md +16 -19
  144. package/prompts/execute.md +73 -144
  145. package/prompts/generate.md +23 -10
  146. package/prompts/plan.md +32 -60
  147. package/prompts/pr-review.md +58 -40
  148. package/prompts/pr.md +50 -16
  149. package/prompts/prd.md +26 -19
  150. package/prompts/review.md +41 -18
  151. package/web/AGENTS.md +115 -7
  152. package/web/public/app.css +393 -245
  153. package/web/public/app.js +212 -143
  154. package/web/public/index.html +106 -78
  155. package/dist/agent-TB6W7PT7.js +0 -30
  156. package/dist/analyze-6M3KEOX7.js.map +0 -1
  157. package/dist/apply-ADB3ZK7W.js.map +0 -1
  158. package/dist/chunk-6YQT3SSM.js.map +0 -1
  159. package/dist/chunk-A4H7P7T7.js.map +0 -1
  160. package/dist/chunk-ANI2NTR4.js.map +0 -1
  161. package/dist/chunk-CRGIQFUQ.js.map +0 -1
  162. package/dist/chunk-D6ZBGJ3L.js.map +0 -1
  163. package/dist/chunk-DV7HUC4Z.js.map +0 -1
  164. package/dist/chunk-E6KXCV3T.js.map +0 -1
  165. package/dist/chunk-HAKDLR6N.js.map +0 -1
  166. package/dist/chunk-HJBF4WD2.js.map +0 -1
  167. package/dist/chunk-HLAPOUEM.js.map +0 -1
  168. package/dist/chunk-HY5HLFDU.js.map +0 -1
  169. package/dist/chunk-ICM463EO.js.map +0 -1
  170. package/dist/chunk-ISZ54SVE.js.map +0 -1
  171. package/dist/chunk-KZ5EY5XX.js.map +0 -1
  172. package/dist/chunk-KZXQWC7U.js.map +0 -1
  173. package/dist/chunk-MI5TREN3.js.map +0 -1
  174. package/dist/chunk-QMIQR7EE.js.map +0 -1
  175. package/dist/chunk-RCZJQUTC.js.map +0 -1
  176. package/dist/chunk-RNFUVKC3.js.map +0 -1
  177. package/dist/chunk-SGTTR4XW.js.map +0 -1
  178. package/dist/chunk-SOM4QA6C.js.map +0 -1
  179. package/dist/chunk-UK4E2FUA.js.map +0 -1
  180. package/dist/chunk-UWDCIFTP.js.map +0 -1
  181. package/dist/chunk-VE3IYNDN.js.map +0 -1
  182. package/dist/chunk-WCCZJL7W.js +0 -141
  183. package/dist/chunk-WCCZJL7W.js.map +0 -1
  184. package/dist/chunk-XEYH6ZZI.js +0 -289
  185. package/dist/chunk-XEYH6ZZI.js.map +0 -1
  186. package/dist/chunk-XZUEFTUP.js +0 -243
  187. package/dist/chunk-XZUEFTUP.js.map +0 -1
  188. package/dist/chunk-YII4U7ZD.js.map +0 -1
  189. package/dist/chunk-YVPZUN6X.js.map +0 -1
  190. package/dist/chunk-Z5O2XBWA.js.map +0 -1
  191. package/dist/diagnostics-HTCWF7F4.js +0 -23
  192. package/dist/execute-QL3ZQIAP.js +0 -37
  193. package/dist/generate-CX6KNHZS.js.map +0 -1
  194. package/dist/init-PDVQDRS6.js +0 -24
  195. package/dist/operations-CAKIT2BO.js.map +0 -1
  196. package/dist/plan-PRORGS3L.js +0 -34
  197. package/dist/pr-27RMQI5F.js +0 -39
  198. package/dist/pr-review-VNESMAMT.js +0 -32
  199. package/dist/prd-ZNC4T5BS.js +0 -33
  200. package/dist/ps-76L6ZAZL.js.map +0 -1
  201. package/dist/registry-XLHHYLTR.js +0 -15
  202. package/dist/resume-VLWIE264.js +0 -244
  203. package/dist/resume-VLWIE264.js.map +0 -1
  204. package/dist/review-7CREJDKB.js +0 -35
  205. package/dist/routing-VCDYHSXA.js +0 -33
  206. package/dist/run-3MPORLF5.js +0 -56
  207. package/dist/usage-U7QNGCPK.js.map +0 -1
  208. package/dist/web-HUNDHCJ4.js +0 -172
  209. package/dist/web-HUNDHCJ4.js.map +0 -1
  210. /package/dist/{agent-TB6W7PT7.js.map → agent-EWKA7357.js.map} +0 -0
  211. /package/dist/{bench-FM7I7EIV.js.map → bench-ZJEIACUK.js.map} +0 -0
  212. /package/dist/{chunk-RYOUZ6EK.js.map → chunk-23VHVVKW.js.map} +0 -0
  213. /package/dist/{chunk-C5TL7T4V.js.map → chunk-2YM2VH6R.js.map} +0 -0
  214. /package/dist/{chunk-QPGEFLMY.js.map → chunk-7CMVL4X6.js.map} +0 -0
  215. /package/dist/{chunk-DUS5ZNKL.js.map → chunk-DIMDAYZS.js.map} +0 -0
  216. /package/dist/{chunk-DGRTJD5R.js.map → chunk-DXH4TQRB.js.map} +0 -0
  217. /package/dist/{chunk-YUNBOUQH.js.map → chunk-ERAG5IQB.js.map} +0 -0
  218. /package/dist/{chunk-B3TSS7BT.js.map → chunk-KDQRNVBZ.js.map} +0 -0
  219. /package/dist/{chunk-3R27MJ5R.js.map → chunk-PFWHHYEO.js.map} +0 -0
  220. /package/dist/{chunk-PPZ3K4YM.js.map → chunk-PRGPSNGA.js.map} +0 -0
  221. /package/dist/{chunk-L6HSKTQE.js.map → chunk-W7LZLJAF.js.map} +0 -0
  222. /package/dist/{config-KGZUPAUP.js.map → compat-YPDZB34U.js.map} +0 -0
  223. /package/dist/{contract-JH2WBIO6.js.map → config-56B4OHZE.js.map} +0 -0
  224. /package/dist/{diagnostics-HTCWF7F4.js.map → contract-HWLFFL3F.js.map} +0 -0
  225. /package/dist/{execute-QL3ZQIAP.js.map → diagnostics-UCTS2VUF.js.map} +0 -0
  226. /package/dist/{git-A2IP32PQ.js.map → execute-R7AVOLNG.js.map} +0 -0
  227. /package/dist/{init-PDVQDRS6.js.map → git-LCG45YXD.js.map} +0 -0
  228. /package/dist/{permissions-G4XCAESF.js.map → init-DD6DBRC5.js.map} +0 -0
  229. /package/dist/{plan-PRORGS3L.js.map → permissions-NNNSRC4P.js.map} +0 -0
  230. /package/dist/{pr-27RMQI5F.js.map → plan-XR6B7EM3.js.map} +0 -0
  231. /package/dist/{pr-review-VNESMAMT.js.map → pr-OSCNJCOO.js.map} +0 -0
  232. /package/dist/{prd-ZNC4T5BS.js.map → pr-review-IKIYHFBO.js.map} +0 -0
  233. /package/dist/{recorder-N7U66HCO.js.map → prd-HFDZJJQR.js.map} +0 -0
  234. /package/dist/{registry-XLHHYLTR.js.map → recorder-T7426CKV.js.map} +0 -0
  235. /package/dist/{review-7CREJDKB.js.map → registry-DWRKD65A.js.map} +0 -0
  236. /package/dist/{routing-VCDYHSXA.js.map → repository-TABBRW2C.js.map} +0 -0
  237. /package/dist/{run-3MPORLF5.js.map → review-JJHMQA2O.js.map} +0 -0
  238. /package/dist/{session-publisher-KAJQRHNZ.js.map → routing-FPNUVE7T.js.map} +0 -0
  239. /package/dist/{state-manager-UPHO2EA2.js.map → run-RLCURLLL.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/execute.ts","../src/core/engine.ts","../src/ui/progress.ts","../src/ui/summary.ts","../src/ui/text.ts","../src/core/executor.ts","../src/storage/db/session-publisher.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { createConfig, loadWebConfig, resolvePaths, validateDependencies } from '../config.js';\nimport { runEngine } from '../core/engine.js';\nimport { MultiPublisher } from '../core/journal.js';\nimport { getSessionPublisher, setSessionPublisher } from '../core/session-publisher.js';\nimport { FilePublisher, NullPublisher, type SessionPublisher } from '../core/session-state.js';\nimport { allStoriesPass, isoNow, loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { getPlanRepository } from '../storage/db/repository.js';\nimport { SqliteSessionPublisher } from '../storage/db/session-publisher.js';\nimport { resolveIssuePaths } from '../storage/resolve.js';\nimport { printError } from '../ui/logger.js';\nimport { getPackageVersion } from '../version.js';\nimport { ensureWebMonitor } from '../web/lock.js';\n\nexport interface ExecuteOptions {\n inPipeline?: boolean;\n issue?: string;\n maxIterations?: number;\n retryLimit?: number;\n retryForever?: boolean;\n /** `--restart-web`: replace the detached monitor before direct execution. */\n restartWeb?: boolean;\n /**\n * Conventional-commit scope for the stories of this issue (`issue-71`).\n * Only the multi-issue queue sets it — see `EngineConfig.commitScope`.\n */\n commitScope?: string;\n}\n\nexport async function runExecute(\n positionalMaxIter: number | undefined,\n options: ExecuteOptions,\n): Promise<number> {\n const errors = await validateDependencies();\n if (errors.length > 0) {\n printError('The following required tools are not installed:');\n for (const err of errors) {\n console.log(err);\n }\n return 1;\n }\n\n const maxIterations = options.maxIterations ?? positionalMaxIter;\n\n const config = createConfig({\n issueNumber: options.issue,\n inPipeline: options.inPipeline,\n maxIterations,\n retryLimit: options.retryLimit,\n retryForever: options.retryForever,\n commitScope: options.commitScope,\n });\n\n const paths = await resolvePaths(config);\n const webConfig = await loadWebConfig();\n const inheritedPublisher = getSessionPublisher();\n let standalonePublisher: SessionPublisher | null = null;\n\n // `run` owns the publisher when it delegates to this command. A direct\n // `execute --issue N --web` has no owner, so install the same file-backed\n // surface here and tear down only what this invocation created.\n if (\n webConfig.enabled &&\n config.issueNumber !== undefined &&\n inheritedPublisher instanceof NullPublisher\n ) {\n const issuePaths = await resolveIssuePaths(config.issueNumber, {\n projectRoot: paths.projectRoot,\n });\n const filePublisher = new FilePublisher(issuePaths.sessionFile, {\n logLimit: webConfig.logLimit,\n includeLogs: webConfig.includeLogs,\n });\n const repository = getPlanRepository(issuePaths.tasksFile);\n const publisher =\n repository === undefined\n ? filePublisher\n : new MultiPublisher([\n new SqliteSessionPublisher(repository, {\n logLimit: webConfig.logLimit,\n includeLogs: webConfig.includeLogs,\n }),\n filePublisher,\n ]);\n standalonePublisher = publisher;\n setSessionPublisher(publisher);\n const numericIssue = /^\\d+$/.test(config.issueNumber) ? Number(config.issueNumber) : null;\n publisher.publish({\n type: 'session:start',\n at: isoNow(),\n sessionId: randomUUID(),\n issueNumber: numericIssue,\n phases: ['execute'],\n // The agent is only settled per invocation here, so the run-wide fields\n // stay null; the version is what makes the session attributable to a build.\n environment: {\n node: process.version,\n platform: process.platform,\n agent: null,\n model: null,\n cliVersion: getPackageVersion(),\n },\n });\n publisher.publish({ type: 'phase:start', at: isoNow(), phase: 'execute' });\n await ensureWebMonitor(\n {\n publisher,\n port: webConfig.port,\n host: webConfig.host,\n refreshSeconds: webConfig.refreshSeconds,\n },\n { restart: options.restartWeb === true },\n );\n }\n\n let exitCode: number | null = null;\n try {\n exitCode = await runEngine(config, paths);\n\n // Update pipeline state if all stories pass\n if (exitCode === 0 && config.issueNumber) {\n try {\n const plan = await loadTaskPlan(paths.prdFile);\n if (allStoriesPass(plan)) {\n plan.pipeline.executionCompleted = true;\n await saveTaskPlan(paths.prdFile, plan);\n }\n } catch {\n // Non-critical — engine already handled state\n }\n }\n\n return exitCode;\n } finally {\n if (standalonePublisher !== null) {\n const success = exitCode === 0;\n standalonePublisher.publish({\n type: 'phase:end',\n at: isoNow(),\n phase: 'execute',\n success,\n ...(success ? {} : { error: 'Execute phase failed' }),\n });\n standalonePublisher.publish({\n type: 'session:end',\n at: isoNow(),\n status: success ? 'completed' : 'failed',\n });\n await standalonePublisher.close();\n setSessionPublisher(undefined);\n }\n }\n}\n","import { existsSync } from 'node:fs';\nimport { cp, mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { resolveAgentFor } from '../agents/resolve.js';\nimport { getActiveResilienceConfig, loadGlobalConfig } from '../config.js';\nimport { archiveFolderName, type ChangeType, commitMessage } from '../conventions/git/index.js';\nimport { resolvePolicyPlaceholders } from '../policy/placeholders.js';\nimport { classify } from '../resilience/errors.js';\nimport type { RetryPolicy } from '../resilience/policy.js';\nimport { resolvePolicy } from '../resilience/policy.js';\nimport { fixedBackoffPolicy, withRetry } from '../resilience/retry.js';\nimport {\n getLastBranchRepository,\n getPlanRepository,\n ingestAgentPlan,\n loadStoredLastBranch,\n saveStoredLastBranch,\n setAgentProjectionWindow,\n} from '../storage/db/repository.js';\nimport type { ClaudeResult, EngineConfig, ResolvedPaths, TaskPlan, UserStory } from '../types.js';\nimport { printError, printInfo, printRetry, printSuccess, printWarning } from '../ui/logger.js';\nimport { printIterationHeader } from '../ui/progress.js';\nimport { printStartupHeader, printSummaryBox } from '../ui/summary.js';\nimport {\n committedStoryIds,\n getBaseBranch,\n getCurrentBranch,\n isWorkingTreeClean,\n} from '../utils/git.js';\nimport { applyAcceptanceToPlan, resolveIssueDir, runAcceptanceGate } from '../verify/gate.js';\nimport { executeClaude } from './executor.js';\nimport { divideUsage } from './metrics.js';\nimport { applyPlaceholders, loadPrompt } from './prompt-resolver.js';\nimport {\n EXECUTE_PHASE,\n elapsedSecondsSince,\n getPhaseUsageTotals,\n publishIterationMetrics,\n publishStoryMetrics,\n} from './session-metrics.js';\nimport { getSessionPublisher } from './session-publisher.js';\nimport { getShutdownSignal } from './shutdown.js';\nimport {\n allStoriesPass,\n applyStoryMetrics,\n clearLastError,\n hasPendingCorrection,\n initializeState,\n isoNow,\n loadTaskPlan,\n markIssueCompleted,\n markIssueInProgress,\n saveTaskPlan,\n selectActiveStory,\n setLastError,\n trimErrorMessage,\n} from './state-manager.js';\nimport { executionContext, inspectTaskPlan } from './task-plan.js';\nimport {\n getInactivityTimeout,\n getOutputCallback,\n getStoryStageCallback,\n getStoryUpdateCallback,\n isVerbose,\n} from './verbose.js';\nimport { executionCompletion } from './workflow-contract.js';\n\n/**\n * Emit a message through the output callback if available, otherwise console.log.\n * Used for bare console.log calls in engine functions so they route through listr2.\n */\nfunction emitLog(message: string): void {\n const cb = getOutputCallback();\n if (cb) {\n // Skip empty lines in listr2 context — they don't render meaningfully\n if (message) {\n cb(message);\n }\n } else {\n console.log(message);\n }\n}\n\n/** What one pass of the execute loop produces, retry or not. */\ninterface ExecuteAttempt {\n result: ClaudeResult;\n /** When the attempt started, for `clearLastError`. */\n startedAt: string;\n /** How long the attempt took, for the metrics split across stories. */\n seconds: number;\n /** The stories as they were before the agent ran, for attribution. */\n storiesBefore: UserStory[];\n lastErrorBefore: TaskPlan['lastError'];\n}\n\n/**\n * The execute loop's retry budget, unchanged in effect from the hand-rolled\n * loop it replaces: `retryLimit` counts *retries*, so the attempt budget is one\n * more than it, and `retryForever` lifts the count while `backoffMaxSeconds`\n * still caps the wait.\n */\nfunction executeRetryPolicy(config: EngineConfig): RetryPolicy {\n return fixedBackoffPolicy(\n config.retryLimit + 1,\n config.backoffBaseSeconds,\n config.backoffMaxSeconds,\n { retryForever: config.retryForever },\n );\n}\n\n/**\n * Ids of the stories that flipped from `passes: false` to `passes: true`\n * between the two readings of the plan taken around one execute iteration.\n *\n * Stories already passing before the iteration, and stories that only exist in\n * the newer plan, are never attributed: nothing says this iteration paid for\n * them.\n */\nfunction newlyCompletedStoryIds(before: UserStory[], after: UserStory[]): string[] {\n const wasPassing = new Map(before.map((story) => [story.id, story.passes]));\n return after\n .filter((story) => story.passes && wasPassing.get(story.id) === false)\n .map((story) => story.id);\n}\n\nexport interface CommitPlaceholderOptions {\n issueNumber?: string | number | null;\n signoff?: boolean;\n type?: ChangeType;\n}\n\nfunction numericIssue(value: string | number | null | undefined): number | undefined {\n if (value === undefined || value === null || value === '') return undefined;\n const parsed = typeof value === 'number' ? value : Number(value);\n return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;\n}\n\n/**\n * Commit message formats handed to the execute prompt.\n *\n * Built by `commitMessage()` so Conventional Commits is the default even when\n * the repository declared nothing. A scope is the issue id of a queue, which\n * is what makes `git log` on the shared branch readable per issue.\n */\nexport function commitPlaceholders(\n scope?: string,\n convention?: string | null,\n options?: CommitPlaceholderOptions,\n): Record<string, string> {\n const declared = convention !== undefined && convention !== null && convention !== '';\n const type = options?.type ?? 'feat';\n const issueNumber = numericIssue(options?.issueNumber);\n const resolvedScope = scope === undefined || scope === '' ? undefined : scope;\n\n if (declared && options?.type === undefined) {\n const suffix = resolvedScope === undefined ? '' : `(${resolvedScope})`;\n return {\n __COMMIT_MESSAGE__: commitMessage({\n type,\n scope: resolvedScope,\n subject: '[Story ID] - [Story Title]',\n issueNumber,\n signoff: options?.signoff,\n }).replace(`${type}${suffix}:`, `<type>${suffix}:`),\n __FIX_COMMIT_MESSAGE__: commitMessage({\n type: 'fix',\n scope: resolvedScope,\n subject: 'address review findings',\n issueNumber,\n signoff: options?.signoff,\n }),\n };\n }\n\n return {\n __COMMIT_MESSAGE__: commitMessage({\n type,\n scope: resolvedScope,\n subject: '[Story ID] - [Story Title]',\n issueNumber,\n signoff: options?.signoff,\n }),\n __FIX_COMMIT_MESSAGE__: commitMessage({\n type: 'fix',\n scope: resolvedScope,\n subject: 'address review findings',\n issueNumber,\n signoff: options?.signoff,\n }),\n };\n}\n\n/**\n * The watchdog budget of one iteration, from the process-wide setting.\n *\n * Absent means the default; `0` turns the watchdog off. Reading it here rather\n * than threading it through the engine's config keeps it a single value per\n * process, which is what it is.\n */\nfunction inactivityOptions(): { inactivityTimeoutMs?: number } {\n const configured = getInactivityTimeout();\n return configured === undefined ? {} : { inactivityTimeoutMs: configured };\n}\n\n/**\n * Mark as passing every pending story whose commit is already on the branch.\n *\n * Two conditions, and both are required:\n *\n * - the story's id appears in a commit subject of this branch, which is what\n * the execute prompt writes (`<type>(scope): US-001 - Title`);\n * - the working tree is **clean**, so nothing is half-applied. A dirty tree\n * means work in flight, and adopting a story on that basis would call\n * finished something that is not.\n *\n * Returns the ids it adopted. Never throws: a git that cannot answer, or a plan\n * that cannot be written, leaves the loop doing exactly what it did before.\n */\nasync function adoptCommittedStories(tasksPath: string): Promise<string[]> {\n try {\n const plan = await loadTaskPlan(tasksPath);\n const pending = plan.userStories.filter((story) => !story.passes);\n if (pending.length === 0) return [];\n\n if (!(await isWorkingTreeClean())) return [];\n\n const committed = await committedStoryIds(await getBaseBranch());\n const adopted = pending.filter((story) => committed.has(story.id)).map((story) => story.id);\n if (adopted.length === 0) return [];\n\n await saveTaskPlan(tasksPath, {\n ...plan,\n userStories: plan.userStories.map((story) =>\n adopted.includes(story.id) ? { ...story, passes: true } : story,\n ),\n });\n return adopted;\n } catch {\n return [];\n }\n}\n\n/**\n * Initialize the progress file if it doesn't exist.\n */\nasync function ensureProgressFile(progressFile: string): Promise<void> {\n if (!existsSync(progressFile)) {\n const content = `# Issue Flow Progress Log\\nStarted: ${new Date().toString()}\\n---\\n`;\n await writeFile(progressFile, content, 'utf-8');\n }\n}\n\n/**\n * Archive previous run artifacts if the branch has changed.\n */\nasync function archiveIfBranchChanged(plan: TaskPlan, paths: ResolvedPaths): Promise<void> {\n const { lastBranchFile, archiveDir, prdFile, progressFile } = paths;\n\n const currentBranch = plan.branchName ?? '';\n const repository = getLastBranchRepository(lastBranchFile);\n let lastBranch = repository === undefined ? null : await loadStoredLastBranch(repository);\n if (lastBranch === null && existsSync(lastBranchFile)) {\n try {\n lastBranch = (await readFile(lastBranchFile, 'utf-8')).trim() || null;\n } catch {\n return;\n }\n }\n if (lastBranch === null) return;\n\n if (currentBranch && lastBranch && currentBranch !== lastBranch) {\n const dateStr = new Date().toISOString().split('T')[0];\n const folderName = archiveFolderName(lastBranch);\n const archiveFolder = join(archiveDir, `${dateStr}-${folderName}`);\n\n printInfo(`Archiving previous run: ${lastBranch}`);\n await mkdir(archiveFolder, { recursive: true });\n\n if (existsSync(prdFile)) {\n await cp(prdFile, join(archiveFolder, 'tasks.json'));\n }\n if (existsSync(progressFile)) {\n await cp(progressFile, join(archiveFolder, 'progress.txt'));\n }\n\n printInfo(` Archived to: ${archiveFolder}`);\n\n // Reset progress file for new run\n await writeFile(\n progressFile,\n `# Issue Flow Progress Log\\nStarted: ${new Date().toString()}\\n---\\n`,\n 'utf-8',\n );\n }\n}\n\n/**\n * Write the current branch to the last-branch tracking file.\n */\nasync function trackBranch(plan: TaskPlan, lastBranchFile: string): Promise<void> {\n const branch = plan.branchName ?? '';\n if (branch) {\n const repository = getLastBranchRepository(lastBranchFile);\n if (repository !== undefined) {\n await saveStoredLastBranch(repository, branch);\n return;\n }\n await writeFile(lastBranchFile, `${branch}\\n`, 'utf-8');\n }\n}\n\n/**\n * Run the issue-flow engine loop.\n *\n * This replicates the full execution flow:\n * 1. Load and initialize task plan state\n * 2. Check for early exit (already complete)\n * 3. Archive previous run if branch changed\n * 4. Resolve prompt\n * 5. Main loop: iterate, execute Claude, handle results\n * 6. Print summary\n */\nasync function validateExecutionInput(plan: TaskPlan): Promise<string | null> {\n const result = inspectTaskPlan(plan);\n if (!result.ok) return result.errors.map((error) => `${error.code}: ${error.message}`).join('\\n');\n if (plan.userStories.length === 0) return 'Task plan has no stories';\n if (plan.noBranch === true) {\n const branch = await getCurrentBranch().catch(() => '');\n if (!branch || branch !== plan.branchName)\n return `Current branch must match ${plan.branchName}; refusing to switch in no-branch mode`;\n }\n return null;\n}\n\nexport async function runEngine(config: EngineConfig, paths: ResolvedPaths): Promise<number> {\n // Load task plan\n if (!existsSync(paths.prdFile)) {\n printError(`PRD file not found at ${paths.prdFile}`);\n if (config.issueNumber) {\n emitLog(`Have you run the resolve-issue skill for issue #${config.issueNumber} first?`);\n }\n return 1;\n }\n\n let plan = await loadTaskPlan(paths.prdFile);\n const closure = { closeIssue: plan.closeIssue, issueClosedAt: plan.issueClosedAt };\n const inputError = await validateExecutionInput(plan);\n if (inputError) {\n printError(inputError);\n return 1;\n }\n plan = initializeState(plan);\n await saveTaskPlan(paths.prdFile, plan);\n\n // Check if already completed. A pending correction (a failed review whose\n // findings haven't been addressed yet) always overrides this, even though\n // every userStories[].passes is already true — otherwise a correction\n // cycle's re-run of execute would exit here without ever looking at what\n // the review found.\n if (\n plan.issueStatus === 'completed' &&\n allStoriesPass(plan) &&\n !hasPendingCorrection(plan) &&\n !plan.lastError\n ) {\n emitLog(`Issue already marked complete in ${paths.prdFile}`);\n return finishWithAcceptance(config, paths, plan);\n }\n\n // Warn if marked complete but stories still pending\n if (plan.issueStatus === 'completed' && !allStoriesPass(plan)) {\n printWarning(\n 'Issue marked completed but some stories are still pending. Resetting to in_progress.',\n );\n plan = markIssueInProgress(plan);\n plan = setLastError(\n plan,\n 'invalid_completion_state',\n 'tasks.json claimed the issue was completed before every story had passes=true.',\n );\n await saveTaskPlan(paths.prdFile, plan);\n }\n\n // Check if all stories already pass\n if (allStoriesPass(plan) && !hasPendingCorrection(plan) && !plan.lastError) {\n emitLog('All user stories already pass. Marking issue as completed.');\n plan = executionCompletion(markIssueCompleted(plan), config.inPipeline === true);\n await saveTaskPlan(paths.prdFile, plan);\n return finishWithAcceptance(config, paths, plan);\n }\n\n // Archive previous run if branch changed\n await archiveIfBranchChanged(plan, paths);\n\n // Track current branch\n await trackBranch(plan, paths.lastBranchFile);\n\n // Initialize progress file\n await ensureProgressFile(paths.progressFile);\n\n // Load prompt template. The root is handed over rather than rediscovered:\n // it was resolved once by `resolvePaths()`, and a second `git rev-parse` here\n // would be a subprocess per run for an answer already in hand.\n const promptTemplate = await loadPrompt('execute', { projectRoot: paths.projectRoot });\n\n // Resolve policy once for this loop; referenced document contents remain\n // available for decision-specific reads during each iteration.\n const policy = await resolvePolicyPlaceholders({ root: paths.projectRoot, phase: 'execute' });\n const signoff = (await loadGlobalConfig()).commit?.signoff === true;\n\n const executeAgent = await resolveAgentFor('execute');\n printStartupHeader(config, plan, {\n provider: executeAgent.provider,\n model: executeAgent.model,\n ...(executeAgent.codex.reasoningEffort ? { detail: executeAgent.codex.reasoningEffort } : {}),\n });\n\n const startTime = Date.now();\n let i = 0;\n let totalRetryCount = 0;\n\n // Main loop\n while (true) {\n // Check iteration limit\n if (config.maxIterations !== undefined && i >= config.maxIterations) {\n break;\n }\n\n i++;\n\n // Safety net for the crash between a story's commit and the agent writing\n // `passes: true` (scenario I). The commit is the durable fact; the plan is\n // written after it, so a process that died in between leaves work that is\n // done and a plan that says it is not — and the next iteration redoes it on\n // top of a commit that already exists.\n //\n // This never *replaces* the agent's `passes`, which stays the primary\n // source: it only closes the window the agent cannot close itself.\n const adopted = await adoptCommittedStories(paths.prdFile);\n if (adopted.length > 0) {\n printInfo(\n `Adopting ${adopted.join(', ')}: already committed on this branch with a clean tree.`,\n );\n plan = await loadTaskPlan(paths.prdFile);\n if (\n plan.userStories.every((story) => story.passes) &&\n plan.lastReviewFindings === null &&\n !plan.lastError\n ) {\n // Everything the loop was going to do is already done.\n break;\n }\n }\n\n // One iteration is one attempt of the Claude CLI, retried in place by the\n // project's single retry executor (`resilience/retry.ts`). A retry costs no\n // iteration budget, exactly as the `i--` it replaces cost none. Everything a\n // retried attempt has to redo — re-reading the plan, republishing\n // `iteration:start`, re-rendering the prompt — lives inside `attempt`,\n // because the loop this used to be written as re-ran all of it too.\n const attempted = await withRetry<ExecuteAttempt>(\n async () => {\n // Re-read plan to get latest state\n plan = await loadTaskPlan(paths.prdFile);\n const inputError = await validateExecutionInput(plan);\n if (inputError) throw new Error(`Invalid task plan: ${inputError}`);\n // Publish the plan before naming its active story. This covers direct\n // `issue-flow execute` runs and resumes that never pass through the\n // plan runner, after validating the task graph.\n if (plan.userStories.length > 0) {\n getSessionPublisher().publish({\n type: 'stories:update',\n at: isoNow(),\n stories: plan.userStories,\n });\n }\n // Baseline for story attribution: whatever was still pending before the\n // agent ran is what this iteration can claim credit for.\n const storiesBefore = plan.userStories;\n const lastErrorBefore = plan.lastError;\n\n // Highest-priority story with passes: false — the same rule\n // prompts/execute.md gives the agent. Computed once, here, and shared by\n // the published event and the verbose terminal header, so every surface\n // agrees on who is active instead of each deriving its own heuristic.\n const activeStoryId = hasPendingCorrection(plan)\n ? undefined\n : selectActiveStory(plan.userStories)?.id;\n getSessionPublisher().publish({\n type: 'iteration:start',\n at: isoNow(),\n iteration: i,\n storyId: activeStoryId,\n });\n getStoryStageCallback()?.(activeStoryId);\n\n if (isVerbose()) {\n printIterationHeader(i, config.maxIterations, plan.userStories, activeStoryId);\n }\n\n // Apply placeholders to prompt\n const prompt = applyPlaceholders(promptTemplate, {\n __EXECUTION_CONTEXT__: JSON.stringify(executionContext(plan)),\n __CORRECTION_MODE__: hasPendingCorrection(plan) ? 'enabled' : '',\n __ACTIVE_STORY__: activeStoryId ?? 'Address pending review findings',\n __EXECUTION_SCOPE__: config.inPipeline\n ? 'This is one pipeline phase: keep issueStatus=in_progress and completedAt=null.'\n : 'Standalone execution may mark the issue completed after verification.',\n __PRD_FILE__: paths.prdFile,\n __PROGRESS_FILE__: paths.progressFile,\n ...policy,\n // The convention comes off the projection above rather than from a second\n // discovery: one resolution per run is the whole point of the cache.\n ...commitPlaceholders(config.commitScope, policy.__COMMIT_CONVENTION__, {\n issueNumber: plan.issueNumber,\n signoff,\n }),\n __STORIES_PER_ITERATION__: String(config.storiesPerIteration ?? 1),\n });\n\n const startedAt = isoNow();\n plan = markIssueInProgress(plan, startedAt);\n await saveTaskPlan(paths.prdFile, plan);\n\n // Execute Claude\n const startedAtMs = Date.now();\n setAgentProjectionWindow(paths.prdFile, true);\n let result: ClaudeResult;\n try {\n result = await executeClaude(prompt, {\n ...inactivityOptions(),\n iteration: i,\n correctionCycle: plan.correctionCycle,\n ...(activeStoryId === undefined ? {} : { storyIds: [activeStoryId] }),\n });\n } finally {\n setAgentProjectionWindow(paths.prdFile, false);\n }\n // The agent receives a materialized compatibility file. Reingest its\n // intentionally narrow mutation before any pipeline/telemetry reader\n // observes the next state, so a concurrent execution row cannot erase\n // `passes` or `notes`.\n const repository = getPlanRepository(paths.prdFile);\n if (repository !== undefined) {\n plan = await ingestAgentPlan(repository, plan);\n } else {\n plan = { ...(await loadTaskPlan(paths.prdFile)), ...closure };\n await saveTaskPlan(paths.prdFile, plan);\n }\n const seconds = elapsedSecondsSince(startedAtMs);\n\n if (result.exitCode !== 0) {\n // A failed iteration still burned tokens: report them on the execute\n // phase, with no attribution to any story.\n publishIterationMetrics(i, result.cost, seconds);\n }\n\n return { result, startedAt, seconds, storiesBefore, lastErrorBefore };\n },\n {\n policy: executeRetryPolicy(config),\n // A `Ctrl+C` during a fifteen-minute backoff must stop in that instant,\n // not fifteen minutes later. `abortableDelay` resolves `false` on the\n // abort, which `withRetry` reports as `aborted` rather than a failure.\n signal: getShutdownSignal(),\n // The exit code and the output are all the evidence the CLI leaves\n // behind, which is exactly what `isTransientFailure()` used to be given\n // here — `classify()` is the same verdict plus the kind.\n evaluate: ({ result }) =>\n result.exitCode === 0\n ? null\n : classify({ source: 'agent', exitCode: result.exitCode, stdout: result.output }),\n onAttempt: async ({ value, attempt, failure, willRetry, delayMs }) => {\n if (failure === null) return;\n\n const errorMessage = trimErrorMessage(value.result.output);\n\n if (!failure.retryable) {\n plan = await loadTaskPlan(paths.prdFile);\n plan = setLastError(plan, 'fatal_claude_failure', errorMessage);\n if (resolvePolicy(failure.kind, getActiveResilienceConfig()).onExhausted === 'block') {\n plan = {\n ...plan,\n runState: {\n currentPhase: 'execute',\n attempt,\n owner: plan.runState?.owner ?? null,\n status: 'blocked',\n blockedReason: errorMessage,\n lastHeartbeatAt: isoNow(),\n },\n };\n }\n await saveTaskPlan(paths.prdFile, plan);\n return;\n }\n\n // Recorded even on the attempt that spends the last of the budget:\n // the run ends on a transient failure, and the plan has to say so.\n totalRetryCount++;\n plan = await loadTaskPlan(paths.prdFile);\n plan = setLastError(plan, 'transient_claude_failure', errorMessage);\n await saveTaskPlan(paths.prdFile, plan);\n if (!willRetry) return;\n\n const delaySeconds = delayMs / 1000;\n getSessionPublisher().publish({\n type: 'retry',\n at: isoNow(),\n attempt,\n delaySeconds,\n reason: 'transient_claude_failure',\n kind: failure.kind,\n });\n\n if (isVerbose()) {\n emitLog('');\n }\n printRetry(\n `Transient Claude failure on iteration ${i} (attempt ${attempt}). Retrying in ${delaySeconds}s.`,\n );\n },\n },\n );\n\n const {\n result,\n seconds: iterationSeconds,\n storiesBefore: storiesBeforeIteration,\n } = attempted.value;\n const iterationStartedAt = attempted.value.startedAt;\n\n if (result.exitCode !== 0) {\n const elapsed = Math.floor((Date.now() - startTime) / 1000);\n plan = await loadTaskPlan(paths.prdFile);\n printSummaryBox(\n 'failed',\n i,\n totalRetryCount,\n elapsed,\n plan,\n attempted.exhausted\n ? `Exceeded retry limit (${config.retryLimit}) on transient errors`\n : `Claude CLI failed with exit code ${result.exitCode}`,\n getPhaseUsageTotals(EXECUTE_PHASE),\n );\n return result.exitCode;\n }\n\n plan = await loadTaskPlan(paths.prdFile);\n const outputError = await validateExecutionInput(plan);\n if (outputError) {\n printError(`Invalid execution result: ${outputError}`);\n return 1;\n }\n if (\n plan.lastError &&\n (JSON.stringify(plan.lastError) !== JSON.stringify(attempted.value.lastErrorBefore) ||\n ![\n 'fatal_claude_failure',\n 'transient_claude_failure',\n 'invalid_completion_signal',\n 'task_execution',\n ].includes(plan.lastError.category))\n ) {\n publishIterationMetrics(i, result.cost, iterationSeconds);\n getSessionPublisher().publish({ type: 'iteration:end', at: isoNow(), iteration: i });\n printError(`Execution blocked: ${plan.lastError.message}`);\n return 1;\n }\n plan = clearLastError(plan, iterationStartedAt);\n await saveTaskPlan(paths.prdFile, plan);\n\n // Notify story progress listeners (e.g., listr2 subtasks)\n const storyUpdateCb = getStoryUpdateCallback();\n if (storyUpdateCb) {\n storyUpdateCb(plan.userStories);\n }\n getSessionPublisher().publish({\n type: 'stories:update',\n at: isoNow(),\n stories: plan.userStories,\n });\n\n // Story attribution — an approximation by construction: the CLI reports a\n // single usage for the whole iteration, so the stories that flipped to\n // passing in it split tokens, cost and duration evenly. With none, nothing\n // is attributed and the whole cost stays on the execute phase. Published\n // after stories:update, which rebuilds the stories array.\n const completedStoryIds = newlyCompletedStoryIds(storiesBeforeIteration, plan.userStories);\n if (completedStoryIds.length > 0) {\n const storyShare = divideUsage(result.cost, completedStoryIds.length);\n const storySeconds = Math.round(iterationSeconds / completedStoryIds.length);\n for (const storyId of completedStoryIds) {\n publishStoryMetrics(storyId, storyShare, storySeconds);\n }\n\n // The same shares also land on tasks.json, so the numbers outlive the\n // session and are readable with web monitoring off. Persisting them is\n // observational: a write failure must never change the iteration's\n // outcome, so the plan in memory only advances when the write succeeded.\n const planWithMetrics = applyStoryMetrics(plan, completedStoryIds, storyShare, storySeconds);\n try {\n await saveTaskPlan(paths.prdFile, planWithMetrics);\n plan = planWithMetrics;\n } catch {\n // Metrics are a nice-to-have; the iteration succeeded regardless.\n }\n }\n\n getSessionPublisher().publish({ type: 'iteration:end', at: isoNow(), iteration: i });\n publishIterationMetrics(i, result.cost, iterationSeconds);\n\n // Check for completion signal\n if (result.output.includes('<promise>COMPLETE</promise>')) {\n plan = await loadTaskPlan(paths.prdFile);\n if (allStoriesPass(plan) && !hasPendingCorrection(plan)) {\n plan = executionCompletion(markIssueCompleted(plan), config.inPipeline === true);\n await saveTaskPlan(paths.prdFile, plan);\n\n const elapsed = Math.floor((Date.now() - startTime) / 1000);\n printSummaryBox(\n 'success',\n i,\n totalRetryCount,\n elapsed,\n plan,\n undefined,\n getPhaseUsageTotals(EXECUTE_PHASE),\n );\n return finishWithAcceptance(config, paths, plan);\n }\n\n plan = setLastError(\n plan,\n 'invalid_completion_signal',\n hasPendingCorrection(plan)\n ? 'Claude returned <promise>COMPLETE</promise> while lastReviewFindings was still set.'\n : 'Claude returned <promise>COMPLETE</promise> before every story had passes=true.',\n );\n await saveTaskPlan(paths.prdFile, plan);\n\n emitLog('');\n printWarning(\n hasPendingCorrection(plan)\n ? 'Claude returned a completion signal, but lastReviewFindings is still set. Ignoring completion and continuing.'\n : 'Claude returned a completion signal, but tasks.json still has pending stories. Ignoring completion and continuing.',\n );\n }\n\n if (isVerbose()) {\n printSuccess(`Iteration ${i} complete. Continuing...`);\n }\n }\n\n // Reached max iterations\n const elapsed = Math.floor((Date.now() - startTime) / 1000);\n plan = await loadTaskPlan(paths.prdFile);\n printSummaryBox(\n 'incomplete',\n config.maxIterations ?? i,\n totalRetryCount,\n elapsed,\n plan,\n 'Reached max iterations without completing all tasks.',\n getPhaseUsageTotals(EXECUTE_PHASE),\n );\n return 1;\n}\n\nasync function finishWithAcceptance(\n config: EngineConfig,\n paths: ResolvedPaths,\n plan: TaskPlan,\n): Promise<number> {\n const issueDir = resolveIssueDir(config, paths);\n const outcome = await runAcceptanceGate({\n issueDir,\n cwd: paths.projectRoot,\n addDirs: config.issueNumber === undefined ? undefined : [issueDir],\n skipReviewer: true,\n });\n const applied = applyAcceptanceToPlan(plan, outcome);\n if (applied.failed) {\n await saveTaskPlan(paths.prdFile, applied.plan);\n return 1;\n }\n await saveTaskPlan(\n paths.prdFile,\n executionCompletion(markIssueCompleted(applied.plan), config.inPipeline === true),\n );\n return 0;\n}\n","import chalk from 'chalk';\nimport { getOutputCallback } from '../core/verbose.js';\nimport type { UserStory } from '../types.js';\nimport { getIcons, useColor, useUnicode } from './logger.js';\n\n/**\n * Print a visual progress bar showing passed/total stories.\n */\nexport function printProgressBar(passed: number, total: number): string {\n const barWidth = 20;\n let pct = 0;\n let filled = 0;\n\n if (total > 0) {\n pct = Math.floor((passed * 100) / total);\n filled = Math.floor((passed * barWidth) / total);\n }\n\n const empty = barWidth - filled;\n const fillChar = useUnicode() ? '\\u2588' : '#';\n const emptyChar = useUnicode() ? '\\u2591' : '-';\n\n const bar = fillChar.repeat(filled) + emptyChar.repeat(empty);\n const text = `${passed}/${total} (${pct}%)`;\n\n if (useColor()) {\n return `${chalk.green(bar)} ${text}`;\n }\n return `${bar} ${text}`;\n}\n\n/**\n * Print the iteration header showing story statuses and progress bar.\n *\n * When a global output callback is set (e.g., inside a listr2 task),\n * output is routed through it instead of console.log.\n *\n * `activeStoryId`, when provided, identifies the story the engine is\n * actually about to work on this iteration (the same id published on\n * `iteration:start` \\u2014 see `core/state-manager.ts`'s `selectActiveStory`),\n * so the header agrees with the session snapshot instead of guessing.\n * Omitted, it falls back to the previous heuristic (\"first non-passing story\n * in array order\"), which existing callers that cannot supply the real id\n * keep relying on.\n */\nexport function printIterationHeader(\n iteration: number,\n maxIter: number | undefined,\n stories: UserStory[],\n activeStoryId?: string,\n): void {\n const icons = getIcons();\n const colored = useColor();\n const cb = getOutputCallback();\n\n const iterLabel = maxIter ? `Iteration ${iteration} of ${maxIter}` : `Iteration ${iteration}`;\n\n const total = stories.length;\n const passed = stories.filter((s) => s.passes).length;\n\n const lines: string[] = [];\n\n if (colored) {\n lines.push(chalk.blue(`\\u2501\\u2501\\u2501 ${icons.start} ${iterLabel} \\u2501\\u2501\\u2501`));\n } else {\n lines.push(`--- ${icons.start} ${iterLabel} ---`);\n }\n\n // Display each story status\n let foundFirstPending = false;\n for (const story of stories) {\n let icon: string;\n let colorFn: (s: string) => string;\n const isActive =\n activeStoryId !== undefined\n ? story.id === activeStoryId\n : !story.passes && !foundFirstPending;\n\n if (story.passes) {\n icon = icons.success;\n colorFn = colored ? chalk.green : (s: string) => s;\n } else if (isActive) {\n icon = icons.pending;\n colorFn = colored ? chalk.yellow : (s: string) => s;\n foundFirstPending = true;\n } else {\n // Not yet reached\n icon = icons.notReached;\n colorFn = colored ? chalk.gray : (s: string) => s;\n }\n\n lines.push(colorFn(` ${icon} ${story.id}: ${story.title}`));\n }\n\n lines.push(` ${printProgressBar(passed, total)}`);\n\n if (cb) {\n // Send as single output to listr2 task context\n cb(lines.join('\\n'));\n } else {\n console.log('');\n for (const line of lines) {\n console.log(line);\n }\n console.log('');\n }\n}\n","import chalk from 'chalk';\nimport { type ClaudeUsage, formatTokens } from '../core/metrics.js';\nimport { groupBy, summarize } from '../telemetry/aggregate.js';\nimport type { EngineConfig, PrReviewRecommendation, TaskPlan } from '../types.js';\nimport {\n formatDuration,\n getIcons,\n getTermWidth,\n printSuccess,\n printWarning,\n useColor,\n useUnicode,\n} from './logger.js';\n\n/**\n * Truncate or pad a string to fit within a given width.\n */\nfunction fitLine(text: string, width: number): string {\n if (text.length > width) {\n return text.substring(0, width);\n }\n return text.padEnd(width);\n}\n\n/**\n * Print a box with border characters around content lines.\n * Lines equal to \"---\" render as separator rows.\n */\nexport function printBox(lines: string[]): void {\n const colored = useColor();\n const unicode = useUnicode();\n const termWidth = getTermWidth();\n\n // Find max content width\n let maxContentWidth = 0;\n for (const line of lines) {\n if (line !== '---' && line.length > maxContentWidth) {\n maxContentWidth = line.length;\n }\n }\n\n // Cap to terminal width (border + padding = 4 chars)\n const available = termWidth - 4;\n if (maxContentWidth > available) {\n maxContentWidth = available;\n }\n if (maxContentWidth < 20) {\n maxContentWidth = 20;\n }\n\n // Box drawing characters\n const tl = unicode ? '\\u256D' : '+';\n const tr = unicode ? '\\u256E' : '+';\n const bl = unicode ? '\\u2570' : '+';\n const br = unicode ? '\\u256F' : '+';\n const h = unicode ? '\\u2500' : '-';\n const v = unicode ? '\\u2502' : '|';\n const sepL = unicode ? '\\u251C' : '+';\n const sepR = unicode ? '\\u2524' : '+';\n\n const hrule = h.repeat(maxContentWidth + 2);\n\n const blue = colored ? chalk.blue : (s: string) => s;\n const _reset = (s: string) => s;\n\n // Top border\n console.log(blue(`${tl}${hrule}${tr}`));\n\n // Content lines\n for (const line of lines) {\n if (line === '---') {\n console.log(blue(`${sepL}${hrule}${sepR}`));\n } else {\n const fitted = fitLine(line, maxContentWidth);\n console.log(`${blue(v)} ${fitted} ${blue(v)}`);\n }\n }\n\n // Bottom border\n console.log(blue(`${bl}${hrule}${br}`));\n}\n\n/**\n * Print the startup header box showing engine configuration.\n */\nexport interface StartupAgentInfo {\n provider: string;\n model?: string | null;\n detail?: string;\n}\n\nexport function printStartupHeader(\n config: EngineConfig,\n plan: TaskPlan,\n agent?: StartupAgentInfo,\n): void {\n const icons = getIcons();\n\n const storiesTotal = plan.userStories.length;\n const storiesPassing = plan.userStories.filter((s) => s.passes).length;\n const branchName = plan.branchName ?? 'N/A';\n\n const issueLabel = config.issueNumber ? `Issue #${config.issueNumber}` : 'Standalone mode';\n\n const maxIterLabel =\n config.maxIterations !== undefined ? String(config.maxIterations) : 'unlimited';\n\n const retryLabel = config.retryForever\n ? 'unlimited retries'\n : `${config.retryLimit} consecutive retries`;\n\n const lines = [\n `${icons.start} Issue Flow`,\n '---',\n `Issue: ${issueLabel}`,\n `Branch: ${branchName}`,\n `Stories: ${storiesPassing}/${storiesTotal} passing`,\n `Iterations: ${maxIterLabel}`,\n `Retries: ${retryLabel}`,\n ];\n const remaining = storiesTotal - storiesPassing;\n if (remaining > 0) {\n // p50 of execute on the #79 baseline (US-009/US-010 mean, single observation).\n const p50ExecuteSeconds = 518;\n lines.push(\n `Estimate: ~${formatDuration(remaining * p50ExecuteSeconds)} (${remaining} × p50 execute)`,\n );\n }\n if (agent) {\n const model = agent.model ? ` · ${agent.model}` : '';\n const detail = agent.detail ? ` (${agent.detail})` : '';\n lines.push(`Agent: ${agent.provider}${model}${detail}`);\n }\n printBox(lines);\n}\n\n/**\n * Print the final summary box.\n */\nexport function printSummaryBox(\n status: 'success' | 'incomplete' | 'failed',\n iterations: number,\n totalRetries: number,\n elapsedSeconds: number,\n plan: TaskPlan,\n extraInfo?: string,\n /**\n * Tokens and cost spent by this run, already resolved by the caller (the\n * process-owned counters in `core/session-metrics.ts`, never the session\n * snapshot — that one is empty whenever web monitoring is off). Omitted or\n * empty means \"the CLI reported nothing\", and the line is skipped entirely.\n */\n usage?: ClaudeUsage | null,\n usageByAgent?: Record<string, ClaudeUsage>,\n): void {\n const icons = getIcons();\n\n const storiesTotal = plan.userStories.length;\n const storiesPassing = plan.userStories.filter((s) => s.passes).length;\n const duration = formatDuration(elapsedSeconds);\n\n let statusIcon: string;\n let statusLabel: string;\n\n switch (status) {\n case 'success':\n statusIcon = icons.success;\n statusLabel = 'Completed';\n break;\n case 'incomplete':\n statusIcon = icons.warn;\n statusLabel = 'Incomplete';\n break;\n case 'failed':\n statusIcon = icons.fail;\n statusLabel = 'Failed';\n break;\n }\n\n const boxLines = [\n `${icons.end} Issue Flow Summary`,\n '---',\n `Status: ${statusIcon} ${statusLabel}`,\n `Stories: ${storiesPassing}/${storiesTotal} passing`,\n `Iterations: ${iterations}`,\n `Duration: ${duration}`,\n ];\n\n boxLines.push(...tokenBoxLines(usage, usageByAgent));\n boxLines.push(...executionCostLines(plan));\n\n boxLines.push(`Retries: ${totalRetries}`);\n\n if (extraInfo) {\n boxLines.push('---');\n boxLines.push(extraInfo);\n }\n\n console.log('');\n printBox(boxLines);\n}\n\n/**\n * What the optional `pr-review` phase produced, as far as the summary cares.\n * `null` in `recommendation` means the verdict could not be recovered from\n * disk — the exit code still told us whether changes were requested.\n */\nexport interface RunSummaryPrReview {\n requestedChanges: boolean;\n recommendation: PrReviewRecommendation | null;\n reportPath: string | null;\n}\n\n/**\n * Everything the final `run` summary prints, already resolved by the caller.\n */\nexport interface RunSummaryInfo {\n issueNumber: string;\n /** 'unknown' when the branch could not be detected. */\n branchName: string;\n /** Pipelines run with --no-branch open no Pull Request. */\n noBranch: boolean;\n storyCount: number;\n elapsedSeconds: number;\n /** 'unknown' when no Pull Request could be resolved. */\n prUrl: string;\n /** Only set when the pr-review phase actually ran. */\n prReview?: RunSummaryPrReview | null;\n /**\n * Tokens and cost of the whole pipeline, from the process-owned counters in\n * `core/session-metrics.ts`. Absent or empty omits the line.\n */\n usage?: ClaudeUsage | null;\n /** When more than one agent ran, the summary prints one Tokens line each. */\n usageByAgent?: Record<string, ClaudeUsage>;\n /** Acceptance-contract verdict. Absent when the contract never ran. */\n verification?: { verdict: 'passed' | 'failed' | 'unverified'; level?: string | null } | null;\n}\n\nfunction executionCostLines(plan: TaskPlan, prefix = 'Cost: '): string[] {\n const records = plan.executions ?? [];\n if (records.length === 0) return [];\n const lines: string[] = [];\n const groups = groupBy(records, 'harness');\n let index = 0;\n const pad = ' '.repeat(prefix.length);\n for (const [harness, summary] of groups) {\n const parts: string[] = [];\n if (summary.totalCost.reported > 0) {\n parts.push(`$${summary.totalCost.reported.toFixed(4)} reported`);\n }\n if (summary.totalCost.estimated > 0) {\n parts.push(`$${summary.totalCost.estimated.toFixed(4)} estimated`);\n }\n if (summary.totalCost.unknownExecutions > 0) {\n parts.push(`${summary.totalCost.unknownExecutions} unknown`);\n }\n if (parts.length === 0) {\n const totals = summarize(records).totalCost;\n if (totals.reported === 0 && totals.estimated === 0) {\n parts.push('not reported');\n }\n }\n if (parts.length === 0) continue;\n lines.push(`${index === 0 ? prefix : pad}${harness} · ${parts.join(' · ')}`);\n index += 1;\n }\n return lines;\n}\n\nfunction tokenBoxLines(\n usage?: ClaudeUsage | null,\n usageByAgent?: Record<string, ClaudeUsage>,\n): string[] {\n const agents = usageByAgent ? Object.keys(usageByAgent) : [];\n if (agents.length > 1) {\n const lines: string[] = [];\n for (const [index, id] of agents.entries()) {\n const tokens = formatTokens(usageByAgent?.[id]);\n if (tokens === '') continue;\n lines.push(index === 0 ? `Tokens: ${id} · ${tokens}` : ` ${id} · ${tokens}`);\n }\n return lines;\n }\n const tokens = formatTokens(usage);\n return tokens === '' ? [] : [`Tokens: ${tokens}`];\n}\n\nfunction tokenSummaryLines(\n usage?: ClaudeUsage | null,\n usageByAgent?: Record<string, ClaudeUsage>,\n prefix = ' Tokens: ',\n): string[] {\n const agents = usageByAgent ? Object.keys(usageByAgent) : [];\n if (agents.length > 1) {\n const pad = ' '.repeat(prefix.length);\n const lines: string[] = [];\n for (const [index, id] of agents.entries()) {\n const tokens = formatTokens(usageByAgent?.[id]);\n if (tokens === '') continue;\n lines.push(`${index === 0 ? prefix : pad}${id} · ${tokens}`);\n }\n return lines;\n }\n const tokens = formatTokens(usage);\n return tokens === '' ? [] : [`${prefix}${tokens}`];\n}\n\n/**\n * Detail lines of the final `run` summary (everything below the headline).\n *\n * Kept separate from the printing so the shape is testable without capturing\n * stdout. Without `prReview` the lines are exactly the ones the pipeline has\n * always printed.\n */\nexport function buildRunSummaryLines(info: RunSummaryInfo): string[] {\n const lines = [\n ` Branch: ${info.branchName}${info.noBranch ? ' (current)' : ''}`,\n ` Stories: ${info.storyCount}`,\n ` Duration: ${formatDuration(info.elapsedSeconds)}`,\n ];\n\n lines.push(...tokenSummaryLines(info.usage, info.usageByAgent));\n\n if (!info.noBranch) {\n lines.push(` PR: ${info.prUrl}`);\n }\n\n if (info.verification != null) {\n const label =\n info.verification.verdict === 'passed'\n ? 'passed'\n : info.verification.verdict === 'failed'\n ? 'failed'\n : 'unverified';\n lines.push(` Contract: ${label}`);\n }\n\n const review = info.prReview;\n if (review) {\n // The exit code is the source of truth for the verdict, so a report whose\n // recommendation could not be read still reports REQUEST_CHANGES.\n const verdict =\n review.recommendation ?? (review.requestedChanges ? 'REQUEST_CHANGES' : 'unknown');\n lines.push(` Review: ${verdict}`);\n if (review.reportPath !== null) {\n lines.push(` Report: ${review.reportPath}`);\n }\n }\n\n return lines;\n}\n\n/** One issue of a multi-issue queue, as the consolidated summary reports it. */\nexport interface QueueIssueSummary {\n id: string;\n title: string;\n storyCount: number;\n elapsedSeconds: number;\n /** Tokens and cost of this issue alone — never of the whole queue. */\n usage?: ClaudeUsage | null;\n}\n\n/** Everything the consolidated summary of a queue prints. */\nexport interface QueueSummaryInfo {\n /** Identifier of the queue, i.e. of its primary issue. */\n queueId: string;\n /** Branch every issue shared, `null` when the queue ran with --no-branch. */\n branchName: string | null;\n issues: QueueIssueSummary[];\n /** Issues discovered but left out of the run. */\n excluded: { id: string; title: string }[];\n elapsedSeconds: number;\n /** The single consolidated Pull Request, `null` when none was opened. */\n prUrl: string | null;\n /** Tokens and cost of the whole queue. */\n usage?: ClaudeUsage | null;\n /** Verdict of the pr-review phase, when the queue ran it. */\n prReview?: RunSummaryPrReview | null;\n}\n\n/**\n * Detail lines of a queue's final summary.\n *\n * Per-issue lines carry their own duration and cost, which is the visible half\n * of the per-issue metric scoping: what one issue spent is never folded into\n * another's line.\n */\nexport function buildQueueSummaryLines(info: QueueSummaryInfo): string[] {\n const lines: string[] = [];\n\n lines.push(` Branch: ${info.branchName ?? 'current'}`);\n lines.push(` Issues: ${info.issues.length}`);\n\n for (const issue of info.issues) {\n const tokens = formatTokens(issue.usage);\n const title = issue.title === '' ? '' : ` ${issue.title}`;\n lines.push(\n ` #${issue.id}${title} — ${issue.storyCount} stories, ${formatDuration(issue.elapsedSeconds)}` +\n (tokens === '' ? '' : `, ${tokens}`),\n );\n }\n\n lines.push(` Duration: ${formatDuration(info.elapsedSeconds)}`);\n\n const tokens = formatTokens(info.usage);\n if (tokens !== '') {\n lines.push(` Tokens: ${tokens}`);\n }\n if (info.prUrl !== null) {\n lines.push(` PR: ${info.prUrl}`);\n }\n if (info.excluded.length > 0) {\n lines.push(` Skipped: ${info.excluded.map((entry) => `#${entry.id}`).join(', ')}`);\n }\n\n const review = info.prReview;\n if (review) {\n lines.push(\n ` Review: ${review.recommendation ?? (review.requestedChanges ? 'REQUEST_CHANGES' : 'unknown')}`,\n );\n if (review.reportPath !== null) {\n lines.push(` Report: ${review.reportPath}`);\n }\n }\n\n return lines;\n}\n\n/** Print the consolidated summary of a multi-issue queue. */\nexport function printQueueSummary(info: QueueSummaryInfo): void {\n console.log('');\n if (info.prReview?.requestedChanges === true) {\n printWarning(`Queue finished for issue #${info.queueId}, but the PR review requested changes.`);\n } else {\n printSuccess(`Queue complete for issue #${info.queueId} (${info.issues.length} issues)!`);\n }\n for (const line of buildQueueSummaryLines(info)) {\n console.log(line);\n }\n}\n\n/**\n * Print the final summary of a `run` (the flat listing, not the box used by\n * the execute engine).\n */\nexport function printRunSummary(info: RunSummaryInfo): void {\n console.log('');\n if (info.prReview?.requestedChanges) {\n printWarning(\n `Pipeline finished for issue #${info.issueNumber}, but the PR review requested changes.`,\n );\n } else if (info.verification?.verdict === 'unverified') {\n printWarning(\n `Pipeline finished for issue #${info.issueNumber}, but the acceptance contract is unverified.`,\n );\n } else if (info.verification?.verdict === 'failed') {\n printWarning(\n `Pipeline finished for issue #${info.issueNumber}, but the acceptance contract failed.`,\n );\n } else {\n printSuccess(`Pipeline complete for issue #${info.issueNumber}!`);\n }\n for (const line of buildRunSummaryLines(info)) {\n console.log(line);\n }\n}\n","/**\n * Strip the markdown markers that leak into a terminal that cannot render them.\n *\n * Used by the clean view and by failure excerpts. Verbose mode keeps the\n * original text — this is only for the default surface.\n */\nexport function stripMarkdown(text: string): string {\n return text\n .replace(/```[\\s\\S]*?```/g, ' ')\n .replace(/`([^`]+)`/g, '$1')\n .replace(/\\*\\*([^*]+)\\*\\*/g, '$1')\n .replace(/__([^_]+)__/g, '$1')\n .replace(/(?<!\\*)\\*([^*]+)\\*(?!\\*)/g, '$1')\n .replace(/^#{1,6}\\s+/gm, '')\n .replace(/^\\s*[-*+]\\s+/gm, '')\n .replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, '$1')\n .replace(/[ \\t]{2,}/g, ' ')\n .trim();\n}\n\n/** Last non-empty lines of a failure report, already stripped of markdown. */\nexport function failureExcerpt(output: string, limit = 8): string[] {\n return output\n .split('\\n')\n .map((line) => stripMarkdown(line))\n .filter((line) => line.length > 0)\n .slice(-limit);\n}\n","import { invokeSelectedAgent } from '../agents/invoke.js';\nimport type { ClaudeResult } from '../types.js';\nimport { failureExcerpt } from '../ui/text.js';\nimport { getOutputCallback, isVerbose } from './verbose.js';\n\nexport interface ExecuteClaudeOptions {\n /**\n * Silence tolerated before the agent is considered stuck. `0` disables the\n * watchdog, which is the behaviour every release before it had.\n */\n inactivityTimeoutMs?: number;\n iteration?: number;\n correctionCycle?: number;\n storyIds?: string[];\n}\n\n/**\n * Execute the resolved agent with a prompt piped to stdin.\n *\n * The default (Claude, unconfigured) is still\n * `claude --dangerously-skip-permissions --print --output-format stream-json --verbose`\n * with the prompt on stdin and `timeout: 0`. Callers see `exitCode`, the\n * assistant text as `output`, and usage. On failure the raw diagnostics stay\n * unwrapped so `isTransientFailure()` can read them.\n */\nexport async function executeClaude(\n prompt: string,\n options: ExecuteClaudeOptions = {},\n): Promise<ClaudeResult> {\n let selected: Awaited<ReturnType<typeof invokeSelectedAgent>>;\n try {\n selected = await invokeSelectedAgent({\n prompt,\n phase: 'execute',\n timeout: 0,\n permission: 'autonomous',\n inactivityTimeoutMs: options.inactivityTimeoutMs,\n iteration: options.iteration,\n correctionCycle: options.correctionCycle,\n storyIds: options.storyIds,\n });\n } catch (err) {\n return {\n exitCode: 1,\n output: err instanceof Error ? err.message : String(err),\n cost: null,\n };\n }\n const { run } = selected;\n\n let output: string;\n let cost = run.usage;\n\n if (!run.success || run.exitCode !== 0) {\n output = run.rawOutput || run.error || '';\n cost = null;\n } else {\n output = run.result || run.rawOutput;\n }\n\n emitAgentOutput(output, !(run.success && run.exitCode === 0));\n\n return {\n exitCode: run.exitCode === 0 && run.success ? 0 : run.exitCode === 0 ? 1 : run.exitCode,\n output,\n cost,\n };\n}\n\n/**\n * Clean mode never dumps the agent report. Verbose breaks it line by line.\n * A failure always prints a short excerpt — the user should not need\n * `--verbose` to see why a story stopped.\n */\nfunction emitAgentOutput(output: string, failed: boolean): void {\n const onOutput = getOutputCallback();\n if (!onOutput) return;\n const trimmed = output.trim();\n if (!trimmed) return;\n\n if (isVerbose()) {\n for (const line of trimmed.split('\\n')) {\n if (line.trim()) onOutput(line);\n }\n return;\n }\n\n if (failed) {\n for (const line of failureExcerpt(trimmed)) {\n onOutput(line);\n }\n }\n}\n","import {\n DEFAULT_SESSION_HEARTBEAT_MS,\n MemoryPublisher,\n type MemoryPublisherOptions,\n type SessionEvent,\n type SessionPublisher,\n} from '../../core/session-state.js';\nimport { type PlanRepositoryContext, saveSessionEvent, touchStoredSession } from './repository.js';\n\n/**\n * SQLite history surface for a live session.\n *\n * The reducer remains synchronous and in-memory just like every other session\n * publisher. Database writes are serialized behind it and failures are reduced\n * to the publisher's best-effort warning contract, never a pipeline failure.\n */\nexport class SqliteSessionPublisher extends MemoryPublisher implements SessionPublisher {\n private readonly context: PlanRepositoryContext;\n private writeChain: Promise<void> = Promise.resolve();\n private readonly heartbeatTimer: NodeJS.Timeout;\n private closed = false;\n\n constructor(context: PlanRepositoryContext, options: MemoryPublisherOptions = {}) {\n super(options);\n this.context = context;\n this.heartbeatTimer = setInterval(() => this.enqueueHeartbeat(), DEFAULT_SESSION_HEARTBEAT_MS);\n this.heartbeatTimer.unref();\n }\n\n protected override afterPublish(event: SessionEvent): void {\n if (this.closed) return;\n const sessionId = this.snapshot().sessionId;\n // Events before session:start do not belong to a durable run yet.\n if (sessionId === null) return;\n const snapshot = this.snapshot();\n const sequence = this.version();\n this.writeChain = this.writeChain.then(async () => {\n try {\n await saveSessionEvent(this.context, { sessionId, sequence, event, snapshot });\n } catch (error) {\n this.warnOnce(error);\n }\n });\n }\n\n override async flush(): Promise<void> {\n await this.writeChain;\n }\n\n override async close(): Promise<void> {\n this.closed = true;\n clearInterval(this.heartbeatTimer);\n await this.flush();\n }\n\n private enqueueHeartbeat(): void {\n if (this.closed) return;\n const sessionId = this.snapshot().sessionId;\n if (sessionId === null || this.snapshot().status !== 'running') return;\n this.writeChain = this.writeChain.then(async () => {\n try {\n await touchStoredSession(this.context, sessionId);\n } catch (error) {\n this.warnOnce(error);\n }\n });\n }\n}\n"],"mappings":"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","names":["chalk","chalk","tokens","inputError","result","elapsed","numericIssue"]}
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  printWarning
4
- } from "./chunk-XZUEFTUP.js";
4
+ } from "./chunk-J6VJ5M2U.js";
5
5
  import {
6
6
  getProjectRoot
7
- } from "./chunk-YVPZUN6X.js";
7
+ } from "./chunk-TJVX42KY.js";
8
8
 
9
9
  // src/core/prompt-resolver.ts
10
- import { existsSync } from "fs";
11
- import { readFile } from "fs/promises";
12
- import { dirname, join } from "path";
13
- import { fileURLToPath } from "url";
10
+ import { existsSync } from "node:fs";
11
+ import { readFile } from "node:fs/promises";
12
+ import { dirname, join } from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
14
  function resolvePackageDir(name, startDir) {
15
15
  let dir = startDir ?? dirname(fileURLToPath(import.meta.url));
16
16
  for (let i = 0; i < 5; i++) {
@@ -95,11 +95,11 @@ ${body}`
95
95
  return result;
96
96
  }
97
97
  function applyPlaceholders(template, vars) {
98
- let result = applyConditionalSections(template, vars);
99
- for (const [key, value] of Object.entries(vars)) {
100
- result = result.replaceAll(key, value);
101
- }
102
- return result;
98
+ const result = applyConditionalSections(template, vars).replace(
99
+ /^<!-- Generated[^\n]+-->\n\n/,
100
+ ""
101
+ );
102
+ return result.replace(/__[A-Z0-9_]+__/g, (key) => vars[key] ?? key);
103
103
  }
104
104
 
105
105
  export {
@@ -107,4 +107,4 @@ export {
107
107
  loadPrompt,
108
108
  applyPlaceholders
109
109
  };
110
- //# sourceMappingURL=chunk-7F7LGMVO.js.map
110
+ //# sourceMappingURL=chunk-XKR3VD2Z.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/prompt-resolver.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { printWarning } from '../ui/logger.js';\nimport { getProjectRoot } from '../utils/git.js';\n\n/**\n * Resolve the absolute path to a directory shipped at the package root\n * (e.g. 'prompts', 'web/public'). Works from both source (src/core/) and\n * compiled (dist/) locations by walking up the directory tree until the\n * named folder is found. Returns null when it cannot be located.\n *\n * `startDir` is injectable for tests; it defaults to this module's directory.\n */\nexport function resolvePackageDir(name: string, startDir?: string): string | null {\n let dir = startDir ?? dirname(fileURLToPath(import.meta.url));\n\n for (let i = 0; i < 5; i++) {\n const candidate = join(dir, name);\n if (existsSync(candidate)) {\n return candidate;\n }\n dir = dirname(dir);\n }\n\n return null;\n}\n\nfunction getPromptsDir(): string {\n const dir = resolvePackageDir('prompts');\n if (dir === null) {\n throw new Error(\n 'Could not locate the prompts/ directory. Ensure the package is installed correctly.',\n );\n }\n return dir;\n}\n\n/** Directory a repository puts its own prompt overrides in. */\nexport const PROMPT_OVERRIDE_DIR = '.issue-flow/prompts';\n\nexport interface LoadPromptOptions {\n /**\n * Repository root holding `.issue-flow/prompts/`. Defaults to the git project\n * root; when there is none, no override is looked for.\n */\n projectRoot?: string;\n /** Warning sink. Defaults to printWarning. */\n warn?: (message: string) => void;\n}\n\n/** Read a file, answering null for \"absent\" and for \"unreadable\". */\nasync function readOverride(\n filePath: string,\n warn: (message: string) => void,\n): Promise<string | null> {\n try {\n return await readFile(filePath, 'utf-8');\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n // Absence is the common case and says nothing.\n return null;\n }\n // A file the repository *did* write and we cannot read is worth a warning:\n // silently ignoring it would look like the override simply had no effect.\n warn(`Ignoring prompt override ${filePath}: ${(err as Error).message}`);\n return null;\n }\n}\n\n/**\n * Load a prompt template by name, honouring the repository's overrides.\n *\n * Resolution order:\n *\n * 1. `<root>/.issue-flow/prompts/<name>.md` — replaces the packaged prompt;\n * 2. `<root>/.issue-flow/prompts/<name>.append.md` — appended to it;\n * 3. `prompts/<name>.md` of the package.\n *\n * `append` is the recommended form. Replacing a whole prompt makes the\n * repository inherit its maintenance: every improvement shipped by a new\n * release stops reaching it, silently.\n *\n * Overrides are opt-in and best-effort — a repository with none, or one that is\n * not a git checkout at all, gets exactly the packaged prompt it got before.\n *\n * @param name - Prompt name without extension (e.g., 'execute', 'analyze')\n * @returns The raw template content with placeholders intact\n */\nexport async function loadPrompt(name: string, options: LoadPromptOptions = {}): Promise<string> {\n const warn = options.warn ?? printWarning;\n const promptsDir = getPromptsDir();\n const filePath = join(promptsDir, `${name}.md`);\n\n let packaged: string;\n try {\n packaged = await readFile(filePath, 'utf-8');\n } catch {\n throw new Error(`Prompt template not found: ${filePath}`);\n }\n\n let root = options.projectRoot;\n if (root === undefined) {\n try {\n root = await getProjectRoot();\n } catch {\n // Not a git repository: no override directory to look in.\n return packaged;\n }\n }\n\n const overrideDir = join(root, PROMPT_OVERRIDE_DIR);\n const [replacement, appendix] = await Promise.all([\n readOverride(join(overrideDir, `${name}.md`), warn),\n readOverride(join(overrideDir, `${name}.append.md`), warn),\n ]);\n\n if (replacement !== null) {\n if (appendix !== null) {\n warn(\n `Both ${name}.md and ${name}.append.md exist in ${PROMPT_OVERRIDE_DIR}; ` +\n 'using the replacement and ignoring the appendix.',\n );\n }\n return replacement;\n }\n\n if (appendix !== null) {\n // One blank line between the two, whatever the packaged prompt ends with,\n // so the appendix never runs into the last paragraph.\n return `${packaged.replace(/\\s*$/, '')}\\n\\n${appendix}`;\n }\n\n return packaged;\n}\n\n/**\n * Drop the conditional sections whose placeholder resolves to nothing, and\n * unwrap the ones that stay.\n *\n * A prompt marks an optional section with HTML comments, which stay invisible\n * if a template is ever read as plain markdown:\n *\n * ```markdown\n * <!-- if:__REPO_POLICY__ -->\n * ## Repository policy\n *\n * __REPO_POLICY__\n * <!-- /if -->\n * ```\n *\n * Separate two consecutive blocks with a blank line. Removal consumes the\n * newline **before** the opening marker, so back-to-back blocks make the second\n * removal eat the line that ended the prompt body — the file loses its trailing\n * newline. `prompt-override.test.ts` asserts that newline for every packaged\n * prompt, which is what catches it.\n *\n * This exists for one reason: a repository that declares no policy must get a\n * prompt that is **byte for byte** the one it got before the policy layer\n * existed. Leaving an empty heading behind would break that, and an empty\n * \"Repository policy\" section is also actively harmful — it invites the agent\n * to wonder what was supposed to be there.\n */\nexport function applyConditionalSections(template: string, vars: Record<string, string>): string {\n let result = template;\n\n for (const [key, value] of Object.entries(vars)) {\n // The key is a placeholder like __REPO_POLICY__: word characters only, so\n // it needs no escaping to be safe inside the pattern below.\n if (!/^__[A-Z0-9_]+__$/.test(key)) continue;\n\n const block = new RegExp(`\\\\n?<!-- if:${key} -->\\\\n([\\\\s\\\\S]*?)<!-- /if -->\\\\n?`, 'g');\n result = result.replace(block, (_match, body: string) =>\n value.trim() === '' ? '' : `\\n${body}`,\n );\n }\n\n return result;\n}\n\n/**\n * Replace placeholders in a prompt template with actual values.\n *\n * Placeholders use the format __KEY__ (e.g., __ISSUE_NUMBER__, __PRD_FILE__).\n *\n * Conditional sections are resolved first, so a section whose placeholder is\n * empty disappears entirely instead of rendering as an empty heading.\n */\nexport function applyPlaceholders(template: string, vars: Record<string, string>): string {\n let result = applyConditionalSections(template, vars);\n for (const [key, value] of Object.entries(vars)) {\n result = result.replaceAll(key, value);\n }\n return result;\n}\n"],"mappings":";;;;;;;;;AAAA,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;AACzB,SAAS,SAAS,YAAY;AAC9B,SAAS,qBAAqB;AAYvB,SAAS,kBAAkB,MAAc,UAAkC;AAChF,MAAI,MAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AAE5D,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAM,YAAY,KAAK,KAAK,IAAI;AAChC,QAAI,WAAW,SAAS,GAAG;AACzB,aAAO;AAAA,IACT;AACA,UAAM,QAAQ,GAAG;AAAA,EACnB;AAEA,SAAO;AACT;AAEA,SAAS,gBAAwB;AAC/B,QAAM,MAAM,kBAAkB,SAAS;AACvC,MAAI,QAAQ,MAAM;AAChB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAGO,IAAM,sBAAsB;AAanC,eAAe,aACb,UACA,MACwB;AACxB,MAAI;AACF,WAAO,MAAM,SAAS,UAAU,OAAO;AAAA,EACzC,SAAS,KAAc;AACrB,QAAK,IAA8B,SAAS,UAAU;AAEpD,aAAO;AAAA,IACT;AAGA,SAAK,4BAA4B,QAAQ,KAAM,IAAc,OAAO,EAAE;AACtE,WAAO;AAAA,EACT;AACF;AAqBA,eAAsB,WAAW,MAAc,UAA6B,CAAC,GAAoB;AAC/F,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,aAAa,cAAc;AACjC,QAAM,WAAW,KAAK,YAAY,GAAG,IAAI,KAAK;AAE9C,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,SAAS,UAAU,OAAO;AAAA,EAC7C,QAAQ;AACN,UAAM,IAAI,MAAM,8BAA8B,QAAQ,EAAE;AAAA,EAC1D;AAEA,MAAI,OAAO,QAAQ;AACnB,MAAI,SAAS,QAAW;AACtB,QAAI;AACF,aAAO,MAAM,eAAe;AAAA,IAC9B,QAAQ;AAEN,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,cAAc,KAAK,MAAM,mBAAmB;AAClD,QAAM,CAAC,aAAa,QAAQ,IAAI,MAAM,QAAQ,IAAI;AAAA,IAChD,aAAa,KAAK,aAAa,GAAG,IAAI,KAAK,GAAG,IAAI;AAAA,IAClD,aAAa,KAAK,aAAa,GAAG,IAAI,YAAY,GAAG,IAAI;AAAA,EAC3D,CAAC;AAED,MAAI,gBAAgB,MAAM;AACxB,QAAI,aAAa,MAAM;AACrB;AAAA,QACE,QAAQ,IAAI,WAAW,IAAI,uBAAuB,mBAAmB;AAAA,MAEvE;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,MAAI,aAAa,MAAM;AAGrB,WAAO,GAAG,SAAS,QAAQ,QAAQ,EAAE,CAAC;AAAA;AAAA,EAAO,QAAQ;AAAA,EACvD;AAEA,SAAO;AACT;AA6BO,SAAS,yBAAyB,UAAkB,MAAsC;AAC/F,MAAI,SAAS;AAEb,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,GAAG;AAG/C,QAAI,CAAC,mBAAmB,KAAK,GAAG,EAAG;AAEnC,UAAM,QAAQ,IAAI,OAAO,eAAe,GAAG,uCAAuC,GAAG;AACrF,aAAS,OAAO;AAAA,MAAQ;AAAA,MAAO,CAAC,QAAQ,SACtC,MAAM,KAAK,MAAM,KAAK,KAAK;AAAA,EAAK,IAAI;AAAA,IACtC;AAAA,EACF;AAEA,SAAO;AACT;AAUO,SAAS,kBAAkB,UAAkB,MAAsC;AACxF,MAAI,SAAS,yBAAyB,UAAU,IAAI;AACpD,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,GAAG;AAC/C,aAAS,OAAO,WAAW,KAAK,KAAK;AAAA,EACvC;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/core/prompt-resolver.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { printWarning } from '../ui/logger.js';\nimport { getProjectRoot } from '../utils/git.js';\n\n/**\n * Resolve the absolute path to a directory shipped at the package root\n * (e.g. 'prompts', 'web/public'). Works from both source (src/core/) and\n * compiled (dist/) locations by walking up the directory tree until the\n * named folder is found. Returns null when it cannot be located.\n *\n * `startDir` is injectable for tests; it defaults to this module's directory.\n */\nexport function resolvePackageDir(name: string, startDir?: string): string | null {\n let dir = startDir ?? dirname(fileURLToPath(import.meta.url));\n\n for (let i = 0; i < 5; i++) {\n const candidate = join(dir, name);\n if (existsSync(candidate)) {\n return candidate;\n }\n dir = dirname(dir);\n }\n\n return null;\n}\n\nfunction getPromptsDir(): string {\n const dir = resolvePackageDir('prompts');\n if (dir === null) {\n throw new Error(\n 'Could not locate the prompts/ directory. Ensure the package is installed correctly.',\n );\n }\n return dir;\n}\n\n/** Directory a repository puts its own prompt overrides in. */\nexport const PROMPT_OVERRIDE_DIR = '.issue-flow/prompts';\n\nexport interface LoadPromptOptions {\n /**\n * Repository root holding `.issue-flow/prompts/`. Defaults to the git project\n * root; when there is none, no override is looked for.\n */\n projectRoot?: string;\n /** Warning sink. Defaults to printWarning. */\n warn?: (message: string) => void;\n}\n\n/** Read a file, answering null for \"absent\" and for \"unreadable\". */\nasync function readOverride(\n filePath: string,\n warn: (message: string) => void,\n): Promise<string | null> {\n try {\n return await readFile(filePath, 'utf-8');\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n // Absence is the common case and says nothing.\n return null;\n }\n // A file the repository *did* write and we cannot read is worth a warning:\n // silently ignoring it would look like the override simply had no effect.\n warn(`Ignoring prompt override ${filePath}: ${(err as Error).message}`);\n return null;\n }\n}\n\n/**\n * Load a prompt template by name, honouring the repository's overrides.\n *\n * Resolution order:\n *\n * 1. `<root>/.issue-flow/prompts/<name>.md` — replaces the packaged prompt;\n * 2. `<root>/.issue-flow/prompts/<name>.append.md` — appended to it;\n * 3. `prompts/<name>.md` of the package.\n *\n * `append` is the recommended form. Replacing a whole prompt makes the\n * repository inherit its maintenance: every improvement shipped by a new\n * release stops reaching it, silently.\n *\n * Overrides are opt-in and best-effort — a repository with none, or one that is\n * not a git checkout at all, gets exactly the packaged prompt it got before.\n *\n * @param name - Prompt name without extension (e.g., 'execute', 'analyze')\n * @returns The raw template content with placeholders intact\n */\nexport async function loadPrompt(name: string, options: LoadPromptOptions = {}): Promise<string> {\n const warn = options.warn ?? printWarning;\n const promptsDir = getPromptsDir();\n const filePath = join(promptsDir, `${name}.md`);\n\n let packaged: string;\n try {\n packaged = await readFile(filePath, 'utf-8');\n } catch {\n throw new Error(`Prompt template not found: ${filePath}`);\n }\n\n let root = options.projectRoot;\n if (root === undefined) {\n try {\n root = await getProjectRoot();\n } catch {\n // Not a git repository: no override directory to look in.\n return packaged;\n }\n }\n\n const overrideDir = join(root, PROMPT_OVERRIDE_DIR);\n const [replacement, appendix] = await Promise.all([\n readOverride(join(overrideDir, `${name}.md`), warn),\n readOverride(join(overrideDir, `${name}.append.md`), warn),\n ]);\n\n if (replacement !== null) {\n if (appendix !== null) {\n warn(\n `Both ${name}.md and ${name}.append.md exist in ${PROMPT_OVERRIDE_DIR}; ` +\n 'using the replacement and ignoring the appendix.',\n );\n }\n return replacement;\n }\n\n if (appendix !== null) {\n // One blank line between the two, whatever the packaged prompt ends with,\n // so the appendix never runs into the last paragraph.\n return `${packaged.replace(/\\s*$/, '')}\\n\\n${appendix}`;\n }\n\n return packaged;\n}\n\n/**\n * Drop the conditional sections whose placeholder resolves to nothing, and\n * unwrap the ones that stay.\n *\n * A prompt marks an optional section with HTML comments, which stay invisible\n * if a template is ever read as plain markdown:\n *\n * ```markdown\n * <!-- if:__REPO_POLICY__ -->\n * ## Repository policy\n *\n * __REPO_POLICY__\n * <!-- /if -->\n * ```\n *\n * Separate two consecutive blocks with a blank line. Removal consumes the\n * newline **before** the opening marker, so back-to-back blocks make the second\n * removal eat the line that ended the prompt body — the file loses its trailing\n * newline. `prompt-override.test.ts` asserts that newline for every packaged\n * prompt, which is what catches it.\n *\n * This exists for one reason: a repository that declares no policy must get a\n * prompt that is **byte for byte** the one it got before the policy layer\n * existed. Leaving an empty heading behind would break that, and an empty\n * \"Repository policy\" section is also actively harmful — it invites the agent\n * to wonder what was supposed to be there.\n */\nexport function applyConditionalSections(template: string, vars: Record<string, string>): string {\n let result = template;\n\n for (const [key, value] of Object.entries(vars)) {\n // The key is a placeholder like __REPO_POLICY__: word characters only, so\n // it needs no escaping to be safe inside the pattern below.\n if (!/^__[A-Z0-9_]+__$/.test(key)) continue;\n\n const block = new RegExp(`\\\\n?<!-- if:${key} -->\\\\n([\\\\s\\\\S]*?)<!-- /if -->\\\\n?`, 'g');\n result = result.replace(block, (_match, body: string) =>\n value.trim() === '' ? '' : `\\n${body}`,\n );\n }\n\n return result;\n}\n\n/**\n * Replace placeholders in a prompt template with actual values.\n *\n * Placeholders use the format __KEY__ (e.g., __ISSUE_NUMBER__, __PRD_FILE__).\n *\n * Conditional sections are resolved first, so a section whose placeholder is\n * empty disappears entirely instead of rendering as an empty heading.\n */\nexport function applyPlaceholders(template: string, vars: Record<string, string>): string {\n const result = applyConditionalSections(template, vars).replace(\n /^<!-- Generated[^\\n]+-->\\n\\n/,\n '',\n );\n return result.replace(/__[A-Z0-9_]+__/g, (key) => vars[key] ?? key);\n}\n"],"mappings":";;;;;;;;;AAAA,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;AACzB,SAAS,SAAS,YAAY;AAC9B,SAAS,qBAAqB;AAYvB,SAAS,kBAAkB,MAAc,UAAkC;AAChF,MAAI,MAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AAE5D,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAM,YAAY,KAAK,KAAK,IAAI;AAChC,QAAI,WAAW,SAAS,GAAG;AACzB,aAAO;AAAA,IACT;AACA,UAAM,QAAQ,GAAG;AAAA,EACnB;AAEA,SAAO;AACT;AAEA,SAAS,gBAAwB;AAC/B,QAAM,MAAM,kBAAkB,SAAS;AACvC,MAAI,QAAQ,MAAM;AAChB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAGO,IAAM,sBAAsB;AAanC,eAAe,aACb,UACA,MACwB;AACxB,MAAI;AACF,WAAO,MAAM,SAAS,UAAU,OAAO;AAAA,EACzC,SAAS,KAAc;AACrB,QAAK,IAA8B,SAAS,UAAU;AAEpD,aAAO;AAAA,IACT;AAGA,SAAK,4BAA4B,QAAQ,KAAM,IAAc,OAAO,EAAE;AACtE,WAAO;AAAA,EACT;AACF;AAqBA,eAAsB,WAAW,MAAc,UAA6B,CAAC,GAAoB;AAC/F,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,aAAa,cAAc;AACjC,QAAM,WAAW,KAAK,YAAY,GAAG,IAAI,KAAK;AAE9C,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,SAAS,UAAU,OAAO;AAAA,EAC7C,QAAQ;AACN,UAAM,IAAI,MAAM,8BAA8B,QAAQ,EAAE;AAAA,EAC1D;AAEA,MAAI,OAAO,QAAQ;AACnB,MAAI,SAAS,QAAW;AACtB,QAAI;AACF,aAAO,MAAM,eAAe;AAAA,IAC9B,QAAQ;AAEN,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,cAAc,KAAK,MAAM,mBAAmB;AAClD,QAAM,CAAC,aAAa,QAAQ,IAAI,MAAM,QAAQ,IAAI;AAAA,IAChD,aAAa,KAAK,aAAa,GAAG,IAAI,KAAK,GAAG,IAAI;AAAA,IAClD,aAAa,KAAK,aAAa,GAAG,IAAI,YAAY,GAAG,IAAI;AAAA,EAC3D,CAAC;AAED,MAAI,gBAAgB,MAAM;AACxB,QAAI,aAAa,MAAM;AACrB;AAAA,QACE,QAAQ,IAAI,WAAW,IAAI,uBAAuB,mBAAmB;AAAA,MAEvE;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,MAAI,aAAa,MAAM;AAGrB,WAAO,GAAG,SAAS,QAAQ,QAAQ,EAAE,CAAC;AAAA;AAAA,EAAO,QAAQ;AAAA,EACvD;AAEA,SAAO;AACT;AA6BO,SAAS,yBAAyB,UAAkB,MAAsC;AAC/F,MAAI,SAAS;AAEb,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,GAAG;AAG/C,QAAI,CAAC,mBAAmB,KAAK,GAAG,EAAG;AAEnC,UAAM,QAAQ,IAAI,OAAO,eAAe,GAAG,uCAAuC,GAAG;AACrF,aAAS,OAAO;AAAA,MAAQ;AAAA,MAAO,CAAC,QAAQ,SACtC,MAAM,KAAK,MAAM,KAAK,KAAK;AAAA,EAAK,IAAI;AAAA,IACtC;AAAA,EACF;AAEA,SAAO;AACT;AAUO,SAAS,kBAAkB,UAAkB,MAAsC;AACxF,QAAM,SAAS,yBAAyB,UAAU,IAAI,EAAE;AAAA,IACtD;AAAA,IACA;AAAA,EACF;AACA,SAAO,OAAO,QAAQ,mBAAmB,CAAC,QAAQ,KAAK,GAAG,KAAK,GAAG;AACpE;","names":[]}