issue-flow 0.12.0 → 0.14.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 (197) hide show
  1. package/README.md +79 -274
  2. package/dist/agent-OX3KPHE4.js +30 -0
  3. package/dist/{analyze-OEET7KGE.js → analyze-WIND2SEB.js} +31 -16
  4. package/dist/analyze-WIND2SEB.js.map +1 -0
  5. package/dist/{apply-ZIA3C52S.js → apply-JNOI2LR4.js} +18 -7
  6. package/dist/{apply-ZIA3C52S.js.map → apply-JNOI2LR4.js.map} +1 -1
  7. package/dist/bench-B4VZON7E.js +910 -0
  8. package/dist/bench-B4VZON7E.js.map +1 -0
  9. package/dist/{chunk-FADTCQGM.js → chunk-2IQ6M4ZE.js} +94 -32
  10. package/dist/chunk-2IQ6M4ZE.js.map +1 -0
  11. package/dist/chunk-3BWZO4YI.js +141 -0
  12. package/dist/chunk-3BWZO4YI.js.map +1 -0
  13. package/dist/chunk-3WFZETIM.js +163 -0
  14. package/dist/chunk-3WFZETIM.js.map +1 -0
  15. package/dist/chunk-67YIU2VB.js +362 -0
  16. package/dist/chunk-67YIU2VB.js.map +1 -0
  17. package/dist/{chunk-QCPGHBSZ.js → chunk-6BENHR2I.js} +63 -21
  18. package/dist/chunk-6BENHR2I.js.map +1 -0
  19. package/dist/{chunk-BYZDPR7N.js → chunk-6QYMSNVA.js} +194 -235
  20. package/dist/chunk-6QYMSNVA.js.map +1 -0
  21. package/dist/{chunk-Y5ZDJ4PJ.js → chunk-76MMPJ53.js} +20 -10
  22. package/dist/chunk-76MMPJ53.js.map +1 -0
  23. package/dist/chunk-7Z4Y3VKM.js +26 -0
  24. package/dist/chunk-7Z4Y3VKM.js.map +1 -0
  25. package/dist/{chunk-3QHK4YQA.js → chunk-B72RQZBV.js} +346 -119
  26. package/dist/chunk-B72RQZBV.js.map +1 -0
  27. package/dist/chunk-BEZ5BA5U.js +625 -0
  28. package/dist/chunk-BEZ5BA5U.js.map +1 -0
  29. package/dist/chunk-BRCNQXYQ.js +74 -0
  30. package/dist/chunk-BRCNQXYQ.js.map +1 -0
  31. package/dist/chunk-CGKLE6WR.js +1203 -0
  32. package/dist/chunk-CGKLE6WR.js.map +1 -0
  33. package/dist/chunk-CRGIQFUQ.js +83 -0
  34. package/dist/chunk-CRGIQFUQ.js.map +1 -0
  35. package/dist/{chunk-NS6R37II.js → chunk-F6TOYSYY.js} +141 -12
  36. package/dist/chunk-F6TOYSYY.js.map +1 -0
  37. package/dist/chunk-G5EK2XRN.js +25 -0
  38. package/dist/chunk-G5EK2XRN.js.map +1 -0
  39. package/dist/chunk-HAKDLR6N.js +134 -0
  40. package/dist/chunk-HAKDLR6N.js.map +1 -0
  41. package/dist/chunk-J3SOTUVI.js +298 -0
  42. package/dist/chunk-J3SOTUVI.js.map +1 -0
  43. package/dist/chunk-JN6D3MY6.js +30 -0
  44. package/dist/chunk-JN6D3MY6.js.map +1 -0
  45. package/dist/{chunk-LAFNLLQP.js → chunk-KMRFRXIT.js} +5 -5
  46. package/dist/chunk-KT7JKOXB.js +72 -0
  47. package/dist/chunk-KT7JKOXB.js.map +1 -0
  48. package/dist/chunk-L6HSKTQE.js +77 -0
  49. package/dist/chunk-L6HSKTQE.js.map +1 -0
  50. package/dist/chunk-LBGHI4S2.js +1264 -0
  51. package/dist/chunk-LBGHI4S2.js.map +1 -0
  52. package/dist/chunk-LEW4QM74.js +180 -0
  53. package/dist/chunk-LEW4QM74.js.map +1 -0
  54. package/dist/{chunk-FJP3UQGD.js → chunk-LFEQIZLB.js} +115 -20
  55. package/dist/chunk-LFEQIZLB.js.map +1 -0
  56. package/dist/chunk-OOVG2KBY.js +77 -0
  57. package/dist/chunk-OOVG2KBY.js.map +1 -0
  58. package/dist/{chunk-56KS3TFQ.js → chunk-OR43364P.js} +115 -160
  59. package/dist/chunk-OR43364P.js.map +1 -0
  60. package/dist/chunk-P3LRUNC6.js +16 -0
  61. package/dist/chunk-P3LRUNC6.js.map +1 -0
  62. package/dist/{chunk-TSOLWK7Y.js → chunk-PBTA3QQX.js} +8 -7
  63. package/dist/{chunk-TSOLWK7Y.js.map → chunk-PBTA3QQX.js.map} +1 -1
  64. package/dist/{chunk-MJULIKQU.js → chunk-PHW2YV6N.js} +23 -47
  65. package/dist/chunk-PHW2YV6N.js.map +1 -0
  66. package/dist/{chunk-73ATPIYX.js → chunk-PZTWU277.js} +36 -71
  67. package/dist/chunk-PZTWU277.js.map +1 -0
  68. package/dist/{chunk-M5UCXTIT.js → chunk-QD4VOILZ.js} +22 -18
  69. package/dist/chunk-QD4VOILZ.js.map +1 -0
  70. package/dist/chunk-QJ5UZAH5.js +2718 -0
  71. package/dist/chunk-QJ5UZAH5.js.map +1 -0
  72. package/dist/chunk-QK7WUO7A.js +283 -0
  73. package/dist/chunk-QK7WUO7A.js.map +1 -0
  74. package/dist/{chunk-A4SLHOW7.js → chunk-QZVKRDJY.js} +27 -19
  75. package/dist/chunk-QZVKRDJY.js.map +1 -0
  76. package/dist/chunk-REO3PXXO.js +71 -0
  77. package/dist/chunk-REO3PXXO.js.map +1 -0
  78. package/dist/{chunk-EZATO3DW.js → chunk-RVO7L4FX.js} +594 -17
  79. package/dist/chunk-RVO7L4FX.js.map +1 -0
  80. package/dist/{chunk-XQUSIEKA.js → chunk-SZMHCEYV.js} +63 -14
  81. package/dist/chunk-SZMHCEYV.js.map +1 -0
  82. package/dist/chunk-TYRNTSPJ.js +243 -0
  83. package/dist/chunk-TYRNTSPJ.js.map +1 -0
  84. package/dist/chunk-UJ3L2MS7.js +238 -0
  85. package/dist/chunk-UJ3L2MS7.js.map +1 -0
  86. package/dist/chunk-V7MO5EDS.js +120 -0
  87. package/dist/chunk-V7MO5EDS.js.map +1 -0
  88. package/dist/chunk-WB2X37MO.js +415 -0
  89. package/dist/chunk-WB2X37MO.js.map +1 -0
  90. package/dist/{chunk-NTMMF5UD.js → chunk-X44FKNKP.js} +525 -252
  91. package/dist/chunk-X44FKNKP.js.map +1 -0
  92. package/dist/chunk-XEYH6ZZI.js +289 -0
  93. package/dist/chunk-XEYH6ZZI.js.map +1 -0
  94. package/dist/cli.js +267 -44
  95. package/dist/cli.js.map +1 -1
  96. package/dist/{config-PRJGWW7J.js → config-HMFMCY2M.js} +31 -5
  97. package/dist/contract-JH2WBIO6.js +8 -0
  98. package/dist/conventions-NJF2OPVQ.js +146 -0
  99. package/dist/conventions-NJF2OPVQ.js.map +1 -0
  100. package/dist/diagnostics-THH42ZMP.js +23 -0
  101. package/dist/execute-KFNNZZHI.js +35 -0
  102. package/dist/{generate-EGB4KV2B.js → generate-FW5C6NP7.js} +37 -20
  103. package/dist/{generate-EGB4KV2B.js.map → generate-FW5C6NP7.js.map} +1 -1
  104. package/dist/git-W72QQ6GT.js +33 -0
  105. package/dist/init-HTN4V67Q.js +24 -0
  106. package/dist/{operations-BYT626KQ.js → operations-BCFOBH42.js} +27 -15
  107. package/dist/{operations-BYT626KQ.js.map → operations-BCFOBH42.js.map} +1 -1
  108. package/dist/permissions-4AKNQZHK.js +18 -0
  109. package/dist/plan-SIZLGA4V.js +34 -0
  110. package/dist/{policy-6S4GFC76.js → policy-DT6Q6ZVM.js} +20 -6
  111. package/dist/policy-DT6Q6ZVM.js.map +1 -0
  112. package/dist/pr-OMWCGUOW.js +39 -0
  113. package/dist/pr-review-N24CZPKR.js +32 -0
  114. package/dist/pr-review-N24CZPKR.js.map +1 -0
  115. package/dist/prd-5OFTZAHJ.js +33 -0
  116. package/dist/prd-5OFTZAHJ.js.map +1 -0
  117. package/dist/ps-RFORTCUD.js +93 -0
  118. package/dist/ps-RFORTCUD.js.map +1 -0
  119. package/dist/recorder-KCCPMKQG.js +32 -0
  120. package/dist/recorder-KCCPMKQG.js.map +1 -0
  121. package/dist/registry-GLPXOMFG.js +15 -0
  122. package/dist/registry-GLPXOMFG.js.map +1 -0
  123. package/dist/{resume-PPCNQVVV.js → resume-M5RLKP4I.js} +55 -34
  124. package/dist/{resume-PPCNQVVV.js.map → resume-M5RLKP4I.js.map} +1 -1
  125. package/dist/review-YM4JAABY.js +35 -0
  126. package/dist/review-YM4JAABY.js.map +1 -0
  127. package/dist/routing-NNT5WKGX.js +94 -0
  128. package/dist/routing-NNT5WKGX.js.map +1 -0
  129. package/dist/run-PZUKHPQK.js +54 -0
  130. package/dist/run-PZUKHPQK.js.map +1 -0
  131. package/dist/session-publisher-T7EI3CIP.js +11 -0
  132. package/dist/session-publisher-T7EI3CIP.js.map +1 -0
  133. package/dist/{state-manager-2EDVDBAR.js → state-manager-GQH32C44.js} +4 -2
  134. package/dist/state-manager-GQH32C44.js.map +1 -0
  135. package/dist/usage-V2CHE5PY.js +177 -0
  136. package/dist/usage-V2CHE5PY.js.map +1 -0
  137. package/dist/{web-EF5WIIGG.js → web-EA3KGNFN.js} +45 -30
  138. package/dist/web-EA3KGNFN.js.map +1 -0
  139. package/package.json +6 -2
  140. package/prompts/execute.md +7 -8
  141. package/prompts/plan.md +4 -5
  142. package/prompts/pr.md +2 -2
  143. package/web/AGENTS.md +152 -13
  144. package/web/public/app.css +380 -109
  145. package/web/public/app.js +525 -25
  146. package/web/public/index.html +68 -1
  147. package/dist/analyze-OEET7KGE.js.map +0 -1
  148. package/dist/chunk-3QHK4YQA.js.map +0 -1
  149. package/dist/chunk-42PJFMAR.js +0 -605
  150. package/dist/chunk-42PJFMAR.js.map +0 -1
  151. package/dist/chunk-4JSOYE4G.js +0 -368
  152. package/dist/chunk-4JSOYE4G.js.map +0 -1
  153. package/dist/chunk-56KS3TFQ.js.map +0 -1
  154. package/dist/chunk-73ATPIYX.js.map +0 -1
  155. package/dist/chunk-A4SLHOW7.js.map +0 -1
  156. package/dist/chunk-BAUPQXPE.js +0 -173
  157. package/dist/chunk-BAUPQXPE.js.map +0 -1
  158. package/dist/chunk-BYZDPR7N.js.map +0 -1
  159. package/dist/chunk-CD5ULRVC.js +0 -615
  160. package/dist/chunk-CD5ULRVC.js.map +0 -1
  161. package/dist/chunk-DIDUVL6O.js +0 -1365
  162. package/dist/chunk-DIDUVL6O.js.map +0 -1
  163. package/dist/chunk-EZATO3DW.js.map +0 -1
  164. package/dist/chunk-FADTCQGM.js.map +0 -1
  165. package/dist/chunk-FJP3UQGD.js.map +0 -1
  166. package/dist/chunk-LWRSMTTZ.js +0 -18
  167. package/dist/chunk-LWRSMTTZ.js.map +0 -1
  168. package/dist/chunk-M5UCXTIT.js.map +0 -1
  169. package/dist/chunk-M66SJHQQ.js +0 -206
  170. package/dist/chunk-M66SJHQQ.js.map +0 -1
  171. package/dist/chunk-MJULIKQU.js.map +0 -1
  172. package/dist/chunk-NS6R37II.js.map +0 -1
  173. package/dist/chunk-NTMMF5UD.js.map +0 -1
  174. package/dist/chunk-QCPGHBSZ.js.map +0 -1
  175. package/dist/chunk-XQUSIEKA.js.map +0 -1
  176. package/dist/chunk-Y5ZDJ4PJ.js.map +0 -1
  177. package/dist/execute-XBNVL753.js +0 -18
  178. package/dist/init-AJHCXJBP.js +0 -10
  179. package/dist/plan-QOZ3RIO6.js +0 -21
  180. package/dist/policy-6S4GFC76.js.map +0 -1
  181. package/dist/pr-EBBHD2ZX.js +0 -26
  182. package/dist/pr-review-VVMW5PO4.js +0 -20
  183. package/dist/prd-JQSZF7PR.js +0 -21
  184. package/dist/review-3F6MARWH.js +0 -19
  185. package/dist/run-RXE5GDQO.js +0 -36
  186. package/dist/web-EF5WIIGG.js.map +0 -1
  187. /package/dist/{config-PRJGWW7J.js.map → agent-OX3KPHE4.js.map} +0 -0
  188. /package/dist/{chunk-LAFNLLQP.js.map → chunk-KMRFRXIT.js.map} +0 -0
  189. /package/dist/{execute-XBNVL753.js.map → config-HMFMCY2M.js.map} +0 -0
  190. /package/dist/{init-AJHCXJBP.js.map → contract-JH2WBIO6.js.map} +0 -0
  191. /package/dist/{plan-QOZ3RIO6.js.map → diagnostics-THH42ZMP.js.map} +0 -0
  192. /package/dist/{pr-EBBHD2ZX.js.map → execute-KFNNZZHI.js.map} +0 -0
  193. /package/dist/{pr-review-VVMW5PO4.js.map → git-W72QQ6GT.js.map} +0 -0
  194. /package/dist/{prd-JQSZF7PR.js.map → init-HTN4V67Q.js.map} +0 -0
  195. /package/dist/{review-3F6MARWH.js.map → permissions-4AKNQZHK.js.map} +0 -0
  196. /package/dist/{run-RXE5GDQO.js.map → plan-SIZLGA4V.js.map} +0 -0
  197. /package/dist/{state-manager-2EDVDBAR.js.map → pr-OMWCGUOW.js.map} +0 -0
