issue-flow 0.11.1 → 0.13.0

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 (180) hide show
  1. package/README.md +79 -274
  2. package/dist/agent-BXRIEKVM.js +238 -0
  3. package/dist/agent-BXRIEKVM.js.map +1 -0
  4. package/dist/{analyze-XV3K25K3.js → analyze-WS47UYGN.js} +32 -15
  5. package/dist/analyze-WS47UYGN.js.map +1 -0
  6. package/dist/{apply-WUJHDD7A.js → apply-OS62UWRN.js} +15 -5
  7. package/dist/{apply-WUJHDD7A.js.map → apply-OS62UWRN.js.map} +1 -1
  8. package/dist/bench-SA3BOME5.js +906 -0
  9. package/dist/bench-SA3BOME5.js.map +1 -0
  10. package/dist/chunk-37N2RJW2.js +120 -0
  11. package/dist/chunk-37N2RJW2.js.map +1 -0
  12. package/dist/chunk-3XLCNVMD.js +180 -0
  13. package/dist/chunk-3XLCNVMD.js.map +1 -0
  14. package/dist/chunk-5TB6KLC2.js +72 -0
  15. package/dist/chunk-5TB6KLC2.js.map +1 -0
  16. package/dist/{chunk-OBLVLIUS.js → chunk-664Y3NDX.js} +25 -13
  17. package/dist/chunk-664Y3NDX.js.map +1 -0
  18. package/dist/chunk-6FDFRNXZ.js +357 -0
  19. package/dist/chunk-6FDFRNXZ.js.map +1 -0
  20. package/dist/chunk-7KD66LIM.js +658 -0
  21. package/dist/chunk-7KD66LIM.js.map +1 -0
  22. package/dist/chunk-7Z4Y3VKM.js +26 -0
  23. package/dist/chunk-7Z4Y3VKM.js.map +1 -0
  24. package/dist/chunk-ARFVHC2F.js +1111 -0
  25. package/dist/chunk-ARFVHC2F.js.map +1 -0
  26. package/dist/{chunk-RKTEV6EG.js → chunk-BHJCGWQX.js} +141 -12
  27. package/dist/chunk-BHJCGWQX.js.map +1 -0
  28. package/dist/chunk-BQDQTIM7.js +74 -0
  29. package/dist/chunk-BQDQTIM7.js.map +1 -0
  30. package/dist/chunk-CNBYCMSU.js +2709 -0
  31. package/dist/chunk-CNBYCMSU.js.map +1 -0
  32. package/dist/chunk-CRGIQFUQ.js +83 -0
  33. package/dist/chunk-CRGIQFUQ.js.map +1 -0
  34. package/dist/chunk-DIG5XKQQ.js +298 -0
  35. package/dist/chunk-DIG5XKQQ.js.map +1 -0
  36. package/dist/chunk-FZDR7CGH.js +71 -0
  37. package/dist/chunk-FZDR7CGH.js.map +1 -0
  38. package/dist/chunk-HEF2RMKN.js +413 -0
  39. package/dist/chunk-HEF2RMKN.js.map +1 -0
  40. package/dist/{chunk-IVHZEYAX.js → chunk-HJGGNLWI.js} +25 -19
  41. package/dist/chunk-HJGGNLWI.js.map +1 -0
  42. package/dist/chunk-HQAHNIAR.js +283 -0
  43. package/dist/chunk-HQAHNIAR.js.map +1 -0
  44. package/dist/{chunk-GANSDJV7.js → chunk-IHIEXUTC.js} +46 -24
  45. package/dist/chunk-IHIEXUTC.js.map +1 -0
  46. package/dist/chunk-IV5SS4JN.js +84 -0
  47. package/dist/chunk-IV5SS4JN.js.map +1 -0
  48. package/dist/{chunk-HC2MT7BX.js → chunk-IYV6KNVF.js} +158 -592
  49. package/dist/chunk-IYV6KNVF.js.map +1 -0
  50. package/dist/{chunk-LVKQ2AKT.js → chunk-J62QSVP6.js} +10 -8
  51. package/dist/chunk-J62QSVP6.js.map +1 -0
  52. package/dist/chunk-KMMASOA6.js +134 -0
  53. package/dist/chunk-KMMASOA6.js.map +1 -0
  54. package/dist/chunk-KQHB7ZT3.js +559 -0
  55. package/dist/chunk-KQHB7ZT3.js.map +1 -0
  56. package/dist/chunk-L6HSKTQE.js +77 -0
  57. package/dist/chunk-L6HSKTQE.js.map +1 -0
  58. package/dist/{chunk-6N7LZZ5U.js → chunk-LZ6GFQAG.js} +474 -160
  59. package/dist/chunk-LZ6GFQAG.js.map +1 -0
  60. package/dist/{chunk-HQYXVIH6.js → chunk-MIVHK5YI.js} +5 -5
  61. package/dist/chunk-NEZZ6KDR.js +1217 -0
  62. package/dist/chunk-NEZZ6KDR.js.map +1 -0
  63. package/dist/{run-DBF2VWUN.js → chunk-OPNGPUKJ.js} +747 -594
  64. package/dist/chunk-OPNGPUKJ.js.map +1 -0
  65. package/dist/chunk-P3LRUNC6.js +16 -0
  66. package/dist/chunk-P3LRUNC6.js.map +1 -0
  67. package/dist/{chunk-BXCPXRBR.js → chunk-PIVGJM6K.js} +129 -32
  68. package/dist/chunk-PIVGJM6K.js.map +1 -0
  69. package/dist/{chunk-AAY6KM7N.js → chunk-PV4OX5TC.js} +167 -196
  70. package/dist/chunk-PV4OX5TC.js.map +1 -0
  71. package/dist/chunk-QCXPNSEL.js +141 -0
  72. package/dist/chunk-QCXPNSEL.js.map +1 -0
  73. package/dist/{chunk-34WNTK3K.js → chunk-QXDNBYQF.js} +53 -13
  74. package/dist/chunk-QXDNBYQF.js.map +1 -0
  75. package/dist/chunk-SNTTOQAD.js +30 -0
  76. package/dist/chunk-SNTTOQAD.js.map +1 -0
  77. package/dist/{chunk-EY6WMHV2.js → chunk-TM67EHSV.js} +67 -23
  78. package/dist/chunk-TM67EHSV.js.map +1 -0
  79. package/dist/chunk-TVN2JVOF.js +77 -0
  80. package/dist/chunk-TVN2JVOF.js.map +1 -0
  81. package/dist/chunk-TX5WIVYP.js +129 -0
  82. package/dist/chunk-TX5WIVYP.js.map +1 -0
  83. package/dist/{chunk-KQ4JQN7T.js → chunk-UJ7XWK7J.js} +83 -21
  84. package/dist/chunk-UJ7XWK7J.js.map +1 -0
  85. package/dist/{chunk-47VIN33V.js → chunk-UOCPC2Y2.js} +596 -17
  86. package/dist/chunk-UOCPC2Y2.js.map +1 -0
  87. package/dist/chunk-XEYH6ZZI.js +289 -0
  88. package/dist/chunk-XEYH6ZZI.js.map +1 -0
  89. package/dist/cli.js +390 -40
  90. package/dist/cli.js.map +1 -1
  91. package/dist/config-MEM2IWWN.js +76 -0
  92. package/dist/contract-JH2WBIO6.js +8 -0
  93. package/dist/conventions-WV4NDXGH.js +143 -0
  94. package/dist/conventions-WV4NDXGH.js.map +1 -0
  95. package/dist/execute-NITIHHCK.js +31 -0
  96. package/dist/{generate-TBVTLMOP.js → generate-MVQI7A45.js} +39 -19
  97. package/dist/{generate-TBVTLMOP.js.map → generate-MVQI7A45.js.map} +1 -1
  98. package/dist/git-YRVRUN3C.js +31 -0
  99. package/dist/init-OKWEC3EV.js +21 -0
  100. package/dist/operations-LU6JA3BX.js +374 -0
  101. package/dist/operations-LU6JA3BX.js.map +1 -0
  102. package/dist/permissions-7NMDFU27.js +18 -0
  103. package/dist/plan-LEJJF3DE.js +31 -0
  104. package/dist/{policy-XQW5NUIE.js → policy-JSYEK5B2.js} +17 -4
  105. package/dist/policy-JSYEK5B2.js.map +1 -0
  106. package/dist/pr-HPVUZ7S3.js +36 -0
  107. package/dist/pr-review-R2QZBT3F.js +29 -0
  108. package/dist/pr-review-R2QZBT3F.js.map +1 -0
  109. package/dist/prd-5O6GYCC6.js +30 -0
  110. package/dist/prd-5O6GYCC6.js.map +1 -0
  111. package/dist/ps-TNE44R5V.js +90 -0
  112. package/dist/ps-TNE44R5V.js.map +1 -0
  113. package/dist/recorder-XGF55WXI.js +31 -0
  114. package/dist/recorder-XGF55WXI.js.map +1 -0
  115. package/dist/registry-LGPE53QD.js +15 -0
  116. package/dist/registry-LGPE53QD.js.map +1 -0
  117. package/dist/resume-QMEPMSZW.js +238 -0
  118. package/dist/resume-QMEPMSZW.js.map +1 -0
  119. package/dist/review-YKWD6SNN.js +32 -0
  120. package/dist/review-YKWD6SNN.js.map +1 -0
  121. package/dist/routing-2ZUMZPJB.js +91 -0
  122. package/dist/routing-2ZUMZPJB.js.map +1 -0
  123. package/dist/run-YU4TR62R.js +50 -0
  124. package/dist/run-YU4TR62R.js.map +1 -0
  125. package/dist/state-manager-X3PT3FGT.js +36 -0
  126. package/dist/state-manager-X3PT3FGT.js.map +1 -0
  127. package/dist/usage-VCFLDA5Y.js +174 -0
  128. package/dist/usage-VCFLDA5Y.js.map +1 -0
  129. package/dist/{web-ANQQTTRD.js → web-XVHLX62B.js} +43 -27
  130. package/dist/web-XVHLX62B.js.map +1 -0
  131. package/package.json +6 -2
  132. package/prompts/execute.md +7 -8
  133. package/prompts/plan.md +4 -5
  134. package/prompts/pr.md +13 -6
  135. package/web/AGENTS.md +3 -3
  136. package/web/public/app.css +38 -0
  137. package/web/public/app.js +132 -10
  138. package/web/public/index.html +24 -0
  139. package/dist/analyze-XV3K25K3.js.map +0 -1
  140. package/dist/chunk-34WNTK3K.js.map +0 -1
  141. package/dist/chunk-3BOHTH4V.js +0 -58
  142. package/dist/chunk-3BOHTH4V.js.map +0 -1
  143. package/dist/chunk-47VIN33V.js.map +0 -1
  144. package/dist/chunk-6N7LZZ5U.js.map +0 -1
  145. package/dist/chunk-AAY6KM7N.js.map +0 -1
  146. package/dist/chunk-BXCPXRBR.js.map +0 -1
  147. package/dist/chunk-EWDJENFW.js +0 -303
  148. package/dist/chunk-EWDJENFW.js.map +0 -1
  149. package/dist/chunk-EY6WMHV2.js.map +0 -1
  150. package/dist/chunk-GANSDJV7.js.map +0 -1
  151. package/dist/chunk-HC2MT7BX.js.map +0 -1
  152. package/dist/chunk-HDV3YDTG.js +0 -52
  153. package/dist/chunk-HDV3YDTG.js.map +0 -1
  154. package/dist/chunk-IVHZEYAX.js.map +0 -1
  155. package/dist/chunk-KQ4JQN7T.js.map +0 -1
  156. package/dist/chunk-LVKQ2AKT.js.map +0 -1
  157. package/dist/chunk-OBLVLIUS.js.map +0 -1
  158. package/dist/chunk-RKTEV6EG.js.map +0 -1
  159. package/dist/chunk-VQ5VN7DF.js +0 -1018
  160. package/dist/chunk-VQ5VN7DF.js.map +0 -1
  161. package/dist/config-4THHOXNN.js +0 -41
  162. package/dist/execute-5OEKLM5X.js +0 -15
  163. package/dist/init-PRMOTQ7N.js +0 -9
  164. package/dist/plan-Y2BYZD24.js +0 -17
  165. package/dist/policy-XQW5NUIE.js.map +0 -1
  166. package/dist/pr-H6GLOSXF.js +0 -21
  167. package/dist/pr-review-CK5FR6WX.js +0 -17
  168. package/dist/prd-4PWLCGH5.js +0 -17
  169. package/dist/review-QGVAHEZF.js +0 -15
  170. package/dist/run-DBF2VWUN.js.map +0 -1
  171. package/dist/web-ANQQTTRD.js.map +0 -1
  172. /package/dist/{chunk-HQYXVIH6.js.map → chunk-MIVHK5YI.js.map} +0 -0
  173. /package/dist/{config-4THHOXNN.js.map → config-MEM2IWWN.js.map} +0 -0
  174. /package/dist/{execute-5OEKLM5X.js.map → contract-JH2WBIO6.js.map} +0 -0
  175. /package/dist/{init-PRMOTQ7N.js.map → execute-NITIHHCK.js.map} +0 -0
  176. /package/dist/{plan-Y2BYZD24.js.map → git-YRVRUN3C.js.map} +0 -0
  177. /package/dist/{pr-H6GLOSXF.js.map → init-OKWEC3EV.js.map} +0 -0
  178. /package/dist/{pr-review-CK5FR6WX.js.map → permissions-7NMDFU27.js.map} +0 -0
  179. /package/dist/{prd-4PWLCGH5.js.map → plan-LEJJF3DE.js.map} +0 -0
  180. /package/dist/{review-QGVAHEZF.js.map → pr-HPVUZ7S3.js.map} +0 -0
