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
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/telemetry/recorder.ts","../src/telemetry/pricing.ts","../src/telemetry/types.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { hostname } from 'node:os';\nimport type { ClaudeUsage } from '../core/metrics.js';\nimport { isoNow, loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { classify } from '../resilience/errors.js';\nimport type { TaskPlan } from '../types.js';\nimport { resolveCost } from './pricing.js';\nimport { redactFailureMessage } from './redact.js';\nimport { getTelemetrySessionId, setTelemetrySessionId } from './session-id.js';\nimport {\n DEFAULT_TELEMETRY_CONFIG,\n type ExecutionPurpose,\n type ExecutionRecord,\n type ExecutionStatus,\n type ExecutionTrigger,\n type NormalizedUsage,\n type StopReason,\n type TelemetryConfig,\n} from './types.js';\n\nexport interface TelemetryContext {\n tasksPath: string;\n sessionId?: string | null;\n config?: TelemetryConfig;\n}\n\nlet context: TelemetryContext | null = null;\nlet discardedCount = 0;\n\nexport function bindTelemetry(next: TelemetryContext | null): void {\n context = next;\n}\n\nexport function getTelemetryContext(): TelemetryContext | null {\n return context;\n}\n\nexport function discardedExecutionCount(): number {\n return discardedCount;\n}\n\nexport function resetTelemetryState(): void {\n context = null;\n discardedCount = 0;\n setTelemetrySessionId(null);\n}\n\nasync function resolveConfig(ctx: TelemetryContext): Promise<TelemetryConfig> {\n if (ctx.config !== undefined) return ctx.config;\n try {\n const { loadTelemetryConfig } = await import('../config.js');\n ctx.config = await loadTelemetryConfig();\n return ctx.config;\n } catch {\n return DEFAULT_TELEMETRY_CONFIG;\n }\n}\n\nfunction cap(records: ExecutionRecord[], max: number): ExecutionRecord[] {\n if (records.length <= max) return records;\n discardedCount += records.length - max;\n return records.slice(records.length - max);\n}\n\nasync function mutate(tasksPath: string, update: (plan: TaskPlan) => TaskPlan): Promise<void> {\n const plan = await loadTaskPlan(tasksPath);\n await saveTaskPlan(tasksPath, update(plan));\n}\n\nexport function usageFromClaude(usage: ClaudeUsage | null | undefined): NormalizedUsage | null {\n if (usage === undefined || usage === null) return null;\n const next: NormalizedUsage = { source: 'provider' };\n if (usage.inputTokens !== undefined) next.inputTokens = usage.inputTokens;\n if (usage.outputTokens !== undefined) next.outputTokens = usage.outputTokens;\n if (usage.cacheReadTokens !== undefined) next.cacheReadTokens = usage.cacheReadTokens;\n if (usage.cacheCreationTokens !== undefined) next.cacheCreationTokens = usage.cacheCreationTokens;\n return next;\n}\n\nexport interface BeginExecutionInput {\n purpose: ExecutionPurpose;\n attempt?: number;\n trigger?: ExecutionTrigger;\n triggerReason?: ExecutionRecord['triggerReason'];\n harness: string;\n provider?: string | null;\n harnessVersion?: string | null;\n modelRequested?: string | null;\n modelResolved?: string | null;\n modelSource?: ExecutionRecord['agent']['model']['source'];\n iteration?: number;\n correctionCycle?: number;\n storyIds?: string[];\n routingDecision?: ExecutionRecord['routingDecision'];\n}\n\nexport async function beginExecution(input: BeginExecutionInput): Promise<string | null> {\n const ctx = context;\n if (ctx === null) return null;\n const cfg = await resolveConfig(ctx);\n if (!cfg.enabled) return null;\n\n const id = randomUUID();\n const record: ExecutionRecord = {\n id,\n sessionId: ctx.sessionId ?? getTelemetrySessionId(),\n purpose: input.purpose,\n attempt: input.attempt ?? 1,\n trigger: input.trigger ?? 'initial',\n triggerReason: input.triggerReason ?? null,\n agent: {\n harness: input.harness,\n provider: input.provider ?? null,\n harnessVersion: input.harnessVersion ?? null,\n model: {\n requested: input.modelRequested ?? null,\n resolved: input.modelResolved ?? null,\n source: input.modelSource ?? (input.modelResolved ? 'provider' : 'unavailable'),\n },\n providerSessionId: null,\n },\n startedAt: isoNow(),\n finishedAt: null,\n durationMs: null,\n usage: null,\n cost: { status: 'unknown', reason: 'not_reported' },\n status: 'running',\n failure: null,\n owner: { pid: process.pid, host: hostname() },\n ...(input.iteration === undefined ? {} : { iteration: input.iteration }),\n ...(input.correctionCycle === undefined ? {} : { correctionCycle: input.correctionCycle }),\n ...(input.storyIds === undefined ? {} : { storyIds: input.storyIds }),\n ...(input.routingDecision === undefined || input.routingDecision === null\n ? {}\n : { routingDecision: input.routingDecision }),\n };\n\n try {\n await mutate(ctx.tasksPath, (plan) => ({\n ...plan,\n executions: cap([...(plan.executions ?? []), record], cfg.maxExecutions),\n }));\n const { getSessionPublisher } = await import('../core/session-publisher.js');\n getSessionPublisher().publish({\n type: 'execution:update',\n at: record.startedAt,\n execution: record,\n });\n return id;\n } catch {\n return null;\n }\n}\n\n/** Wall clock vs CLI envelope: startup the harness does not report. */\nexport function timingFromUsage(\n wallClockMs: number | null,\n usage: ClaudeUsage | null | undefined,\n): Pick<\n ExecutionRecord,\n 'cliDurationMs' | 'harnessStartupMs' | 'apiDurationMs' | 'ttftMs' | 'numTurns'\n> {\n const cli = usage?.cliDurationMs;\n const cliDurationMs = cli === undefined ? null : cli;\n const harnessStartupMs =\n wallClockMs !== null && cli !== undefined ? Math.max(0, wallClockMs - cli) : null;\n return {\n cliDurationMs,\n harnessStartupMs,\n apiDurationMs: usage?.apiDurationMs ?? null,\n ttftMs: usage?.ttftMs ?? null,\n numTurns: usage?.numTurns ?? null,\n };\n}\n\nexport async function attachVerdict(verdict: ExecutionRecord['verdict']): Promise<void> {\n const ctx = context;\n if (ctx === null || verdict == null) return;\n try {\n await mutate(ctx.tasksPath, (plan) => {\n const records = [...(plan.executions ?? [])];\n if (records.length === 0) return plan;\n const index = records.length - 1;\n const current = records[index];\n if (current === undefined) return plan;\n records[index] = { ...current, verdict };\n return { ...plan, executions: records };\n });\n } catch {\n // Observational: a failed write must never change the invocation outcome.\n }\n}\n\nexport interface EndExecutionInput {\n id: string;\n status?: ExecutionStatus;\n usage?: ClaudeUsage | null;\n reportedUsd?: number | null;\n error?: string | null;\n exitCode?: number | null;\n storyIds?: string[];\n cancelled?: boolean;\n timedOut?: boolean;\n modelResolved?: string | null;\n modelSource?: ExecutionRecord['agent']['model']['source'];\n harnessVersion?: string | null;\n providerSessionId?: string | null;\n stopReason?: StopReason | null;\n}\n\nexport async function endExecution(input: EndExecutionInput): Promise<void> {\n const ctx = context;\n if (ctx === null) return;\n const cfg = await resolveConfig(ctx);\n if (!cfg.enabled) return;\n\n const usage = usageFromClaude(input.usage);\n const timedOut = input.timedOut === true || /\\btimed out\\b/i.test(input.error ?? '');\n const cancelled = input.cancelled === true;\n let status: ExecutionStatus = input.status ?? 'completed';\n if (input.status === undefined) {\n if (cancelled) status = 'cancelled';\n else if (timedOut) status = 'timeout';\n else if (input.error) status = 'failed';\n }\n\n const failure =\n status === 'completed'\n ? null\n : {\n kind: classify({\n source: 'agent',\n stdout: input.error ?? '',\n exitCode: input.exitCode,\n timedOut,\n }).kind,\n message: redactFailureMessage(input.error ?? status),\n exitCode: input.exitCode ?? null,\n };\n\n const completed: { value: ExecutionRecord | null } = { value: null };\n try {\n await mutate(ctx.tasksPath, (plan) => {\n const records = [...(plan.executions ?? [])];\n const index = records.findIndex((record) => record.id === input.id);\n if (index === -1) return plan;\n const current = records[index];\n if (current === undefined) return plan;\n const started = Date.parse(current.startedAt);\n const finishedAt = isoNow();\n const modelResolved = input.modelResolved ?? current.agent.model.resolved;\n const modelKey = modelResolved ?? current.agent.model.requested;\n records[index] = {\n ...current,\n agent: {\n ...current.agent,\n harnessVersion: input.harnessVersion ?? current.agent.harnessVersion,\n providerSessionId: input.providerSessionId ?? current.agent.providerSessionId,\n model: {\n ...current.agent.model,\n resolved: modelResolved,\n source:\n input.modelSource ??\n (modelResolved\n ? current.agent.model.source === 'unavailable'\n ? 'provider'\n : current.agent.model.source\n : current.agent.model.source),\n },\n },\n finishedAt,\n durationMs: Number.isFinite(started) ? Math.max(0, Date.now() - started) : null,\n ...timingFromUsage(\n Number.isFinite(started) ? Math.max(0, Date.now() - started) : null,\n input.usage,\n ),\n usage,\n cost: resolveCost({\n reportedUsd: input.reportedUsd ?? input.usage?.costUsd,\n usage,\n modelKey,\n estimate: cfg.pricing.estimate,\n overrides: cfg.pricing.overrides,\n }),\n status,\n failure,\n owner: null,\n ...(input.storyIds === undefined ? {} : { storyIds: input.storyIds }),\n ...(input.stopReason === undefined ? {} : { stopReason: input.stopReason }),\n };\n completed.value = records[index] ?? null;\n return { ...plan, executions: records };\n });\n if (completed.value !== null) {\n const { getSessionPublisher } = await import('../core/session-publisher.js');\n getSessionPublisher().publish({\n type: 'execution:update',\n at: completed.value.finishedAt ?? isoNow(),\n execution: completed.value,\n });\n }\n } catch {\n // Observational: a failed write must never change the invocation outcome.\n }\n}\n\nexport async function recordInvocation<T>(\n input: BeginExecutionInput,\n invoke: () => Promise<T>,\n finish: (result: T) => EndExecutionInput,\n): Promise<T> {\n const id = await beginExecution(input);\n try {\n const result = await invoke();\n if (id !== null) {\n await endExecution({ ...finish(result), id });\n }\n return result;\n } catch (err) {\n if (id !== null) {\n await endExecution({\n id,\n status: 'failed',\n error: err instanceof Error ? err.message : String(err),\n });\n }\n throw err;\n }\n}\n","import { isoNow } from '../core/state-manager.js';\nimport type { CostRecord, NormalizedUsage, PricingSnapshot, TelemetryConfig } from './types.js';\n\nexport const PRICING_TABLE_VERSION = '2026-08-30';\n\ninterface PricingRow {\n inputPerMillion: number;\n outputPerMillion: number;\n cacheReadPerMillion?: number;\n cacheWritePerMillion?: number;\n}\n\n/** Microdollars-per-million would be more precise; dollars keep the snapshot readable. */\nconst TABLE: Record<string, PricingRow> = {\n 'claude-sonnet-4': {\n inputPerMillion: 3,\n outputPerMillion: 15,\n cacheReadPerMillion: 0.3,\n cacheWritePerMillion: 3.75,\n },\n 'claude-sonnet-4-5': {\n inputPerMillion: 3,\n outputPerMillion: 15,\n cacheReadPerMillion: 0.3,\n cacheWritePerMillion: 3.75,\n },\n 'claude-opus-4': {\n inputPerMillion: 15,\n outputPerMillion: 75,\n cacheReadPerMillion: 1.5,\n cacheWritePerMillion: 18.75,\n },\n 'gpt-5': { inputPerMillion: 1.25, outputPerMillion: 10 },\n 'gpt-5.1': { inputPerMillion: 1.25, outputPerMillion: 10 },\n};\n\n/** Aliases a harness accepts in place of a model id. */\nconst ALIASES: Record<string, string> = {\n sonnet: 'claude-sonnet-4-5',\n opus: 'claude-opus-4',\n};\n\n/**\n * Map a resolved model id onto a table key.\n *\n * What reaches here is whatever the harness reported: a dated snapshot\n * (`claude-sonnet-4-5-20250929`), a vendor-prefixed id\n * (`anthropic/claude-sonnet-4-5`) or a configured alias (`sonnet`). An exact\n * lookup misses all three, and the estimate silently degrades to `unknown`.\n * An override is always honoured under the key the user wrote.\n */\nexport function normalizeModelKey(modelKey: string): string {\n const lower = modelKey.trim().toLowerCase();\n const withoutVendor = lower.slice(lower.lastIndexOf('/') + 1);\n const withoutSnapshot = withoutVendor.replace(/-\\d{8}$/, '').replace(/-latest$/, '');\n return ALIASES[withoutSnapshot] ?? withoutSnapshot;\n}\n\nfunction rowFor(\n modelKey: string,\n overrides: TelemetryConfig['pricing']['overrides'],\n): PricingRow | null {\n const normalized = normalizeModelKey(modelKey);\n const override = overrides[modelKey] ?? overrides[normalized];\n const base = TABLE[modelKey] ?? TABLE[normalized];\n if (override === undefined && base === undefined) return null;\n return {\n inputPerMillion: override?.inputPerMillion ?? base?.inputPerMillion ?? 0,\n outputPerMillion: override?.outputPerMillion ?? base?.outputPerMillion ?? 0,\n cacheReadPerMillion: override?.cacheReadPerMillion ?? base?.cacheReadPerMillion,\n cacheWritePerMillion: override?.cacheWritePerMillion ?? base?.cacheWritePerMillion,\n };\n}\n\nfunction dollars(tokens: number | undefined, perMillion: number | undefined): number {\n if (tokens === undefined || perMillion === undefined) return 0;\n return (tokens * perMillion) / 1_000_000;\n}\n\nexport function resolveCost(options: {\n reportedUsd?: number | null;\n usage: NormalizedUsage | null;\n modelKey: string | null;\n estimate: boolean;\n overrides?: TelemetryConfig['pricing']['overrides'];\n}): CostRecord {\n if (options.reportedUsd !== undefined && options.reportedUsd !== null) {\n return { status: 'reported', amount: options.reportedUsd, currency: 'USD' };\n }\n if (!options.estimate) {\n return { status: 'unknown', reason: 'not_reported' };\n }\n if (options.modelKey === null || options.modelKey === '') {\n return { status: 'unknown', reason: 'unknown_model' };\n }\n const row = rowFor(options.modelKey, options.overrides ?? {});\n if (row === null) {\n return { status: 'unknown', reason: 'unknown_model' };\n }\n if (options.usage === null || options.usage.source === 'unavailable') {\n return { status: 'unknown', reason: 'not_reported' };\n }\n const amount =\n dollars(options.usage.inputTokens, row.inputPerMillion) +\n dollars(options.usage.outputTokens, row.outputPerMillion) +\n dollars(options.usage.cacheReadTokens, row.cacheReadPerMillion) +\n dollars(options.usage.cacheCreationTokens, row.cacheWritePerMillion);\n const pricing: PricingSnapshot = {\n tableVersion: PRICING_TABLE_VERSION,\n modelKey: options.modelKey,\n inputPerMillion: row.inputPerMillion,\n outputPerMillion: row.outputPerMillion,\n capturedAt: isoNow(),\n ...(row.cacheReadPerMillion === undefined\n ? {}\n : { cacheReadPerMillion: row.cacheReadPerMillion }),\n ...(row.cacheWritePerMillion === undefined\n ? {}\n : { cacheWritePerMillion: row.cacheWritePerMillion }),\n };\n return { status: 'estimated', amount, currency: 'USD', pricing };\n}\n\nexport function estimateCost(\n usage: NormalizedUsage,\n modelKey: string,\n overrides: TelemetryConfig['pricing']['overrides'] = {},\n): CostRecord {\n return resolveCost({ usage, modelKey, estimate: true, overrides });\n}\n","import type { FailureKind } from '../resilience/errors.js';\n\nexport const EXECUTION_PURPOSES = [\n 'analyze',\n 'generate',\n 'prd',\n 'plan',\n 'execute',\n 'review',\n 'pr',\n 'pr-review',\n 'verify',\n] as const;\n\nexport type ExecutionPurpose = (typeof EXECUTION_PURPOSES)[number];\n\nexport type ExecutionTrigger = 'initial' | 'retry' | 'fallback' | 'correction' | 'escalation';\n\nexport type ExecutionStatus =\n | 'running'\n | 'completed'\n | 'failed'\n | 'timeout'\n | 'cancelled'\n | 'interrupted';\n\nexport type StopReason =\n | 'completed'\n | 'failed'\n | 'timeout'\n | 'cancelled'\n | 'max_attempts'\n | 'max_cost'\n | 'max_duration';\n\nexport type VerdictStatus = 'passed' | 'failed' | 'unverified';\n\nexport interface NormalizedUsage {\n inputTokens?: number;\n outputTokens?: number;\n cacheReadTokens?: number;\n cacheCreationTokens?: number;\n reasoningTokens?: number;\n details?: Record<string, number>;\n source: 'provider' | 'unavailable';\n}\n\nexport interface PricingSnapshot {\n tableVersion: string;\n modelKey: string;\n inputPerMillion: number;\n outputPerMillion: number;\n cacheReadPerMillion?: number;\n cacheWritePerMillion?: number;\n capturedAt: string;\n}\n\nexport type CostRecord =\n | { status: 'reported'; amount: number; currency: 'USD' }\n | { status: 'estimated'; amount: number; currency: 'USD'; pricing: PricingSnapshot }\n | {\n status: 'unknown';\n reason: 'not_reported' | 'no_pricing' | 'unknown_model' | 'subscription' | 'zero_rated';\n };\n\nexport interface ExecutionAgent {\n harness: string;\n provider: string | null;\n harnessVersion?: string | null;\n model: {\n requested: string | null;\n resolved: string | null;\n source: 'provider' | 'config' | 'routing' | 'unavailable';\n };\n providerSessionId: string | null;\n}\n\nexport interface ExecutionOwner {\n pid: number;\n host: string;\n}\n\nexport interface ExecutionVerdict {\n status: VerdictStatus;\n level?: string | null;\n independence?: string | null;\n}\n\nexport interface RoutingDecision {\n selected: string | { harness: string; provider: string; model?: string | null; tier?: string };\n actual?: string | { harness: string; provider: string; model?: string | null; tier?: string };\n candidates?: unknown[];\n reasonCodes?: string[];\n [key: string]: unknown;\n}\n\nexport interface ExecutionRecord {\n id: string;\n sessionId: string | null;\n purpose: ExecutionPurpose;\n attempt: number;\n trigger: ExecutionTrigger;\n triggerReason: FailureKind | null;\n agent: ExecutionAgent;\n startedAt: string;\n finishedAt: string | null;\n durationMs: number | null;\n /** Envelope duration when the CLI reports it. */\n cliDurationMs?: number | null;\n /** wallClock − cliDuration: startup the CLI does not see. */\n harnessStartupMs?: number | null;\n /** Envelope `duration_api_ms` when reported. */\n apiDurationMs?: number | null;\n /** Time to first output, when reported. */\n ttftMs?: number | null;\n numTurns?: number | null;\n usage: NormalizedUsage | null;\n cost: CostRecord;\n status: ExecutionStatus;\n failure: {\n kind: FailureKind;\n message: string;\n exitCode: number | null;\n } | null;\n stopReason?: StopReason | null;\n iteration?: number;\n /** Correction cycle active when this invocation started; 0 is the initial pass. */\n correctionCycle?: number;\n storyIds?: string[];\n owner?: ExecutionOwner | null;\n routingDecision?: RoutingDecision | null;\n verdict?: ExecutionVerdict | null;\n}\n\nexport interface CostTotals {\n reported: number;\n estimated: number;\n unknownExecutions: number;\n}\n\nexport interface ExecutionSummary {\n count: number;\n discarded: number;\n byStatus: Partial<Record<ExecutionStatus, number>>;\n usage: {\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens: number;\n cacheCreationTokens: number;\n reasoningTokens: number;\n };\n totalCost: CostTotals;\n}\n\nexport interface TelemetryConfig {\n enabled: boolean;\n maxExecutions: number;\n pricing: {\n estimate: boolean;\n overrides: Record<string, Partial<PricingSnapshot>>;\n };\n}\n\nexport const DEFAULT_TELEMETRY_CONFIG: TelemetryConfig = {\n enabled: true,\n maxExecutions: 500,\n pricing: { estimate: false, overrides: {} },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;;;ACElB,IAAM,wBAAwB;AAUrC,IAAM,QAAoC;AAAA,EACxC,mBAAmB;AAAA,IACjB,iBAAiB;AAAA,IACjB,kBAAkB;AAAA,IAClB,qBAAqB;AAAA,IACrB,sBAAsB;AAAA,EACxB;AAAA,EACA,qBAAqB;AAAA,IACnB,iBAAiB;AAAA,IACjB,kBAAkB;AAAA,IAClB,qBAAqB;AAAA,IACrB,sBAAsB;AAAA,EACxB;AAAA,EACA,iBAAiB;AAAA,IACf,iBAAiB;AAAA,IACjB,kBAAkB;AAAA,IAClB,qBAAqB;AAAA,IACrB,sBAAsB;AAAA,EACxB;AAAA,EACA,SAAS,EAAE,iBAAiB,MAAM,kBAAkB,GAAG;AAAA,EACvD,WAAW,EAAE,iBAAiB,MAAM,kBAAkB,GAAG;AAC3D;AAGA,IAAM,UAAkC;AAAA,EACtC,QAAQ;AAAA,EACR,MAAM;AACR;AAWO,SAAS,kBAAkB,UAA0B;AAC1D,QAAM,QAAQ,SAAS,KAAK,EAAE,YAAY;AAC1C,QAAM,gBAAgB,MAAM,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC;AAC5D,QAAM,kBAAkB,cAAc,QAAQ,WAAW,EAAE,EAAE,QAAQ,YAAY,EAAE;AACnF,SAAO,QAAQ,eAAe,KAAK;AACrC;AAEA,SAAS,OACP,UACA,WACmB;AACnB,QAAM,aAAa,kBAAkB,QAAQ;AAC7C,QAAM,WAAW,UAAU,QAAQ,KAAK,UAAU,UAAU;AAC5D,QAAM,OAAO,MAAM,QAAQ,KAAK,MAAM,UAAU;AAChD,MAAI,aAAa,UAAa,SAAS,OAAW,QAAO;AACzD,SAAO;AAAA,IACL,iBAAiB,UAAU,mBAAmB,MAAM,mBAAmB;AAAA,IACvE,kBAAkB,UAAU,oBAAoB,MAAM,oBAAoB;AAAA,IAC1E,qBAAqB,UAAU,uBAAuB,MAAM;AAAA,IAC5D,sBAAsB,UAAU,wBAAwB,MAAM;AAAA,EAChE;AACF;AAEA,SAAS,QAAQ,QAA4B,YAAwC;AACnF,MAAI,WAAW,UAAa,eAAe,OAAW,QAAO;AAC7D,SAAQ,SAAS,aAAc;AACjC;AAEO,SAAS,YAAY,SAMb;AACb,MAAI,QAAQ,gBAAgB,UAAa,QAAQ,gBAAgB,MAAM;AACrE,WAAO,EAAE,QAAQ,YAAY,QAAQ,QAAQ,aAAa,UAAU,MAAM;AAAA,EAC5E;AACA,MAAI,CAAC,QAAQ,UAAU;AACrB,WAAO,EAAE,QAAQ,WAAW,QAAQ,eAAe;AAAA,EACrD;AACA,MAAI,QAAQ,aAAa,QAAQ,QAAQ,aAAa,IAAI;AACxD,WAAO,EAAE,QAAQ,WAAW,QAAQ,gBAAgB;AAAA,EACtD;AACA,QAAM,MAAM,OAAO,QAAQ,UAAU,QAAQ,aAAa,CAAC,CAAC;AAC5D,MAAI,QAAQ,MAAM;AAChB,WAAO,EAAE,QAAQ,WAAW,QAAQ,gBAAgB;AAAA,EACtD;AACA,MAAI,QAAQ,UAAU,QAAQ,QAAQ,MAAM,WAAW,eAAe;AACpE,WAAO,EAAE,QAAQ,WAAW,QAAQ,eAAe;AAAA,EACrD;AACA,QAAM,SACJ,QAAQ,QAAQ,MAAM,aAAa,IAAI,eAAe,IACtD,QAAQ,QAAQ,MAAM,cAAc,IAAI,gBAAgB,IACxD,QAAQ,QAAQ,MAAM,iBAAiB,IAAI,mBAAmB,IAC9D,QAAQ,QAAQ,MAAM,qBAAqB,IAAI,oBAAoB;AACrE,QAAM,UAA2B;AAAA,IAC/B,cAAc;AAAA,IACd,UAAU,QAAQ;AAAA,IAClB,iBAAiB,IAAI;AAAA,IACrB,kBAAkB,IAAI;AAAA,IACtB,YAAY,OAAO;AAAA,IACnB,GAAI,IAAI,wBAAwB,SAC5B,CAAC,IACD,EAAE,qBAAqB,IAAI,oBAAoB;AAAA,IACnD,GAAI,IAAI,yBAAyB,SAC7B,CAAC,IACD,EAAE,sBAAsB,IAAI,qBAAqB;AAAA,EACvD;AACA,SAAO,EAAE,QAAQ,aAAa,QAAQ,UAAU,OAAO,QAAQ;AACjE;;;ACvHO,IAAM,qBAAqB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAuJO,IAAM,2BAA4C;AAAA,EACvD,SAAS;AAAA,EACT,eAAe;AAAA,EACf,SAAS,EAAE,UAAU,OAAO,WAAW,CAAC,EAAE;AAC5C;;;AF7IA,IAAI,UAAmC;AACvC,IAAI,iBAAiB;AAEd,SAAS,cAAc,MAAqC;AACjE,YAAU;AACZ;AAEO,SAAS,sBAA+C;AAC7D,SAAO;AACT;AAEO,SAAS,0BAAkC;AAChD,SAAO;AACT;AAEO,SAAS,sBAA4B;AAC1C,YAAU;AACV,mBAAiB;AACjB,wBAAsB,IAAI;AAC5B;AAEA,eAAe,cAAc,KAAiD;AAC5E,MAAI,IAAI,WAAW,OAAW,QAAO,IAAI;AACzC,MAAI;AACF,UAAM,EAAE,oBAAoB,IAAI,MAAM,OAAO,sBAAc;AAC3D,QAAI,SAAS,MAAM,oBAAoB;AACvC,WAAO,IAAI;AAAA,EACb,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,IAAI,SAA4B,KAAgC;AACvE,MAAI,QAAQ,UAAU,IAAK,QAAO;AAClC,oBAAkB,QAAQ,SAAS;AACnC,SAAO,QAAQ,MAAM,QAAQ,SAAS,GAAG;AAC3C;AAEA,eAAe,OAAO,WAAmB,QAAqD;AAC5F,QAAM,OAAO,MAAM,aAAa,SAAS;AACzC,QAAM,aAAa,WAAW,OAAO,IAAI,CAAC;AAC5C;AAEO,SAAS,gBAAgB,OAA+D;AAC7F,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,QAAM,OAAwB,EAAE,QAAQ,WAAW;AACnD,MAAI,MAAM,gBAAgB,OAAW,MAAK,cAAc,MAAM;AAC9D,MAAI,MAAM,iBAAiB,OAAW,MAAK,eAAe,MAAM;AAChE,MAAI,MAAM,oBAAoB,OAAW,MAAK,kBAAkB,MAAM;AACtE,MAAI,MAAM,wBAAwB,OAAW,MAAK,sBAAsB,MAAM;AAC9E,SAAO;AACT;AAmBA,eAAsB,eAAe,OAAoD;AACvF,QAAM,MAAM;AACZ,MAAI,QAAQ,KAAM,QAAO;AACzB,QAAM,MAAM,MAAM,cAAc,GAAG;AACnC,MAAI,CAAC,IAAI,QAAS,QAAO;AAEzB,QAAM,KAAK,WAAW;AACtB,QAAM,SAA0B;AAAA,IAC9B;AAAA,IACA,WAAW,IAAI,aAAa,sBAAsB;AAAA,IAClD,SAAS,MAAM;AAAA,IACf,SAAS,MAAM,WAAW;AAAA,IAC1B,SAAS,MAAM,WAAW;AAAA,IAC1B,eAAe,MAAM,iBAAiB;AAAA,IACtC,OAAO;AAAA,MACL,SAAS,MAAM;AAAA,MACf,UAAU,MAAM,YAAY;AAAA,MAC5B,gBAAgB,MAAM,kBAAkB;AAAA,MACxC,OAAO;AAAA,QACL,WAAW,MAAM,kBAAkB;AAAA,QACnC,UAAU,MAAM,iBAAiB;AAAA,QACjC,QAAQ,MAAM,gBAAgB,MAAM,gBAAgB,aAAa;AAAA,MACnE;AAAA,MACA,mBAAmB;AAAA,IACrB;AAAA,IACA,WAAW,OAAO;AAAA,IAClB,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,MAAM,EAAE,QAAQ,WAAW,QAAQ,eAAe;AAAA,IAClD,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,OAAO,EAAE,KAAK,QAAQ,KAAK,MAAM,SAAS,EAAE;AAAA,IAC5C,GAAI,MAAM,cAAc,SAAY,CAAC,IAAI,EAAE,WAAW,MAAM,UAAU;AAAA,IACtE,GAAI,MAAM,oBAAoB,SAAY,CAAC,IAAI,EAAE,iBAAiB,MAAM,gBAAgB;AAAA,IACxF,GAAI,MAAM,aAAa,SAAY,CAAC,IAAI,EAAE,UAAU,MAAM,SAAS;AAAA,IACnE,GAAI,MAAM,oBAAoB,UAAa,MAAM,oBAAoB,OACjE,CAAC,IACD,EAAE,iBAAiB,MAAM,gBAAgB;AAAA,EAC/C;AAEA,MAAI;AACF,UAAM,OAAO,IAAI,WAAW,CAAC,UAAU;AAAA,MACrC,GAAG;AAAA,MACH,YAAY,IAAI,CAAC,GAAI,KAAK,cAAc,CAAC,GAAI,MAAM,GAAG,IAAI,aAAa;AAAA,IACzE,EAAE;AACF,UAAM,EAAE,oBAAoB,IAAI,MAAM,OAAO,iCAA8B;AAC3E,wBAAoB,EAAE,QAAQ;AAAA,MAC5B,MAAM;AAAA,MACN,IAAI,OAAO;AAAA,MACX,WAAW;AAAA,IACb,CAAC;AACD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,gBACd,aACA,OAIA;AACA,QAAM,MAAM,OAAO;AACnB,QAAM,gBAAgB,QAAQ,SAAY,OAAO;AACjD,QAAM,mBACJ,gBAAgB,QAAQ,QAAQ,SAAY,KAAK,IAAI,GAAG,cAAc,GAAG,IAAI;AAC/E,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,eAAe,OAAO,iBAAiB;AAAA,IACvC,QAAQ,OAAO,UAAU;AAAA,IACzB,UAAU,OAAO,YAAY;AAAA,EAC/B;AACF;AAEA,eAAsB,cAAc,SAAoD;AACtF,QAAM,MAAM;AACZ,MAAI,QAAQ,QAAQ,WAAW,KAAM;AACrC,MAAI;AACF,UAAM,OAAO,IAAI,WAAW,CAAC,SAAS;AACpC,YAAM,UAAU,CAAC,GAAI,KAAK,cAAc,CAAC,CAAE;AAC3C,UAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,YAAM,QAAQ,QAAQ,SAAS;AAC/B,YAAM,UAAU,QAAQ,KAAK;AAC7B,UAAI,YAAY,OAAW,QAAO;AAClC,cAAQ,KAAK,IAAI,EAAE,GAAG,SAAS,QAAQ;AACvC,aAAO,EAAE,GAAG,MAAM,YAAY,QAAQ;AAAA,IACxC,CAAC;AAAA,EACH,QAAQ;AAAA,EAER;AACF;AAmBA,eAAsB,aAAa,OAAyC;AAC1E,QAAM,MAAM;AACZ,MAAI,QAAQ,KAAM;AAClB,QAAM,MAAM,MAAM,cAAc,GAAG;AACnC,MAAI,CAAC,IAAI,QAAS;AAElB,QAAM,QAAQ,gBAAgB,MAAM,KAAK;AACzC,QAAM,WAAW,MAAM,aAAa,QAAQ,iBAAiB,KAAK,MAAM,SAAS,EAAE;AACnF,QAAM,YAAY,MAAM,cAAc;AACtC,MAAI,SAA0B,MAAM,UAAU;AAC9C,MAAI,MAAM,WAAW,QAAW;AAC9B,QAAI,UAAW,UAAS;AAAA,aACf,SAAU,UAAS;AAAA,aACnB,MAAM,MAAO,UAAS;AAAA,EACjC;AAEA,QAAM,UACJ,WAAW,cACP,OACA;AAAA,IACE,MAAM,SAAS;AAAA,MACb,QAAQ;AAAA,MACR,QAAQ,MAAM,SAAS;AAAA,MACvB,UAAU,MAAM;AAAA,MAChB;AAAA,IACF,CAAC,EAAE;AAAA,IACH,SAAS,qBAAqB,MAAM,SAAS,MAAM;AAAA,IACnD,UAAU,MAAM,YAAY;AAAA,EAC9B;AAEN,QAAM,YAA+C,EAAE,OAAO,KAAK;AACnE,MAAI;AACF,UAAM,OAAO,IAAI,WAAW,CAAC,SAAS;AACpC,YAAM,UAAU,CAAC,GAAI,KAAK,cAAc,CAAC,CAAE;AAC3C,YAAM,QAAQ,QAAQ,UAAU,CAAC,WAAW,OAAO,OAAO,MAAM,EAAE;AAClE,UAAI,UAAU,GAAI,QAAO;AACzB,YAAM,UAAU,QAAQ,KAAK;AAC7B,UAAI,YAAY,OAAW,QAAO;AAClC,YAAM,UAAU,KAAK,MAAM,QAAQ,SAAS;AAC5C,YAAM,aAAa,OAAO;AAC1B,YAAM,gBAAgB,MAAM,iBAAiB,QAAQ,MAAM,MAAM;AACjE,YAAM,WAAW,iBAAiB,QAAQ,MAAM,MAAM;AACtD,cAAQ,KAAK,IAAI;AAAA,QACf,GAAG;AAAA,QACH,OAAO;AAAA,UACL,GAAG,QAAQ;AAAA,UACX,gBAAgB,MAAM,kBAAkB,QAAQ,MAAM;AAAA,UACtD,mBAAmB,MAAM,qBAAqB,QAAQ,MAAM;AAAA,UAC5D,OAAO;AAAA,YACL,GAAG,QAAQ,MAAM;AAAA,YACjB,UAAU;AAAA,YACV,QACE,MAAM,gBACL,gBACG,QAAQ,MAAM,MAAM,WAAW,gBAC7B,aACA,QAAQ,MAAM,MAAM,SACtB,QAAQ,MAAM,MAAM;AAAA,UAC5B;AAAA,QACF;AAAA,QACA;AAAA,QACA,YAAY,OAAO,SAAS,OAAO,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,IAAI;AAAA,QAC3E,GAAG;AAAA,UACD,OAAO,SAAS,OAAO,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,IAAI;AAAA,UAC/D,MAAM;AAAA,QACR;AAAA,QACA;AAAA,QACA,MAAM,YAAY;AAAA,UAChB,aAAa,MAAM,eAAe,MAAM,OAAO;AAAA,UAC/C;AAAA,UACA;AAAA,UACA,UAAU,IAAI,QAAQ;AAAA,UACtB,WAAW,IAAI,QAAQ;AAAA,QACzB,CAAC;AAAA,QACD;AAAA,QACA;AAAA,QACA,OAAO;AAAA,QACP,GAAI,MAAM,aAAa,SAAY,CAAC,IAAI,EAAE,UAAU,MAAM,SAAS;AAAA,QACnE,GAAI,MAAM,eAAe,SAAY,CAAC,IAAI,EAAE,YAAY,MAAM,WAAW;AAAA,MAC3E;AACA,gBAAU,QAAQ,QAAQ,KAAK,KAAK;AACpC,aAAO,EAAE,GAAG,MAAM,YAAY,QAAQ;AAAA,IACxC,CAAC;AACD,QAAI,UAAU,UAAU,MAAM;AAC5B,YAAM,EAAE,oBAAoB,IAAI,MAAM,OAAO,iCAA8B;AAC3E,0BAAoB,EAAE,QAAQ;AAAA,QAC5B,MAAM;AAAA,QACN,IAAI,UAAU,MAAM,cAAc,OAAO;AAAA,QACzC,WAAW,UAAU;AAAA,MACvB,CAAC;AAAA,IACH;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAEA,eAAsB,iBACpB,OACA,QACA,QACY;AACZ,QAAM,KAAK,MAAM,eAAe,KAAK;AACrC,MAAI;AACF,UAAM,SAAS,MAAM,OAAO;AAC5B,QAAI,OAAO,MAAM;AACf,YAAM,aAAa,EAAE,GAAG,OAAO,MAAM,GAAG,GAAG,CAAC;AAAA,IAC9C;AACA,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,QAAI,OAAO,MAAM;AACf,YAAM,aAAa;AAAA,QACjB;AAAA,QACA,QAAQ;AAAA,QACR,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACxD,CAAC;AAAA,IACH;AACA,UAAM;AAAA,EACR;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/utils/shell.ts","../src/resilience/policy.ts","../src/resilience/retry.ts","../src/utils/git.ts"],"sourcesContent":["import { basename } from 'node:path';\nimport { type Options as ExecaOptions, execa } from 'execa';\nimport {\n type ClassifiedFailure,\n classify,\n type FailureSignal,\n type FailureSource,\n} from '../resilience/errors.js';\nimport type { RetryPolicy } from '../resilience/policy.js';\nimport { type RetryPolicyFor, withRetry } from '../resilience/retry.js';\n\nexport interface ExecResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n /**\n * The classified failure of the last attempt. Only ever set on the retry\n * path, and only when that attempt failed — a caller that passes no `retry`\n * gets exactly the three fields it has always got.\n */\n failure?: ClassifiedFailure;\n /** How many times the command actually ran. Only set on the retry path. */\n attempts?: number;\n}\n\n/** What `onRetryAttempt` is told once an attempt has produced a result. */\nexport interface RunRetryAttempt {\n /** 1-based number of the attempt that just finished. */\n attempt: number;\n result: ExecResult;\n /** `null` when the attempt succeeded. */\n failure: ClassifiedFailure | null;\n /** Whether another attempt follows this one. */\n willRetry: boolean;\n /** The wait before that next attempt; `0` when there is none. */\n delayMs: number;\n}\n\nexport interface RunOptions extends ExecaOptions {\n /** Skip global diagnostics when a non-zero exit is an expected probe result. */\n diagnostics?: boolean;\n /**\n * Opt in to retrying this invocation.\n *\n * Absent — the default, and what every call site of this project did before\n * this option existed — means one `execa` call with `reject: false` and no\n * extra attempt, byte for byte.\n *\n * Only pass a policy for a command whose non-zero exit means *it failed*.\n * A command whose non-zero exit is a legitimate answer (`git rev-parse\n * --verify --quiet`, `gh pr view` on a branch with no PR) is safe anyway —\n * an unclassifiable exit lands on `unknown`, which is not a retryable kind —\n * but the intent is worth being explicit about.\n *\n * A function instead of a policy resolves it from the failure that actually\n * happened, which is how one `gh` call gets the `network` budget for a DNS\n * blip and the `rate_limit` budget for a rate limit.\n */\n retry?: RetryPolicy | RetryPolicyFor;\n /** Overrides the subsystem inferred from `command` (`git`, `gh`). */\n source?: FailureSource;\n /**\n * Cuts a pending backoff short. Named apart from execa's own `cancelSignal`\n * because it governs the wait *between* attempts, not the child process.\n */\n retrySignal?: AbortSignal;\n /** Observe each attempt: log it, publish an event, persist the error. */\n onRetryAttempt?: (info: RunRetryAttempt) => void | Promise<void>;\n}\n\n/* ── which subsystem produced the failure ───────────────────────────────── */\n\nconst SOURCE_BY_COMMAND: Readonly<Record<string, FailureSource>> = {\n git: 'git',\n gh: 'github',\n};\n\nfunction inferSource(command: string): FailureSource {\n return SOURCE_BY_COMMAND[basename(command)] ?? 'internal';\n}\n\nfunction diagnoseCommandFailure(command: string, args: string[], result: ExecResult): void {\n if (result.exitCode === 0) return;\n void import('../storage/diagnostics.js').then(({ writeDiagnostic }) => {\n writeDiagnostic({\n level: 'warning',\n message: `Command failed: ${basename(command)}`,\n context: {\n command: basename(command),\n args,\n exitCode: result.exitCode,\n stderr: result.stderr.slice(0, 4_000),\n },\n });\n });\n}\n\n/* ── git invocations that are never repeated ────────────────────────────── */\n\n/**\n * `git` global options that swallow the token after them, so the subcommand of\n * `git -C /repo push --force` is `push` and not `/repo`.\n */\nconst GIT_GLOBAL_OPTIONS_WITH_VALUE: ReadonlySet<string> = new Set([\n '-C',\n '-c',\n '--git-dir',\n '--work-tree',\n '--namespace',\n '--exec-path',\n '--super-prefix',\n]);\n\n/**\n * git invocations no retry policy may repeat, whatever the caller asked for.\n *\n * `true` means the subcommand is never retried; a list means it is destructive\n * only when one of those tokens is present. This is the second of the Epic's\n * three limits — *no destructive operation is run automatically to fix state* —\n * enforced at the one chokepoint every `git` call of the project goes through.\n *\n * It errs on the side of not retrying: a sequencer operation left half-applied\n * (`rebase`, `cherry-pick`, `merge`, `am`, `revert`) is exactly the state a\n * blind second attempt turns into a lost commit, so those are absolute even\n * though a transient failure of one of them is conceivable.\n */\nconst DESTRUCTIVE_GIT_COMMANDS: Readonly<Record<string, true | readonly string[]>> = {\n rebase: true,\n 'cherry-pick': true,\n revert: true,\n merge: true,\n am: true,\n restore: true,\n 'filter-branch': true,\n gc: true,\n prune: true,\n 'update-ref': true,\n push: ['-f', '--force', '--force-with-lease', '--force-if-includes', '--delete', '--mirror'],\n reset: ['--hard', '--merge', '--keep'],\n clean: ['-f', '--force'],\n checkout: ['-f', '--force', '-B'],\n switch: ['-f', '--force', '--discard-changes'],\n branch: ['-d', '-D', '--delete', '-M'],\n tag: ['-d', '--delete', '-f', '--force'],\n stash: ['drop', 'clear', 'pop'],\n worktree: ['remove', 'prune'],\n submodule: ['deinit'],\n};\n\nfunction gitSubcommand(args: readonly string[]): { name: string; rest: readonly string[] } | null {\n for (let index = 0; index < args.length; index++) {\n const arg = args[index];\n if (arg === undefined) continue;\n if (GIT_GLOBAL_OPTIONS_WITH_VALUE.has(arg)) {\n index++;\n continue;\n }\n if (arg.startsWith('-')) continue;\n return { name: arg, rest: args.slice(index + 1) };\n }\n return null;\n}\n\n/**\n * Whether `rest` carries `token`. Long options also match their `=value` form,\n * and a short option also matches bundled into another (`git clean -fd` is\n * `-f -d`), which is how `-f` is actually written in practice.\n */\nfunction hasToken(rest: readonly string[], token: string): boolean {\n if (token.startsWith('--')) {\n return rest.some((arg) => arg === token || arg.startsWith(`${token}=`));\n }\n if (token.startsWith('-')) {\n const letter = token.slice(1);\n return rest.some((arg) => /^-[A-Za-z]+$/.test(arg) && arg.slice(1).includes(letter));\n }\n return rest.includes(token);\n}\n\n/** Whether this invocation may be retried at all, whatever the policy says. */\nexport function isRetryableInvocation(command: string, args: readonly string[]): boolean {\n if (basename(command) !== 'git') return true;\n\n const subcommand = gitSubcommand(args);\n if (subcommand === null) return true;\n\n const rule = DESTRUCTIVE_GIT_COMMANDS[subcommand.name];\n if (rule === undefined) return true;\n if (rule === true) return false;\n\n return !rule.some((token) => hasToken(subcommand.rest, token));\n}\n\n/* ── the failure signal ─────────────────────────────────────────────────── */\n\n/**\n * The fields execa puts on a failed result when `reject: false` is set. They\n * are not on its success type, hence the narrowing: `code` is where the spawn\n * errno lands (`ENOENT`, `ETIMEDOUT`, `ECONNRESET`, `EAI_AGAIN`), and\n * `originalMessage` is the only place the reason shows up when the child never\n * started and therefore wrote nothing to stderr.\n */\ninterface ExecaFailureFields {\n code?: unknown;\n signal?: unknown;\n timedOut?: unknown;\n originalMessage?: unknown;\n shortMessage?: unknown;\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' && value !== '' ? value : undefined;\n}\n\nfunction toFailureSignal(raw: unknown, result: ExecResult, source: FailureSource): FailureSignal {\n const fields = (raw ?? {}) as ExecaFailureFields;\n const errno = asString(fields.code);\n const signal = asString(fields.signal);\n // A command that never started has an empty stderr; execa's own message is\n // then the only evidence there is, so it stands in for the missing output.\n const stderr =\n result.stderr.trim() !== ''\n ? result.stderr\n : (asString(fields.originalMessage) ?? asString(fields.shortMessage) ?? '');\n\n return {\n source,\n exitCode: result.exitCode,\n stdout: result.stdout,\n stderr,\n ...(errno === undefined ? {} : { errno }),\n ...(signal === undefined ? {} : { signal }),\n ...(fields.timedOut === true ? { timedOut: true } : {}),\n };\n}\n\n/* ── the executor ───────────────────────────────────────────────────────── */\n\nasync function execOnce(\n command: string,\n args: string[],\n options: ExecaOptions,\n): Promise<{ result: ExecResult; raw: unknown }> {\n const raw = await execa(command, args, {\n reject: false,\n ...options,\n });\n\n return {\n raw,\n result: {\n stdout: raw.stdout?.toString() ?? '',\n stderr: raw.stderr?.toString() ?? '',\n exitCode: raw.exitCode ?? 1,\n },\n };\n}\n\n/**\n * Execute a command with arguments and capture its output.\n * Uses execa which calls execFile internally (no shell injection risk).\n * Does not throw on non-zero exit codes.\n *\n * With `options.retry` it becomes the retrying chokepoint every `gh` and `git`\n * call of the project already passes through: the failure is classified from\n * structured evidence (errno, signal, `timedOut`, exit code — text last) and\n * handed to `withRetry`, the single retry executor. Without it nothing changes.\n */\nexport async function run(\n command: string,\n args: string[] = [],\n options?: RunOptions,\n): Promise<ExecResult> {\n const {\n retry,\n source,\n retrySignal,\n onRetryAttempt,\n diagnostics = true,\n ...execaOptions\n } = options ?? {};\n\n if (retry === undefined || !isRetryableInvocation(command, args)) {\n const result = (await execOnce(command, args, execaOptions)).result;\n if (diagnostics) diagnoseCommandFailure(command, args, result);\n return result;\n }\n\n const failureSource = source ?? inferSource(command);\n\n const outcome = await withRetry(\n async () => {\n const { result, raw } = await execOnce(command, args, execaOptions);\n const failure =\n result.exitCode === 0 ? null : classify(toFailureSignal(raw, result, failureSource));\n return { result, failure };\n },\n {\n policy: retry,\n evaluate: (attempt) => attempt.failure,\n ...(retrySignal === undefined ? {} : { signal: retrySignal }),\n ...(onRetryAttempt === undefined\n ? {}\n : {\n onAttempt: (info) =>\n onRetryAttempt({\n attempt: info.attempt,\n result: info.value.result,\n failure: info.value.failure,\n willRetry: info.willRetry,\n delayMs: info.delayMs,\n }),\n }),\n },\n );\n\n const result = {\n ...outcome.value.result,\n attempts: outcome.attempts,\n ...(outcome.failure === null ? {} : { failure: outcome.failure }),\n };\n if (diagnostics) diagnoseCommandFailure(command, args, result);\n return result;\n}\n","/**\n * Retry policy: how long to wait, how many times, and what to do when the\n * budget runs out.\n *\n * `errors.ts` answers *what went wrong*; this module answers *what to do about\n * it*. It is pure — no I/O, no clock of its own, no knowledge of phases — so\n * every decision here is reproducible from its arguments. `retry.ts` is the\n * only executor that acts on what this module returns.\n *\n * The run-level state machine lives here too, for the same reason: whether a\n * run may go from `blocked` to `running` is a policy question, and there must\n * be exactly one answer to it in the codebase.\n *\n * The invariants of this layer are in `src/resilience/AGENTS.md`.\n */\n\nimport { type FailureKind, requiresHumanAction } from './errors.js';\n\n/* ── the policy ─────────────────────────────────────────────────────────── */\n\n/** What to do with the provider once a kind keeps failing. */\nexport type FailoverMode = 'never' | 'after_attempts' | 'immediate';\n\n/** What a failure becomes once the attempt budget is spent. */\nexport type ExhaustedAction = 'fail' | 'block';\n\nexport interface RetryPolicy {\n /** `0` means the kind is never retried by this layer. */\n maxAttempts: number;\n initialDelayMs: number;\n /** The ceiling of the backoff — honoured even under `retryForever`. */\n maxDelayMs: number;\n backoffFactor: number;\n jitter: 'none' | 'full';\n retryForever: boolean;\n failover: FailoverMode;\n /** Attempts to burn before `failover: 'after_attempts'` kicks in. */\n failoverAfterAttempts: number;\n onExhausted: ExhaustedAction;\n}\n\n/** A partial policy, as a configuration layer supplies it. */\nexport type RetryPolicyOverride = Partial<RetryPolicy>;\n\n/* ── configuration ──────────────────────────────────────────────────────── */\n\nexport type ResilienceProfile = 'default' | 'continuous';\n\n/**\n * The configuration keys of `resilience.retry`, one per `FailureKind` in the\n * camelCase spelling the JSON uses.\n */\nexport type RetryConfigKey =\n | 'network'\n | 'timeout'\n | 'stalled'\n | 'rateLimit'\n | 'providerDown'\n | 'providerCrash'\n | 'authentication'\n | 'configuration'\n | 'repositoryState'\n | 'taskExecution'\n | 'internal'\n | 'unknown';\n\nconst CONFIG_KEYS: Readonly<Record<FailureKind, RetryConfigKey>> = {\n network: 'network',\n timeout: 'timeout',\n stalled: 'stalled',\n rate_limit: 'rateLimit',\n provider_down: 'providerDown',\n provider_crash: 'providerCrash',\n authentication: 'authentication',\n configuration: 'configuration',\n repository_state: 'repositoryState',\n task_execution: 'taskExecution',\n internal: 'internal',\n unknown: 'unknown',\n};\n\n/** The configuration key `resilience.retry` uses for `kind`. */\nexport function retryConfigKey(kind: FailureKind): RetryConfigKey {\n return CONFIG_KEYS[kind];\n}\n\n/**\n * Every key `resilience.retry` accepts, for the layers that have to iterate\n * them (the schema in `storage/schemas.ts`, the merge in `config.ts`). Derived\n * from `CONFIG_KEYS` rather than written a second time, so a new `FailureKind`\n * cannot reach the configuration surface half-declared.\n */\nexport const RETRY_CONFIG_KEYS: readonly RetryConfigKey[] = Object.values(CONFIG_KEYS);\n\n/**\n * The slice of the `resilience` configuration this module reads. The full key\n * (providers, queue, watchdog, journal, decompose) is a superset of this, and\n * satisfies it structurally.\n */\nexport interface PolicyConfig {\n profile?: ResilienceProfile;\n /**\n * Whether a credential failure may migrate to another provider. Explicit on\n * purpose, never a default: switching provider because the main one lost its\n * credential hides exactly what the user needs to be told.\n */\n failoverOnAuth?: boolean;\n retry?: Partial<Record<RetryConfigKey, RetryPolicyOverride>>;\n}\n\n/* ── the defaults ───────────────────────────────────────────────────────── */\n\nconst SECOND = 1000;\n\nfunction policy(overrides: RetryPolicyOverride): RetryPolicy {\n return {\n maxAttempts: 0,\n initialDelayMs: 0,\n maxDelayMs: 0,\n backoffFactor: 2,\n jitter: 'full',\n retryForever: false,\n failover: 'never',\n failoverAfterAttempts: 2,\n onExhausted: 'fail',\n ...overrides,\n };\n}\n\n/**\n * The table of the PRD, verbatim. Changing a number here changes documented\n * behaviour: `policy.test.ts` asserts this table cell by cell on purpose.\n */\nconst BASE_POLICIES: Readonly<Record<FailureKind, RetryPolicy>> = {\n network: policy({ maxAttempts: 8, initialDelayMs: 2 * SECOND, maxDelayMs: 120 * SECOND }),\n timeout: policy({\n maxAttempts: 2,\n initialDelayMs: 30 * SECOND,\n maxDelayMs: 120 * SECOND,\n failover: 'after_attempts',\n }),\n stalled: policy({\n maxAttempts: 2,\n initialDelayMs: 15 * SECOND,\n maxDelayMs: 15 * SECOND,\n failover: 'after_attempts',\n }),\n rate_limit: policy({\n maxAttempts: 6,\n initialDelayMs: 60 * SECOND,\n maxDelayMs: 900 * SECOND,\n failover: 'after_attempts',\n }),\n provider_down: policy({\n maxAttempts: 4,\n initialDelayMs: 10 * SECOND,\n maxDelayMs: 300 * SECOND,\n failover: 'after_attempts',\n }),\n provider_crash: policy({\n maxAttempts: 3,\n initialDelayMs: 5 * SECOND,\n maxDelayMs: 60 * SECOND,\n failover: 'after_attempts',\n }),\n authentication: policy({ maxAttempts: 0, onExhausted: 'block' }),\n configuration: policy({ maxAttempts: 0 }),\n repository_state: policy({ maxAttempts: 0, onExhausted: 'block' }),\n task_execution: policy({ maxAttempts: 0 }),\n internal: policy({ maxAttempts: 2, initialDelayMs: 5 * SECOND, maxDelayMs: 5 * SECOND }),\n unknown: policy({ maxAttempts: 2, initialDelayMs: 5 * SECOND, maxDelayMs: 5 * SECOND }),\n};\n\n/**\n * The `continuous` profile — autonomous, long-running execution. It only ever\n * *widens* a budget: what is not retryable under `default` is not retryable\n * here either.\n */\nconst CONTINUOUS_OVERRIDES: Readonly<Partial<Record<FailureKind, RetryPolicyOverride>>> = {\n network: { retryForever: true },\n timeout: { maxAttempts: 3 },\n stalled: { maxAttempts: 3 },\n rate_limit: { retryForever: true },\n provider_down: { retryForever: true, failover: 'after_attempts' },\n provider_crash: { maxAttempts: 5 },\n};\n\n/**\n * Resolve the policy of a `FailureKind`: base table → profile → user\n * configuration, in that order.\n *\n * The kinds that need a human (`authentication`, `configuration`,\n * `repository_state`, `task_execution`) are clamped **after** the user layer:\n * no configuration, no profile and no `retryForever` can buy them an attempt.\n * That clamp is the golden rule of `AGENTS.md` made unbypassable.\n */\nexport function resolvePolicy(kind: FailureKind, config: PolicyConfig = {}): RetryPolicy {\n const base = BASE_POLICIES[kind];\n const profile = config.profile === 'continuous' ? CONTINUOUS_OVERRIDES[kind] : undefined;\n const user = config.retry?.[retryConfigKey(kind)];\n\n const resolved: RetryPolicy = { ...base, ...profile, ...user };\n\n if (!requiresHumanAction(kind)) return sanitize(resolved);\n\n return sanitize({\n ...resolved,\n maxAttempts: 0,\n retryForever: false,\n failover:\n kind === 'authentication' && config.failoverOnAuth === true ? resolved.failover : 'never',\n });\n}\n\n/** Guard against a configuration layer supplying a number that breaks the math. */\nfunction sanitize(value: RetryPolicy): RetryPolicy {\n return {\n ...value,\n maxAttempts: Math.max(0, Math.trunc(value.maxAttempts)),\n initialDelayMs: Math.max(0, value.initialDelayMs),\n maxDelayMs: Math.max(0, value.maxDelayMs),\n backoffFactor: value.backoffFactor >= 1 ? value.backoffFactor : 1,\n failoverAfterAttempts: Math.max(1, Math.trunc(value.failoverAfterAttempts)),\n };\n}\n\n/* ── the decisions ──────────────────────────────────────────────────────── */\n\n/**\n * Whether another attempt is allowed after `attemptsMade` attempts have\n * already failed.\n */\nexport function shouldRetry(policy: RetryPolicy, attemptsMade: number): boolean {\n if (policy.maxAttempts <= 0) return false;\n if (policy.retryForever) return true;\n return attemptsMade < policy.maxAttempts;\n}\n\n/** Whether the next attempt should be handed to a different provider. */\nexport function shouldFailover(policy: RetryPolicy, attemptsMade: number): boolean {\n if (policy.failover === 'never') return false;\n if (policy.failover === 'immediate') return true;\n return attemptsMade >= policy.failoverAfterAttempts;\n}\n\nexport interface DelayOptions {\n /** Injectable RNG, so the jitter is deterministic under test. */\n random?: () => number;\n /** What the server asked for, when it asked (`Retry-After`). */\n retryAfterMs?: number;\n}\n\n/**\n * The delay before attempt number `attemptsMade + 1`.\n *\n * `ceiling = min(maxDelayMs, initialDelayMs * backoffFactor^(attemptsMade-1))`,\n * and full jitter — the default — draws uniformly from `[0, ceiling)`. The\n * ceiling applies under `retryForever` exactly as it does otherwise: waiting\n * forever is allowed, hammering is not.\n *\n * A server-supplied `Retry-After` wins outright, un-jittered and uncapped: the\n * point of honouring it is not to come back before it says, and capping it by\n * `maxDelayMs` would do precisely that.\n */\nexport function computeDelayMs(\n policy: RetryPolicy,\n attemptsMade: number,\n options: DelayOptions = {},\n): number {\n if (options.retryAfterMs !== undefined && options.retryAfterMs >= 0) {\n return Math.round(options.retryAfterMs);\n }\n\n const exponent = Math.max(0, attemptsMade - 1);\n const growth = policy.initialDelayMs * policy.backoffFactor ** exponent;\n const ceiling = Math.min(policy.maxDelayMs, growth);\n if (ceiling <= 0) return 0;\n\n if (policy.jitter === 'none') return Math.round(ceiling);\n\n const random = options.random ?? Math.random;\n return Math.round(random() * ceiling);\n}\n\n/* ── the abortable delay ────────────────────────────────────────────────── */\n\n/**\n * Wait `ms`, unless `signal` fires first.\n *\n * Resolves `true` when the full delay elapsed and `false` when it was cut\n * short — a caller that waited fifteen minutes and a caller that was\n * interrupted must not take the same next step. It never throws: an abort is\n * an expected outcome here, not an error.\n */\nexport function abortableDelay(\n ms: number,\n options: { signal?: AbortSignal } = {},\n): Promise<boolean> {\n const { signal } = options;\n if (signal?.aborted === true) return Promise.resolve(false);\n if (ms <= 0) return Promise.resolve(true);\n\n return new Promise((resolve) => {\n const onAbort = () => {\n clearTimeout(timer);\n resolve(false);\n };\n\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve(true);\n }, ms);\n\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n\n/* ── the run state machine ──────────────────────────────────────────────── */\n\n/**\n * The state of a run — a queued issue in `execution-plan.json`, or the single\n * issue of a `tasks.json`.\n */\nexport type RunStatus =\n | 'queued'\n | 'running'\n | 'waiting'\n | 'retrying'\n | 'paused'\n | 'blocked'\n | 'failed'\n | 'completed'\n | 'cancelled';\n\n/**\n * Every legal transition, in one place.\n *\n * `completed` and `cancelled` are terminal: nothing leaves them. `blocked` has\n * exits, but none the pipeline may take on its own — see `canTransition`.\n */\nexport const RUN_TRANSITIONS: Readonly<Record<RunStatus, readonly RunStatus[]>> = {\n queued: ['running', 'blocked', 'cancelled'],\n running: ['waiting', 'retrying', 'paused', 'blocked', 'failed', 'completed', 'cancelled'],\n waiting: ['running', 'retrying', 'paused', 'blocked', 'failed', 'cancelled'],\n retrying: ['running', 'waiting', 'paused', 'blocked', 'failed', 'cancelled'],\n paused: ['running', 'blocked', 'cancelled'],\n blocked: ['running', 'cancelled'],\n failed: ['running', 'cancelled'],\n completed: [],\n cancelled: [],\n};\n\n/** Nothing leaves these, by anyone. */\nconst TERMINAL_STATUSES: ReadonlySet<RunStatus> = new Set<RunStatus>(['completed', 'cancelled']);\n\n/** Whether `status` is terminal. */\nexport function isTerminalRunStatus(status: RunStatus): boolean {\n return TERMINAL_STATUSES.has(status);\n}\n\n/** Who is attempting the transition. The pipeline is the default. */\nexport type TransitionActor = 'pipeline' | 'human';\n\n/**\n * Whether `from → to` is legal for `actor`.\n *\n * `blocked` exists precisely to stop the machine until someone looks at it, so\n * the pipeline may enter it but never leave it: only `actor: 'human'` — a\n * `resume`, a `cancel` — can. A pipeline able to unblock itself would spin on\n * the same missing credential forever.\n */\nexport function canTransition(\n from: RunStatus,\n to: RunStatus,\n options: { actor?: TransitionActor } = {},\n): boolean {\n if (!RUN_TRANSITIONS[from].includes(to)) return false;\n if (from === 'blocked' && (options.actor ?? 'pipeline') !== 'human') return false;\n return true;\n}\n","/**\n * The single retry executor of the project.\n *\n * `errors.ts` says *what went wrong*, `policy.ts` says *what to do about it*,\n * and this module is the only place that actually does it: one loop, one\n * backoff, one veto. Before it existed the codebase had two independent retry\n * loops — `core/phase-runner.ts` for single-shot phases and the `execute` loop\n * of `core/engine.ts` — with their own counters, their own delays and no way\n * to keep them in step. Both now delegate here.\n *\n * The loop performs no I/O and knows nothing about phases, sessions or\n * publishers: everything observable is handed back through `onAttempt`, so the\n * caller keeps ownership of its own logging, events and persistence.\n *\n * The invariants of this layer are in `src/resilience/AGENTS.md`.\n */\n\nimport { type ClassifiedFailure, requiresHumanAction } from './errors.js';\nimport { abortableDelay, computeDelayMs, type RetryPolicy, shouldRetry } from './policy.js';\n\n/** What `onAttempt` is told once an attempt has produced a value. */\nexport interface RetryAttemptInfo<T> {\n /** 1-based number of the attempt that just finished. */\n attempt: number;\n /** Whatever `fn` returned, failure or not. */\n value: T;\n /** `null` when the attempt succeeded. */\n failure: ClassifiedFailure | null;\n /** Whether another attempt follows this one. */\n willRetry: boolean;\n /** The wait before that next attempt; `0` when there is none. */\n delayMs: number;\n}\n\n/**\n * A policy chosen from the failure that just happened.\n *\n * The kind is only known *after* an attempt, so a call site that spans several\n * kinds — every `gh` invocation is a network failure, a rate limit or an\n * authentication failure depending on the day — hands over the resolver rather\n * than one frozen policy. `resolvePolicy(failure.kind, config)` is what a\n * caller normally passes.\n */\nexport type RetryPolicyFor = (failure: ClassifiedFailure) => RetryPolicy;\n\nexport interface WithRetryOptions<T> {\n /** One policy, or one chosen per classified failure. */\n policy: RetryPolicy | RetryPolicyFor;\n /**\n * The verdict on one attempt's value: `null` for success, a classified\n * failure otherwise.\n *\n * It belongs to the caller because only the caller knows what a failure\n * looks like in its own return type — an exit code, an `ok: false`, a\n * rejected HTTP response. Callers holding structured evidence should build\n * the failure with `classify()` rather than re-deriving it from text.\n */\n evaluate: (value: T, attempt: number) => ClassifiedFailure | null;\n /** Cuts a pending backoff short; see `abortableDelay`. */\n signal?: AbortSignal;\n /**\n * Called once per attempt, before the backoff, with the decision already\n * taken. This is where a caller publishes its `retry` event, prints, or\n * persists an error — awaited, so those writes finish before the wait.\n */\n onAttempt?: (info: RetryAttemptInfo<T>) => void | Promise<void>;\n /** Injectable RNG for the jitter, so a test can assert the exact curve. */\n random?: () => number;\n /** Injectable wait, so a test never sleeps for real. */\n delay?: (ms: number, options: { signal?: AbortSignal }) => Promise<boolean>;\n}\n\nexport interface RetryOutcome<T> {\n /** The value of the last attempt — the successful one, or the final failure. */\n value: T;\n /** How many times `fn` ran. */\n attempts: number;\n /** `null` when the last attempt succeeded. */\n failure: ClassifiedFailure | null;\n /** The failure was retryable and the budget ran out. */\n exhausted: boolean;\n /** `signal` fired during a backoff, so the next attempt never happened. */\n aborted: boolean;\n}\n\n/**\n * Run `fn` until it succeeds, until the policy's budget is spent, or until\n * `signal` fires.\n *\n * Three separate conditions have to hold for another attempt, and all three\n * are checked here rather than by the caller:\n *\n * 1. the failure is retryable at all (`ClassifiedFailure.retryable`);\n * 2. the kind is not one that needs a human — `task_execution`,\n * `authentication`, `configuration` and `repository_state` are vetoed\n * outright, whatever the policy says and even under `retryForever`. This is\n * the golden rule of `AGENTS.md`, enforced a second time here: a caller can\n * hand-build a `RetryPolicy` without going through `resolvePolicy()`, so\n * the clamp there is not on its own enough;\n * 3. the policy still has budget (`shouldRetry`).\n */\nexport async function withRetry<T>(\n fn: (attempt: number) => Promise<T>,\n options: WithRetryOptions<T>,\n): Promise<RetryOutcome<T>> {\n const { policy, evaluate, signal, onAttempt, random } = options;\n const delay = options.delay ?? abortableDelay;\n const policyFor: RetryPolicyFor = typeof policy === 'function' ? policy : () => policy;\n\n for (let attempt = 1; ; attempt++) {\n const value = await fn(attempt);\n const failure = evaluate(value, attempt);\n\n if (failure === null) {\n await onAttempt?.({ attempt, value, failure, willRetry: false, delayMs: 0 });\n return { value, attempts: attempt, failure: null, exhausted: false, aborted: false };\n }\n\n const active = policyFor(failure);\n const eligible = failure.retryable && !requiresHumanAction(failure.kind);\n const willRetry = eligible && shouldRetry(active, attempt);\n const delayMs = willRetry\n ? computeDelayMs(active, attempt, { random, retryAfterMs: failure.retryAfterMs })\n : 0;\n\n await onAttempt?.({ attempt, value, failure, willRetry, delayMs });\n\n if (!willRetry) {\n return { value, attempts: attempt, failure, exhausted: eligible, aborted: false };\n }\n\n if (!(await delay(delayMs, { signal }))) {\n return { value, attempts: attempt, failure, exhausted: false, aborted: true };\n }\n }\n}\n\n/**\n * A fixed policy in the shape the two legacy call sites express their settings\n * in: a number of attempts and a base/max delay in seconds.\n *\n * Deliberately un-jittered. Both call sites have always used a bare\n * `base * 2^(n-1)` and both publish that number in a `retry` event and print\n * it; adding jitter here would change today's behaviour with no configuration\n * asking for it. Jitter arrives with the `resilience` key that configures it.\n */\nexport function fixedBackoffPolicy(\n maxAttempts: number,\n baseSeconds: number,\n maxSeconds: number,\n overrides: Partial<RetryPolicy> = {},\n): RetryPolicy {\n return {\n maxAttempts,\n initialDelayMs: baseSeconds * 1000,\n maxDelayMs: maxSeconds * 1000,\n backoffFactor: 2,\n jitter: 'none',\n retryForever: false,\n failover: 'never',\n failoverAfterAttempts: 2,\n onExhausted: 'fail',\n ...overrides,\n };\n}\n","import { type ExecResult, run } from './shell.js';\n\n/**\n * Get the root directory of the current git repository.\n * Throws if not inside a git repository.\n */\nexport async function getProjectRoot(): Promise<string> {\n const result = await run('git', ['rev-parse', '--show-toplevel']);\n\n if (result.exitCode !== 0) {\n throw new Error(\n 'Not inside a git repository. Please run issue-flow from within a git project.',\n );\n }\n\n return result.stdout.trim();\n}\n\n/**\n * Get the current git branch name.\n * Returns an empty string if in detached HEAD state.\n */\nexport async function getCurrentBranch(): Promise<string> {\n const result = await run('git', ['branch', '--show-current']);\n\n if (result.exitCode !== 0) {\n throw new Error(\n 'Failed to detect git branch. Ensure git is installed and you are inside a repository.',\n );\n }\n\n return result.stdout.trim();\n}\n\n/** Whether a local branch exists, without consulting or mutating remotes. */\nexport async function localBranchExists(name: string, cwd?: string): Promise<boolean> {\n if (name.trim() === '') return false;\n const result = await run('git', ['show-ref', '--verify', '--quiet', `refs/heads/${name}`], {\n ...(cwd === undefined ? {} : { cwd }),\n diagnostics: false,\n });\n return result.exitCode === 0;\n}\n\nexport interface CommitInfo {\n hash: string;\n subject: string;\n committedAt?: string | null;\n storyId?: string | null;\n}\n\n/**\n * Get the base branch of the repository: the remote HEAD (origin/HEAD) when\n * available, otherwise the first of main/master that exists locally.\n * Never throws; defaults to 'main'.\n */\nexport async function getBaseBranch(): Promise<string> {\n const remoteHead = await run('git', ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD'], {\n diagnostics: false,\n });\n if (remoteHead.exitCode === 0) {\n const name = remoteHead.stdout.trim().replace(/^origin\\//, '');\n if (name) return name;\n }\n\n for (const candidate of ['main', 'master']) {\n const check = await run(\n 'git',\n ['rev-parse', '--verify', '--quiet', `refs/heads/${candidate}`],\n { diagnostics: false },\n );\n if (check.exitCode === 0) return candidate;\n }\n\n return 'main';\n}\n\n/**\n * Get the URL of the `origin` remote.\n * Never throws: a missing remote, a non-zero exit code, an empty stdout or a\n * git binary that cannot even be spawned all resolve to `null` — the same\n * discipline as {@link getBaseBranch}, so callers can treat \"no remote\" as an\n * ordinary state instead of an error path.\n *\n * `cwd` selects which repository is queried; omitting it falls back to\n * `process.cwd()`, which is almost never what a caller resolving a specific\n * `projectRoot` wants — pass it explicitly whenever one is available.\n */\nexport async function getRemoteUrl(cwd?: string): Promise<string | null> {\n let result: ExecResult;\n try {\n result = await run('git', ['remote', 'get-url', 'origin'], { cwd, diagnostics: false });\n } catch {\n return null;\n }\n\n if (result.exitCode !== 0) return null;\n\n const url = result.stdout.trim();\n return url === '' ? null : url;\n}\n\n/**\n * Get the abbreviated hash of the current HEAD commit.\n * Never throws, same discipline as {@link getRemoteUrl}: a repository with no\n * commits yet, a non-zero exit code, an empty stdout or a git binary that\n * cannot be spawned all resolve to `null`, so callers can treat \"no HEAD\" as\n * an ordinary state instead of an error path.\n *\n * `cwd` selects which repository is queried; omitting it falls back to\n * `process.cwd()`.\n */\nexport async function getHeadCommit(cwd?: string): Promise<string | null> {\n let result: ExecResult;\n try {\n result = await run('git', ['rev-parse', '--short', 'HEAD'], { cwd, diagnostics: false });\n } catch {\n return null;\n }\n\n if (result.exitCode !== 0) return null;\n\n const hash = result.stdout.trim();\n return hash === '' ? null : hash;\n}\n\n/**\n * Reduce a git remote URL to a canonical `host/path` identity, so that every\n * way of addressing the same repository collapses to the same string:\n *\n * ```\n * https://github.com/org/repo.git -> github.com/org/repo\n * git@github.com:org/repo.git -> github.com/org/repo\n * ssh://git@github.com:22/org/repo.git -> github.com/org/repo\n * https://user:token@github.com/org/repo -> github.com/org/repo\n * https://github.com/Org/Repo/ -> github.com/org/repo\n * ```\n *\n * Pure function: it never shells out, so it is testable without a git\n * repository. Returns `null` when no host and path can be extracted.\n *\n * Known limitation: both host and path are lowercased. Hostnames are\n * case-insensitive by definition, but repository paths are case-sensitive on\n * some self-hosted servers — so `host/org/Repo` and `host/org/repo` normalize\n * to the same identity even if that server considers them distinct. This is a\n * deliberate trade: matching the same repository across clones (where users\n * routinely type the wrong case) matters more than telling apart two repos\n * whose paths differ only by case.\n */\nexport function normalizeRemoteUrl(url: string | null | undefined): string | null {\n if (typeof url !== 'string') return null;\n\n const trimmed = url.trim();\n if (trimmed === '') return null;\n\n let authority: string;\n let path: string;\n\n const scheme = /^[a-zA-Z][a-zA-Z0-9+.-]*:\\/\\//.exec(trimmed);\n if (scheme) {\n const rest = trimmed.slice(scheme[0].length);\n const slash = rest.indexOf('/');\n if (slash === -1) return null;\n authority = rest.slice(0, slash);\n path = rest.slice(slash + 1);\n } else {\n // scp-like syntax: [user@]host:path — everything after the first colon is\n // the path, which is how git itself reads it (no port is possible here).\n const colon = trimmed.indexOf(':');\n if (colon === -1) return null;\n authority = trimmed.slice(0, colon);\n path = trimmed.slice(colon + 1);\n }\n\n // Drop embedded credentials (user, user:token) and the ssh user.\n const at = authority.lastIndexOf('@');\n if (at !== -1) authority = authority.slice(at + 1);\n\n const host = authority.replace(/:\\d+$/, '').toLowerCase();\n if (host === '') return null;\n\n const normalizedPath = path\n .replace(/[?#].*$/, '')\n .replace(/\\/+/g, '/')\n .replace(/^\\/+|\\/+$/g, '')\n .replace(/\\.git$/i, '')\n .replace(/\\/+$/, '')\n .toLowerCase();\n if (normalizedPath === '') return null;\n\n return `${host}/${normalizedPath}`;\n}\n\n/**\n * Remove Basic-Auth-style credentials (`user:token@`, `user@`) embedded in an\n * `http(s)` remote URL, without touching anything else about it — case, port,\n * the `.git` suffix, query string and trailing slashes are all preserved.\n *\n * Deliberately scoped to `http`/`https`: that is the only place a git remote\n * can carry a real secret (a PAT is routinely embedded as\n * `https://x-access-token:TOKEN@host/...` by CI and automation), and the URL\n * stays a valid, clonable remote once the userinfo is gone. SSH has no\n * equivalent syntax — both `ssh://user@host/path` and the scp-like shorthand\n * `user@host:path` require that user segment to connect at all (it is almost\n * always the fixed, non-secret `git` service account), so this function\n * leaves any other scheme, the scp-like form and local paths untouched.\n *\n * Unlike {@link normalizeRemoteUrl}, which collapses a URL to a lossy\n * `host/path` identity, this keeps the URL \"as configured\" — it exists for\n * surfaces (like the web monitor's `repository.remoteUrl`) that want to show\n * the real remote, minus whatever secret an HTTPS token commonly embeds in\n * it.\n *\n * Pure function, never throws. Returns `null` for a nullish or empty input.\n */\nexport function stripRemoteUrlCredentials(url: string | null | undefined): string | null {\n if (typeof url !== 'string') return null;\n\n const trimmed = url.trim();\n if (trimmed === '') return null;\n\n const httpScheme = /^https?:\\/\\//i.exec(trimmed);\n if (!httpScheme) return trimmed;\n\n const rest = trimmed.slice(httpScheme[0].length);\n const slash = rest.indexOf('/');\n const authority = slash === -1 ? rest : rest.slice(0, slash);\n const remainder = slash === -1 ? '' : rest.slice(slash);\n\n const at = authority.lastIndexOf('@');\n if (at === -1) return trimmed;\n return `${httpScheme[0]}${authority.slice(at + 1)}${remainder}`;\n}\n\n/**\n * List commits on HEAD that are not on the given base branch (base..HEAD),\n * most recent first. Never throws; returns [] when the range cannot be\n * resolved (e.g. unknown base branch or shallow clone).\n */\nexport async function getCommitsSince(base: string): Promise<CommitInfo[]> {\n const result = await run('git', [\n 'log',\n '--pretty=format:%h%x09%cI%x09%s%x09%b%x1e',\n `${base}..HEAD`,\n ]);\n if (result.exitCode !== 0) return [];\n\n if (!result.stdout.includes('\\x1e')) {\n return result.stdout\n .split('\\n')\n .filter((line) => line.trim() !== '')\n .map((line) => {\n const tab = line.indexOf('\\t');\n return tab === -1\n ? { hash: line.trim(), subject: '' }\n : { hash: line.slice(0, tab), subject: line.slice(tab + 1) };\n });\n }\n const records = result.stdout.split('\\x1e');\n return records.flatMap((raw) => {\n const record = raw.trim();\n if (record === '') return [];\n const fields = record.split('\\t');\n const [hash = '', committedAt = '', subject = '', ...bodyParts] = fields;\n const body = bodyParts.join('\\t');\n const storyId = /(?:Story:\\s*|\\[)(US-\\d+)/i.exec(`${subject}\\n${body}`)?.[1] ?? null;\n return [\n {\n hash,\n subject,\n committedAt: committedAt || null,\n storyId: storyId?.toUpperCase() ?? null,\n },\n ];\n });\n}\n\n/* ── repository preflight ───────────────────────────────────────────────── */\n\n/**\n * A repository state that stops the pipeline.\n *\n * The whole point of this type is that it is *reported*, never acted on: the\n * second explicit limit of the resilience Epic is that **no destructive\n * operation is ever run automatically to fix state**. A repository mid-rebase\n * is a person's unfinished work, and an `--abort` issued by a tool at 3am is\n * indistinguishable from data loss.\n */\nexport type PreflightBlockKind =\n | 'rebase_in_progress'\n | 'merge_in_progress'\n | 'cherry_pick_in_progress'\n | 'revert_in_progress'\n | 'unmerged_paths'\n | 'detached_head'\n | 'branch_mismatch'\n | 'dirty_tree';\n\nexport interface PreflightBlock {\n kind: PreflightBlockKind;\n /** What is wrong, in one line. */\n message: string;\n /** The command a human would run. Printed, never executed. */\n suggestion: string;\n}\n\nexport interface PreflightResult {\n ok: boolean;\n blocks: PreflightBlock[];\n /** Current branch, or `null` on a detached HEAD. */\n branch: string | null;\n /** Whether the working tree has uncommitted changes. */\n dirty: boolean;\n}\n\n/** What the caller is about to do, which decides how a dirty tree is read. */\nexport type PreflightIntent =\n | /** Continuing the phase that produced the changes. */ 'resume-same-phase'\n | /** Starting a different phase, or a different issue. */ 'new-phase';\n\nexport interface PreflightOptions {\n /** Branch the plan says this work belongs to. `null` skips the check. */\n expectedBranch?: string | null;\n /** Default `new-phase`, the strict reading. */\n intent?: PreflightIntent;\n cwd?: string;\n}\n\n/** `git rev-parse --verify --quiet <ref>` — exit 0 means the ref exists. */\nasync function refExists(ref: string, cwd?: string): Promise<boolean> {\n const result = await run('git', ['rev-parse', '--verify', '--quiet', ref], {\n ...(cwd === undefined ? {} : { cwd }),\n diagnostics: false,\n });\n return result.exitCode === 0;\n}\n\n/** The in-progress sequencer operations, by the ref each one leaves behind. */\nconst SEQUENCER_REFS: readonly {\n ref: string;\n kind: PreflightBlockKind;\n name: string;\n suggestion: string;\n}[] = [\n {\n ref: 'REBASE_HEAD',\n kind: 'rebase_in_progress',\n name: 'a rebase',\n suggestion: 'git rebase --continue (or git rebase --abort)',\n },\n {\n ref: 'MERGE_HEAD',\n kind: 'merge_in_progress',\n name: 'a merge',\n suggestion: 'git merge --continue (or git merge --abort)',\n },\n {\n ref: 'CHERRY_PICK_HEAD',\n kind: 'cherry_pick_in_progress',\n name: 'a cherry-pick',\n suggestion: 'git cherry-pick --continue (or git cherry-pick --abort)',\n },\n {\n ref: 'REVERT_HEAD',\n kind: 'revert_in_progress',\n name: 'a revert',\n suggestion: 'git revert --continue (or git revert --abort)',\n },\n];\n\n/**\n * Describe the repository and decide whether it is safe to write to it.\n *\n * Every check is a *read*: `rev-parse`, `symbolic-ref`, `diff --name-only`,\n * `status --porcelain`, `branch --show-current`. Nothing here checks anything\n * out, resets anything, aborts anything or stashes anything — not on a resume,\n * not under a continuous profile, not ever. A repository in an ambiguous state\n * escalates to a human, which is the only correct answer for a tool that did\n * not put it there.\n *\n * A **dirty tree is not always a problem**: resuming the phase that produced\n * those changes is exactly the case where uncommitted work is expected. It\n * blocks only when the run is about to move on to something else, where the\n * changes would be carried into work they do not belong to.\n */\nexport async function preflightRepository(\n options: PreflightOptions = {},\n): Promise<PreflightResult> {\n const { cwd, expectedBranch, intent = 'new-phase' } = options;\n const at = cwd === undefined ? {} : { cwd };\n const blocks: PreflightBlock[] = [];\n\n for (const sequencer of SEQUENCER_REFS) {\n if (await refExists(sequencer.ref, cwd)) {\n blocks.push({\n kind: sequencer.kind,\n message: `The repository is in the middle of ${sequencer.name} (${sequencer.ref} is present).`,\n suggestion: sequencer.suggestion,\n });\n }\n }\n\n const unmerged = await run('git', ['diff', '--name-only', '--diff-filter=U'], at);\n const conflicted = unmerged.stdout\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => line !== '');\n if (conflicted.length > 0) {\n blocks.push({\n kind: 'unmerged_paths',\n message: `${conflicted.length} file(s) still have unresolved conflicts: ${conflicted.slice(0, 5).join(', ')}${conflicted.length > 5 ? ', …' : ''}.`,\n suggestion: 'Resolve them, then git add the files',\n });\n }\n\n // `symbolic-ref -q HEAD` fails on a detached HEAD, which is the whole test.\n const symbolic = await run('git', ['symbolic-ref', '-q', 'HEAD'], at);\n const detached = symbolic.exitCode !== 0;\n const branch = detached ? null : symbolic.stdout.trim().replace(/^refs\\/heads\\//, '');\n\n if (detached) {\n blocks.push({\n kind: 'detached_head',\n message: 'HEAD is detached, so a commit would belong to no branch.',\n suggestion: `git switch ${expectedBranch ?? '<branch>'}`,\n });\n } else if (\n expectedBranch !== undefined &&\n expectedBranch !== null &&\n expectedBranch !== '' &&\n branch !== expectedBranch\n ) {\n blocks.push({\n kind: 'branch_mismatch',\n message: `The plan works on '${expectedBranch}' but the repository is on '${branch}'.`,\n suggestion: `git switch ${expectedBranch}`,\n });\n }\n\n const status = await run('git', ['status', '--porcelain'], at);\n const dirty = status.stdout.trim() !== '';\n if (dirty && intent !== 'resume-same-phase') {\n blocks.push({\n kind: 'dirty_tree',\n message: 'The working tree has uncommitted changes that do not belong to this phase.',\n suggestion: 'Commit or stash them yourself, then run again',\n });\n }\n\n return { ok: blocks.length === 0, blocks, branch, dirty };\n}\n\n/** The report a caller prints when the preflight blocks. Never a fix. */\nexport function describePreflight(result: PreflightResult): string[] {\n return result.blocks.map((block) => `${block.message} Suggested: ${block.suggestion}`);\n}\n\n/* ── story-level idempotence ────────────────────────────────────────────── */\n\n/** A story id as it appears in a commit subject: `feat(issue-63): US-012 - …`. */\nconst STORY_ID_IN_SUBJECT = /\\bUS-\\d+\\b/g;\n\n/**\n * The story ids already committed on this branch.\n *\n * The execute prompt commits one story per commit, with the id in the subject\n * (`<type>(scope): US-001 - Title`), so the branch's own history is a record of\n * what is done — and it is the *only* record that survives a crash between the\n * commit and the agent writing `passes: true` into the plan.\n *\n * Read-only, and deliberately so: this answers a question, it never fixes\n * anything. Never throws — an unreadable history means \"nothing known\", which\n * leaves the engine doing exactly what it did before.\n */\nexport async function committedStoryIds(baseBranch: string, cwd?: string): Promise<Set<string>> {\n const at = cwd === undefined ? {} : { cwd };\n const range = baseBranch === '' ? 'HEAD' : `${baseBranch}..HEAD`;\n const result = await run('git', ['log', range, '--format=%s'], at);\n if (result.exitCode !== 0) return new Set();\n\n const ids = new Set<string>();\n for (const subject of result.stdout.split('\\n')) {\n for (const match of subject.matchAll(STORY_ID_IN_SUBJECT)) {\n ids.add(match[0]);\n }\n }\n return ids;\n}\n\n/** Whether the working tree has no uncommitted change. Never throws. */\nexport async function isWorkingTreeClean(cwd?: string): Promise<boolean> {\n const at = cwd === undefined ? {} : { cwd };\n const result = await run('git', ['status', '--porcelain'], at);\n if (result.exitCode !== 0) return false;\n return result.stdout.trim() === '';\n}\n"],"mappings":"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","names":["policy","policy","result"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/pr-review.ts","../src/core/pr-review/discovery.ts","../src/core/pr-review/publisher.ts","../src/core/pr-review/report.ts"],"sourcesContent":["import { join } from 'node:path';\nimport { loadPrReviewConfig } from '../config.js';\nimport { DEFAULT_HEADLESS_TIMEOUT_MS, runHeadless } from '../core/headless.js';\nimport {\n PrDiscoveryError,\n type ResolvedPullRequest,\n resolvePullRequest,\n} from '../core/pr-review/discovery.js';\nimport { createPrReviewPublisher, type PrReviewPublisher } from '../core/pr-review/publisher.js';\nimport {\n buildReportMarkdown,\n type PrReviewPullRequest,\n parseFindings,\n parsePrReviewResult,\n prReviewDir,\n reportFileName,\n resolveRound,\n} from '../core/pr-review/report.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { publishPhaseMetrics } from '../core/session-metrics.js';\nimport { isoNow, loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { resolvePolicyPlaceholders } from '../policy/placeholders.js';\nimport { resolveIssuePaths } from '../storage/resolve.js';\nimport type { PrReviewRecommendation } from '../types.js';\nimport { printError, printInfo, printSuccess } from '../ui/logger.js';\nimport { run } from '../utils/shell.js';\n\n/**\n * The `pr-review` phase: review a Pull Request as a whole.\n *\n * It follows the five steps of `review.ts` — resolve the target, build the\n * prompt, run headless, parse deterministically, persist state — with two\n * differences that come from reviewing a Pull Request instead of an Issue: the\n * target has to be discovered (`core/pr-review/discovery.ts`) and the outcome\n * is an artifact on disk, written through a `PrReviewPublisher`.\n *\n * The phase is intended to be read-only: Write/Edit are not allowed, and the\n * prompt forbids commits and `gh pr review|comment|merge`. Bash remains\n * available for `gh`/`git` inspection, so the restriction is policy plus tool\n * allow-list — not a sandbox that can stop every write.\n */\n\n/** Which verdicts make the command fail. */\nexport type PrReviewFailOn = 'request-changes' | 'suggestions' | 'none';\n\nconst FAIL_ON_LEVELS: PrReviewFailOn[] = ['request-changes', 'suggestions', 'none'];\nconst DEFAULT_FAIL_ON: PrReviewFailOn = 'request-changes';\n\n/** Placeholder value for context the run does not have. */\nconst NO_ISSUE = 'none';\nconst NO_PATH = '(none)';\n\nexport interface PrReviewOptions {\n /** Issue the Pull Request belongs to; enables plan lookup and state writes. */\n issue?: string;\n /** `--round <n>`: rewrite one specific round instead of appending a new one. */\n round?: string | number;\n /** Skip the discovery confirmation (`--yes`, and every call from `run`). */\n yes?: boolean;\n failOn?: string;\n /** Injection point for tests and for future publishers (GitHub). */\n publisher?: PrReviewPublisher;\n}\n\n/**\n * Exit code for a parsed verdict. `1` is reserved for execution failures, so a\n * review that ran to completion answers only `0` or `2`.\n */\nexport function exitCodeFor(\n recommendation: PrReviewRecommendation,\n failOn: PrReviewFailOn,\n): number {\n if (failOn === 'none') {\n return 0;\n }\n if (recommendation === 'REQUEST_CHANGES') {\n return 2;\n }\n return failOn === 'suggestions' && recommendation === 'APPROVE_WITH_SUGGESTIONS' ? 2 : 0;\n}\n\nfunction parseFailOn(value: string | undefined): PrReviewFailOn | null {\n if (value === undefined || value === '') {\n return DEFAULT_FAIL_ON;\n }\n const normalized = value.trim().toLowerCase();\n return FAIL_ON_LEVELS.find((level) => level === normalized) ?? null;\n}\n\n/** Metadata `gh` knows and the discovery may not — title, URL, head revision. */\ninterface GhPullRequest {\n title?: string;\n url?: string;\n headRefName?: string;\n headRefOid?: string;\n}\n\n/**\n * Best-effort `gh pr view`. A failure here costs the report a title and the\n * index a head SHA; it never costs the review, so nothing is thrown.\n */\nasync function fetchPullRequestMetadata(number: number): Promise<GhPullRequest | null> {\n try {\n const result = await run('gh', [\n 'pr',\n 'view',\n String(number),\n '--json',\n 'title,url,headRefName,headRefOid',\n ]);\n if (result.exitCode !== 0) {\n return null;\n }\n const parsed: unknown = JSON.parse(result.stdout);\n return typeof parsed === 'object' && parsed !== null ? (parsed as GhPullRequest) : null;\n } catch {\n return null;\n }\n}\n\nfunction mergePullRequest(\n target: ResolvedPullRequest,\n meta: GhPullRequest | null,\n): PrReviewPullRequest {\n return {\n number: target.number,\n url: target.url ?? meta?.url ?? null,\n title: target.title ?? meta?.title ?? null,\n headBranch: target.headBranch ?? meta?.headRefName ?? null,\n };\n}\n\ninterface PersistInput {\n tasksPath: string;\n pullRequestNumber: number;\n round: number;\n /** null when the verdict could not be parsed. */\n recommendation: PrReviewRecommendation | null;\n at: string;\n}\n\n/**\n * Record the round on the plan. Only `prReviewCompleted` and `prReview` are\n * touched: the other phases own the rest of `pipeline`.\n *\n * Best-effort like `review.ts`: reviewing a Pull Request with no associated\n * Issue (or whose plan is gone) is a supported use of the command, not a\n * failure — the report on disk is the deliverable either way.\n */\nasync function persistState(input: PersistInput): Promise<void> {\n try {\n const plan = await loadTaskPlan(input.tasksPath);\n plan.pipeline.prReviewCompleted =\n input.recommendation === 'APPROVE' || input.recommendation === 'APPROVE_WITH_SUGGESTIONS';\n plan.prReview = {\n // Preserved, never turned on here: opting the pipeline in is `run\n // --pr-review`'s decision, not a side effect of a manual review.\n enabled: plan.prReview?.enabled ?? false,\n pullRequestNumber: input.pullRequestNumber,\n // `--round <n>` rewrites an earlier round, which must not shrink the count.\n rounds: Math.max(plan.prReview?.rounds ?? 0, input.round),\n lastRecommendation: input.recommendation ?? plan.prReview?.lastRecommendation,\n lastReviewedAt: input.at,\n };\n await saveTaskPlan(input.tasksPath, plan);\n } catch {\n // tasks.json may not exist.\n }\n}\n\nexport async function runPrReview(prArg?: string, opts: PrReviewOptions = {}): Promise<number> {\n const failOn = parseFailOn(opts.failOn);\n if (failOn === null) {\n printError(`Invalid --fail-on value '${opts.failOn}'. Expected ${FAIL_ON_LEVELS.join(', ')}.`);\n return 1;\n }\n\n const explicitRound = opts.round === undefined ? undefined : Number(opts.round);\n if (explicitRound !== undefined && (!Number.isInteger(explicitRound) || explicitRound < 1)) {\n printError(`Invalid --round value '${opts.round}'. Expected a positive integer.`);\n return 1;\n }\n\n const issueRef = opts.issue?.replace(/^#/, '').trim();\n const issue = issueRef === undefined || issueRef === '' ? undefined : issueRef;\n\n let target: ResolvedPullRequest;\n try {\n target = await resolvePullRequest(prArg, { issue, yes: opts.yes });\n } catch (err) {\n if (err instanceof PrDiscoveryError) {\n printError(err.message);\n return err.exitCode;\n }\n printError(\n `Could not determine which Pull Request to review: ${err instanceof Error ? err.message : String(err)}`,\n );\n return 1;\n }\n\n // With no Issue there is nothing to resolve: the placeholders take NO_PATH\n // and the report lands under the synthetic `pr-<N>` slug resolved below.\n const issuePaths = issue === undefined ? null : await resolveIssuePaths(issue);\n const issueDir = issuePaths?.issueDir ?? null;\n const tasksPath = issuePaths?.tasksFile ?? null;\n const prdPath = issuePaths?.prdFile ?? null;\n\n const dir = await prReviewDir({ issue, pullRequest: target.number });\n const round = await resolveRound(dir, target.number, explicitRound);\n const reportPath = join(dir, reportFileName(target.number, round));\n\n const template = await loadPrompt('pr-review');\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()),\n __PR_NUMBER__: String(target.number),\n __ISSUE_NUMBER__: issue ?? NO_ISSUE,\n __TASKS_PATH__: tasksPath ?? NO_PATH,\n __PRD_PATH__: prdPath ?? NO_PATH,\n __REPORT_PATH__: reportPath,\n __ROUND__: String(round),\n });\n\n const startedAtMs = Date.now();\n const result = await runHeadless({\n prompt,\n maxTurns: 40,\n timeout: getGlobalTimeout() ?? DEFAULT_HEADLESS_TIMEOUT_MS,\n // json (not text) so the CLI reports usage: the envelope's `result` field\n // carries the same assistant text the parsers below already consumed.\n outputFormat: 'json',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep'],\n // With an Issue, `dir` sits under `issueDir`, so the parent covers both the\n // report destination and the tasks.json/prd.md the prompt points at.\n addDirs: issueDir === null ? [dir] : [issueDir],\n statusMessage: `Reviewing Pull Request #${target.number} (round ${round})...`,\n phase: 'pr-review',\n permission: 'read-only',\n });\n // Before the success check: the tokens were spent either way.\n publishPhaseMetrics('pr-review', result.cost, startedAtMs, result.agent?.provider);\n\n if (!result.success) {\n printError(`PR review failed: ${result.error}`);\n return 1;\n }\n\n const parsed = parsePrReviewResult(result.result);\n const recommendation = parsed.ok ? parsed.result.recommendation : null;\n const blockers = parsed.ok ? parsed.result.blockers : [];\n\n const meta = await fetchPullRequestMetadata(target.number);\n const pullRequest = mergePullRequest(target, meta);\n const at = isoNow();\n const headSha = meta?.headRefOid ?? null;\n\n const report = {\n pullRequest,\n round,\n at,\n headSha,\n recommendation,\n blockers,\n findings: parseFindings(result.result),\n markdown: buildReportMarkdown({\n pullRequest,\n round,\n at,\n headSha,\n recommendation,\n body: result.result,\n parseError: parsed.ok ? null : parsed.error,\n }),\n };\n\n // Which publisher runs is configuration (`prReview.publisher`), never a code\n // path here: the command only ever holds the interface.\n const publisher =\n opts.publisher ?? createPrReviewPublisher(dir, (await loadPrReviewConfig()).publisher);\n try {\n await publisher.publish(report);\n } catch (err) {\n printError(\n `Could not publish the review report: ${err instanceof Error ? err.message : String(err)}`,\n );\n return 1;\n }\n\n if (tasksPath !== null) {\n await persistState({\n tasksPath,\n pullRequestNumber: target.number,\n round,\n recommendation,\n at,\n });\n }\n\n printInfo(`Report: ${reportPath}`);\n\n // A verdict that could not be read is an execution failure, never an\n // approval — the raw output is in the report for whoever has to look.\n if (!parsed.ok || recommendation === null) {\n printError(`PR review could not be parsed: ${parsed.ok ? 'no recommendation' : parsed.error}`);\n return 1;\n }\n\n if (recommendation === 'REQUEST_CHANGES') {\n printError(`PR review #${target.number}: REQUEST_CHANGES`);\n for (const blocker of blockers) {\n console.log(` - ${blocker}`);\n }\n return exitCodeFor(recommendation, failOn);\n }\n\n printSuccess(\n recommendation === 'APPROVE'\n ? `PR review #${target.number}: APPROVE`\n : `PR review #${target.number}: APPROVE_WITH_SUGGESTIONS — no blockers, improvements suggested`,\n );\n return exitCodeFor(recommendation, failOn);\n}\n","import { createInterface } from 'node:readline';\nimport { resolveIssuePaths } from '../../storage/resolve.js';\nimport type { PullRequestRef } from '../../types.js';\nimport { printInfo, printWarning } from '../../ui/logger.js';\nimport { getCurrentBranch } from '../../utils/git.js';\nimport { listPullRequests } from '../session-git.js';\nimport { getSessionPublisher } from '../session-publisher.js';\nimport type { SessionPullRequest } from '../session-state.js';\nimport { loadTaskPlan } from '../state-manager.js';\n\n/**\n * Which Pull Request the `pr-review` phase should look at.\n *\n * The order is deterministic and the search never falls back to \"some PR of\n * this repository\": a review pointed at an unrelated Pull Request is worse\n * than no review at all, so an exhausted search is an actionable error.\n */\n\n/** Origin of the resolved Pull Request, in precedence order. */\nexport type PullRequestSource = 'argument' | 'session' | 'plan' | 'branch';\n\nexport interface ResolvedPullRequest {\n number: number;\n /** Only known when the origin carried it; never synthesized. */\n url: string | null;\n title: string | null;\n headBranch: string | null;\n source: PullRequestSource;\n}\n\n/**\n * Failure to settle on a Pull Request. Carries the exit code the CLI should\n * return, mirroring IssueResolutionError (`issues/resolver.ts`).\n */\nexport class PrDiscoveryError extends Error {\n readonly exitCode: number;\n\n constructor(message: string, exitCode = 1) {\n super(message);\n this.name = 'PrDiscoveryError';\n this.exitCode = exitCode;\n }\n}\n\n/** Data sources, injectable for tests — the pattern of `GitStateSources`. */\nexport interface PrDiscoverySources {\n /** PRs already known to the active session publisher (`--web` runs). */\n sessionPullRequests(): SessionPullRequest[];\n /** `plan.pullRequest` persisted by the `pr` phase, or null. */\n planPullRequest(issue: string): Promise<PullRequestRef | null>;\n currentBranch(): Promise<string>;\n branchPullRequests(branch: string): Promise<SessionPullRequest[]>;\n}\n\nexport interface ResolvePullRequestOptions {\n /** Issue the pipeline is working on; enables the `plan` source. */\n issue?: string;\n /** `--yes`, or any non-interactive caller (`run`): skip the confirmation. */\n yes?: boolean;\n /**\n * Whether a prompt may be shown. Defaults to a real TTY on both ends outside\n * CI, which is what keeps the confirmation from blocking a headless run.\n */\n interactive?: boolean;\n /** Input stream for the confirmation. Defaults to process.stdin. */\n stdin?: NodeJS.ReadableStream;\n /** Output stream for the confirmation. Defaults to process.stdout. */\n stdout?: NodeJS.WritableStream;\n /** Informational sink. Defaults to printInfo. */\n info?: (message: string) => void;\n /** Warning sink. Defaults to printWarning. */\n warn?: (message: string) => void;\n sources?: Partial<PrDiscoverySources>;\n}\n\n/** How many invalid answers are tolerated before the prompt gives up. */\nconst MAX_PROMPT_ATTEMPTS = 3;\n\nconst SOURCE_LABELS: Record<PullRequestSource, string> = {\n argument: 'the command argument',\n session: 'the current session',\n plan: 'tasks.json',\n branch: 'the current branch',\n};\n\n/**\n * `plan.pullRequest` of the issue's global tasks.json. Best-effort: a missing\n * plan, an invalid one or a plan written before the `pr` phase ran is simply\n * \"no candidate\", never a failure — the next source still gets its turn.\n */\nasync function loadPlanPullRequest(issue: string): Promise<PullRequestRef | null> {\n try {\n const plan = await loadTaskPlan((await resolveIssuePaths(issue)).tasksFile);\n return plan.pullRequest ?? null;\n } catch {\n return null;\n }\n}\n\nconst defaultSources: PrDiscoverySources = {\n sessionPullRequests: () => getSessionPublisher().snapshot().pullRequests,\n planPullRequest: loadPlanPullRequest,\n currentBranch: getCurrentBranch,\n branchPullRequests: listPullRequests,\n};\n\n/**\n * The most recent of several Pull Requests: the highest number, which does not\n * depend on the order the source returned them.\n */\nexport function mostRecent(pullRequests: SessionPullRequest[]): SessionPullRequest | null {\n let latest: SessionPullRequest | null = null;\n for (const pr of pullRequests) {\n if (latest === null || pr.number > latest.number) {\n latest = pr;\n }\n }\n return latest;\n}\n\n/**\n * The PR number inside an explicit argument: `184`, `#184` or a GitHub URL.\n * Returns null for anything else, so a typo fails loudly instead of being\n * coerced into a number that addresses an unrelated Pull Request.\n */\nfunction parsePrArgument(arg: string): number | null {\n const trimmed = arg.trim();\n const fromUrl = trimmed.match(/\\/pull\\/(\\d+)/);\n if (fromUrl?.[1] !== undefined) {\n return Number(fromUrl[1]);\n }\n const plain = trimmed.match(/^#?(\\d+)$/);\n return plain?.[1] === undefined ? null : Number(plain[1]);\n}\n\nfunction isInteractiveByDefault(): boolean {\n const ci = process.env.CI;\n const inCi = ci !== undefined && ci !== '' && ci !== '0' && ci.toLowerCase() !== 'false';\n return Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY) && !inCi;\n}\n\n/** Human-readable one-liner for logs and the confirmation prompt. */\nfunction describe(pr: ResolvedPullRequest): string {\n const parts = [`#${pr.number}`];\n if (pr.title !== null) parts.push(`\"${pr.title}\"`);\n if (pr.headBranch !== null) parts.push(`(${pr.headBranch})`);\n return parts.join(' ');\n}\n\n/**\n * Ask for confirmation, defaulting to yes on an empty answer.\n *\n * Lines are queued rather than read on demand for the same reason as\n * `issues/resolver.ts`: `rl.question` drops input that arrives before the\n * prompt is written, which silently loses a piped answer. EOF answers null,\n * which the caller treats as a refusal — never as consent.\n */\nasync function confirm(\n query: string,\n stdin: NodeJS.ReadableStream,\n stdout: NodeJS.WritableStream,\n warn: (message: string) => void,\n): Promise<boolean> {\n const rl = createInterface({ input: stdin, output: stdout });\n const queued: string[] = [];\n let pending: ((line: string | null) => void) | null = null;\n let closed = false;\n\n rl.on('line', (line: string) => {\n if (pending !== null) {\n const resolve = pending;\n pending = null;\n resolve(line);\n return;\n }\n queued.push(line);\n });\n rl.on('close', () => {\n closed = true;\n if (pending !== null) {\n const resolve = pending;\n pending = null;\n resolve(null);\n }\n });\n\n const ask = (): Promise<string | null> =>\n new Promise<string | null>((resolve) => {\n const buffered = queued.shift();\n if (buffered !== undefined) {\n resolve(buffered);\n return;\n }\n if (closed) {\n resolve(null);\n return;\n }\n stdout.write(query);\n pending = resolve;\n });\n\n try {\n for (let attempt = 0; attempt < MAX_PROMPT_ATTEMPTS; attempt++) {\n const answer = await ask();\n if (answer === null) {\n return false;\n }\n const normalized = answer.trim().toLowerCase();\n if (normalized === '' || normalized === 'y' || normalized === 'yes') {\n return true;\n }\n if (normalized === 'n' || normalized === 'no') {\n return false;\n }\n warn(`Invalid answer: '${answer.trim()}'. Enter y or n.`);\n }\n return false;\n } finally {\n rl.close();\n }\n}\n\n/**\n * Settle on the Pull Request to review, in this order:\n *\n * 1. the explicit argument;\n * 2. `plan.pullRequest`, written by the `pr` phase (when an issue is known);\n * 3. the PRs the active in-memory session publisher already knows about\n * (populated during `run --web` via `publishGitState` — not by reading\n * `session.json` from disk);\n * 4. the PRs of the current branch, via `gh`;\n * 5. an actionable failure.\n *\n * The plan is preferred over the session: after `pr`, `plan.pullRequest` is the\n * authoritative record of what this pipeline opened, while the session may\n * still list older PRs for the same branch (`gh pr list --state all`).\n *\n * Sources 2–4 were discovered rather than requested, so an interactive\n * terminal is asked to confirm before a long (and paid) review runs against\n * the wrong Pull Request. Anywhere else — `--yes`, CI, a pipe or the `run`\n * command — the discovery is only logged.\n */\nexport async function resolvePullRequest(\n prArg?: string,\n opts: ResolvePullRequestOptions = {},\n): Promise<ResolvedPullRequest> {\n const info = opts.info ?? printInfo;\n const warn = opts.warn ?? printWarning;\n const sources: PrDiscoverySources = { ...defaultSources, ...opts.sources };\n\n if (prArg !== undefined && prArg.trim() !== '') {\n const number = parsePrArgument(prArg);\n if (number === null) {\n throw new PrDiscoveryError(\n `Invalid Pull Request reference '${prArg.trim()}'. ` +\n 'Use a number (e.g. 184) or a GitHub Pull Request URL.',\n );\n }\n const url = prArg.trim().includes('/pull/') ? prArg.trim() : null;\n return { number, url, title: null, headBranch: null, source: 'argument' };\n }\n\n const discovered = await discover(sources, opts.issue, warn);\n if (discovered === null) {\n throw new PrDiscoveryError(\n 'No Pull Request found for this branch, in tasks.json or in the current session. ' +\n 'Run `issue-flow pr-review <number>` with the Pull Request number.',\n );\n }\n\n const interactive = opts.yes === true ? false : (opts.interactive ?? isInteractiveByDefault());\n if (!interactive) {\n info(\n `Reviewing Pull Request ${describe(discovered)}, found in ${SOURCE_LABELS[discovered.source]}.`,\n );\n return discovered;\n }\n\n info(`Pull Request found in ${SOURCE_LABELS[discovered.source]}:`);\n info(` number: #${discovered.number}`);\n info(` title: ${discovered.title ?? '(unknown)'}`);\n info(` branch: ${discovered.headBranch ?? '(unknown)'}`);\n\n const accepted = await confirm(\n `Review Pull Request #${discovered.number}? (Y/n): `,\n opts.stdin ?? process.stdin,\n opts.stdout ?? process.stdout,\n warn,\n );\n if (!accepted) {\n throw new PrDiscoveryError(\n `Cancelled: Pull Request #${discovered.number} was not confirmed. ` +\n 'Run `issue-flow pr-review <number>` to review a specific Pull Request.',\n );\n }\n return discovered;\n}\n\n/**\n * Sources 2–4, in order, stopping at the first hit. Every source is allowed to\n * fail on its own: a broken `gh` or an unreadable plan must not mask a\n * candidate another source could still provide.\n */\nasync function discover(\n sources: PrDiscoverySources,\n issue: string | undefined,\n warn: (message: string) => void,\n): Promise<ResolvedPullRequest | null> {\n if (issue !== undefined && issue !== '') {\n try {\n const fromPlan = await sources.planPullRequest(issue);\n if (fromPlan !== null) {\n return {\n number: fromPlan.number,\n url: fromPlan.url,\n title: null,\n headBranch: fromPlan.headBranch,\n source: 'plan',\n };\n }\n } catch {\n // An unreadable plan is \"no candidate\", never a hard failure.\n }\n }\n\n let sessionPrs: SessionPullRequest[] = [];\n try {\n sessionPrs = sources.sessionPullRequests();\n } catch {\n // The monitoring snapshot is an optimization, never a requirement.\n }\n const fromSession = mostRecent(sessionPrs);\n if (fromSession !== null) {\n return {\n number: fromSession.number,\n url: fromSession.url,\n title: fromSession.title,\n headBranch: null,\n source: 'session',\n };\n }\n\n let branch = '';\n try {\n branch = await sources.currentBranch();\n } catch (err) {\n warn(\n `Could not detect the current branch: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n if (branch === '') {\n return null;\n }\n\n let branchPrs: SessionPullRequest[] = [];\n try {\n branchPrs = await sources.branchPullRequests(branch);\n } catch (err) {\n warn(\n `Could not list Pull Requests for branch '${branch}': ${err instanceof Error ? err.message : String(err)}`,\n );\n return null;\n }\n const fromBranch = mostRecent(branchPrs);\n if (fromBranch === null) {\n return null;\n }\n return {\n number: fromBranch.number,\n url: fromBranch.url,\n title: fromBranch.title,\n headBranch: branch,\n source: 'branch',\n };\n}\n","import { mkdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { PrReviewRecommendation } from '../../types.js';\nimport { writeFileAtomic } from '../../utils/fs.js';\nimport { run } from '../../utils/shell.js';\nimport {\n type PrReviewFinding,\n type PrReviewIndex,\n type PrReviewPullRequest,\n readPrReviewIndex,\n reportFileName,\n upsertRound,\n} from './report.js';\n\n/**\n * Where the outcome of a review round goes.\n *\n * The phase itself only knows this interface, so pointing the results at\n * GitHub later (a review comment, a check run) is a new implementation and a\n * configuration change — not a change to the phase.\n */\n\n/** One finished review round, ready to be published. */\nexport interface PrReviewReport {\n pullRequest: PrReviewPullRequest;\n round: number;\n at: string;\n headSha: string | null;\n /** null when the agent output could not be parsed. */\n recommendation: PrReviewRecommendation | null;\n blockers: string[];\n findings: PrReviewFinding[];\n /** The full Markdown report, already assembled by `buildReportMarkdown()`. */\n markdown: string;\n}\n\nexport interface PrReviewPublisher {\n publish(report: PrReviewReport): Promise<void>;\n}\n\n/**\n * Writes `pr-<N>-round-<k>.md` plus `index.json` under the artifact directory.\n *\n * Rounds are additive: an existing index is loaded and merged instead of being\n * replaced, so round N+1 never erases the trail of the rounds before it. Both\n * files go through `writeFileAtomic()`, the mechanism `saveTaskPlan()` uses —\n * an interrupted write leaves the previous artifacts intact rather than a\n * truncated report.\n *\n * This publisher never writes to GitHub.\n */\nexport class LocalReportPublisher implements PrReviewPublisher {\n constructor(private readonly dir: string) {}\n\n async publish(report: PrReviewReport): Promise<void> {\n await mkdir(this.dir, { recursive: true });\n\n const fileName = reportFileName(report.pullRequest.number, report.round);\n await writeFileAtomic(join(this.dir, fileName), report.markdown);\n\n const existing = await readPrReviewIndex(this.dir);\n const index: PrReviewIndex = {\n schemaVersion: 1,\n pullRequest: report.pullRequest,\n rounds: existing?.rounds ?? [],\n };\n\n const updated = upsertRound(index, {\n round: report.round,\n at: report.at,\n recommendation: report.recommendation,\n headSha: report.headSha,\n reportPath: fileName,\n findings: report.findings,\n });\n\n await writeFileAtomic(join(this.dir, 'index.json'), `${JSON.stringify(updated, null, 2)}\\n`);\n }\n}\n\n/**\n * The marker that makes a published review addressable.\n *\n * A review round is *republished* more often than it is first published — a\n * retry of the phase, a re-run after a correction cycle, a resume — and without\n * a way to recognise its own previous comment every one of those leaves another\n * copy on the Pull Request. The marker is per round on purpose: round 2 is a\n * different statement from round 1 and gets its own comment, while a second\n * publication of round 1 updates the one that is there.\n *\n * It is an HTML comment, so it is invisible in the rendered body and survives\n * GitHub's Markdown untouched.\n */\nexport function reviewCommentMarker(round: number): string {\n return `<!-- issue-flow:review:${round} -->`;\n}\n\ninterface GitHubComment {\n id: number;\n body: string;\n}\n\nfunction parseComments(stdout: string): GitHubComment[] {\n try {\n const parsed: unknown = JSON.parse(stdout || '[]');\n if (!Array.isArray(parsed)) return [];\n return parsed.filter(\n (entry): entry is GitHubComment =>\n typeof entry === 'object' &&\n entry !== null &&\n typeof (entry as { id?: unknown }).id === 'number' &&\n typeof (entry as { body?: unknown }).body === 'string',\n );\n } catch {\n return [];\n }\n}\n\n/**\n * Publishes the report as a comment on the Pull Request, **updating** the\n * comment of the same round instead of adding a second one.\n *\n * The sequence is: read the existing comments, look for this round's marker,\n * and either `PATCH` that comment or post a new one. A `gh` that cannot answer\n * the first question falls through to posting — the failure mode of a\n * duplicated comment is better than the failure mode of a review nobody sees —\n * and nothing here throws: a Pull Request comment is a report, and a report\n * that fails to publish must not fail the phase.\n */\nexport class GitHubCommentPublisher implements PrReviewPublisher {\n async publish(report: PrReviewReport): Promise<void> {\n const marker = reviewCommentMarker(report.round);\n const body = `${marker}\\n${report.markdown}`;\n const pr = report.pullRequest.number;\n\n const listed = await run('gh', [\n 'api',\n `repos/{owner}/{repo}/issues/${pr}/comments`,\n '--paginate',\n ]);\n\n const existing =\n listed.exitCode === 0\n ? parseComments(listed.stdout).find((comment) => comment.body.includes(marker))\n : undefined;\n\n if (existing !== undefined) {\n await run('gh', [\n 'api',\n '--method',\n 'PATCH',\n `repos/{owner}/{repo}/issues/comments/${existing.id}`,\n '-f',\n `body=${body}`,\n ]);\n return;\n }\n\n await run('gh', ['pr', 'comment', String(pr), '--body', body]);\n }\n}\n\n/** Runs several publishers over one report, in order. */\nexport class CompositePrReviewPublisher implements PrReviewPublisher {\n constructor(private readonly members: readonly PrReviewPublisher[]) {}\n\n async publish(report: PrReviewReport): Promise<void> {\n for (const member of this.members) {\n await member.publish(report);\n }\n }\n}\n\n/**\n * Publishers selectable by configuration.\n *\n * `github` composes rather than replaces: the local artifacts are what the\n * correction cycle and `resume` read, and a Pull Request comment is an\n * additional audience, never a substitute for them.\n */\nexport type PrReviewPublisherKind = 'local' | 'github';\n\nconst PUBLISHERS: Record<PrReviewPublisherKind, (dir: string) => PrReviewPublisher> = {\n local: (dir) => new LocalReportPublisher(dir),\n github: (dir) =>\n new CompositePrReviewPublisher([new LocalReportPublisher(dir), new GitHubCommentPublisher()]),\n};\n\n/**\n * The publisher the phase should use. Going through the factory is what keeps\n * the command bound to `PrReviewPublisher` alone: adding a GitHub adapter is a\n * new entry here plus a configuration key, never a change to the phase.\n */\nexport function createPrReviewPublisher(\n dir: string,\n kind: PrReviewPublisherKind = 'local',\n): PrReviewPublisher {\n return PUBLISHERS[kind](dir);\n}\n","import { readdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { resolveIssuePaths } from '../../storage/resolve.js';\nimport type { PrReviewRecommendation } from '../../types.js';\n\n/**\n * Everything the `pr-review` phase produces out of a headless run: the parsed\n * verdict, the Markdown report and the structured index.\n *\n * The Markdown is what a human reads; `index.json` is what an automation reads.\n * Keeping both means a future GitHub publisher never has to re-parse Markdown\n * to know what the review concluded.\n */\n\n/** The Pull Request a report is about. Fields the source never knew stay null. */\nexport interface PrReviewPullRequest {\n number: number;\n url: string | null;\n title: string | null;\n headBranch: string | null;\n}\n\nexport type FindingSeverity = 'blocker' | 'high' | 'medium' | 'low';\n\nexport interface PrReviewFinding {\n severity: FindingSeverity;\n file: string | null;\n line: number | null;\n title: string;\n}\n\nexport interface PrReviewResult {\n recommendation: PrReviewRecommendation;\n /** Items listed under `BLOCKERS:`; empty when the review found none. */\n blockers: string[];\n}\n\n/**\n * Parsing outcome. A failure is explicit rather than an `APPROVE` default:\n * silently approving a Pull Request because the agent's output was malformed\n * is the one failure mode this phase must never have.\n */\nexport type PrReviewParse = { ok: true; result: PrReviewResult } | { ok: false; error: string };\n\nexport interface PrReviewRoundEntry {\n round: number;\n at: string;\n /** null when the output could not be parsed — never coerced to APPROVE. */\n recommendation: PrReviewRecommendation | null;\n headSha: string | null;\n /** Relative to the directory holding index.json. */\n reportPath: string;\n findings: PrReviewFinding[];\n}\n\nexport interface PrReviewIndex {\n schemaVersion: 1;\n pullRequest: PrReviewPullRequest;\n rounds: PrReviewRoundEntry[];\n}\n\nconst RECOMMENDATIONS: Record<string, PrReviewRecommendation> = {\n APPROVE: 'APPROVE',\n APPROVE_WITH_SUGGESTIONS: 'APPROVE_WITH_SUGGESTIONS',\n REQUEST_CHANGES: 'REQUEST_CHANGES',\n};\n\n/**\n * Read the `<pr-review-result>` block. Tolerant in the spirit of\n * `parseReviewResult()` (`commands/review.ts`): without the block, a bare\n * `RECOMMENDATION:` line in the raw output still counts. Anything else — no\n * recommendation at all, or one outside the enum — is a parse failure.\n */\nexport function parsePrReviewResult(output: string): PrReviewParse {\n const block = output.match(/<pr-review-result>([\\s\\S]*?)<\\/pr-review-result>/);\n const scope = block?.[1] ?? output;\n\n const line = scope.match(/RECOMMENDATION:\\s*([^\\n\\r]+)/i);\n if (line?.[1] === undefined) {\n return {\n ok: false,\n error: block\n ? 'The <pr-review-result> block has no RECOMMENDATION line.'\n : 'No <pr-review-result> block and no RECOMMENDATION line in the output.',\n };\n }\n\n // Markdown emphasis around the verdict is stripped, but never the inner\n // underscores of APPROVE_WITH_SUGGESTIONS.\n const raw = line[1]\n .trim()\n .replace(/[*`]/g, '')\n .replace(/^_+|[_.]+$/g, '')\n .trim();\n const normalized = raw.toUpperCase().replace(/[\\s-]+/g, '_');\n const recommendation = RECOMMENDATIONS[normalized];\n if (recommendation === undefined) {\n return {\n ok: false,\n error:\n `Unknown recommendation '${raw}'. ` +\n 'Expected APPROVE, APPROVE_WITH_SUGGESTIONS or REQUEST_CHANGES.',\n };\n }\n\n return { ok: true, result: { recommendation, blockers: parseBlockers(scope) } };\n}\n\n/** Items under `BLOCKERS:`, up to the end of the block or the next label. */\nfunction parseBlockers(scope: string): string[] {\n const match = scope.match(/BLOCKERS:([\\s\\S]*)/i);\n if (match?.[1] === undefined) return [];\n\n return match[1]\n .split('\\n')\n .map((entry) => entry.trim())\n .filter((entry) => /^[-*]\\s+/.test(entry))\n .map((entry) => entry.replace(/^[-*]\\s+/, '').trim())\n .filter((entry) => entry !== '' && !/^(none|nenhum|n\\/a)\\.?$/i.test(entry));\n}\n\n/** The sections every report carries, in order. */\nexport const REPORT_SECTIONS = [\n 'Executive summary',\n 'Strengths',\n 'Issues found',\n 'Suggested improvements',\n 'Architectural observations',\n 'Risks identified',\n 'Required before merge',\n 'Final recommendation',\n] as const;\n\nexport type ReportSection = (typeof REPORT_SECTIONS)[number];\n\n/**\n * Headings that map onto a canonical section. The agent writes prose, so the\n * mapping is deliberately forgiving — an unmatched heading is not dropped, it\n * lands in \"Additional notes\".\n */\nconst SECTION_ALIASES: Record<ReportSection, string[]> = {\n 'Executive summary': ['executive summary', 'summary', 'overview', 'resumo executivo', 'resumo'],\n Strengths: ['strengths', 'positives', 'highlights', 'what went well', 'pontos positivos'],\n 'Issues found': ['issues found', 'issues', 'problems', 'findings', 'problemas encontrados'],\n 'Suggested improvements': [\n 'suggested improvements',\n 'suggestions',\n 'improvements',\n 'sugestoes de melhoria',\n 'sugestoes',\n ],\n 'Architectural observations': [\n 'architectural observations',\n 'architecture',\n 'architectural notes',\n 'observacoes arquiteturais',\n 'arquitetura',\n ],\n 'Risks identified': ['risks identified', 'risks', 'riscos identificados', 'riscos'],\n 'Required before merge': [\n 'required before merge',\n 'blockers',\n 'must fix before merge',\n 'itens obrigatorios antes do merge',\n ],\n 'Final recommendation': [\n 'final recommendation',\n 'recommendation',\n 'verdict',\n 'recomendacao final',\n 'recomendacao',\n ],\n};\n\n/** lowercase, unaccented, without list numbering or trailing punctuation. */\nfunction normalizeHeading(heading: string): string {\n return heading\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toLowerCase()\n .replace(/^\\d+[.)]\\s*/, '')\n .replace(/[*_`#]/g, '')\n .replace(/[.:;!?]+$/, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction matchSection(heading: string): ReportSection | null {\n const normalized = normalizeHeading(heading);\n for (const section of REPORT_SECTIONS) {\n if (SECTION_ALIASES[section].includes(normalized)) return section;\n }\n return null;\n}\n\ninterface SplitBody {\n sections: Map<ReportSection, string>;\n /** Preamble and unrecognized headings, preserved verbatim. */\n extra: string;\n}\n\n/**\n * Split the agent's Markdown by heading. Content that matches no canonical\n * section is kept rather than discarded: the report is the record of the\n * review, so nothing the agent wrote may be lost in the rewrite.\n */\nfunction splitBody(body: string): SplitBody {\n const sections = new Map<ReportSection, string>();\n const extra: string[] = [];\n let current: ReportSection | null = null;\n let buffer: string[] = [];\n\n const flush = () => {\n const content = buffer.join('\\n').trim();\n buffer = [];\n if (content === '') return;\n if (current === null) {\n extra.push(content);\n return;\n }\n const previous = sections.get(current);\n sections.set(current, previous === undefined ? content : `${previous}\\n\\n${content}`);\n };\n\n for (const line of body.split('\\n')) {\n const heading = line.match(/^#{1,6}\\s+(.*)$/);\n if (heading?.[1] === undefined) {\n buffer.push(line);\n continue;\n }\n flush();\n const section = matchSection(heading[1]);\n current = section;\n if (section === null) {\n buffer.push(line);\n }\n }\n flush();\n\n return { sections, extra: extra.join('\\n\\n').trim() };\n}\n\nconst FINDING_LINE = /^[-*]\\s*\\[(\\w+)\\]\\s+(.+)$/;\nconst FINDING_LOCATION = /^(\\S+?)(?::(\\d+))?\\s+[—–-]\\s+(.+)$/;\n\nconst SEVERITY_ALIASES: Record<string, FindingSeverity> = {\n blocker: 'blocker',\n blocking: 'blocker',\n critical: 'blocker',\n high: 'high',\n major: 'high',\n medium: 'medium',\n moderate: 'medium',\n low: 'low',\n minor: 'low',\n nit: 'low',\n};\n\n/**\n * Structured findings out of the report body, from list items written as\n * `- [severity] path/to/file.ts:12 — Title` under \"Issues found\" and\n * \"Required before merge\" (the format the prompt asks for). Items in any other\n * shape stay in the Markdown only: the index is a convenience for automations,\n * never the authoritative record.\n */\nexport function parseFindings(body: string): PrReviewFinding[] {\n const { sections } = splitBody(body);\n const scopes = [sections.get('Issues found'), sections.get('Required before merge')];\n const findings: PrReviewFinding[] = [];\n\n for (const scope of scopes) {\n if (scope === undefined) continue;\n for (const line of scope.split('\\n')) {\n const item = line.trim().match(FINDING_LINE);\n if (item?.[1] === undefined || item[2] === undefined) continue;\n\n const severity = SEVERITY_ALIASES[item[1].toLowerCase()];\n // Unknown markers stay in the Markdown only — inventing `medium` would\n // mislead any automation that keys off index.json severities.\n if (severity === undefined) continue;\n\n const rest = item[2].trim();\n const location = rest.match(FINDING_LOCATION);\n if (location?.[1] === undefined || location[3] === undefined) {\n findings.push({ severity, file: null, line: null, title: rest });\n continue;\n }\n findings.push({\n severity,\n file: location[1],\n line: location[2] === undefined ? null : Number(location[2]),\n title: location[3].trim(),\n });\n }\n }\n\n return dedupeFindings(findings);\n}\n\n/** A finding listed both as an issue and as a merge blocker is still one finding. */\nfunction dedupeFindings(findings: PrReviewFinding[]): PrReviewFinding[] {\n const seen = new Set<string>();\n return findings.filter((finding) => {\n const key = `${finding.file ?? ''}:${finding.line ?? ''}:${finding.title}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n\nexport interface BuildReportInput {\n pullRequest: PrReviewPullRequest;\n round: number;\n at: string;\n headSha?: string | null;\n /** null when parsing failed. */\n recommendation: PrReviewRecommendation | null;\n /** Raw headless output. */\n body: string;\n /** Set when `parsePrReviewResult()` failed, so the raw output is kept. */\n parseError?: string | null;\n}\n\n/**\n * Assemble the Markdown report: a metadata header, the canonical sections in a\n * fixed order and — when parsing failed — the raw output verbatim, so a\n * malformed run is still auditable instead of being reduced to an exit code.\n */\nexport function buildReportMarkdown(input: BuildReportInput): string {\n const pr = input.pullRequest;\n const parts: string[] = [`# Pull Request Review — #${pr.number} (round ${input.round})`, ''];\n\n const heading = pr.url === null ? `#${pr.number}` : `[#${pr.number}](${pr.url})`;\n parts.push(`- **Pull Request:** ${heading}${pr.title === null ? '' : ` — ${pr.title}`}`);\n if (pr.headBranch !== null) parts.push(`- **Head branch:** ${pr.headBranch}`);\n if (input.headSha != null) parts.push(`- **Head SHA:** ${input.headSha}`);\n parts.push(`- **Round:** ${input.round}`);\n parts.push(`- **Reviewed at:** ${input.at}`);\n parts.push(`- **Recommendation:** ${input.recommendation ?? 'UNPARSED'}`);\n parts.push('');\n\n if (input.parseError != null && input.parseError !== '') {\n parts.push(`> Verdict could not be parsed: ${input.parseError}`, '');\n }\n\n const { sections, extra } = splitBody(input.body);\n for (const section of REPORT_SECTIONS) {\n parts.push(`## ${section}`, '', sections.get(section) ?? '_Not reported._', '');\n }\n\n if (input.parseError != null && input.parseError !== '') {\n parts.push('## Raw agent output', '', '```text', input.body.trim(), '```', '');\n } else if (extra !== '') {\n parts.push('## Additional notes', '', extra, '');\n }\n\n return `${parts.join('\\n').trimEnd()}\\n`;\n}\n\n/**\n * The global `issues/<N>/pr-review/`, or `issues/pr-<N>/pr-review/` with no\n * issue.\n *\n * A Pull Request with no associated Issue still needs somewhere to keep its\n * rounds, so it gets the synthetic `pr-<N>` identifier — `getIssuePaths()`\n * accepts non-numeric identifiers, so it is a first-class issue directory as\n * far as the storage layer is concerned.\n */\nexport async function prReviewDir(opts: { issue?: string; pullRequest: number }): Promise<string> {\n const slug =\n opts.issue === undefined || opts.issue === ''\n ? `pr-${opts.pullRequest}`\n : opts.issue.replace(/^#/, '');\n return (await resolveIssuePaths(slug)).prReviewDir;\n}\n\nexport function reportFileName(pullRequest: number, round: number): string {\n return `pr-${pullRequest}-round-${round}.md`;\n}\n\nconst REPORT_FILE = /^pr-(\\d+)-round-(\\d+)\\.md$/;\n\n/**\n * Read `index.json`. Never throws: a missing or corrupt index means \"no rounds\n * recorded yet\", which the round resolution below cross-checks against the\n * report files actually on disk.\n */\nexport async function readPrReviewIndex(dir: string): Promise<PrReviewIndex | null> {\n try {\n const parsed: unknown = JSON.parse(await readFile(join(dir, 'index.json'), 'utf-8'));\n if (typeof parsed !== 'object' || parsed === null) return null;\n const index = parsed as Partial<PrReviewIndex>;\n if (!Array.isArray(index.rounds) || typeof index.pullRequest !== 'object') return null;\n return {\n schemaVersion: 1,\n pullRequest: index.pullRequest as PrReviewPullRequest,\n rounds: index.rounds,\n };\n } catch {\n return null;\n }\n}\n\n/** Rounds already written for this Pull Request, from the report file names. */\nasync function roundsOnDisk(dir: string, pullRequest: number): Promise<number[]> {\n try {\n return (await readdir(dir))\n .map((name) => name.match(REPORT_FILE))\n .filter((match): match is RegExpMatchArray => match !== null)\n .filter((match) => Number(match[1]) === pullRequest)\n .map((match) => Number(match[2]));\n } catch {\n return [];\n }\n}\n\n/**\n * Which round to write: the explicit `--round <n>`, which rewrites that round,\n * or one past the highest round seen. Both the index and the files on disk are\n * consulted so a corrupt index can never overwrite an existing report.\n */\nexport async function resolveRound(\n dir: string,\n pullRequest: number,\n explicit?: number,\n): Promise<number> {\n if (explicit !== undefined) return explicit;\n\n const index = await readPrReviewIndex(dir);\n const known = [\n ...(index?.rounds ?? []).map((entry) => entry.round),\n ...(await roundsOnDisk(dir, pullRequest)),\n ];\n return known.length === 0 ? 1 : Math.max(...known) + 1;\n}\n\n/**\n * Add or replace a round. Additive by construction: writing round N+1 keeps\n * every earlier entry, and `--round n` replaces exactly one.\n */\nexport function upsertRound(index: PrReviewIndex, entry: PrReviewRoundEntry): PrReviewIndex {\n const rounds = index.rounds.filter((existing) => existing.round !== entry.round);\n rounds.push(entry);\n rounds.sort((a, b) => a.round - b.round);\n return { ...index, rounds };\n}\n"],"mappings":"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","names":["join","join","join","join"]}
@@ -1,23 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- bindDiagnosticContext,
4
- flushDiagnostics,
5
- readDiagnostics,
6
- resetDiagnosticsState,
7
- writeDiagnostic
8
- } from "./chunk-QPGEFLMY.js";
9
- import "./chunk-HAKDLR6N.js";
10
- import "./chunk-YVPZUN6X.js";
11
- import "./chunk-PPZ3K4YM.js";
12
- import "./chunk-SOM4QA6C.js";
13
- import "./chunk-ICM463EO.js";
14
- import "./chunk-XEYH6ZZI.js";
15
- import "./chunk-3R27MJ5R.js";
16
- export {
17
- bindDiagnosticContext,
18
- flushDiagnostics,
19
- readDiagnostics,
20
- resetDiagnosticsState,
21
- writeDiagnostic
22
- };
23
- //# sourceMappingURL=diagnostics-HTCWF7F4.js.map
@@ -1,37 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- runExecute
4
- } from "./chunk-KZXQWC7U.js";
5
- import "./chunk-2SUFAE6X.js";
6
- import "./chunk-6YQT3SSM.js";
7
- import "./chunk-SGTTR4XW.js";
8
- import "./chunk-CRGIQFUQ.js";
9
- import "./chunk-L6HSKTQE.js";
10
- import "./chunk-UWDCIFTP.js";
11
- import "./chunk-RCZJQUTC.js";
12
- import "./chunk-HLAPOUEM.js";
13
- import "./chunk-7F7LGMVO.js";
14
- import "./chunk-UK4E2FUA.js";
15
- import "./chunk-WCCZJL7W.js";
16
- import "./chunk-DGRTJD5R.js";
17
- import "./chunk-D6ZBGJ3L.js";
18
- import "./chunk-C5TL7T4V.js";
19
- import "./chunk-E6KXCV3T.js";
20
- import "./chunk-HY5HLFDU.js";
21
- import "./chunk-DV7HUC4Z.js";
22
- import "./chunk-XZUEFTUP.js";
23
- import "./chunk-QPGEFLMY.js";
24
- import "./chunk-HAKDLR6N.js";
25
- import "./chunk-YVPZUN6X.js";
26
- import "./chunk-DUS5ZNKL.js";
27
- import "./chunk-YII4U7ZD.js";
28
- import "./chunk-PPZ3K4YM.js";
29
- import "./chunk-SOM4QA6C.js";
30
- import "./chunk-ICM463EO.js";
31
- import "./chunk-XEYH6ZZI.js";
32
- import "./chunk-P3LRUNC6.js";
33
- import "./chunk-3R27MJ5R.js";
34
- export {
35
- runExecute
36
- };
37
- //# sourceMappingURL=execute-QL3ZQIAP.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/issues/draft.ts","../src/issues/label-policy.ts","../src/commands/generate.ts"],"sourcesContent":["import type { IssueDraft } from './types.js';\n\n/**\n * Structured Issue draft emitted by the `generate` prompt.\n *\n * The agent no longer creates the Issue itself: it returns the content and the\n * providers persist it, so the same draft can reach GitHub, the local files, or\n * both. Capturing the result therefore has to be exact — the old approach of\n * fishing an issue URL out of free-form prose only ever worked for GitHub.\n *\n * The block mirrors the `<issue-body>` convention already used by the phase\n * templates, which keeps a multi-line markdown body readable and free of the\n * escaping a JSON payload would demand.\n */\n\n/** Wrapper the agent must emit around the draft. */\nexport const DRAFT_TAG = 'issue-draft';\n\n/** Failure to capture a draft from the agent output. */\nexport class IssueDraftParseError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'IssueDraftParseError';\n }\n}\n\n/**\n * Last occurrence of `<tag>...</tag>` in `source`, or `null`.\n *\n * The last one wins: an agent that shows the format before filling it in (or\n * restates the draft after a correction) would otherwise have its first,\n * throwaway attempt captured. The closing tag is searched from the end for the\n * same reason a body may legitimately quote one.\n */\nfunction extractTag(source: string, tag: string): string | null {\n const open = `<${tag}>`;\n const close = `</${tag}>`;\n\n const start = source.lastIndexOf(open);\n if (start === -1) return null;\n\n const end = source.lastIndexOf(close);\n if (end === -1 || end < start) return null;\n\n return source.slice(start + open.length, end);\n}\n\nfunction parseLabels(raw: string | null): string[] {\n if (raw === null) return [];\n return raw\n .split(',')\n .map((label) => label.trim())\n .filter((label) => label.length > 0 && label.toLowerCase() !== '(none)');\n}\n\n/**\n * Read the draft the agent produced.\n *\n * @throws IssueDraftParseError when the block is absent or has no usable\n * title/body, so a malformed answer fails loudly instead of creating an empty\n * Issue.\n */\nexport function parseIssueDraft(output: string): IssueDraft {\n const normalized = output.replace(/\\r\\n?/g, '\\n');\n const block = extractTag(normalized, DRAFT_TAG);\n\n if (block === null) {\n throw new IssueDraftParseError(\n `The agent did not emit an <${DRAFT_TAG}> block, so there is no Issue to create. ` +\n `Output was: ${normalized.trim().slice(0, 300) || '(empty)'}`,\n );\n }\n\n const title = (extractTag(block, 'title') ?? '').trim();\n const body = (extractTag(block, 'body') ?? '').trim();\n\n if (title.length === 0) {\n throw new IssueDraftParseError(`The <${DRAFT_TAG}> block has no <title>.`);\n }\n if (body.length === 0) {\n throw new IssueDraftParseError(`The <${DRAFT_TAG}> block has no <body>.`);\n }\n\n // Optional, and deliberately so: a repository with no Issue Types and no\n // templates emits neither, and the draft is exactly what it was before.\n const type = (extractTag(block, 'type') ?? '').trim();\n const template = (extractTag(block, 'template') ?? '').trim();\n\n return {\n title,\n body,\n labels: parseLabels(extractTag(block, 'labels')),\n ...(type === '' || type.toLowerCase() === '(none)' ? {} : { type }),\n ...(template === '' || template.toLowerCase() === '(none)' ? {} : { template }),\n };\n}\n","import type { LabelDefinition } from '../policy/types.js';\nimport { run } from '../utils/shell.js';\n\n/**\n * Reconciling the labels an agent suggested with the ones the repository\n * actually has.\n *\n * A workflow tool has no business changing a repository's taxonomy. Labels are\n * governance: a team that deleted `high`/`medium`/`low` in favour of a native\n * priority field, or `bug`/`enhancement` in favour of Issue Types, made a\n * decision — and a generator that silently recreates them undoes it, quietly and\n * repository-wide.\n *\n * So the default is to **validate, never create**. `policy.issues.allowLabelCreation`\n * restores the old behaviour for repositories that want it.\n */\n\nexport interface LabelReconciliation {\n /** Labels to apply, in the repository's own casing. */\n labels: string[];\n /** Suggested labels the repository does not have. */\n missing: string[];\n}\n\n/**\n * Intersect the suggested labels with the repository's real ones.\n *\n * Matching is case-insensitive and the result carries the **repository's**\n * casing: `gh issue create --label Bug` fails on a repository whose label is\n * `bug`, and an agent has no way to know which one it is.\n *\n * An empty `known` means the labels could not be read at all — no policy, no\n * `gh`, no network. That is not evidence the labels do not exist, so the\n * suggestions pass through untouched and the behaviour is the one that shipped\n * before this layer. Refusing to label an issue because discovery was offline\n * would be the wrong failure.\n */\nexport function reconcileLabels(\n suggested: string[],\n known: LabelDefinition[],\n): LabelReconciliation {\n if (known.length === 0) {\n return { labels: [...suggested], missing: [] };\n }\n\n const canonical = new Map(known.map((label) => [label.name.toLowerCase(), label.name]));\n\n const labels: string[] = [];\n const missing: string[] = [];\n for (const suggestion of suggested) {\n const match = canonical.get(suggestion.trim().toLowerCase());\n if (match === undefined) {\n if (!missing.includes(suggestion)) missing.push(suggestion);\n continue;\n }\n if (!labels.includes(match)) labels.push(match);\n }\n\n return { labels, missing };\n}\n\n/**\n * Create the labels the repository is missing, for\n * `policy.issues.allowLabelCreation: true`.\n *\n * Best-effort per label: one that cannot be created is reported and skipped, so\n * a single failure does not cost the Issue. Returns the labels that now exist.\n */\nexport async function createMissingLabels(\n names: string[],\n warn: (message: string) => void,\n): Promise<string[]> {\n const created: string[] = [];\n\n for (const name of names) {\n const result = await run('gh', ['label', 'create', name]);\n if (result.exitCode === 0) {\n created.push(name);\n continue;\n }\n // gh answers \"already exists\" when a label was created between the\n // discovery and now, which is a success for our purposes.\n const detail = (result.stderr || result.stdout).trim();\n if (/already exists/i.test(detail)) {\n created.push(name);\n continue;\n }\n warn(`Could not create label \"${name}\": ${detail || 'gh label create failed'}`);\n }\n\n return created;\n}\n","import { loadIssuesConfig, loadPolicyConfig } from '../config.js';\nimport { DEFAULT_HEADLESS_TIMEOUT_MS, runHeadless } from '../core/headless.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { publishPhaseMetrics } from '../core/session-metrics.js';\nimport { isoNow } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { ensureProvidersRegistered } from '../issues/bootstrap.js';\nimport { localIssueRef } from '../issues/context.js';\nimport { IssueDraftParseError, parseIssueDraft } from '../issues/draft.js';\nimport { createMissingLabels, reconcileLabels } from '../issues/label-policy.js';\nimport { getProvider } from '../issues/registry.js';\nimport type { Issue, IssueDraft, IssueGenerateTarget } from '../issues/types.js';\nimport { loadRepositoryPolicy } from '../policy/index.js';\nimport { resolvePolicyPlaceholders } from '../policy/placeholders.js';\nimport { resolveProjectPaths } from '../storage/resolve.js';\nimport { printError, printInfo, printSuccess, printWarning } from '../ui/logger.js';\n\n/** Human-readable pointer to a created Issue. */\nfunction issueLocation(issue: Issue): string {\n return issue.remoteRef ?? localIssueRef(issue.id);\n}\n\n/**\n * Ask the agent for the Issue content.\n *\n * The agent only drafts: creation belongs to the providers, so the same draft\n * can be persisted to GitHub, to the local files, or to both.\n */\nasync function draftIssue(promptText: string): Promise<IssueDraft> {\n // The duplicate check reads the local issues, which live in the global\n // storage: the path has to be handed over (and allowed) explicitly, since it\n // is outside the working directory the agent is started in.\n const { issuesDir } = await resolveProjectPaths();\n\n const template = await loadPrompt('generate');\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()),\n __USER_PROMPT__: promptText,\n __LOCAL_ISSUES_DIR__: issuesDir,\n });\n\n const startedAtMs = Date.now();\n const result = await runHeadless({\n prompt,\n maxTurns: 15,\n timeout: getGlobalTimeout() ?? DEFAULT_HEADLESS_TIMEOUT_MS,\n // json (not text) so the CLI reports usage: the envelope's `result` field\n // carries the same assistant text parseIssueDraft() already consumed.\n outputFormat: 'json',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep'],\n addDirs: [issuesDir],\n statusMessage: 'Drafting issue...',\n phase: 'generate',\n permission: 'workspace',\n });\n // Before the success check: the tokens were spent either way.\n publishPhaseMetrics('generate', result.cost, startedAtMs, result.agent?.provider);\n\n if (!result.success) {\n throw new Error(`Issue creation failed: ${result.error}`);\n }\n\n return parseIssueDraft(result.result);\n}\n\n/**\n * Create the GitHub Issue and mirror it locally.\n *\n * The remote comes first on purpose: it owns the identifier, and the mirror has\n * to reuse it so both copies describe one demand. A remote failure therefore\n * leaves nothing behind, and a mirror failure is reported with the remote\n * reference that already exists.\n */\nasync function createBoth(draft: IssueDraft): Promise<Issue[]> {\n const remote = await getProvider('github').create(draft);\n\n try {\n const mirror = await getProvider('local').create({\n ...draft,\n id: remote.id,\n ...(remote.remoteRef\n ? {\n remote: {\n provider: 'github' as const,\n ref: remote.remoteRef,\n syncedAt: isoNow(),\n syncedContentHash: remote.contentHash,\n },\n }\n : {}),\n });\n return [remote, mirror];\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `GitHub issue created at ${issueLocation(remote)}, but the local mirror was not written: ` +\n `${message}. Nothing else was persisted; re-run with --local to create the mirror.`,\n );\n }\n}\n\nasync function createIssues(target: IssueGenerateTarget, draft: IssueDraft): Promise<Issue[]> {\n if (target === 'both') {\n return createBoth(draft);\n }\n return [await getProvider(target).create(draft)];\n}\n\n/**\n * Reconcile the draft's labels with the ones the repository really has.\n *\n * Two failures are being prevented here, and only one of them is cosmetic.\n * GitHub rejects an issue whose label does not exist, so an invented label costs\n * the whole Issue. And a tool that creates the label instead rewrites a taxonomy\n * the team may have curated deliberately — which is worse, because it succeeds.\n *\n * Never throws: a policy that could not be resolved leaves the draft untouched.\n */\nasync function applyLabelPolicy(draft: IssueDraft): Promise<IssueDraft> {\n let known: Awaited<ReturnType<typeof loadRepositoryPolicy>>['issues']['labels'];\n let allowCreation: boolean;\n try {\n const policy = await loadRepositoryPolicy();\n known = policy.issues.labels;\n const config = await loadPolicyConfig({ projectRoot: policy.root });\n allowCreation = config.issues.allowLabelCreation ?? false;\n } catch {\n return draft;\n }\n\n const { labels, missing } = reconcileLabels(draft.labels, known);\n if (missing.length === 0) {\n return { ...draft, labels };\n }\n\n if (!allowCreation) {\n printWarning(\n `Dropping ${missing.length} label(s) this repository does not have: ${missing.join(', ')}. ` +\n 'Issue Flow does not create labels; set policy.issues.allowLabelCreation to change that.',\n );\n return { ...draft, labels };\n }\n\n const created = await createMissingLabels(missing, printWarning);\n return { ...draft, labels: [...labels, ...created] };\n}\n\nexport async function runGenerate(\n promptText: string,\n target?: IssueGenerateTarget,\n): Promise<number> {\n const config = await loadIssuesConfig();\n const destination = target ?? config.defaultGenerateTarget;\n\n ensureProvidersRegistered();\n\n let draft: IssueDraft;\n try {\n draft = await draftIssue(promptText);\n } catch (err) {\n if (err instanceof IssueDraftParseError) {\n printError(`Could not read the generated Issue draft: ${err.message}`);\n } else {\n printError(err instanceof Error ? err.message : String(err));\n }\n return 1;\n }\n\n const finalDraft = await applyLabelPolicy(draft);\n if (finalDraft.template !== undefined) {\n printInfo(`Following the repository's Issue Template: ${finalDraft.template}`);\n }\n if (finalDraft.type !== undefined) {\n printInfo(`Issue Type: ${finalDraft.type}`);\n }\n\n let created: Issue[];\n try {\n created = await createIssues(destination, finalDraft);\n } catch (err) {\n printError(err instanceof Error ? err.message : String(err));\n return 1;\n }\n\n for (const issue of created) {\n printSuccess(`Issue created (${issue.source}): ${issueLocation(issue)}`);\n }\n return 0;\n}\n"],"mappings":"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","names":[]}
@@ -1,24 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- runInit,
4
- summarizePreflight
5
- } from "./chunk-ISZ54SVE.js";
6
- import "./chunk-HY5HLFDU.js";
7
- import "./chunk-DV7HUC4Z.js";
8
- import "./chunk-XZUEFTUP.js";
9
- import "./chunk-QPGEFLMY.js";
10
- import "./chunk-HAKDLR6N.js";
11
- import "./chunk-YVPZUN6X.js";
12
- import "./chunk-DUS5ZNKL.js";
13
- import "./chunk-YII4U7ZD.js";
14
- import "./chunk-PPZ3K4YM.js";
15
- import "./chunk-SOM4QA6C.js";
16
- import "./chunk-ICM463EO.js";
17
- import "./chunk-XEYH6ZZI.js";
18
- import "./chunk-P3LRUNC6.js";
19
- import "./chunk-3R27MJ5R.js";
20
- export {
21
- runInit,
22
- summarizePreflight
23
- };
24
- //# sourceMappingURL=init-PDVQDRS6.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/operations.ts"],"sourcesContent":["import { readdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { parseJournal } from '../core/journal.js';\nimport { PIPELINE_PHASES, PipelineManager, type PipelinePhase } from '../core/pipeline.js';\nimport type { SessionEvent, SessionSnapshot } from '../core/session-state.js';\nimport { loadTaskPlan } from '../core/state-manager.js';\nimport { loadExecutionPlan } from '../execution/plan.js';\nimport type { ExecutionPlan } from '../execution/types.js';\nimport { describeRunLockOwner, isRunLockStale, readRunLock } from '../storage/lock.js';\nimport { getIssuePaths, QUEUES_DIR_NAME } from '../storage/paths.js';\nimport { resolveProjectPaths } from '../storage/resolve.js';\nimport type { RunLock } from '../storage/schemas.js';\nimport type { TaskPlan } from '../types.js';\nimport { printError, printInfo, printWarning } from '../ui/logger.js';\n\n/**\n * The operation surface of a long run: `status`, `runs`, `logs`, `pause`,\n * `cancel`.\n *\n * The CLI declared thirteen commands and not one of them answered \"what is\n * running\", \"what failed\", or \"stop what you are doing\". During a six-hour\n * unattended execution the only way to find out was to read JSON by hand, and\n * the only way to stop it was to find the pid.\n *\n * Everything here **reads state that already exists** — `run.lock`,\n * `tasks.json`, `session.json`, `execution-plan.json`, `events.jsonl` — and\n * writes nothing except the two control commands, which do the one thing a\n * person cannot do from outside: ask the owner to stop, gracefully.\n */\n\ninterface ProjectPaths {\n projectId: string;\n projectDir: string;\n issuesDir: string;\n runLockFile: string;\n}\n\n/** One issue's state, assembled from every artifact that knows part of it. */\ninterface IssueState {\n id: string;\n plan: TaskPlan | null;\n snapshot: SessionSnapshot | null;\n nextPhase: PipelinePhase | null;\n paths: ReturnType<typeof getIssuePaths>;\n}\n\nasync function project(): Promise<ProjectPaths | null> {\n try {\n return await resolveProjectPaths();\n } catch (err) {\n printError(`Not inside a usable project: ${err instanceof Error ? err.message : String(err)}`);\n return null;\n }\n}\n\nasync function readJson<T>(path: string): Promise<T | null> {\n try {\n return JSON.parse(await readFile(path, 'utf-8')) as T;\n } catch {\n return null;\n }\n}\n\nasync function loadIssueState(projectId: string, id: string): Promise<IssueState | null> {\n let paths: ReturnType<typeof getIssuePaths>;\n try {\n paths = getIssuePaths(projectId, id);\n } catch {\n return null;\n }\n\n let plan: TaskPlan | null = null;\n try {\n plan = await loadTaskPlan(paths.tasksFile);\n } catch {\n plan = null;\n }\n if (plan === null) return null;\n\n const snapshot = await readJson<SessionSnapshot>(paths.sessionFile);\n const phases: PipelinePhase[] =\n plan.noBranch === true\n ? PIPELINE_PHASES.filter((phase) => phase !== 'pr')\n : [...PIPELINE_PHASES];\n if (plan.prReview?.enabled === true) phases.push('pr-review');\n\n return {\n id,\n plan,\n snapshot,\n nextPhase: new PipelineManager(plan, paths.tasksFile, phases).getNextPhase(),\n paths,\n };\n}\n\n/** Every issue this project has state for, newest attempt first. */\nasync function allIssues(paths: ProjectPaths): Promise<IssueState[]> {\n let ids: string[];\n try {\n ids = await readdir(paths.issuesDir);\n } catch {\n return [];\n }\n\n const states: IssueState[] = [];\n for (const id of ids) {\n const state = await loadIssueState(paths.projectId, id);\n if (state !== null) states.push(state);\n }\n return states.sort((a, b) => attemptedAt(b.plan) - attemptedAt(a.plan));\n}\n\nfunction attemptedAt(plan: TaskPlan | null): number {\n const at = Date.parse(plan?.lastAttemptAt ?? '');\n return Number.isNaN(at) ? 0 : at;\n}\n\n/** Every queue plan of the project. */\nasync function allQueues(paths: ProjectPaths): Promise<ExecutionPlan[]> {\n const dir = join(paths.projectDir, QUEUES_DIR_NAME);\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch {\n return [];\n }\n\n const plans: ExecutionPlan[] = [];\n for (const entry of entries) {\n const plan = await loadExecutionPlan(join(dir, entry, 'execution-plan.json'));\n if (plan !== null) plans.push(plan);\n }\n return plans;\n}\n\n/** `2m 13s ago`, or `never`. */\nfunction since(at: string | null | undefined, now = Date.now()): string {\n if (at === null || at === undefined || at === '') return 'never';\n const parsed = Date.parse(at);\n if (Number.isNaN(parsed)) return 'unknown';\n return `${formatSeconds(Math.max(0, Math.round((now - parsed) / 1000)))} ago`;\n}\n\nfunction formatSeconds(total: number): string {\n if (total < 60) return `${total}s`;\n const minutes = Math.floor(total / 60);\n if (minutes < 60) return `${minutes}m ${total % 60}s`;\n const hours = Math.floor(minutes / 60);\n return `${hours}h ${minutes % 60}m`;\n}\n\n/* ── status ─────────────────────────────────────────────────────────────── */\n\nexport interface StatusOptions {\n /** Emit the assembled state as JSON instead of a screen. */\n json?: boolean;\n}\n\n/**\n * One screen answering \"what is happening right now\".\n *\n * The three artifacts each know part of it and none knows all of it: the lock\n * knows *who* and *how long since it last said anything*, the task plan knows\n * *which phase and which attempt*, and the queue plan knows *where in the\n * queue*. Reading them together is the whole command.\n */\nexport async function runStatus(issue?: string, options: StatusOptions = {}): Promise<number> {\n const paths = await project();\n if (paths === null) return 1;\n\n const lock = await readRunLock(paths.runLockFile);\n const issues =\n issue === undefined\n ? await allIssues(paths)\n : [await loadIssueState(paths.projectId, issue)].filter(\n (state): state is IssueState => state !== null,\n );\n const queues = await allQueues(paths);\n\n if (options.json === true) {\n printInfo(\n JSON.stringify(\n {\n owner: lock,\n ownerStale: lock === null ? null : isRunLockStale(lock),\n issues: issues.map((state) => ({\n id: state.id,\n issueStatus: state.plan?.issueStatus ?? null,\n runState: state.plan?.runState ?? null,\n nextPhase: state.nextPhase,\n branch: state.plan?.branchName ?? null,\n lastActivityAt: state.snapshot?.updatedAt ?? state.plan?.lastAttemptAt ?? null,\n retries: state.snapshot?.execution.retries ?? null,\n })),\n queues: queues.map((plan) => ({\n id: plan.id,\n status: plan.status,\n issues: plan.issues.map((entry) => ({\n id: entry.id,\n status: entry.status,\n attempts: entry.attempts,\n blockedReason: entry.blockedReason,\n })),\n })),\n },\n null,\n 2,\n ),\n );\n return 0;\n }\n\n printOwner(lock);\n\n if (issues.length === 0) {\n printInfo('No issue in this project has any state yet.');\n return 0;\n }\n\n for (const state of issues.slice(0, issue === undefined ? 5 : 1)) {\n printIssueLine(state);\n }\n\n for (const plan of queues) {\n if (plan.status === 'completed') continue;\n printInfo(`Queue ${plan.id} — ${plan.status}`);\n for (const entry of plan.issues) {\n const attempts = entry.attempts > 0 ? `, ${entry.attempts} attempt(s)` : '';\n const blocked = entry.blockedReason === null ? '' : ` — ${entry.blockedReason}`;\n printInfo(` #${entry.id} ${entry.status}${attempts}${blocked}`);\n }\n }\n\n return 0;\n}\n\nfunction printOwner(lock: RunLock | null): void {\n if (lock === null) {\n printInfo('Nothing is running in this project (no run.lock).');\n return;\n }\n if (isRunLockStale(lock)) {\n printWarning(`A stale lock is left behind: ${describeRunLockOwner(lock)}.`);\n printInfo('It will be taken over by the next run or resume.');\n return;\n }\n printInfo(`Running: ${describeRunLockOwner(lock)}`);\n printInfo(`Last heartbeat ${since(lock.lastHeartbeatAt)}.`);\n}\n\nfunction printIssueLine(state: IssueState): void {\n const plan = state.plan;\n const run = plan?.runState;\n const phase = run?.currentPhase ?? state.nextPhase ?? '—';\n const attempt = run !== undefined && run.attempt > 0 ? `, attempt ${run.attempt}` : '';\n const retries = state.snapshot?.execution.retries;\n const retryLine = retries === undefined || retries === 0 ? '' : `, ${retries} retry(ies)`;\n const activity = since(state.snapshot?.updatedAt ?? plan?.lastAttemptAt);\n\n printInfo(\n `#${state.id} ${plan?.issueStatus ?? 'unknown'}${run === undefined ? '' : ` / ${run.status}`}` +\n ` — phase ${phase}${attempt}${retryLine}; last activity ${activity}`,\n );\n if (run?.blockedReason !== null && run?.blockedReason !== undefined) {\n printWarning(` Blocked: ${run.blockedReason}`);\n }\n}\n\n/* ── runs ───────────────────────────────────────────────────────────────── */\n\n/**\n * The history: what has been run in this project, how it ended, and why.\n *\n * Deliberately built from the same artifacts `status` reads rather than from a\n * separate history file — a second record of the truth is a second thing that\n * can be wrong.\n */\nexport async function runRuns(): Promise<number> {\n const paths = await project();\n if (paths === null) return 1;\n\n const issues = await allIssues(paths);\n if (issues.length === 0) {\n printInfo('No runs recorded for this project yet.');\n return 0;\n }\n\n for (const state of issues) {\n const plan = state.plan;\n if (plan === null) continue;\n const duration =\n state.snapshot?.elapsedSeconds === null || state.snapshot?.elapsedSeconds === undefined\n ? '—'\n : formatSeconds(state.snapshot.elapsedSeconds);\n const cause =\n plan.lastError === null ? '' : ` — ${plan.lastError.message.split('\\n')[0] ?? ''}`;\n\n printInfo(\n `#${state.id.padEnd(6)} ${plan.issueStatus.padEnd(12)} ${duration.padEnd(10)} ` +\n `last attempt ${since(plan.lastAttemptAt)}${cause}`,\n );\n }\n return 0;\n}\n\n/* ── logs ───────────────────────────────────────────────────────────────── */\n\nexport interface LogsOptions {\n /** Only these event types (`retry`, `phase:end`, …). Empty means all. */\n kind?: string[];\n /** Keep reading as the journal grows. */\n follow?: boolean;\n /** How many entries to show before following. */\n tail?: number;\n /** Polling interval of `--follow`, for tests. */\n pollMs?: number;\n /** Stops `--follow`. */\n signal?: AbortSignal;\n}\n\nconst DEFAULT_TAIL = 50;\nconst FOLLOW_POLL_MS = 1000;\n\n/**\n * Read the journal, filtered and readable.\n *\n * The journal is the only record that keeps *what happened in which order* —\n * the snapshot folded it away — so this is the command that answers \"what was\n * it doing at 3am\". `--kind retry,failover` is the filter that matters most:\n * on a six-hour run those are a handful of lines among thousands.\n */\nexport async function runLogs(issue?: string, options: LogsOptions = {}): Promise<number> {\n const paths = await project();\n if (paths === null) return 1;\n\n const target = issue ?? (await allIssues(paths))[0]?.id;\n if (target === undefined) {\n printInfo('No issue in this project has a journal yet.');\n return 0;\n }\n\n const issuePaths = getIssuePaths(paths.projectId, target);\n const kinds = new Set(options.kind ?? []);\n const tail = options.tail ?? DEFAULT_TAIL;\n\n const shown = new Set<number>();\n const render = (content: string, limit: number | null): void => {\n const entries = parseJournal(content).filter(\n (entry) => kinds.size === 0 || kinds.has(entry.event.type),\n );\n const slice = limit === null ? entries : entries.slice(-limit);\n for (const entry of slice) {\n if (shown.has(entry.seq)) continue;\n shown.add(entry.seq);\n printInfo(formatEvent(entry.seq, entry.event));\n }\n };\n\n const readAll = async (): Promise<string> =>\n `${await readIfPresent(issuePaths.rotatedEventsFile)}${await readIfPresent(issuePaths.eventsFile)}`;\n\n const initial = await readAll();\n if (initial === '') {\n printInfo(\n `Issue #${target} has no journal. Enable it with resilience.journal.enabled or --continuous.`,\n );\n return 0;\n }\n render(initial, tail);\n\n if (options.follow !== true) return 0;\n\n const poll = options.pollMs ?? FOLLOW_POLL_MS;\n const signal = options.signal;\n while (signal === undefined || !signal.aborted) {\n await sleep(poll, signal);\n if (signal?.aborted === true) break;\n render(await readAll(), null);\n }\n return 0;\n}\n\nfunction formatEvent(seq: number, event: SessionEvent): string {\n const at = 'at' in event ? event.at : '';\n const detail = describeEvent(event);\n return `${String(seq).padStart(5)} ${at} ${event.type}${detail === '' ? '' : ` — ${detail}`}`;\n}\n\nfunction describeEvent(event: SessionEvent): string {\n switch (event.type) {\n case 'phase:start':\n return event.phase;\n case 'phase:end':\n return `${event.phase} ${event.success ? 'ok' : `failed: ${event.error ?? ''}`}`;\n case 'retry':\n return `attempt ${event.attempt}, ${event.delaySeconds}s — ${event.reason}`;\n case 'log':\n return `${event.level}: ${event.message}`;\n case 'session:end':\n return event.status;\n default:\n return '';\n }\n}\n\nasync function readIfPresent(path: string): Promise<string> {\n try {\n return await readFile(path, 'utf-8');\n } catch {\n return '';\n }\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n timer.unref?.();\n signal?.addEventListener('abort', () => {\n clearTimeout(timer);\n resolve();\n });\n });\n}\n\n/* ── pause and cancel ───────────────────────────────────────────────────── */\n\nexport interface ControlOptions {\n /** Injectable signaller, so a test never signals a real process. */\n signalProcess?: (pid: number, signal: NodeJS.Signals) => void;\n}\n\n/**\n * Ask the running owner to stop, gracefully.\n *\n * `SIGTERM` is the whole implementation, and deliberately so: the owner already\n * knows how to stop well (`core/shutdown.ts` writes the checkpoint, stops the\n * agent with a grace period and closes the journal). Reimplementing any of that\n * from outside the process would be a second, worse version of it.\n */\nexport async function runPause(options: ControlOptions = {}): Promise<number> {\n return stopOwner('paused', options);\n}\n\n/**\n * Stop the run and mark it so a later `resume` does not pick it up silently.\n *\n * The difference from `pause` is not in how the process ends — it ends the same\n * way — but in what the state says afterwards: a paused run is meant to be\n * resumed, a cancelled one is not.\n */\nexport async function runCancel(issue?: string, options: ControlOptions = {}): Promise<number> {\n const code = await stopOwner('cancelled', options);\n if (code !== 0) return code;\n\n const paths = await project();\n if (paths === null) return 1;\n\n const target = issue ?? (await allIssues(paths))[0]?.id;\n if (target === undefined) return 0;\n\n const state = await loadIssueState(paths.projectId, target);\n if (state === null || state.plan === null) return 0;\n\n const { saveTaskPlan, isoNow } = await import('../core/state-manager.js');\n await saveTaskPlan(state.paths.tasksFile, {\n ...state.plan,\n runState: {\n currentPhase: state.plan.runState?.currentPhase ?? null,\n attempt: state.plan.runState?.attempt ?? 0,\n owner: null,\n status: 'blocked',\n blockedReason: 'Cancelled by the user',\n lastHeartbeatAt: isoNow(),\n },\n });\n printInfo(`Issue #${target} is marked cancelled. A resume will report it instead of running it.`);\n return 0;\n}\n\nasync function stopOwner(intent: string, options: ControlOptions): Promise<number> {\n const paths = await project();\n if (paths === null) return 1;\n\n const lock = await readRunLock(paths.runLockFile);\n if (lock === null) {\n printInfo('Nothing is running in this project.');\n return 0;\n }\n if (isRunLockStale(lock)) {\n printWarning(`The lock is stale (${describeRunLockOwner(lock)}); nothing to stop.`);\n return 0;\n }\n\n const send =\n options.signalProcess ?? ((pid: number, signal: NodeJS.Signals) => process.kill(pid, signal));\n try {\n send(lock.pid, 'SIGTERM');\n } catch (err) {\n printError(\n `Could not signal pid ${lock.pid}: ${err instanceof Error ? err.message : String(err)}`,\n );\n return 1;\n }\n\n printInfo(\n `Asked pid ${lock.pid} to stop (${intent}). It writes a checkpoint, stops the agent and closes its journal before exiting.`,\n );\n return 0;\n}\n"],"mappings":"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","names":[]}