@@ -1,31 +1,29 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- ensureWebMonitor
4
- } from "./chunk-FJP3UQGD.js";
5
- import {
6
- runPr
7
- } from "./chunk-FADTCQGM.js";
3
+ formatPhaseLine,
4
+ loadPhaseTiming,
5
+ snapshotTimingFields
6
+ } from "./chunk-V7MO5EDS.js";
8
7
  import {
9
8
  mostRecent,
10
9
  prReviewDir,
11
10
  readPrReviewIndex,
12
11
  runPrReview
13
- } from "./chunk-A4SLHOW7.js";
12
+ } from "./chunk-QZVKRDJY.js";
14
13
  import {
15
14
  runPrd
16
- } from "./chunk-M5UCXTIT.js";
15
+ } from "./chunk-QD4VOILZ.js";
17
16
  import {
18
17
  runReview
19
- } from "./chunk-XQUSIEKA.js";
18
+ } from "./chunk-SZMHCEYV.js";
20
19
  import {
21
- JournalPublisher,
22
- MultiPublisher,
23
20
  PIPELINE_PHASES,
24
21
  PIPELINE_PHASES_NO_BRANCH,
25
22
  PIPELINE_PHASES_WITH_PR_REVIEW,
26
23
  PipelineManager,
27
24
  buildDependencyGraph,
28
25
  buildExecutionPlan,
26
+ collectCascadeIds,
29
27
  computeExecutionOrder,
30
28
  describeCycles,
31
29
  isQueueComplete,
@@ -36,250 +34,186 @@ import {
36
34
  markQueueIssueInProgress,
37
35
  markQueueIssueSkipped,
38
36
  nextQueueIssue,
39
- parseJournal,
40
37
  saveExecutionPlan,
41
38
  setQueueBranch,
42
39
  setQueuePullRequest
43
- } from "./chunk-56KS3TFQ.js";
44
- import {
45
- acquireRunLock,
46
- describeRunLockOwner
47
- } from "./chunk-BAUPQXPE.js";
40
+ } from "./chunk-OR43364P.js";
48
41
  import {
49
42
  runInit
50
- } from "./chunk-NS6R37II.js";
43
+ } from "./chunk-F6TOYSYY.js";
51
44
  import {
52
45
  printQueueSummary,
53
46
  printRunSummary,
54
- runExecute
55
- } from "./chunk-3QHK4YQA.js";
47
+ runExecute,
48
+ stripMarkdown
49
+ } from "./chunk-B72RQZBV.js";
56
50
  import {
57
- listPullRequests,
58
- publishGitState
59
- } from "./chunk-TSOLWK7Y.js";
51
+ ensureWebMonitor
52
+ } from "./chunk-LFEQIZLB.js";
60
53
  import {
61
54
  runPlan
62
- } from "./chunk-QCPGHBSZ.js";
55
+ } from "./chunk-6BENHR2I.js";
56
+ import {
57
+ runPr
58
+ } from "./chunk-2IQ6M4ZE.js";
59
+ import {
60
+ listPullRequests,
61
+ publishGitState
62
+ } from "./chunk-PBTA3QQX.js";
63
63
  import {
64
- getProvider,
65
64
  resolveCommandIssue
66
- } from "./chunk-73ATPIYX.js";
65
+ } from "./chunk-REO3PXXO.js";
66
+ import {
67
+ getProvider
68
+ } from "./chunk-PZTWU277.js";
67
69
  import {
68
70
  beginUsageScope,
69
71
  getRunUsageTotals
70
- } from "./chunk-MJULIKQU.js";
72
+ } from "./chunk-PHW2YV6N.js";
71
73
  import {
72
- onShutdown
73
- } from "./chunk-M66SJHQQ.js";
74
+ assessDecomposition,
75
+ buildDecompositionReport,
76
+ proposeSubIssues,
77
+ resetAgentInvocationState
78
+ } from "./chunk-CGKLE6WR.js";
74
79
  import {
80
+ JournalPublisher,
81
+ MultiPublisher
82
+ } from "./chunk-3BWZO4YI.js";
83
+ import {
84
+ describeRunAgents,
75
85
  getActiveResilienceConfig,
76
86
  initResilienceConfig,
77
87
  loadIssuesConfig,
78
- loadWebConfig
79
- } from "./chunk-42PJFMAR.js";
88
+ loadWebConfig,
89
+ onShutdown
90
+ } from "./chunk-QJ5UZAH5.js";
80
91
  import {
81
- committedStoryIds,
82
- describePreflight,
83
- getBaseBranch,
84
- preflightRepository,
85
92
  resolveIssuePaths,
86
93
  resolveProjectPaths,
87
- resolveQueuePaths,
88
- run
89
- } from "./chunk-DIDUVL6O.js";
94
+ resolveQueuePaths
95
+ } from "./chunk-QK7WUO7A.js";
90
96
  import {
91
- FilePublisher,
92
- NullPublisher,
97
+ formatDuration,
98
+ getIcons,
93
99
  getInactivityTimeout,
94
100
  isVerbose,
95
101
  printError,
96
102
  printInfo,
97
103
  printSuccess,
98
104
  printWarning,
105
+ setActivityCallback,
99
106
  setInactivityTimeout,
100
107
  setOutputCallback,
101
- setSessionPublisher,
102
108
  setStoryStageCallback,
103
109
  setStoryUpdateCallback
104
- } from "./chunk-BYZDPR7N.js";
110
+ } from "./chunk-TYRNTSPJ.js";
111
+ import {
112
+ FilePublisher,
113
+ MemoryPublisher,
114
+ getSessionPublisher,
115
+ setSessionPublisher
116
+ } from "./chunk-6QYMSNVA.js";
117
+ import {
118
+ bindDiagnosticContext,
119
+ flushDiagnostics,
120
+ writeDiagnostic
121
+ } from "./chunk-3WFZETIM.js";
122
+ import {
123
+ committedStoryIds,
124
+ describePreflight,
125
+ getBaseBranch,
126
+ getCurrentBranch,
127
+ getHeadCommit,
128
+ getProjectRoot,
129
+ localBranchExists,
130
+ preflightRepository,
131
+ run
132
+ } from "./chunk-BEZ5BA5U.js";
105
133
  import {
106
134
  isoNow,
107
135
  loadTaskPlan,
108
136
  saveTaskPlan
109
- } from "./chunk-CD5ULRVC.js";
137
+ } from "./chunk-J3SOTUVI.js";
138
+ import {
139
+ acquireRunLock,
140
+ describeRunLockOwner
141
+ } from "./chunk-LBGHI4S2.js";
110
142
 