@@ -1,12 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- publishGitState
4
- } from "./chunk-LVKQ2AKT.js";
3
+ ensureWebMonitor
4
+ } from "./chunk-IHIEXUTC.js";
5
5
  import {
6
- isTransientFailure,
7
- retryDelaySeconds,
8
- sleep
9
- } from "./chunk-HDV3YDTG.js";
6
+ applyAcceptanceToPlan,
7
+ resolveIssueDir,
8
+ runAcceptanceGate
9
+ } from "./chunk-BQDQTIM7.js";
10
+ import {
11
+ groupBy,
12
+ summarize
13
+ } from "./chunk-CRGIQFUQ.js";
10
14
  import {
11
15
  EXECUTE_PHASE,
12
16
  elapsedSecondsSince,
@@ -14,49 +18,83 @@ import {
14
18
  publishIterationMetrics,
15
19
  publishStoryMetrics,
16
20
  resolvePolicyPlaceholders
17
- } from "./chunk-OBLVLIUS.js";
21
+ } from "./chunk-664Y3NDX.js";
18
22
  import {
19
23
  applyPlaceholders,
20
24
  loadPrompt
21
- } from "./chunk-HQYXVIH6.js";
25
+ } from "./chunk-MIVHK5YI.js";
26
+ import {
27
+ invokeSelectedAgent
28
+ } from "./chunk-ARFVHC2F.js";
29
+ import {
30
+ archiveFolderName,
31
+ commitMessage
32
+ } from "./chunk-UOCPC2Y2.js";
22
33
  import {
23
34
  createConfig,
35
+ getActiveResilienceConfig,
36
+ getShutdownSignal,
37
+ loadGlobalConfig,
38
+ loadWebConfig,
39
+ resolveAgentFor,
24
40
  resolvePaths,
25
41
  validateDependencies
26
- } from "./chunk-VQ5VN7DF.js";
42
+ } from "./chunk-CNBYCMSU.js";
27
43
  import {
28
- allStoriesPass,
29
- applyStoryMetrics,
30
- clearLastError,
31
- divideUsage,
44
+ resolveIssuePaths
45
+ } from "./chunk-HQAHNIAR.js";
46
+ import {
47
+ FilePublisher,
48
+ NullPublisher,
32
49
  formatDuration,
33
- formatTokens,
34
50
  getIcons,
51
+ getInactivityTimeout,
35
52
  getOutputCallback,
36
53
  getSessionPublisher,
37
54
  getStoryStageCallback,
38
55
  getStoryUpdateCallback,
39
56
  getTermWidth,
40
- hasPendingCorrection,
41
- initializeState,
42
57
  isVerbose,
43
- isoNow,
44
- loadTaskPlan,
45
- markIssueCompleted,
46
- markIssueInProgress,
47
- parseUsage,
48
58
  printError,
49
59
  printInfo,
50
60
  printRetry,
51
61
  printSuccess,
52
62
  printWarning,
63
+ setSessionPublisher,
64
+ useColor,
65
+ useUnicode
66
+ } from "./chunk-IYV6KNVF.js";
67
+ import {
68
+ allStoriesPass,
69
+ applyStoryMetrics,
70
+ clearLastError,
71
+ divideUsage,
72
+ formatTokens,
73
+ hasPendingCorrection,
74
+ initializeState,
75
+ isoNow,
76
+ loadTaskPlan,
77
+ markIssueCompleted,
78
+ markIssueInProgress,
53
79
  saveTaskPlan,
54
80
  selectActiveStory,
55
81
  setLastError,
56
- trimErrorMessage,
57
- useColor,
58
- useUnicode
59
- } from "./chunk-HC2MT7BX.js";
82
+ trimErrorMessage
83
+ } from "./chunk-DIG5XKQQ.js";
84
+ import {
85
+ committedStoryIds,
86
+ fixedBackoffPolicy,
87
+ getBaseBranch,
88
+ isWorkingTreeClean,
89
+ resolvePolicy,
90
+ withRetry
91
+ } from "./chunk-KQHB7ZT3.js";
92
+ import {
93
+ classify
94
+ } from "./chunk-XEYH6ZZI.js";
95
+
96
+ // src/commands/execute.ts
97
+ import { randomUUID } from "crypto";
60
98
 