111
143
  // src/commands/run.ts
112
144
  import { randomUUID } from "crypto";
113
- import { mkdir, readFile, writeFile } from "fs/promises";
145
+ import { mkdir as mkdir2, readFile, writeFile } from "fs/promises";
114
146
  import { join } from "path";
115
147
  import { execa } from "execa";
116
148
 
117
- // src/core/decompose.ts
118
- var DECOMPOSITION_THRESHOLDS = {
119
- /** Timeouts on the same phase. Two is already a pattern, not bad luck. */
120
- timeoutsPerPhase: 2,
121
- /** Stories in the plan. Past this, the plan is a backlog. */
122
- stories: 15,
123
- /** Iterations in a row that completed no story. */
124
- barrenIterations: 5,
125
- /** Files touched on the branch. */
126
- filesTouched: 40,
127
- /** Characters in the issue body. */
128
- issueBodyChars: 2e4
129
- };
130
- var DECOMPOSITION_MIN_SIGNALS = 2;
131
- function timeoutsByPhase(journal) {
132
- const counts = /* @__PURE__ */ new Map();
133
- let phase = "(unknown)";
134
- for (const { event } of parseJournal(journal)) {
135
- if (event.type === "phase:start") {
136
- phase = event.phase;
137
- continue;
138
- }
139
- if (event.type !== "retry") continue;
140
- const kind = event.kind;
141
- const reason = event.reason ?? "";
142
- const timedOut = kind === "timeout" || kind === "stalled" || /timed out|stalled/i.test(reason);
143
- if (!timedOut) continue;
144
- counts.set(phase, (counts.get(phase) ?? 0) + 1);
145
- }
146
- return counts;
147
- }
148
- function longestBarrenRun(journal) {
149
- let passing = 0;
150
- let current = 0;
151
- let longest = 0;
152
- for (const { event } of parseJournal(journal)) {
153
- if (event.type === "iteration:start") {
154
- current++;
155
- longest = Math.max(longest, current);
156
- continue;
157
- }
158
- if (event.type !== "stories:update") continue;
159
- const nowPassing = event.stories.filter((story) => story.passes).length;
160
- if (nowPassing > passing) current = 0;
161
- passing = Math.max(passing, nowPassing);
162
- }
163
- return longest;
164
- }
165
- function assessDecomposition(input) {
166
- const signals = [];
167
- const journal = input.journal ?? "";
168
- for (const [phase, count] of timeoutsByPhase(journal)) {
169
- if (count >= DECOMPOSITION_THRESHOLDS.timeoutsPerPhase) {
170
- signals.push({
171
- id: "repeated-timeouts",
172
- detail: `The '${phase}' phase timed out ${count} times (threshold: ${DECOMPOSITION_THRESHOLDS.timeoutsPerPhase}).`
173
- });
174
- }
175
- }
176
- const stories = input.plan?.userStories.length ?? 0;
177
- if (stories > DECOMPOSITION_THRESHOLDS.stories) {
178
- signals.push({
179
- id: "story-count",
180
- detail: `The plan has ${stories} user stories (threshold: ${DECOMPOSITION_THRESHOLDS.stories}).`
181
- });
182
- }
183
- const barren = longestBarrenRun(journal);
184
- if (barren >= DECOMPOSITION_THRESHOLDS.barrenIterations) {
185
- signals.push({
186
- id: "barren-iterations",
187
- detail: `${barren} iterations in a row completed no story (threshold: ${DECOMPOSITION_THRESHOLDS.barrenIterations}).`
188
- });
149
+ // src/execution/detach.ts
150
+ import { spawn } from "child_process";
151
+ import { mkdir, open } from "fs/promises";
152
+ import { dirname } from "path";
153
+
154
+ // src/storage/run-log.ts
155
+ import { rename, stat } from "fs/promises";
156
+ var DEFAULT_RUN_LOG_MAX_BYTES = 5 * 1024 * 1024;
157
+ async function rotateRunLogIfNeeded(filePath, rotatedPath, maxBytes = DEFAULT_RUN_LOG_MAX_BYTES) {
158
+ if (maxBytes <= 0) return;
159
+ try {
160
+ const size = (await stat(filePath)).size;
161
+ if (size < maxBytes) return;
162
+ } catch {
163
+ return;
189
164
  }
190
- const files = input.filesTouched ?? 0;
191
- if (files > DECOMPOSITION_THRESHOLDS.filesTouched) {
192
- signals.push({
193
- id: "files-touched",
194
- detail: `${files} files were touched on this branch (threshold: ${DECOMPOSITION_THRESHOLDS.filesTouched}).`
195
- });
165
+ try {
166
+ await rename(filePath, rotatedPath);
167
+ } catch {
196
168
  }
197
- const body = input.issueBody?.length ?? 0;
198
- if (body > DECOMPOSITION_THRESHOLDS.issueBodyChars) {
199
- signals.push({
200
- id: "issue-size",
201
- detail: `The issue body is ${body} characters (threshold: ${DECOMPOSITION_THRESHOLDS.issueBodyChars}).`
202
- });
169
+ }
170
+
171
+ // src/execution/detach.ts
172
+ function backgroundRejection(mode, env = process.env) {
173
+ if (mode === "manual") {
174
+ return "--background cannot be used with --mode manual: a detached run cannot answer prompts.";
203
175
  }
204
- if (input.hitMaxIterations === true) {
205
- signals.push({
206
- id: "max-iterations",
207
- detail: "The execute loop stopped because it ran out of iterations, not because it finished."
208
- });
176
+ const ci = env.CI;
177
+ const inCi = ci !== void 0 && ci !== "" && ci !== "0" && ci.toLowerCase() !== "false";
178
+ if (inCi || !process.stdout.isTTY) {
179
+ return "--background needs an interactive terminal. In CI, run in the foreground so the exit code is visible.";
209
180
  }
210
- return { signals, oversized: signals.length >= DECOMPOSITION_MIN_SIGNALS };
181
+ return null;
211
182
  }
212
- var STORIES_PER_SUBISSUE = 5;
213
- function proposeSubIssues(plan) {
214
- const pending = [...plan?.userStories ?? []].filter((story) => !story.passes).sort((a, b) => a.priority - b.priority);
215
- if (pending.length === 0) return [];
216
- const chunks = [];
217
- for (let index = 0; index < pending.length; index += STORIES_PER_SUBISSUE) {
218
- const stories = pending.slice(index, index + STORIES_PER_SUBISSUE);
219
- const first = stories[0];
220
- const last = stories[stories.length - 1];
221
- const range = first === void 0 || last === void 0 || first.id === last.id ? first?.id ?? "" : `${first.id}\u2013${last.id}`;
222
- chunks.push({
223
- title: `${plan?.description ?? "Issue"} \u2014 part ${chunks.length + 1} (${range})`,
224
- stories,
225
- dependsOn: chunks.length === 0 ? [] : [chunks[chunks.length - 1]?.title ?? ""]
226
- });
227
- }
228
- return chunks;
183
+ function childArgv(argv) {
184
+ const next = [];
185
+ for (const arg of argv) {
186
+ if (arg === "--background" || arg === "-d") continue;
187
+ next.push(arg);
188
+ }
189
+ next.push("--detached-child");
190
+ return next;
229
191
  }
230
- function buildDecompositionReport(input) {
231
- const { issueNumber, assessment, plan, at } = input;
232
- const proposals = proposeSubIssues(plan);
233
- const lines = [];
234
- lines.push(`# Issue #${issueNumber} looks larger than one run`, "");
235
- lines.push(`Detected at ${at}.`, "");
236
- lines.push(
237
- "This is a report, not an action. Nothing has been split, and nothing will be",
238
- "without you asking for it.",
239
- ""
240
- );
241
- lines.push("## What was detected", "");
242
- for (const signal of assessment.signals) {
243
- lines.push(`- **${signal.id}** \u2014 ${signal.detail}`);
244
- }
245
- lines.push("");
246
- if (proposals.length === 0) {
247
- lines.push("## Proposed split", "");
248
- lines.push(
249
- "No pending user stories were found, so there is nothing to cut here.",
250
- "The signals above are about the run rather than about the plan.",
251
- ""
252
- );
253
- return `${lines.join("\n")}
254
- `;
255
- }
256
- lines.push("## Proposed split", "");
257
- lines.push(
258
- `The ${proposals.reduce((total, proposal) => total + proposal.stories.length, 0)} pending stories, in priority order, cut into ${proposals.length} sub-issues of at most ${STORIES_PER_SUBISSUE}. Each depends on the one before it, which is the only`,
259
- "dependency shape that can be derived from the plan alone \u2014 read it as a starting",
260
- "point, not as an answer.",
261
- ""
262
- );
263
- for (const proposal of proposals) {
264
- lines.push(`### ${proposal.title}`, "");
265
- if (proposal.dependsOn.length > 0) {
266
- lines.push(`Depends on: ${proposal.dependsOn.join(", ")}`, "");
267
- }
268
- for (const story of proposal.stories) {
269
- lines.push(`- \`${story.id}\` ${story.title}`);
192
+ async function spawnDetachedRun(input) {
193
+ const entryScript = input.entryScript ?? process.argv[1];
194
+ if (entryScript === void 0) {
195
+ throw new Error("Could not determine the CLI entry point to spawn a detached run.");
196
+ }
197
+ await mkdir(dirname(input.paths.runLogFile), { recursive: true });
198
+ await rotateRunLogIfNeeded(input.paths.runLogFile, input.paths.rotatedRunLogFile);
199
+ const log = await open(input.paths.runLogFile, "a");
200
+ try {
201
+ const args = [entryScript, ...childArgv((input.argv ?? process.argv.slice(2)).slice())];
202
+ const spawnFn = input.spawnFn ?? spawn;
203
+ const child = spawnFn(input.execPath ?? process.execPath, args, {
204
+ detached: true,
205
+ stdio: ["ignore", log.fd, log.fd],
206
+ cwd: input.cwd ?? process.cwd(),
207
+ env: input.env ?? process.env
208
+ });
209
+ if (child.pid === void 0) {
210
+ throw new Error("The detached run started but reported no pid.");
270
211
  }
271
- lines.push("");
212
+ child.unref();
213
+ return { pid: child.pid, logFile: input.paths.runLogFile };
214
+ } finally {
215
+ await log.close();
272
216
  }
273
- lines.push("## What to do next", "");
274
- lines.push(
275
- "- Adjust the cut above until it matches how you would actually split the work;",
276
- '- create the sub-issues (`issue-flow generate "\u2026"`) and run them as a queue;',
277
- "- or decide the issue is fine as it is and re-run it \u2014 nothing here blocks that,",
278
- " beyond the report you are reading.",
279
- ""
280
- );
281
- return `${lines.join("\n")}
282
- `;
283
217
  }
284
218
 
285
219
  // src/execution/confirm.ts
@@ -321,16 +255,25 @@ function buildQueueSummaryLines(plan) {
321
255
  const primary = plan.issues.find((entry) => entry.id === plan.id) ?? plan.issues[0];
322
256
  const lines = [];
323
257
  if (primary !== void 0) {
324
- lines.push(` Main issue: ${issueLabel(primary)}${primary.title ? ` ${primary.title}` : ""}`);
258
+ const role = primary.role === "container" ? " (container)" : "";
259
+ lines.push(
260
+ ` Main issue: ${issueLabel(primary)}${primary.title ? ` ${primary.title}` : ""}${role}`
261
+ );
325
262
  }
326
- lines.push(` Total issues: ${plan.issues.length}`);
263
+ const executables = plan.issues.filter((entry) => entry.role !== "container").length;
264
+ const containers = plan.issues.length - executables;
265
+ lines.push(
266
+ containers > 0 ? ` Total issues: ${executables} executable + ${containers} container` : ` Total issues: ${plan.issues.length}`
267
+ );
327
268
  lines.push(" Suggested order:");
328
269
  for (const entry of plan.issues) {
329
270
  const reason = reasonFor(entry);
330
271
  const flag = entry.heuristic ? " ~" : "";
272
+ const external = entry.externalDependencies.length > 0 ? ` \u26A0 depends on ${entry.externalDependencies.map((id) => `#${id}`).join(", ")} (outside this scope)` : "";
273
+ const role = entry.role === "container" ? " (container)" : "";
331
274
  const title = entry.title === "" ? "" : ` ${entry.title}`;
332
275
  lines.push(
333
- ` ${entry.position}. ${issueLabel(entry)}${title}${reason === "" ? "" : ` (${reason})`}${flag}`
276
+ ` ${entry.position}. ${issueLabel(entry)}${title}${role}${reason === "" ? "" : ` (${reason})`}${flag}${external}`
334
277
  );
335
278
  }
336
279
  if (plan.issues.some((entry) => entry.heuristic)) {
@@ -341,6 +284,23 @@ function buildQueueSummaryLines(plan) {
341
284
  }
342
285
  return lines;
343
286
  }
287
+ function buildScopeSummaryLine(plan, choice) {
288
+ if (choice === "cancel") return null;
289
+ const primary = plan.requested[0] ?? plan.id;
290
+ if (choice === "requested") {
291
+ return `Scope: ${plan.requested.length} requested issue(s).`;
292
+ }
293
+ if (choice === "cascade") {
294
+ const executables = plan.issues.filter((entry) => entry.role !== "container").length;
295
+ return `Scope: hierarchy of #${primary} (${executables} executable, container not implemented).`;
296
+ }
297
+ return `Scope: ${plan.issues.length} issues from the hierarchy of #${primary}.`;
298
+ }
299
+ function requestedIsContainer(plan) {
300
+ return plan.issues.some(
301
+ (entry) => plan.requested.includes(entry.id) && entry.role === "container"
302
+ );
303
+ }
344
304
  async function confirmQueue(plan, requestedOnlyCount, options = {}) {
345
305
  const info = options.info ?? printInfo;
346
306
  const warn = options.warn ?? printWarning;
@@ -349,19 +309,41 @@ async function confirmQueue(plan, requestedOnlyCount, options = {}) {
349
309
  (options.stdout ?? process.stdout).write(`${line}
350
310
  `);
351
311
  }
312
+ const container = requestedIsContainer(plan);
352
313
  if (options.only === true) {
314
+ if (container) {
315
+ warn(
316
+ `${plan.requested.map((id) => `#${id}`).join(", ")} is a container. --only will run it as an executable issue.`
317
+ );
318
+ }
353
319
  info(`--only: running just the ${requestedOnlyCount} issue(s) you asked for.`);
320
+ info(buildScopeSummaryLine(plan, "requested") ?? "");
354
321
  return "requested";
355
322
  }
323
+ if (options.cascade === true || options.yes === true && container) {
324
+ info(
325
+ `${options.cascade === true ? "--cascade" : "--yes"}: running the hierarchy of the container, without implementing it.`
326
+ );
327
+ info(buildScopeSummaryLine(plan, "cascade") ?? "");
328
+ return "cascade";
329
+ }
356
330
  if (options.yes === true) {
357
331
  info(`--yes: running the whole hierarchy (${plan.issues.length} issues).`);
332
+ info(buildScopeSummaryLine(plan, "all") ?? "");
358
333
  return "all";
359
334
  }
360
335
  const interactive = options.interactive ?? isInteractiveByDefault();
336
+ if (!interactive && container) {
337
+ throw new QueueConfirmationError(
338
+ `${plan.requested.map((id) => `#${id}`).join(", ")} is a container (umbrella) and the terminal is not interactive. Re-run with --cascade to execute its children, or --only to execute just that issue.`
339
+ );
340
+ }
361
341
  if (!interactive && options.singleRequest === true) {
362
342
  warn(
363
343
  `A larger structure was found, but the terminal is not interactive: running just issue ${plan.requested.map((id) => `#${id}`).join(", ")}. Re-run with --yes to execute the whole hierarchy.`
364
344
  );
345
+ const summary2 = buildScopeSummaryLine(plan, "requested");
346
+ if (summary2) info(summary2);
365
347
  return "requested";
366
348
  }
367
349
  if (!interactive) {
@@ -369,10 +351,19 @@ async function confirmQueue(plan, requestedOnlyCount, options = {}) {
369
351
  "This run involves more than one issue and the terminal is not interactive. Re-run with --yes to execute the whole hierarchy, or --only to execute just the issues you informed."
370
352
  );
371
353
  }
372
- const query = `Which scope should run? [1] Only the issues informed (${requestedOnlyCount}) [2] The whole hierarchy (${plan.issues.length}) [3] Cancel: `;
373
- return await ask(query, options.stdin ?? process.stdin, options.stdout ?? process.stdout, warn);
354
+ const query = container ? `Which scope should run? [1] The hierarchy of #${plan.id} (${plan.issues.filter((entry) => entry.role !== "container").length} issues) [2] Include dependencies outside it [3] Just #${plan.id} itself [4] Cancel: ` : `Which scope should run? [1] Only the issues informed (${requestedOnlyCount}) [2] The whole hierarchy (${plan.issues.length}) [3] Cancel: `;
355
+ const choice = await ask(
356
+ query,
357
+ options.stdin ?? process.stdin,
358
+ options.stdout ?? process.stdout,
359
+ warn,
360
+ container ? "container" : "plain"
361
+ );
362
+ const summary = buildScopeSummaryLine(plan, choice);
363
+ if (summary) info(summary);
364
+ return choice;
374
365
  }
375
- async function ask(query, stdin, stdout, warn) {
366
+ async function ask(query, stdin, stdout, warn, mode = "plain") {
376
367
  const rl = createInterface({ input: stdin, output: stdout });
377
368
  const queued = [];
378
369
  let pending = null;
@@ -414,6 +405,14 @@ async function ask(query, stdin, stdout, warn) {
414
405
  return "cancel";
415
406
  }
416
407
  const choice = answer.trim();
408
+ if (mode === "container") {
409
+ if (choice === "" || choice === "1") return "cascade";
410
+ if (choice === "2") return "all";
411
+ if (choice === "3") return "requested";
412
+ if (choice === "4") return "cancel";
413
+ warn(`Invalid choice: '${choice}'. Enter 1, 2, 3 or 4.`);
414
+ continue;
415
+ }
417
416
  if (choice === "1") return "requested";
418
417
  if (choice === "" || choice === "2") return "all";
419
418
  if (choice === "3") return "cancel";
@@ -456,6 +455,14 @@ async function discoverIssueGraph(roots, options = {}) {
456
455
  }
457
456
 
458
457
  // src/execution/queue.ts
458
+ function forceRequestedExecutable(plan) {
459
+ return {
460
+ ...plan,
461
+ issues: plan.issues.map(
462
+ (entry) => plan.requested.includes(entry.id) ? { ...entry, role: "executable" } : entry
463
+ )
464
+ };
465
+ }
459
466
  async function loadResumableQueue(planFile, requested) {
460
467
  let plan;
461
468
  try {
@@ -540,7 +547,30 @@ async function planQueue(input) {
540
547
  return { kind: "stop", code: 1 };
541
548
  }
542
549
  let plan = suggestedPlan;
543
- if (choice === "requested") {
550
+ if (choice === "cascade") {
551
+ const include = collectCascadeIds(graph, input.requested);
552
+ const cascadeOrder = computeExecutionOrder(graph, { include });
553
+ if (!cascadeOrder.ok) {
554
+ warn(`Dependency cycle between issues: ${describeCycles(cascadeOrder.cycles)}.`);
555
+ return { kind: "stop", code: 1 };
556
+ }
557
+ if (cascadeOrder.order.filter(
558
+ (id) => suggestedPlan.issues.find((entry) => entry.id === id)?.role !== "container"
559
+ ).length === 0) {
560
+ warn(
561
+ `Issue #${input.requested[0]} is a container with no executable children in scope. Re-run with --only if you really want to implement it.`
562
+ );
563
+ return { kind: "stop", code: 1 };
564
+ }
565
+ plan = buildExecutionPlan({
566
+ projectId: input.projectId,
567
+ requested: input.requested,
568
+ graph,
569
+ order: cascadeOrder.order,
570
+ noBranch: input.noBranch,
571
+ prReview: input.prReview
572
+ });
573
+ } else if (choice === "requested") {
544
574
  if (!requestedOrder.ok) {
545
575
  warn(
546
576
  `Dependency cycle between the issues you informed: ${describeCycles(requestedOrder.cycles)}.`
@@ -557,14 +587,16 @@ async function planQueue(input) {
557
587
  }
558
588
  return { kind: "single" };
559
589
  }
560
- plan = buildExecutionPlan({
561
- projectId: input.projectId,
562
- requested: input.requested,
563
- graph,
564
- order: requestedOrder.order,
565
- noBranch: input.noBranch,
566
- prReview: input.prReview
567
- });
590
+ plan = forceRequestedExecutable(
591
+ buildExecutionPlan({
592
+ projectId: input.projectId,
593
+ requested: input.requested,
594
+ graph,
595
+ order: requestedOrder.order,
596
+ noBranch: input.noBranch,
597
+ prReview: input.prReview
598
+ })
599
+ );
568
600
  }
569
601
  await saveExecutionPlan(input.planFile, plan);
570
602
  return { kind: "queue", plan, resumed: false };
@@ -608,6 +640,34 @@ function parseIssueArguments(values) {
608
640
 
609
641
  // src/ui/pipeline-renderer.ts
610
642
  import { Listr, PRESET_TIMER, PRESET_TIMESTAMP } from "listr2";
643
+
644
+ // src/ui/status-view.ts
645
+ function renderExecuteFocus(snapshot, options = {}) {
646
+ const icons = options.icons ?? getIcons();
647
+ const story = activeStory(snapshot);
648
+ if (story === void 0 && snapshot.currentActivity === null) return [];
649
+ const lines = [];
650
+ if (story !== void 0) {
651
+ const duration = story.durationSeconds !== null && story.durationSeconds !== void 0 ? ` ${formatDuration(story.durationSeconds)}` : "";
652
+ lines.push(` ${icons.pending} ${story.id} ${stripMarkdown(story.title)}${duration}`);
653
+ }
654
+ const activity = snapshot.currentActivity;
655
+ if (activity?.tool) {
656
+ const detail = activity.detail ? ` ${stripMarkdown(activity.detail)}` : "";
657
+ lines.push(` ${activity.tool}${detail}`);
658
+ }
659
+ return lines;
660
+ }
661
+ function activeStory(snapshot) {
662
+ const fromActivity = snapshot.currentActivity?.story;
663
+ if (fromActivity) {
664
+ const match = snapshot.stories.find((story) => story.id === fromActivity);
665
+ if (match) return match;
666
+ }
667
+ return snapshot.stories.find((story) => story.stage === "executing");
668
+ }
669
+
670
+ // src/ui/pipeline-renderer.ts
611
671
  var PHASE_LABELS = {
612
672
  prd: "PRD",
613
673
  plan: "Plan",
@@ -656,6 +716,13 @@ function createActiveStoryTracker(stories, storyTasks) {
656
716
  }
657
717
  };
658
718
  }
719
+ function executeExpandsStories(verbose) {
720
+ return verbose;
721
+ }
722
+ function paintExecuteFocus(task) {
723
+ const lines = renderExecuteFocus(getSessionPublisher().snapshot());
724
+ if (lines.length > 0) task.output = lines.join("\n");
725
+ }
659
726
  function buildExecutePhaseTask(runner, tasksPath, verbose) {
660
727
  return async (_ctx, task) => {
661
728
  let stories;
@@ -676,6 +743,31 @@ function buildExecutePhaseTask(runner, tasksPath, verbose) {
676
743
  const totalStories = stories.length;
677
744
  const initialPassed = stories.filter((s) => s.passes).length;
678
745
  task.title = `Execute (${initialPassed}/${totalStories} stories passing)`;
746
+ if (!executeExpandsStories(verbose)) {
747
+ setStoryUpdateCallback((updatedStories) => {
748
+ const passed = updatedStories.filter((s) => s.passes).length;
749
+ task.title = `Execute (${passed}/${totalStories} stories passing)`;
750
+ paintExecuteFocus(task);
751
+ });
752
+ setStoryStageCallback(() => {
753
+ paintExecuteFocus(task);
754
+ });
755
+ setActivityCallback(() => {
756
+ paintExecuteFocus(task);
757
+ });
758
+ setOutputCallback((line) => {
759
+ task.output = line;
760
+ });
761
+ try {
762
+ await runner();
763
+ } finally {
764
+ setOutputCallback(void 0);
765
+ setStoryUpdateCallback(void 0);
766
+ setStoryStageCallback(void 0);
767
+ setActivityCallback(void 0);
768
+ }
769
+ return;
770
+ }
679
771
  const resolvers = /* @__PURE__ */ new Map();
680
772
  setStoryUpdateCallback((updatedStories) => {
681
773
  const passed = updatedStories.filter((s) => s.passes).length;
@@ -742,14 +834,14 @@ function buildExecutePhaseTask(runner, tasksPath, verbose) {
742
834
  };
743
835
  }
744
836
  async function runPipelineWithRenderer(options) {
745
- const { phases, startIndex, verbose, runners, tasksPath } = options;
837
+ const { phases, startIndex, verbose, runners, tasksPath, phaseSuffixes } = options;
746
838
  const overallStart = Date.now();
747
839
  let currentPhase;
748
840
  const renderer = selectRenderer(verbose);
749
841
  const isSimple = renderer === "simple";
750
842
  const tasks = new Listr(
751
843
  phases.map((phase, index) => {
752
- const label = phaseLabel(phase);
844
+ const label = phaseSuffixes?.[phase] ? `${phaseLabel(phase)} (${phaseSuffixes[phase]})` : phaseLabel(phase);
753
845
  const isExecutePhase = phase === "execute" && tasksPath;
754
846
  return {
755
847
  title: label,
@@ -802,6 +894,7 @@ async function runPipelineWithRenderer(options) {
802
894
  setOutputCallback(void 0);
803
895
  setStoryUpdateCallback(void 0);
804
896
  setStoryStageCallback(void 0);
897
+ setActivityCallback(void 0);
805
898
  }
806
899
  const overallElapsedSeconds = Math.floor((Date.now() - overallStart) / 1e3);
807
900
  return {
@@ -816,6 +909,35 @@ function toIssueNumber(id) {
816
909
  const parsed = Number.parseInt(id, 10);
817
910
  return Number.isNaN(parsed) ? null : parsed;
818
911
  }
912
+ async function publishInstrumentedPhaseEnd(publisher, phase, issueNumber, success, error) {
913
+ const at = isoNow();
914
+ const startedAt = publisher.snapshot().phases.find((entry) => entry.name === phase)?.startedAt;
915
+ const wallMs = startedAt === null || startedAt === void 0 ? null : Math.max(0, Date.parse(at) - Date.parse(startedAt));
916
+ const timing = await loadPhaseTiming(phase, wallMs);
917
+ publisher.publish({
918
+ type: "phase:end",
919
+ at,
920
+ phase,
921
+ success,
922
+ ...error === void 0 ? {} : { error },
923
+ ...snapshotTimingFields(timing)
924
+ });
925
+ if (isVerbose()) {
926
+ printInfo(
927
+ formatPhaseLine({
928
+ issueNumber: toIssueNumber(issueNumber) ?? issueNumber,
929
+ phase,
930
+ iteration: timing.iteration,
931
+ wallMs,
932
+ cliDurationMs: timing.cliDurationMs,
933
+ harnessStartupMs: timing.harnessStartupMs,
934
+ ttftMs: timing.ttftMs,
935
+ numTurns: timing.numTurns,
936
+ outputTokens: timing.outputTokens
937
+ })
938
+ );
939
+ }
940
+ }
819
941
  function publishStorySeed(publisher, stories, at) {
820
942
  if (stories.length === 0) return false;
821
943
  publisher.publish({ type: "stories:update", at, stories: [...stories] });
@@ -846,6 +968,10 @@ var QUEUE_PR_PHASES_WITH_REVIEW = [
846
968
  ];
847
969
  var RUNNABLE_QUEUE_PR_PHASES = ["pr"];
848
970
  var RUNNABLE_QUEUE_PR_PHASES_WITH_REVIEW = ["pr", "pr-review"];
971
+ function verificationForSummary(value) {
972
+ if (value === null || value.verdict === null) return null;
973
+ return { verdict: value.verdict, level: value.level };
974
+ }
849
975
  async function readPrReviewOutcome(issue, tasksPath, requestedChanges) {
850
976
  const outcome = { requestedChanges, recommendation: null, reportPath: null };
851
977
  try {
@@ -877,7 +1003,15 @@ async function runPipeline(issue, mode, from, noBranch, prReview, options = {})
877
1003
  printError(err instanceof Error ? err.message : String(err));
878
1004
  return 1;
879
1005
  }
880
- const ownership = await claimRunOwnership(requested[0]);
1006
+ if (options.background === true && options.detachedChild !== true) {
1007
+ const reason = backgroundRejection(mode);
1008
+ if (reason !== null) {
1009
+ printError(reason);
1010
+ return 1;
1011
+ }
1012
+ return detachAfterConfirm(requested, noBranch, prReview, options);
1013
+ }
1014
+ const ownership = await claimRunOwnership(requested[0], options.detachedChild === true);
881
1015
  if (!ownership.ok) {
882
1016
  printError(
883
1017
  `Another issue-flow run owns this project: ${describeRunLockOwner(ownership.owner)}.`
@@ -916,7 +1050,7 @@ async function ensureRepositoryWritable(phase) {
916
1050
  }
917
1051
  throw new Error(`The repository is not in a state the ${phase} phase can write to`);
918
1052
  }
919
- async function claimRunOwnership(target) {
1053
+ async function claimRunOwnership(target, detached = false) {
920
1054
  let lockFile;
921
1055
  try {
922
1056
  lockFile = (await resolveProjectPaths()).runLockFile;
@@ -924,7 +1058,7 @@ async function claimRunOwnership(target) {
924
1058
  return { ok: true, interruptedBy: null, release: async () => {
925
1059
  } };
926
1060
  }
927
- const result = await acquireRunLock(lockFile, { target });
1061
+ const result = await acquireRunLock(lockFile, { target, detached });
928
1062
  if (!result.ok) return result;
929
1063
  return {
930
1064
  ok: true,
@@ -933,7 +1067,23 @@ async function claimRunOwnership(target) {
933
1067
  };
934
1068
  }
935
1069
  async function runIssueSession(issueNumber, mode, input) {
1070
+ resetAgentInvocationState();
936
1071
  const paths = await resolveIssuePaths(issueNumber);
1072
+ try {
1073
+ const project = await resolveProjectPaths();
1074
+ bindDiagnosticContext({
1075
+ project: project.projectId,
1076
+ projectRoot: await getProjectRoot(),
1077
+ issue: issueNumber,
1078
+ sessionId: null,
1079
+ executionId: null,
1080
+ phase: null,
1081
+ story: null,
1082
+ harness: null,
1083
+ model: null
1084
+ });
1085
+ } catch {
1086
+ }
937
1087
  const resilience = await initResilienceConfig();
938
1088
  const configuredInactivity = resilience.watchdog?.inactivityTimeoutMs;
939
1089
  if (configuredInactivity !== void 0 && getInactivityTimeout() === void 0) {
@@ -942,10 +1092,11 @@ async function runIssueSession(issueNumber, mode, input) {
942
1092
  const webConfig = await loadWebConfig();
943
1093
  const surfaces = [];
944
1094
  const journalEnabled = resilience.journal?.enabled === true;
945
- if (webConfig.enabled || journalEnabled) {
946
- await mkdir(paths.issueDir, { recursive: true });
1095
+ const persistSnapshot = webConfig.enabled || input.runOptions?.detachedChild === true;
1096
+ if (persistSnapshot || journalEnabled) {
1097
+ await mkdir2(paths.issueDir, { recursive: true });
947
1098
  }
948
- if (webConfig.enabled) {
1099
+ if (persistSnapshot) {
949
1100
  surfaces.push(
950
1101
  new FilePublisher(paths.sessionFile, {
951
1102
  logLimit: webConfig.logLimit,
@@ -962,7 +1113,7 @@ async function runIssueSession(issueNumber, mode, input) {
962
1113
  })
963
1114
  );
964
1115
  }
965
- const publisher = surfaces.length === 0 ? new NullPublisher() : surfaces.length === 1 ? surfaces[0] : new MultiPublisher(surfaces);
1116
+ const publisher = surfaces.length === 0 ? new MemoryPublisher() : surfaces.length === 1 ? surfaces[0] : new MultiPublisher(surfaces);
966
1117
  setSessionPublisher(publisher);
967
1118
  if (input.interruptedBy !== void 0) {
968
1119
  const previous = input.interruptedBy;
@@ -1024,6 +1175,12 @@ async function runIssueSession(issueNumber, mode, input) {
1024
1175
  });
1025
1176
  }
1026
1177
  await publisher.close();
1178
+ writeDiagnostic({
1179
+ level: result.code === 0 ? "info" : "error",
1180
+ message: `Issue Flow session ${result.code === 0 ? "completed" : "failed"}`,
1181
+ context: { code: result.code, failedPhase: result.failedPhase }
1182
+ });
1183
+ await flushDiagnostics();
1027
1184
  setSessionPublisher(void 0);
1028
1185
  }
1029
1186
  }
@@ -1045,7 +1202,7 @@ async function reportIfOversized(issueNumber, paths, result) {
1045
1202
  at: isoNow()
1046
1203
  });
1047
1204
  const reportFile = paths.decompositionFile;
1048
- await mkdir(paths.issueDir, { recursive: true });
1205
+ await mkdir2(paths.issueDir, { recursive: true });
1049
1206
  await writeFile(reportFile, report, "utf-8");
1050
1207
  printWarning(`Issue #${issueNumber} looks larger than one run. Report: ${reportFile}`);
1051
1208
  for (const signal of assessment.signals) {
@@ -1085,7 +1242,7 @@ async function maybeCreateSubIssues(issueNumber, plan, reportFile) {
1085
1242
  printInfo("Nothing left to split: no pending story.");
1086
1243
  return;
1087
1244
  }
1088
- const { runGenerate } = await import("./generate-EGB4KV2B.js");
1245
+ const { runGenerate } = await import("./generate-FW5C6NP7.js");
1089
1246
  for (const proposal of proposals) {
1090
1247
  const instruction = [
1091
1248
  `Create a sub-issue of #${issueNumber}: ${proposal.title}.`,
@@ -1150,6 +1307,19 @@ async function runPipelinePhases(issueNumber, paths, mode, publisher, input) {
1150
1307
  };
1151
1308
  const tasksPath = paths.tasksFile;
1152
1309
  const sessionId = randomUUID();
1310
+ bindDiagnosticContext({
1311
+ sessionId,
1312
+ issue: issueNumber,
1313
+ executionId: null,
1314
+ phase: null,
1315
+ story: null,
1316
+ harness: null,
1317
+ model: null
1318
+ });
1319
+ const [initialBranch, initialCommit] = await Promise.all([
1320
+ getCurrentBranch().catch(() => ""),
1321
+ getHeadCommit()
1322
+ ]);
1153
1323
  let publishedIssueNumber = toIssueNumber(issueNumber);
1154
1324
  const publishSessionStart = (phases, at, info) => {
1155
1325
  publisher.publish({
@@ -1159,16 +1329,54 @@ async function runPipelinePhases(issueNumber, paths, mode, publisher, input) {
1159
1329
  issueNumber: publishedIssueNumber,
1160
1330
  issueUrl: info?.issueUrl,
1161
1331
  branch: info?.branch,
1332
+ branchCreated: info?.branchCreated,
1333
+ startCommit: info?.startCommit,
1162
1334
  phases: [...phases],
1163
- environment: { node: process.version, platform: process.platform }
1335
+ configuration: configurationSnapshot,
1336
+ environment: {
1337
+ node: process.version,
1338
+ platform: process.platform,
1339
+ agent: agentSummary.defaultProvider,
1340
+ model: agentSummary.defaultModel
1341
+ }
1164
1342
  });
1165
1343
  };
1166
- printInfo(`Starting pipeline for issue #${issueNumber} (mode: ${mode})`);
1344
+ const agentSummary = await describeRunAgents(
1345
+ prReview ? ["prd", "plan", "execute", "review", "pr", "pr-review"] : ["prd", "plan", "execute", "review", "pr"]
1346
+ );
1347
+ const fallbacks = getActiveResilienceConfig().providers?.chain ?? [];
1348
+ const configurationSnapshot = {
1349
+ precedence: ["default", "global", "project", "env", "cli", "step override"],
1350
+ defaultProvider: {
1351
+ value: agentSummary.defaultProvider,
1352
+ source: agentSummary.defaultOrigin.provider
1353
+ },
1354
+ defaultModel: {
1355
+ value: agentSummary.defaultModel,
1356
+ source: agentSummary.defaultOrigin.model
1357
+ },
1358
+ phases: Object.entries(agentSummary.byPhase).map(([phase, resolved]) => ({
1359
+ phase,
1360
+ provider: { value: resolved.provider, source: resolved.origin.provider },
1361
+ model: { value: resolved.model, source: resolved.origin.model }
1362
+ })),
1363
+ fallbacks,
1364
+ overrides: Object.entries(agentSummary.byPhase).filter(
1365
+ ([, resolved]) => resolved.provider !== agentSummary.defaultProvider || resolved.model !== agentSummary.defaultModel
1366
+ ).map(
1367
+ ([phase, resolved]) => `${phase}: ${resolved.provider}${resolved.model ? ` \xB7 ${resolved.model}` : ""}`
1368
+ )
1369
+ };
1370
+ printInfo(
1371
+ `Starting pipeline for issue #${issueNumber} (mode: ${mode}, agent: ${agentSummary.label})`
1372
+ );
1167
1373
  const issuesConfig = await loadIssuesConfig();
1168
1374
  const sessionStartedAt = isoNow();
1169
1375
  if (queue?.preChecked !== true) {
1170
- printInfo("Running prerequisite checks...");
1171
- const initCode = await runInit(issuesConfig.preferredProvider);
1376
+ if (isVerbose()) {
1377
+ printInfo("Running prerequisite checks...");
1378
+ }
1379
+ const initCode = await runInit(issuesConfig.preferredProvider, { compact: !isVerbose() });
1172
1380
  if (initCode !== 0) {
1173
1381
  publishSessionStart(PIPELINE_PHASES, sessionStartedAt);
1174
1382
  publisher.publish({ type: "phase:start", at: sessionStartedAt, phase: "init" });
@@ -1257,6 +1465,8 @@ async function runPipelinePhases(issueNumber, paths, mode, publisher, input) {
1257
1465
  const phaseOrder = finalPr ? effectivePrReview ? RUNNABLE_QUEUE_PR_PHASES_WITH_REVIEW : RUNNABLE_QUEUE_PR_PHASES : inQueue ? RUNNABLE_PHASES_NO_BRANCH : effectiveNoBranch ? RUNNABLE_PHASES_NO_BRANCH : effectivePrReview ? RUNNABLE_PHASES_WITH_PR_REVIEW : RUNNABLE_PHASES;
1258
1466
  const queueCommitScope = queue === void 0 ? void 0 : `issue-${issueNumber}`;
1259
1467
  let producedBranch = null;
1468
+ let plannedExecutionBranch = planBranch ?? null;
1469
+ let branchExistedBeforeExecution = effectiveNoBranch || planBranch === void 0 || planBranch === null ? null : await localBranchExists(planBranch).catch(() => null);
1260
1470
  if (queue !== void 0 && finalPr === void 0 && !effectiveNoBranch) {
1261
1471
  producedBranch = await adoptQueueBranch(tasksPath, queue.plan.branchName);
1262
1472
  if (producedBranch !== null) {
@@ -1265,7 +1475,9 @@ async function runPipelinePhases(issueNumber, paths, mode, publisher, input) {
1265
1475
  }
1266
1476
  publishSessionStart(activePhases, sessionStartedAt, {
1267
1477
  issueUrl: planIssueUrl ?? resolvedIssue.issue.remoteRef ?? void 0,
1268
- branch: planBranch
1478
+ branch: effectiveNoBranch ? initialBranch : planBranch,
1479
+ branchCreated: effectiveNoBranch ? false : branchExistedBeforeExecution === true ? false : null,
1480
+ startCommit: initialCommit
1269
1481
  });
1270
1482
  publishIssueDetails(publisher, resolvedIssue.issue, sessionStartedAt);
1271
1483
  publishStorySeed(publisher, planStories, sessionStartedAt);
@@ -1325,21 +1537,33 @@ async function runPipelinePhases(issueNumber, paths, mode, publisher, input) {
1325
1537
  prd: makeRunner(() => runPrd(issueNumber, resolvedIssue), "prd"),
1326
1538
  plan: async () => {
1327
1539
  await makeRunner(
1328
- () => runPlan(issueNumber, resolvedIssue, { continueFlag: continueNumbering, startUs }),
1540
+ () => runPlan(issueNumber, resolvedIssue, {
1541
+ continueFlag: continueNumbering,
1542
+ startUs,
1543
+ ...effectiveNoBranch && initialBranch ? { branchName: initialBranch } : {}
1544
+ }),
1329
1545
  "plan"
1330
1546
  )();
1331
- if (effectiveNoBranch || effectivePrReview) {
1332
- try {
1333
- const plan = await loadTaskPlan(tasksPath);
1334
- if (effectiveNoBranch) {
1335
- plan.noBranch = true;
1336
- }
1547
+ try {
1548
+ const plan = await loadTaskPlan(tasksPath);
1549
+ if (!effectiveNoBranch) {
1550
+ plannedExecutionBranch = plan.branchName;
1551
+ branchExistedBeforeExecution = await localBranchExists(plan.branchName).catch(() => null);
1552
+ publisher.publish({
1553
+ type: "git:update",
1554
+ at: isoNow(),
1555
+ branchCreated: branchExistedBeforeExecution === true ? false : null
1556
+ });
1557
+ }
1558
+ publishStorySeed(publisher, plan.userStories, isoNow());
1559
+ if (effectiveNoBranch || effectivePrReview) {
1560
+ if (effectiveNoBranch) plan.noBranch = true;
1337
1561
  if (effectivePrReview) {
1338
1562
  plan.prReview = { ...plan.prReview, enabled: true, rounds: plan.prReview?.rounds ?? 0 };
1339
1563
  }
1340
1564
  await saveTaskPlan(tasksPath, plan);
1341
- } catch {
1342
1565
  }
1566
+ } catch {
1343
1567
  }
1344
1568
  if (queue !== void 0 && !effectiveNoBranch) {
1345
1569
  producedBranch = await adoptQueueBranch(tasksPath, queue.plan.branchName);
@@ -1410,28 +1634,38 @@ async function runPipelinePhases(issueNumber, paths, mode, publisher, input) {
1410
1634
  publisher.publish({ type: "phase:start", at: isoNow(), phase });
1411
1635
  try {
1412
1636
  await fn();
1637
+ if (phase === "execute" && branchExistedBeforeExecution === false && plannedExecutionBranch !== null && await getCurrentBranch().catch(() => "") === plannedExecutionBranch) {
1638
+ publisher.publish({ type: "git:update", at: isoNow(), branchCreated: true });
1639
+ }
1413
1640
  await publishGitState(publisher);
1414
- publisher.publish({ type: "phase:end", at: isoNow(), phase, success: true });
1641
+ await publishInstrumentedPhaseEnd(publisher, phase, issueNumber, true);
1415
1642
  } catch (err) {
1416
1643
  await publishGitState(publisher);
1417
- publisher.publish({
1418
- type: "phase:end",
1419
- at: isoNow(),
1644
+ await publishInstrumentedPhaseEnd(
1645
+ publisher,
1420
1646
  phase,
1421
- success: false,
1422
- error: err instanceof Error ? err.message : String(err)
1423
- });
1647
+ issueNumber,
1648
+ false,
1649
+ err instanceof Error ? err.message : String(err)
1650
+ );
1424
1651
  throw err;
1425
1652
  }
1426
1653
  }
1427
1654
  ])
1428
1655
  );
1656
+ const phaseSuffixes = {};
1657
+ for (const [phase, resolved] of Object.entries(agentSummary.byPhase)) {
1658
+ if (resolved.provider !== agentSummary.defaultProvider) {
1659
+ phaseSuffixes[phase] = resolved.model ? `${resolved.provider} \xB7 ${resolved.model}` : resolved.provider;
1660
+ }
1661
+ }
1429
1662
  const result = await runPipelineWithRenderer({
1430
1663
  phases: phaseOrder,
1431
1664
  startIndex: startIdx,
1432
1665
  verbose: isVerbose(),
1433
1666
  runners: instrumentedRunners,
1434
- tasksPath
1667
+ tasksPath,
1668
+ phaseSuffixes
1435
1669
  });
1436
1670
  if (!result.success) {
1437
1671
  printError(`Phase ${result.failedPhase} failed`);
@@ -1512,7 +1746,8 @@ async function runPipelinePhases(issueNumber, paths, mode, publisher, input) {
1512
1746
  prReview: review,
1513
1747
  // Process-owned counters: the session snapshot is empty whenever web
1514
1748
  // monitoring is off, so it cannot be the source of these totals.
1515
- usage: getRunUsageTotals()
1749
+ usage: getRunUsageTotals(),
1750
+ verification: verificationForSummary(getSessionPublisher().snapshot().verification)
1516
1751
  });
1517
1752
  return {
1518
1753
  code: 0,
@@ -1522,6 +1757,37 @@ async function runPipelinePhases(issueNumber, paths, mode, publisher, input) {
1522
1757
  elapsedSeconds: result.overallElapsedSeconds
1523
1758
  };
1524
1759
  }
1760
+ async function detachAfterConfirm(requested, noBranch, prReview, options) {
1761
+ const childFlags = [...process.argv.slice(2)];
1762
+ if (options.yes !== true && options.only !== true) {
1763
+ try {
1764
+ const issuesConfig = await loadIssuesConfig();
1765
+ const resolution = await resolveCommandIssue(requested[0], void 0, {
1766
+ config: issuesConfig
1767
+ });
1768
+ if (resolution.ok) {
1769
+ const decision = await decideQueue({
1770
+ requested,
1771
+ resolved: resolution.resolved,
1772
+ noBranch: noBranch ?? false,
1773
+ prReview: prReview ?? false,
1774
+ runOptions: options
1775
+ });
1776
+ if (decision.kind === "stop") return decision.code;
1777
+ if (decision.kind === "queue" && !childFlags.includes("--yes")) childFlags.push("--yes");
1778
+ if (decision.kind === "single" && !childFlags.includes("--only")) childFlags.push("--only");
1779
+ }
1780
+ } catch {
1781
+ }
1782
+ }
1783
+ const paths = await resolveIssuePaths(requested[0]);
1784
+ const { pid, logFile } = await spawnDetachedRun({ paths, argv: childFlags });
1785
+ printSuccess(`Detached run started (pid ${pid}).`);
1786
+ printInfo(`Log: ${logFile}`);
1787
+ printInfo("Follow with: issue-flow ps");
1788
+ printInfo(`Stop with: kill ${pid}`);
1789
+ return 0;
1790
+ }
1525
1791
  async function decideQueue(input) {
1526
1792
  const primary = input.requested[0];
1527
1793
  const single = input.requested.length === 1;
@@ -1541,7 +1807,11 @@ async function decideQueue(input) {
1541
1807
  planFile: queuePaths.planFile,
1542
1808
  noBranch: input.noBranch,
1543
1809
  prReview: input.prReview,
1544
- confirm: { yes: input.runOptions.yes, only: input.runOptions.only }
1810
+ confirm: {
1811
+ yes: input.runOptions.yes,
1812
+ only: input.runOptions.only,
1813
+ cascade: input.runOptions.cascade
1814
+ }
1545
1815
  });
1546
1816
  } catch (err) {
1547
1817
  const message = err instanceof Error ? err.message : String(err);
@@ -1760,7 +2030,10 @@ async function buildPrQueueContext(plan) {
1760
2030
  number: entry.number,
1761
2031
  title: entry.title,
1762
2032
  url: entry.url,
1763
- source: entry.source
2033
+ source: entry.source,
2034
+ parent: entry.parent,
2035
+ role: entry.role,
2036
+ complete: entry.status === "completed"
1764
2037
  })),
1765
2038
  excluded: plan.excluded.map((entry) => ({
1766
2039
  id: entry.id,
@@ -1829,4 +2102,4 @@ export {
1829
2102
  publishIssueDetails,
1830
2103
  runPipeline
1831
2104
  };
1832
- //# sourceMappingURL=chunk-NTMMF5UD.js.map
2105
+ //# sourceMappingURL=chunk-X44FKNKP.js.map