61
99
  // src/core/engine.ts
62
100
  import { existsSync } from "fs";
@@ -173,7 +211,7 @@ function printBox(lines) {
173
211
  }
174
212
  console.log(blue(`${bl}${hrule}${br}`));
175
213
  }
176
- function printStartupHeader(config, plan) {
214
+ function printStartupHeader(config, plan, agent) {
177
215
  const icons = getIcons();
178
216
  const storiesTotal = plan.userStories.length;
179
217
  const storiesPassing = plan.userStories.filter((s) => s.passes).length;
@@ -181,7 +219,7 @@ function printStartupHeader(config, plan) {
181
219
  const issueLabel = config.issueNumber ? `Issue #${config.issueNumber}` : "Standalone mode";
182
220
  const maxIterLabel = config.maxIterations !== void 0 ? String(config.maxIterations) : "unlimited";
183
221
  const retryLabel = config.retryForever ? "unlimited retries" : `${config.retryLimit} consecutive retries`;
184
- printBox([
222
+ const lines = [
185
223
  `${icons.start} Issue Flow`,
186
224
  "---",
187
225
  `Issue: ${issueLabel}`,
@@ -189,9 +227,22 @@ function printStartupHeader(config, plan) {
189
227
  `Stories: ${storiesPassing}/${storiesTotal} passing`,
190
228
  `Iterations: ${maxIterLabel}`,
191
229
  `Retries: ${retryLabel}`
192
- ]);
230
+ ];
231
+ const remaining = storiesTotal - storiesPassing;
232
+ if (remaining > 0) {
233
+ const p50ExecuteSeconds = 518;
234
+ lines.push(
235
+ `Estimate: ~${formatDuration(remaining * p50ExecuteSeconds)} (${remaining} \xD7 p50 execute)`
236
+ );
237
+ }
238
+ if (agent) {
239
+ const model = agent.model ? ` \xB7 ${agent.model}` : "";
240
+ const detail = agent.detail ? ` (${agent.detail})` : "";
241
+ lines.push(`Agent: ${agent.provider}${model}${detail}`);
242
+ }
243
+ printBox(lines);
193
244
  }
194
- function printSummaryBox(status, iterations, totalRetries, elapsedSeconds, plan, extraInfo, usage) {
245
+ function printSummaryBox(status, iterations, totalRetries, elapsedSeconds, plan, extraInfo, usage, usageByAgent) {
195
246
  const icons = getIcons();
196
247
  const storiesTotal = plan.userStories.length;
197
248
  const storiesPassing = plan.userStories.filter((s) => s.passes).length;
@@ -220,10 +271,8 @@ function printSummaryBox(status, iterations, totalRetries, elapsedSeconds, plan,
220
271
  `Iterations: ${iterations}`,
221
272
  `Duration: ${duration}`
222
273
  ];
223
- const tokens = formatTokens(usage);
224
- if (tokens !== "") {
225
- boxLines.push(`Tokens: ${tokens}`);
226
- }
274
+ boxLines.push(...tokenBoxLines(usage, usageByAgent));
275
+ boxLines.push(...executionCostLines(plan));
227
276
  boxLines.push(`Retries: ${totalRetries}`);
228
277
  if (extraInfo) {
229
278
  boxLines.push("---");
@@ -232,19 +281,79 @@ function printSummaryBox(status, iterations, totalRetries, elapsedSeconds, plan,
232
281
  console.log("");
233
282
  printBox(boxLines);
234
283
  }
284
+ function executionCostLines(plan, prefix = "Cost: ") {
285
+ const records = plan.executions ?? [];
286
+ if (records.length === 0) return [];
287
+ const lines = [];
288
+ const groups = groupBy(records, "harness");
289
+ let index = 0;
290
+ const pad = " ".repeat(prefix.length);
291
+ for (const [harness, summary] of groups) {
292
+ const parts = [];
293
+ if (summary.totalCost.reported > 0) {
294
+ parts.push(`$${summary.totalCost.reported.toFixed(4)} reported`);
295
+ }
296
+ if (summary.totalCost.estimated > 0) {
297
+ parts.push(`$${summary.totalCost.estimated.toFixed(4)} estimated`);
298
+ }
299
+ if (summary.totalCost.unknownExecutions > 0) {
300
+ parts.push(`${summary.totalCost.unknownExecutions} unknown`);
301
+ }
302
+ if (parts.length === 0) {
303
+ const totals = summarize(records).totalCost;
304
+ if (totals.reported === 0 && totals.estimated === 0) {
305
+ parts.push("not reported");
306
+ }
307
+ }
308
+ if (parts.length === 0) continue;
309
+ lines.push(`${index === 0 ? prefix : pad}${harness} \xB7 ${parts.join(" \xB7 ")}`);
310
+ index += 1;
311
+ }
312
+ return lines;
313
+ }
314
+ function tokenBoxLines(usage, usageByAgent) {
315
+ const agents = usageByAgent ? Object.keys(usageByAgent) : [];
316
+ if (agents.length > 1) {
317
+ const lines = [];
318
+ for (const [index, id] of agents.entries()) {
319
+ const tokens2 = formatTokens(usageByAgent?.[id]);
320
+ if (tokens2 === "") continue;
321
+ lines.push(index === 0 ? `Tokens: ${id} \xB7 ${tokens2}` : ` ${id} \xB7 ${tokens2}`);
322
+ }
323
+ return lines;
324
+ }
325
+ const tokens = formatTokens(usage);
326
+ return tokens === "" ? [] : [`Tokens: ${tokens}`];
327
+ }
328
+ function tokenSummaryLines(usage, usageByAgent, prefix = " Tokens: ") {
329
+ const agents = usageByAgent ? Object.keys(usageByAgent) : [];
330
+ if (agents.length > 1) {
331
+ const pad = " ".repeat(prefix.length);
332
+ const lines = [];
333
+ for (const [index, id] of agents.entries()) {
334
+ const tokens2 = formatTokens(usageByAgent?.[id]);
335
+ if (tokens2 === "") continue;
336
+ lines.push(`${index === 0 ? prefix : pad}${id} \xB7 ${tokens2}`);
337
+ }
338
+ return lines;
339
+ }
340
+ const tokens = formatTokens(usage);
341
+ return tokens === "" ? [] : [`${prefix}${tokens}`];
342
+ }
235
343
  function buildRunSummaryLines(info) {
236
344
  const lines = [
237
345
  ` Branch: ${info.branchName}${info.noBranch ? " (current)" : ""}`,
238
346
  ` Stories: ${info.storyCount}`,
239
347
  ` Duration: ${formatDuration(info.elapsedSeconds)}`
240
348
  ];
241
- const tokens = formatTokens(info.usage);
242
- if (tokens !== "") {
243
- lines.push(` Tokens: ${tokens}`);
244
- }
349
+ lines.push(...tokenSummaryLines(info.usage, info.usageByAgent));
245
350
  if (!info.noBranch) {
246
351
  lines.push(` PR: ${info.prUrl}`);
247
352
  }
353
+ if (info.verification != null) {
354
+ const label = info.verification.verdict === "passed" ? "passed" : info.verification.verdict === "failed" ? "failed" : "unverified";
355
+ lines.push(` Contract: ${label}`);
356
+ }
248
357
  const review = info.prReview;
249
358
  if (review) {
250
359
  const verdict = review.recommendation ?? (review.requestedChanges ? "REQUEST_CHANGES" : "unknown");
@@ -305,6 +414,14 @@ function printRunSummary(info) {
305
414
  printWarning(
306
415
  `Pipeline finished for issue #${info.issueNumber}, but the PR review requested changes.`
307
416
  );
417
+ } else if (info.verification?.verdict === "unverified") {
418
+ printWarning(
419
+ `Pipeline finished for issue #${info.issueNumber}, but the acceptance contract is unverified.`
420
+ );
421
+ } else if (info.verification?.verdict === "failed") {
422
+ printWarning(
423
+ `Pipeline finished for issue #${info.issueNumber}, but the acceptance contract failed.`
424
+ );
308
425
  } else {
309
426
  printSuccess(`Pipeline complete for issue #${info.issueNumber}!`);
310
427
  }
@@ -313,48 +430,65 @@ function printRunSummary(info) {
313
430
  }
314
431
  }
315
432
 
433
+ // src/ui/text.ts
434
+ function stripMarkdown(text) {
435
+ return text.replace(/```[\s\S]*?```/g, " ").replace(/`([^`]+)`/g, "$1").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/__([^_]+)__/g, "$1").replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "$1").replace(/^#{1,6}\s+/gm, "").replace(/^\s*[-*+]\s+/gm, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/[ \t]{2,}/g, " ").trim();
436
+ }
437
+ function failureExcerpt(output, limit = 8) {
438
+ return output.split("\n").map((line) => stripMarkdown(line)).filter((line) => line.length > 0).slice(-limit);
439
+ }
440
+
316
441
  // src/core/executor.ts
317
- import { execa } from "execa";
318
- async function executeClaude(prompt) {
319
- const result = await execa(
320
- "claude",
321
- ["--dangerously-skip-permissions", "--print", "--output-format", "json"],
322
- {
323
- input: prompt,
324
- reject: false,
442
+ async function executeClaude(prompt, options = {}) {
443
+ let selected;
444
+ try {
445
+ selected = await invokeSelectedAgent({
446
+ prompt,
447
+ phase: "execute",
325
448
  timeout: 0,
326
- // No timeout — let the engine handle iteration limits
327
- stripFinalNewline: false
328
- }
329
- );
330
- const stdout = result.stdout?.toString() ?? "";
331
- const stderr = result.stderr?.toString() ?? "";
332
- const exitCode = result.exitCode ?? 1;
333
- let output = stdout + (stderr ? `
334
- ${stderr}` : "");
335
- let cost = null;
336
- if (exitCode === 0) {
337
- try {
338
- const parsed = JSON.parse(stdout);
339
- const text = parsed?.result;
340
- output = typeof text === "string" ? text : stdout;
341
- cost = parseUsage(parsed);
342
- } catch {
343
- }
344
- }
345
- const onOutput = getOutputCallback();
346
- if (onOutput) {
347
- const trimmed = output.trim();
348
- if (trimmed) {
349
- onOutput(trimmed);
350
- }
449
+ permission: "autonomous",
450
+ inactivityTimeoutMs: options.inactivityTimeoutMs
451
+ });
452
+ } catch (err) {
453
+ return {
454
+ exitCode: 1,
455
+ output: err instanceof Error ? err.message : String(err),
456
+ cost: null
457
+ };
458
+ }
459
+ const { run } = selected;
460
+ let output;
461
+ let cost = run.usage;
462
+ if (!run.success || run.exitCode !== 0) {
463
+ output = run.rawOutput || run.error || "";
464
+ cost = null;
465
+ } else {
466
+ output = run.result || run.rawOutput;
351
467
  }
468
+ emitAgentOutput(output, !(run.success && run.exitCode === 0));
352
469
  return {
353
- exitCode,
470
+ exitCode: run.exitCode === 0 && run.success ? 0 : run.exitCode === 0 ? 1 : run.exitCode,
354
471
  output,
355
472
  cost
356
473
  };
357
474
  }
475
+ function emitAgentOutput(output, failed) {
476
+ const onOutput = getOutputCallback();
477
+ if (!onOutput) return;
478
+ const trimmed = output.trim();
479
+ if (!trimmed) return;
480
+ if (isVerbose()) {
481
+ for (const line of trimmed.split("\n")) {
482
+ if (line.trim()) onOutput(line);
483
+ }
484
+ return;
485
+ }
486
+ if (failed) {
487
+ for (const line of failureExcerpt(trimmed)) {
488
+ onOutput(line);
489
+ }
490
+ }
491
+ }
358
492
 
359
493
  // src/core/engine.ts
360
494
  function emitLog(message) {
@@ -367,18 +501,88 @@ function emitLog(message) {
367
501
  console.log(message);
368
502
  }
369
503
  }
504
+ function executeRetryPolicy(config) {
505
+ return fixedBackoffPolicy(
506
+ config.retryLimit + 1,
507
+ config.backoffBaseSeconds,
508
+ config.backoffMaxSeconds,
509
+ { retryForever: config.retryForever }
510
+ );
511
+ }
370
512
  function newlyCompletedStoryIds(before, after) {
371
513
  const wasPassing = new Map(before.map((story) => [story.id, story.passes]));
372
514
  return after.filter((story) => story.passes && wasPassing.get(story.id) === false).map((story) => story.id);
373
515
  }
374
- function commitPlaceholders(scope, convention) {
375
- const suffix = scope === void 0 || scope === "" ? "" : `(${scope})`;
516
+ function numericIssue(value) {
517
+ if (value === void 0 || value === null || value === "") return void 0;
518
+ const parsed = typeof value === "number" ? value : Number(value);
519
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
520
+ }
521
+ function commitPlaceholders(scope, convention, options) {
376
522
  const declared = convention !== void 0 && convention !== null && convention !== "";
523
+ const type = options?.type ?? "feat";
524
+ const issueNumber = numericIssue(options?.issueNumber);
525
+ const resolvedScope = scope === void 0 || scope === "" ? void 0 : scope;
526
+ if (declared && options?.type === void 0) {
527
+ const suffix = resolvedScope === void 0 ? "" : `(${resolvedScope})`;
528
+ return {
529
+ __COMMIT_MESSAGE__: commitMessage({
530
+ type,
531
+ scope: resolvedScope,
532
+ subject: "[Story ID] - [Story Title]",
533
+ issueNumber,
534
+ signoff: options?.signoff
535
+ }).replace(`${type}${suffix}:`, `<type>${suffix}:`),
536
+ __FIX_COMMIT_MESSAGE__: commitMessage({
537
+ type: "fix",
538
+ scope: resolvedScope,
539
+ subject: "address review findings",
540
+ issueNumber,
541
+ signoff: options?.signoff
542
+ })
543
+ };
544
+ }
377
545
  return {
378
- __COMMIT_MESSAGE__: declared ? `<type>${suffix}: [Story ID] - [Story Title]` : `feat${suffix}: [Story ID] - [Story Title]`,
379
- __FIX_COMMIT_MESSAGE__: `fix${suffix}: address review findings`
546
+ __COMMIT_MESSAGE__: commitMessage({
547
+ type,
548
+ scope: resolvedScope,
549
+ subject: "[Story ID] - [Story Title]",
550
+ issueNumber,
551
+ signoff: options?.signoff
552
+ }),
553
+ __FIX_COMMIT_MESSAGE__: commitMessage({
554
+ type: "fix",
555
+ scope: resolvedScope,
556
+ subject: "address review findings",
557
+ issueNumber,
558
+ signoff: options?.signoff
559
+ })
380
560
  };
381
561
  }
562
+ function inactivityOptions() {
563
+ const configured = getInactivityTimeout();
564
+ return configured === void 0 ? {} : { inactivityTimeoutMs: configured };
565
+ }
566
+ async function adoptCommittedStories(tasksPath) {
567
+ try {
568
+ const plan = await loadTaskPlan(tasksPath);
569
+ const pending = plan.userStories.filter((story) => !story.passes);
570
+ if (pending.length === 0) return [];
571
+ if (!await isWorkingTreeClean()) return [];
572
+ const committed = await committedStoryIds(await getBaseBranch());
573
+ const adopted = pending.filter((story) => committed.has(story.id)).map((story) => story.id);
574
+ if (adopted.length === 0) return [];
575
+ await saveTaskPlan(tasksPath, {
576
+ ...plan,
577
+ userStories: plan.userStories.map(
578
+ (story) => adopted.includes(story.id) ? { ...story, passes: true } : story
579
+ )
580
+ });
581
+ return adopted;
582
+ } catch {
583
+ return [];
584
+ }
585
+ }
382
586
  async function ensureProgressFile(progressFile) {
383
587
  if (!existsSync(progressFile)) {
384
588
  const content = `# Issue Flow Progress Log
@@ -402,7 +606,7 @@ async function archiveIfBranchChanged(plan, paths) {
402
606
  }
403
607
  if (currentBranch && lastBranch && currentBranch !== lastBranch) {
404
608
  const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
405
- const folderName = lastBranch.replace(/^issue\//, "").replace(/[<>:"|?*\\]/g, "_");
609
+ const folderName = archiveFolderName(lastBranch);
406
610
  const archiveFolder = join(archiveDir, `${dateStr}-${folderName}`);
407
611
  printInfo(`Archiving previous run: ${lastBranch}`);
408
612
  await mkdir(archiveFolder, { recursive: true });
@@ -461,111 +665,158 @@ async function runEngine(config, paths) {
461
665
  emitLog("All user stories already pass. Marking issue as completed.");
462
666
  plan = markIssueCompleted(plan);
463
667
  await saveTaskPlan(paths.prdFile, plan);
464
- return 0;
668
+ return finishWithAcceptance(config, paths, plan);
465
669
  }
466
670
  await archiveIfBranchChanged(plan, paths);
467
671
  await trackBranch(plan, paths.lastBranchFile);
468
672
  await ensureProgressFile(paths.progressFile);
469
673
  const promptTemplate = await loadPrompt("execute", { projectRoot: paths.projectRoot });
470
674
  const policy = await resolvePolicyPlaceholders({ root: paths.projectRoot });
471
- printStartupHeader(config, plan);
675
+ const signoff = (await loadGlobalConfig()).commit?.signoff === true;
676
+ const executeAgent = await resolveAgentFor("execute");
677
+ printStartupHeader(config, plan, {
678
+ provider: executeAgent.provider,
679
+ model: executeAgent.model,
680
+ ...executeAgent.codex.reasoningEffort ? { detail: executeAgent.codex.reasoningEffort } : {}
681
+ });
472
682
  const startTime = Date.now();
473
683
  let i = 0;
474
- let retryCount = 0;
475
684
  let totalRetryCount = 0;
476
685
  while (true) {
477
686
  if (config.maxIterations !== void 0 && i >= config.maxIterations) {
478
687
  break;
479
688
  }
480
689
  i++;
481
- plan = await loadTaskPlan(paths.prdFile);
482
- const storiesBeforeIteration = plan.userStories;
483
- const activeStoryId = selectActiveStory(plan.userStories)?.id;
484
- getSessionPublisher().publish({
485
- type: "iteration:start",
486
- at: isoNow(),
487
- iteration: i,
488
- storyId: activeStoryId
489
- });
490
- getStoryStageCallback()?.(activeStoryId);
491
- if (isVerbose()) {
492
- printIterationHeader(i, config.maxIterations, plan.userStories, activeStoryId);
690
+ const adopted = await adoptCommittedStories(paths.prdFile);
691
+ if (adopted.length > 0) {
692
+ printInfo(
693
+ `Adopting ${adopted.join(", ")}: already committed on this branch with a clean tree.`
694
+ );
695
+ plan = await loadTaskPlan(paths.prdFile);
696
+ if (plan.userStories.every((story) => story.passes) && plan.lastReviewFindings === null) {
697
+ break;
698
+ }
493
699
  }
494
- const prompt = applyPlaceholders(promptTemplate, {
495
- __PRD_FILE__: paths.prdFile,
496
- __PROGRESS_FILE__: paths.progressFile,
497
- ...policy,
498
- // The convention comes off the projection above rather than from a second
499
- // discovery: one resolution per run is the whole point of the cache.
500
- ...commitPlaceholders(config.commitScope, policy.__COMMIT_CONVENTION__)
501
- });
502
- const iterationStartedAt = isoNow();
503
- plan = markIssueInProgress(plan, iterationStartedAt);
504
- await saveTaskPlan(paths.prdFile, plan);
505
- const iterationStartedAtMs = Date.now();
506
- const result = await executeClaude(prompt);
507
- const iterationSeconds = elapsedSecondsSince(iterationStartedAtMs);
508
- if (result.exitCode !== 0) {
509
- publishIterationMetrics(i, result.cost, iterationSeconds);
510
- const errorMessage = trimErrorMessage(result.output);
511
- if (isTransientFailure(result.exitCode, result.output)) {
512
- retryCount++;
513
- totalRetryCount++;
700
+ const attempted = await withRetry(
701
+ async () => {
514
702
  plan = await loadTaskPlan(paths.prdFile);
515
- plan = setLastError(plan, "transient_claude_failure", errorMessage);
516
- await saveTaskPlan(paths.prdFile, plan);
517
- if (!config.retryForever && retryCount > config.retryLimit) {
518
- const elapsed3 = Math.floor((Date.now() - startTime) / 1e3);
519
- plan = await loadTaskPlan(paths.prdFile);
520
- printSummaryBox(
521
- "failed",
522
- i,
523
- totalRetryCount,
524
- elapsed3,
525
- plan,
526
- `Exceeded retry limit (${config.retryLimit}) on transient errors`,
527
- getPhaseUsageTotals(EXECUTE_PHASE)
528
- );
529
- return result.exitCode;
703
+ if (plan.userStories.length > 0) {
704
+ getSessionPublisher().publish({
705
+ type: "stories:update",
706
+ at: isoNow(),
707
+ stories: plan.userStories
708
+ });
530
709
  }
531
- const delaySeconds = retryDelaySeconds(
532
- retryCount,
533
- config.backoffBaseSeconds,
534
- config.backoffMaxSeconds
535
- );
710
+ const storiesBefore = plan.userStories;
711
+ const activeStoryId = selectActiveStory(plan.userStories)?.id;
536
712
  getSessionPublisher().publish({
537
- type: "retry",
713
+ type: "iteration:start",
538
714
  at: isoNow(),
539
- attempt: retryCount,
540
- delaySeconds,
541
- reason: "transient_claude_failure"
715
+ iteration: i,
716
+ storyId: activeStoryId
542
717
  });
718
+ getStoryStageCallback()?.(activeStoryId);
543
719
  if (isVerbose()) {
544
- emitLog("");
720
+ printIterationHeader(i, config.maxIterations, plan.userStories, activeStoryId);
721
+ }
722
+ const prompt = applyPlaceholders(promptTemplate, {
723
+ __PRD_FILE__: paths.prdFile,
724
+ __PROGRESS_FILE__: paths.progressFile,
725
+ ...policy,
726
+ // The convention comes off the projection above rather than from a second
727
+ // discovery: one resolution per run is the whole point of the cache.
728
+ ...commitPlaceholders(config.commitScope, policy.__COMMIT_CONVENTION__, {
729
+ issueNumber: plan.issueNumber,
730
+ signoff
731
+ }),
732
+ __STORIES_PER_ITERATION__: String(config.storiesPerIteration ?? 1)
733
+ });
734
+ const startedAt = isoNow();
735
+ plan = markIssueInProgress(plan, startedAt);
736
+ await saveTaskPlan(paths.prdFile, plan);
737
+ const startedAtMs = Date.now();
738
+ const result2 = await executeClaude(prompt, inactivityOptions());
739
+ const seconds = elapsedSecondsSince(startedAtMs);
740
+ if (result2.exitCode !== 0) {
741
+ publishIterationMetrics(i, result2.cost, seconds);
742
+ }
743
+ return { result: result2, startedAt, seconds, storiesBefore };
744
+ },
745
+ {
746
+ policy: executeRetryPolicy(config),
747
+ // A `Ctrl+C` during a fifteen-minute backoff must stop in that instant,
748
+ // not fifteen minutes later. `abortableDelay` resolves `false` on the
749
+ // abort, which `withRetry` reports as `aborted` rather than a failure.
750
+ signal: getShutdownSignal(),
751
+ // The exit code and the output are all the evidence the CLI leaves
752
+ // behind, which is exactly what `isTransientFailure()` used to be given
753
+ // here — `classify()` is the same verdict plus the kind.
754
+ evaluate: ({ result: result2 }) => result2.exitCode === 0 ? null : classify({ source: "agent", exitCode: result2.exitCode, stdout: result2.output }),
755
+ onAttempt: async ({ value, attempt, failure, willRetry, delayMs }) => {
756
+ if (failure === null) return;
757
+ const errorMessage = trimErrorMessage(value.result.output);
758
+ if (!failure.retryable) {
759
+ plan = await loadTaskPlan(paths.prdFile);
760
+ plan = setLastError(plan, "fatal_claude_failure", errorMessage);
761
+ if (resolvePolicy(failure.kind, getActiveResilienceConfig()).onExhausted === "block") {
762
+ plan = {
763
+ ...plan,
764
+ runState: {
765
+ currentPhase: "execute",
766
+ attempt,
767
+ owner: plan.runState?.owner ?? null,
768
+ status: "blocked",
769
+ blockedReason: errorMessage,
770
+ lastHeartbeatAt: isoNow()
771
+ }
772
+ };
773
+ }
774
+ await saveTaskPlan(paths.prdFile, plan);
775
+ return;
776
+ }
777
+ totalRetryCount++;
778
+ plan = await loadTaskPlan(paths.prdFile);
779
+ plan = setLastError(plan, "transient_claude_failure", errorMessage);
780
+ await saveTaskPlan(paths.prdFile, plan);
781
+ if (!willRetry) return;
782
+ const delaySeconds = delayMs / 1e3;
783
+ getSessionPublisher().publish({
784
+ type: "retry",
785
+ at: isoNow(),
786
+ attempt,
787
+ delaySeconds,
788
+ reason: "transient_claude_failure",
789
+ kind: failure.kind
790
+ });
791
+ if (isVerbose()) {
792
+ emitLog("");
793
+ }
794
+ printRetry(
795
+ `Transient Claude failure on iteration ${i} (attempt ${attempt}). Retrying in ${delaySeconds}s.`
796
+ );
545
797
  }
546
- printRetry(
547
- `Transient Claude failure on iteration ${i} (attempt ${retryCount}). Retrying in ${delaySeconds}s.`
548
- );
549
- i--;
550
- await sleep(delaySeconds);
551
- continue;
552
798
  }
553
- plan = await loadTaskPlan(paths.prdFile);
554
- plan = setLastError(plan, "fatal_claude_failure", errorMessage);
555
- await saveTaskPlan(paths.prdFile, plan);
799
+ );
800
+ const {
801
+ result,
802
+ seconds: iterationSeconds,
803
+ storiesBefore: storiesBeforeIteration
804
+ } = attempted.value;
805
+ const iterationStartedAt = attempted.value.startedAt;
806
+ if (result.exitCode !== 0) {
556
807
  const elapsed2 = Math.floor((Date.now() - startTime) / 1e3);
808
+ plan = await loadTaskPlan(paths.prdFile);
557
809
  printSummaryBox(
558
810
  "failed",
559
811
  i,
560
812
  totalRetryCount,
561
813
  elapsed2,
562
814
  plan,
563
- `Claude CLI failed with exit code ${result.exitCode}`,
815
+ attempted.exhausted ? `Exceeded retry limit (${config.retryLimit}) on transient errors` : `Claude CLI failed with exit code ${result.exitCode}`,
564
816
  getPhaseUsageTotals(EXECUTE_PHASE)
565
817
  );
566
818
  return result.exitCode;
567
819
  }
568
- retryCount = 0;
569
820
  plan = await loadTaskPlan(paths.prdFile);
570
821
  plan = clearLastError(plan, iterationStartedAt);
571
822
  await saveTaskPlan(paths.prdFile, plan);
@@ -594,7 +845,6 @@ async function runEngine(config, paths) {
594
845
  }
595
846
  getSessionPublisher().publish({ type: "iteration:end", at: isoNow(), iteration: i });
596
847
  publishIterationMetrics(i, result.cost, iterationSeconds);
597
- await publishGitState(getSessionPublisher());
598
848
  if (result.output.includes("<promise>COMPLETE</promise>")) {
599
849
  plan = await loadTaskPlan(paths.prdFile);
600
850
  if (allStoriesPass(plan) && !hasPendingCorrection(plan)) {
@@ -610,7 +860,7 @@ async function runEngine(config, paths) {
610
860
  void 0,
611
861
  getPhaseUsageTotals(EXECUTE_PHASE)
612
862
  );
613
- return 0;
863
+ return finishWithAcceptance(config, paths, plan);
614
864
  }
615
865
  plan = setLastError(
616
866
  plan,
@@ -626,7 +876,6 @@ async function runEngine(config, paths) {
626
876
  if (isVerbose()) {
627
877
  printSuccess(`Iteration ${i} complete. Continuing...`);
628
878
  }
629
- await sleep(2);
630
879
  }
631
880
  const elapsed = Math.floor((Date.now() - startTime) / 1e3);
632
881
  plan = await loadTaskPlan(paths.prdFile);
@@ -641,6 +890,21 @@ async function runEngine(config, paths) {
641
890
  );
642
891
  return 1;
643
892
  }
893
+ async function finishWithAcceptance(config, paths, plan) {
894
+ const issueDir = resolveIssueDir(config, paths);
895
+ const outcome = await runAcceptanceGate({
896
+ issueDir,
897
+ cwd: paths.projectRoot,
898
+ addDirs: config.issueNumber === void 0 ? void 0 : [issueDir],
899
+ skipReviewer: true
900
+ });
901
+ const applied = applyAcceptanceToPlan(plan, outcome);
902
+ if (applied.failed) {
903
+ await saveTaskPlan(paths.prdFile, applied.plan);
904
+ return 1;
905
+ }
906
+ return 0;
907
+ }
644
908
 
645
909
  // src/commands/execute.ts
646
910
  async function runExecute(positionalMaxIter, options) {
@@ -661,23 +925,73 @@ async function runExecute(positionalMaxIter, options) {
661
925
  commitScope: options.commitScope
662
926
  });
663
927
  const paths = await resolvePaths(config);
664
- const exitCode = await runEngine(config, paths);
665
- if (exitCode === 0 && config.issueNumber) {
666
- try {
667
- const plan = await loadTaskPlan(paths.prdFile);
668
- if (allStoriesPass(plan)) {
669
- plan.pipeline.executionCompleted = true;
670
- await saveTaskPlan(paths.prdFile, plan);
928
+ const webConfig = await loadWebConfig();
929
+ const inheritedPublisher = getSessionPublisher();
930
+ let standalonePublisher = null;
931
+ if (webConfig.enabled && config.issueNumber !== void 0 && inheritedPublisher instanceof NullPublisher) {
932
+ const issuePaths = await resolveIssuePaths(config.issueNumber, {
933
+ projectRoot: paths.projectRoot
934
+ });
935
+ standalonePublisher = new FilePublisher(issuePaths.sessionFile, {
936
+ logLimit: webConfig.logLimit,
937
+ includeLogs: webConfig.includeLogs
938
+ });
939
+ setSessionPublisher(standalonePublisher);
940
+ const numericIssue2 = /^\d+$/.test(config.issueNumber) ? Number(config.issueNumber) : null;
941
+ standalonePublisher.publish({
942
+ type: "session:start",
943
+ at: isoNow(),
944
+ sessionId: randomUUID(),
945
+ issueNumber: numericIssue2,
946
+ phases: ["execute"]
947
+ });
948
+ standalonePublisher.publish({ type: "phase:start", at: isoNow(), phase: "execute" });
949
+ await ensureWebMonitor({
950
+ publisher: standalonePublisher,
951
+ port: webConfig.port,
952
+ host: webConfig.host,
953
+ refreshSeconds: webConfig.refreshSeconds
954
+ });
955
+ }
956
+ let exitCode = null;
957
+ try {
958
+ exitCode = await runEngine(config, paths);
959
+ if (exitCode === 0 && config.issueNumber) {
960
+ try {
961
+ const plan = await loadTaskPlan(paths.prdFile);
962
+ if (allStoriesPass(plan)) {
963
+ plan.pipeline.executionCompleted = true;
964
+ await saveTaskPlan(paths.prdFile, plan);
965
+ }
966
+ } catch {
671
967
  }
672
- } catch {
968
+ }
969
+ return exitCode;
970
+ } finally {
971
+ if (standalonePublisher !== null) {
972
+ const success = exitCode === 0;
973
+ standalonePublisher.publish({
974
+ type: "phase:end",
975
+ at: isoNow(),
976
+ phase: "execute",
977
+ success,
978
+ ...success ? {} : { error: "Execute phase failed" }
979
+ });
980
+ standalonePublisher.publish({
981
+ type: "session:end",
982
+ at: isoNow(),
983
+ status: success ? "completed" : "failed"
984
+ });
985
+ await standalonePublisher.close();
986
+ setSessionPublisher(void 0);
673
987
  }
674
988
  }
675
- return exitCode;
676
989
  }
677
990
 
678
991
  export {
992
+ stripMarkdown,
679
993
  printQueueSummary,
680
994
  printRunSummary,
681
995
  runExecute
682
996
  };
683
- //# sourceMappingURL=chunk-6N7LZZ5U.js.map
997
+ //# sourceMappingURL=chunk-LZ6GFQAG.js.map