issue-flow 0.16.0 → 0.18.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.
- package/dist/agent-HMZTXCRT.js +30 -0
- package/dist/{analyze-556KCJKZ.js → analyze-Q4ONIWRZ.js} +22 -22
- package/dist/{apply-WVV7G7WN.js → apply-EEFPY2W4.js} +14 -14
- package/dist/{bench-UBFGKUHR.js → bench-WWWXLTFH.js} +23 -23
- package/dist/{chunk-GV2DABNZ.js → chunk-34OCSKOB.js} +12 -12
- package/dist/chunk-3R27MJ5R.js +25 -0
- package/dist/chunk-3R27MJ5R.js.map +1 -0
- package/dist/{chunk-PGN2VX4Y.js → chunk-45WZ6GMI.js} +32 -48
- package/dist/chunk-45WZ6GMI.js.map +1 -0
- package/dist/{chunk-H3EYBJZU.js → chunk-62GVVZQO.js} +10 -10
- package/dist/{chunk-GPLP7EPL.js → chunk-6K2LCFG6.js} +10 -10
- package/dist/{chunk-GPLP7EPL.js.map → chunk-6K2LCFG6.js.map} +1 -1
- package/dist/{chunk-OGVWJO4S.js → chunk-7F7LGMVO.js} +3 -3
- package/dist/{chunk-X6GY3CM2.js → chunk-7UMMFOL2.js} +104 -104
- package/dist/chunk-7UMMFOL2.js.map +1 -0
- package/dist/{chunk-OLUOZLU5.js → chunk-CWSQVHVV.js} +1691 -1075
- package/dist/chunk-CWSQVHVV.js.map +1 -0
- package/dist/{chunk-MXCOSPLJ.js → chunk-DGRTJD5R.js} +3 -3
- package/dist/{chunk-ROIIEVZH.js → chunk-DUS5ZNKL.js} +474 -361
- package/dist/chunk-DUS5ZNKL.js.map +1 -0
- package/dist/{chunk-BSBJXPLC.js → chunk-E4I6MMHD.js} +10 -10
- package/dist/{chunk-QZOBR6YF.js → chunk-ECU7YO7S.js} +4 -4
- package/dist/chunk-ECU7YO7S.js.map +1 -0
- package/dist/{chunk-EPFC4LHT.js → chunk-EG2MGDP5.js} +9 -9
- package/dist/chunk-HOAHMERN.js +229 -0
- package/dist/chunk-HOAHMERN.js.map +1 -0
- package/dist/{chunk-QVLIIGBU.js → chunk-ICM463EO.js} +60 -13
- package/dist/chunk-ICM463EO.js.map +1 -0
- package/dist/{chunk-4FUWWBTP.js → chunk-KMCS76ZW.js} +8 -8
- package/dist/{chunk-SW2MF47D.js → chunk-KRAGAUKD.js} +19 -19
- package/dist/{chunk-OOX3RMD3.js → chunk-LGHND47K.js} +4 -4
- package/dist/{chunk-VTF4RWS5.js → chunk-LI35PIIS.js} +7 -7
- package/dist/{chunk-Q2TZA6AC.js → chunk-LTH6XYZ3.js} +14 -14
- package/dist/{chunk-YA6FWFYW.js → chunk-NEWNXZ65.js} +62 -11
- package/dist/chunk-NEWNXZ65.js.map +1 -0
- package/dist/{chunk-2H43OVTU.js → chunk-OODXFCQV.js} +11 -11
- package/dist/{chunk-KV2K7MK2.js → chunk-PMLE7KAQ.js} +20 -12
- package/dist/chunk-PMLE7KAQ.js.map +1 -0
- package/dist/{chunk-R42YHLDV.js → chunk-PPZ3K4YM.js} +2 -2
- package/dist/{chunk-J2LJB2OH.js → chunk-QF7VEL3M.js} +739 -579
- package/dist/chunk-QF7VEL3M.js.map +1 -0
- package/dist/{chunk-VK65RNFA.js → chunk-QJ44VG5G.js} +3 -3
- package/dist/{chunk-FKWYD44M.js → chunk-QPGEFLMY.js} +2 -2
- package/dist/{chunk-NQCR3HSU.js → chunk-RNFUVKC3.js} +3 -3
- package/dist/{chunk-G5LACAXO.js → chunk-RYOUZ6EK.js} +2 -2
- package/dist/{chunk-MCLPSL6B.js → chunk-SA6HXL4Y.js} +3 -3
- package/dist/{chunk-GVOZ6NDO.js → chunk-SOM4QA6C.js} +3 -3
- package/dist/{chunk-73TS2H42.js → chunk-TZYINKCC.js} +154 -34
- package/dist/chunk-TZYINKCC.js.map +1 -0
- package/dist/{chunk-DBEMD34J.js → chunk-V7QOIQYC.js} +8 -8
- package/dist/{chunk-IAMIBZRF.js → chunk-WCCZJL7W.js} +2 -2
- package/dist/{chunk-OGN2NX57.js → chunk-XUEMATVY.js} +9 -9
- package/dist/{chunk-SVDEWOUD.js → chunk-XZUEFTUP.js} +6 -6
- package/dist/{chunk-WLJVL25O.js → chunk-YUNBOUQH.js} +6 -6
- package/dist/{chunk-WNROH2YE.js → chunk-YVPZUN6X.js} +2 -2
- package/dist/chunk-ZVZMWNCG.js +415 -0
- package/dist/chunk-ZVZMWNCG.js.map +1 -0
- package/dist/cli.js +57 -49
- package/dist/cli.js.map +1 -1
- package/dist/{config-UE7IP36J.js → config-LEBZZCJP.js} +13 -13
- package/dist/{conventions-Z3QC2NSG.js → conventions-XOT6YW5L.js} +15 -15
- package/dist/{diagnostics-2Z5HFQOO.js → diagnostics-HTCWF7F4.js} +7 -7
- package/dist/execute-LELOSR5T.js +37 -0
- package/dist/{generate-PB4L7V5N.js → generate-JGW5JSKB.js} +22 -22
- package/dist/{git-7MUOGVRG.js → git-A2IP32PQ.js} +2 -2
- package/dist/init-TO6MUXO4.js +26 -0
- package/dist/{operations-QDJAFZUN.js → operations-O4FORFIH.js} +15 -15
- package/dist/{permissions-XVXXENVP.js → permissions-G4XCAESF.js} +4 -4
- package/dist/plan-OXAT344Q.js +34 -0
- package/dist/{policy-6WQZB6B2.js → policy-U5NBFAO5.js} +14 -14
- package/dist/pr-UZHNKKZC.js +39 -0
- package/dist/pr-review-Q4ACAWDM.js +32 -0
- package/dist/prd-6CYEMLDY.js +33 -0
- package/dist/{ps-CX7756QG.js → ps-76L6ZAZL.js} +12 -12
- package/dist/{recorder-RK6XZYF3.js → recorder-2BAKPBRA.js} +7 -7
- package/dist/{registry-POSB35Q4.js → registry-XLHHYLTR.js} +5 -5
- package/dist/{resume-OQJZV3JH.js → resume-URCU6CIJ.js} +39 -38
- package/dist/{resume-OQJZV3JH.js.map → resume-URCU6CIJ.js.map} +1 -1
- package/dist/review-LVZT2TMW.js +35 -0
- package/dist/routing-EK6RBZM4.js +33 -0
- package/dist/run-2RXJCJKT.js +56 -0
- package/dist/{session-publisher-VVXOCJ2S.js → session-publisher-KAJQRHNZ.js} +3 -2
- package/dist/{state-manager-TIC73GQY.js → state-manager-UPHO2EA2.js} +4 -4
- package/dist/state-manager-UPHO2EA2.js.map +1 -0
- package/dist/{usage-WDDNSIHP.js → usage-KTMYXAPF.js} +12 -12
- package/dist/{web-IPGY52AK.js → web-B5UPHHR5.js} +20 -18
- package/dist/{web-IPGY52AK.js.map → web-B5UPHHR5.js.map} +1 -1
- package/package.json +6 -5
- package/web/AGENTS.md +125 -18
- package/web/public/app.css +420 -234
- package/web/public/app.js +371 -80
- package/web/public/index.html +106 -78
- package/dist/agent-PMWCDWOW.js +0 -30
- package/dist/chunk-73TS2H42.js.map +0 -1
- package/dist/chunk-7Z4Y3VKM.js +0 -26
- package/dist/chunk-7Z4Y3VKM.js.map +0 -1
- package/dist/chunk-AOAQU7PD.js +0 -180
- package/dist/chunk-AOAQU7PD.js.map +0 -1
- package/dist/chunk-J2LJB2OH.js.map +0 -1
- package/dist/chunk-KV2K7MK2.js.map +0 -1
- package/dist/chunk-OLUOZLU5.js.map +0 -1
- package/dist/chunk-PGN2VX4Y.js.map +0 -1
- package/dist/chunk-QVLIIGBU.js.map +0 -1
- package/dist/chunk-QZOBR6YF.js.map +0 -1
- package/dist/chunk-ROIIEVZH.js.map +0 -1
- package/dist/chunk-X6GY3CM2.js.map +0 -1
- package/dist/chunk-YA6FWFYW.js.map +0 -1
- package/dist/execute-QYLJ5L4T.js +0 -36
- package/dist/init-XBERAZ4O.js +0 -24
- package/dist/plan-RXOW32KY.js +0 -34
- package/dist/pr-3WFYAVA7.js +0 -39
- package/dist/pr-review-RK63ZEMM.js +0 -32
- package/dist/prd-LRV6IW3E.js +0 -33
- package/dist/review-AEFISJZD.js +0 -35
- package/dist/routing-EA5NJGNU.js +0 -94
- package/dist/routing-EA5NJGNU.js.map +0 -1
- package/dist/run-HSC7R33Y.js +0 -55
- /package/dist/{agent-PMWCDWOW.js.map → agent-HMZTXCRT.js.map} +0 -0
- /package/dist/{analyze-556KCJKZ.js.map → analyze-Q4ONIWRZ.js.map} +0 -0
- /package/dist/{apply-WVV7G7WN.js.map → apply-EEFPY2W4.js.map} +0 -0
- /package/dist/{bench-UBFGKUHR.js.map → bench-WWWXLTFH.js.map} +0 -0
- /package/dist/{chunk-GV2DABNZ.js.map → chunk-34OCSKOB.js.map} +0 -0
- /package/dist/{chunk-H3EYBJZU.js.map → chunk-62GVVZQO.js.map} +0 -0
- /package/dist/{chunk-OGVWJO4S.js.map → chunk-7F7LGMVO.js.map} +0 -0
- /package/dist/{chunk-MXCOSPLJ.js.map → chunk-DGRTJD5R.js.map} +0 -0
- /package/dist/{chunk-BSBJXPLC.js.map → chunk-E4I6MMHD.js.map} +0 -0
- /package/dist/{chunk-EPFC4LHT.js.map → chunk-EG2MGDP5.js.map} +0 -0
- /package/dist/{chunk-4FUWWBTP.js.map → chunk-KMCS76ZW.js.map} +0 -0
- /package/dist/{chunk-SW2MF47D.js.map → chunk-KRAGAUKD.js.map} +0 -0
- /package/dist/{chunk-OOX3RMD3.js.map → chunk-LGHND47K.js.map} +0 -0
- /package/dist/{chunk-VTF4RWS5.js.map → chunk-LI35PIIS.js.map} +0 -0
- /package/dist/{chunk-Q2TZA6AC.js.map → chunk-LTH6XYZ3.js.map} +0 -0
- /package/dist/{chunk-2H43OVTU.js.map → chunk-OODXFCQV.js.map} +0 -0
- /package/dist/{chunk-R42YHLDV.js.map → chunk-PPZ3K4YM.js.map} +0 -0
- /package/dist/{chunk-VK65RNFA.js.map → chunk-QJ44VG5G.js.map} +0 -0
- /package/dist/{chunk-FKWYD44M.js.map → chunk-QPGEFLMY.js.map} +0 -0
- /package/dist/{chunk-NQCR3HSU.js.map → chunk-RNFUVKC3.js.map} +0 -0
- /package/dist/{chunk-G5LACAXO.js.map → chunk-RYOUZ6EK.js.map} +0 -0
- /package/dist/{chunk-MCLPSL6B.js.map → chunk-SA6HXL4Y.js.map} +0 -0
- /package/dist/{chunk-GVOZ6NDO.js.map → chunk-SOM4QA6C.js.map} +0 -0
- /package/dist/{chunk-DBEMD34J.js.map → chunk-V7QOIQYC.js.map} +0 -0
- /package/dist/{chunk-IAMIBZRF.js.map → chunk-WCCZJL7W.js.map} +0 -0
- /package/dist/{chunk-OGN2NX57.js.map → chunk-XUEMATVY.js.map} +0 -0
- /package/dist/{chunk-SVDEWOUD.js.map → chunk-XZUEFTUP.js.map} +0 -0
- /package/dist/{chunk-WLJVL25O.js.map → chunk-YUNBOUQH.js.map} +0 -0
- /package/dist/{chunk-WNROH2YE.js.map → chunk-YVPZUN6X.js.map} +0 -0
- /package/dist/{config-UE7IP36J.js.map → config-LEBZZCJP.js.map} +0 -0
- /package/dist/{conventions-Z3QC2NSG.js.map → conventions-XOT6YW5L.js.map} +0 -0
- /package/dist/{diagnostics-2Z5HFQOO.js.map → diagnostics-HTCWF7F4.js.map} +0 -0
- /package/dist/{execute-QYLJ5L4T.js.map → execute-LELOSR5T.js.map} +0 -0
- /package/dist/{generate-PB4L7V5N.js.map → generate-JGW5JSKB.js.map} +0 -0
- /package/dist/{git-7MUOGVRG.js.map → git-A2IP32PQ.js.map} +0 -0
- /package/dist/{init-XBERAZ4O.js.map → init-TO6MUXO4.js.map} +0 -0
- /package/dist/{operations-QDJAFZUN.js.map → operations-O4FORFIH.js.map} +0 -0
- /package/dist/{permissions-XVXXENVP.js.map → permissions-G4XCAESF.js.map} +0 -0
- /package/dist/{plan-RXOW32KY.js.map → plan-OXAT344Q.js.map} +0 -0
- /package/dist/{policy-6WQZB6B2.js.map → policy-U5NBFAO5.js.map} +0 -0
- /package/dist/{pr-3WFYAVA7.js.map → pr-UZHNKKZC.js.map} +0 -0
- /package/dist/{pr-review-RK63ZEMM.js.map → pr-review-Q4ACAWDM.js.map} +0 -0
- /package/dist/{prd-LRV6IW3E.js.map → prd-6CYEMLDY.js.map} +0 -0
- /package/dist/{ps-CX7756QG.js.map → ps-76L6ZAZL.js.map} +0 -0
- /package/dist/{recorder-RK6XZYF3.js.map → recorder-2BAKPBRA.js.map} +0 -0
- /package/dist/{registry-POSB35Q4.js.map → registry-XLHHYLTR.js.map} +0 -0
- /package/dist/{review-AEFISJZD.js.map → review-LVZT2TMW.js.map} +0 -0
- /package/dist/{run-HSC7R33Y.js.map → routing-EK6RBZM4.js.map} +0 -0
- /package/dist/{session-publisher-VVXOCJ2S.js.map → run-2RXJCJKT.js.map} +0 -0
- /package/dist/{state-manager-TIC73GQY.js.map → session-publisher-KAJQRHNZ.js.map} +0 -0
- /package/dist/{usage-WDDNSIHP.js.map → usage-KTMYXAPF.js.map} +0 -0
|
@@ -3,19 +3,26 @@ import {
|
|
|
3
3
|
formatPhaseLine,
|
|
4
4
|
loadPhaseTiming,
|
|
5
5
|
snapshotTimingFields
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-QJ44VG5G.js";
|
|
7
|
+
import {
|
|
8
|
+
runPr
|
|
9
|
+
} from "./chunk-OODXFCQV.js";
|
|
7
10
|
import {
|
|
8
11
|
mostRecent,
|
|
9
12
|
prReviewDir,
|
|
10
13
|
readPrReviewIndex,
|
|
11
14
|
runPrReview
|
|
12
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-LTH6XYZ3.js";
|
|
16
|
+
import {
|
|
17
|
+
listPullRequests,
|
|
18
|
+
publishGitState
|
|
19
|
+
} from "./chunk-YUNBOUQH.js";
|
|
13
20
|
import {
|
|
14
21
|
runPrd
|
|
15
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-XUEMATVY.js";
|
|
16
23
|
import {
|
|
17
24
|
runReview
|
|
18
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-EG2MGDP5.js";
|
|
19
26
|
import {
|
|
20
27
|
PIPELINE_PHASES,
|
|
21
28
|
PIPELINE_PHASES_NO_BRANCH,
|
|
@@ -37,65 +44,65 @@ import {
|
|
|
37
44
|
saveExecutionPlan,
|
|
38
45
|
setQueueBranch,
|
|
39
46
|
setQueuePullRequest
|
|
40
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-7UMMFOL2.js";
|
|
41
48
|
import {
|
|
42
49
|
runInit
|
|
43
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-PMLE7KAQ.js";
|
|
44
51
|
import {
|
|
45
52
|
printQueueSummary,
|
|
46
53
|
printRunSummary,
|
|
47
54
|
runExecute,
|
|
48
55
|
stripMarkdown
|
|
49
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-KRAGAUKD.js";
|
|
50
57
|
import {
|
|
51
58
|
ensureWebMonitor
|
|
52
|
-
} from "./chunk-
|
|
59
|
+
} from "./chunk-NEWNXZ65.js";
|
|
53
60
|
import {
|
|
54
61
|
runPlan
|
|
55
|
-
} from "./chunk-
|
|
56
|
-
import {
|
|
57
|
-
runPr
|
|
58
|
-
} from "./chunk-2H43OVTU.js";
|
|
59
|
-
import {
|
|
60
|
-
listPullRequests,
|
|
61
|
-
publishGitState
|
|
62
|
-
} from "./chunk-WLJVL25O.js";
|
|
62
|
+
} from "./chunk-34OCSKOB.js";
|
|
63
63
|
import {
|
|
64
64
|
resolveCommandIssue
|
|
65
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-SA6HXL4Y.js";
|
|
66
66
|
import {
|
|
67
67
|
getProvider
|
|
68
|
-
} from "./chunk-
|
|
68
|
+
} from "./chunk-62GVVZQO.js";
|
|
69
69
|
import {
|
|
70
70
|
beginUsageScope,
|
|
71
71
|
getRunUsageTotals
|
|
72
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-KMCS76ZW.js";
|
|
73
73
|
import {
|
|
74
74
|
assessDecomposition,
|
|
75
75
|
buildDecompositionReport,
|
|
76
76
|
proposeSubIssues,
|
|
77
77
|
resetAgentInvocationState
|
|
78
|
-
} from "./chunk-
|
|
78
|
+
} from "./chunk-TZYINKCC.js";
|
|
79
79
|
import {
|
|
80
80
|
JournalPublisher,
|
|
81
81
|
MultiPublisher
|
|
82
|
-
} from "./chunk-
|
|
82
|
+
} from "./chunk-WCCZJL7W.js";
|
|
83
|
+
import {
|
|
84
|
+
recommendedTarget
|
|
85
|
+
} from "./chunk-ZVZMWNCG.js";
|
|
83
86
|
import {
|
|
84
87
|
getPackageVersion
|
|
85
88
|
} from "./chunk-C5TL7T4V.js";
|
|
86
89
|
import {
|
|
87
90
|
describeRunAgents,
|
|
88
91
|
getActiveResilienceConfig,
|
|
92
|
+
getAgentCliOverrides,
|
|
93
|
+
hasExplicitAgentSelection,
|
|
89
94
|
initResilienceConfig,
|
|
95
|
+
loadAgentConfig,
|
|
90
96
|
loadIssuesConfig,
|
|
97
|
+
loadRoutingConfig,
|
|
91
98
|
loadWebConfig,
|
|
92
99
|
onShutdown
|
|
93
|
-
} from "./chunk-
|
|
100
|
+
} from "./chunk-QF7VEL3M.js";
|
|
94
101
|
import {
|
|
95
102
|
resolveIssuePaths,
|
|
96
103
|
resolveProjectPaths,
|
|
97
104
|
resolveQueuePaths
|
|
98
|
-
} from "./chunk-
|
|
105
|
+
} from "./chunk-LI35PIIS.js";
|
|
99
106
|
import {
|
|
100
107
|
formatDuration,
|
|
101
108
|
getIcons,
|
|
@@ -110,18 +117,12 @@ import {
|
|
|
110
117
|
setOutputCallback,
|
|
111
118
|
setStoryStageCallback,
|
|
112
119
|
setStoryUpdateCallback
|
|
113
|
-
} from "./chunk-
|
|
114
|
-
import {
|
|
115
|
-
FilePublisher,
|
|
116
|
-
MemoryPublisher,
|
|
117
|
-
getSessionPublisher,
|
|
118
|
-
setSessionPublisher
|
|
119
|
-
} from "./chunk-ROIIEVZH.js";
|
|
120
|
+
} from "./chunk-XZUEFTUP.js";
|
|
120
121
|
import {
|
|
121
122
|
bindDiagnosticContext,
|
|
122
123
|
flushDiagnostics,
|
|
123
124
|
writeDiagnostic
|
|
124
|
-
} from "./chunk-
|
|
125
|
+
} from "./chunk-QPGEFLMY.js";
|
|
125
126
|
import {
|
|
126
127
|
committedStoryIds,
|
|
127
128
|
describePreflight,
|
|
@@ -132,22 +133,22 @@ import {
|
|
|
132
133
|
localBranchExists,
|
|
133
134
|
preflightRepository,
|
|
134
135
|
run
|
|
135
|
-
} from "./chunk-
|
|
136
|
+
} from "./chunk-YVPZUN6X.js";
|
|
137
|
+
import {
|
|
138
|
+
FilePublisher,
|
|
139
|
+
MemoryPublisher,
|
|
140
|
+
getSessionPublisher,
|
|
141
|
+
setSessionPublisher
|
|
142
|
+
} from "./chunk-DUS5ZNKL.js";
|
|
136
143
|
import {
|
|
137
144
|
isoNow,
|
|
138
145
|
loadTaskPlan,
|
|
139
146
|
saveTaskPlan
|
|
140
|
-
} from "./chunk-
|
|
147
|
+
} from "./chunk-SOM4QA6C.js";
|
|
141
148
|
import {
|
|
142
149
|
acquireRunLock,
|
|
143
150
|
describeRunLockOwner
|
|
144
|
-
} from "./chunk-
|
|
145
|
-
|
|
146
|
-
// src/commands/run.ts
|
|
147
|
-
import { randomUUID } from "crypto";
|
|
148
|
-
import { mkdir as mkdir2, readFile, writeFile } from "fs/promises";
|
|
149
|
-
import { join } from "path";
|
|
150
|
-
import { execa } from "execa";
|
|
151
|
+
} from "./chunk-ICM463EO.js";
|
|
151
152
|
|
|
152
153
|
// src/execution/detach.ts
|
|
153
154
|
import { spawn } from "child_process";
|
|
@@ -219,6 +220,42 @@ async function spawnDetachedRun(input) {
|
|
|
219
220
|
}
|
|
220
221
|
}
|
|
221
222
|
|
|
223
|
+
// src/issues/args.ts
|
|
224
|
+
var IssueArgumentError = class extends Error {
|
|
225
|
+
constructor(message) {
|
|
226
|
+
super(message);
|
|
227
|
+
this.name = "IssueArgumentError";
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
var INVALID_ID = /[/\\\s]/;
|
|
231
|
+
function parseIssueArguments(values) {
|
|
232
|
+
const ids = [];
|
|
233
|
+
const seen = /* @__PURE__ */ new Set();
|
|
234
|
+
for (const value of values) {
|
|
235
|
+
for (const raw of value.split(",")) {
|
|
236
|
+
const id = raw.trim().replace(/^#/, "").trim();
|
|
237
|
+
if (id === "") {
|
|
238
|
+
if (raw.trim() !== "" || value.includes(",")) {
|
|
239
|
+
throw new IssueArgumentError(
|
|
240
|
+
`Invalid issue list '${value}': it has an empty entry. Use '42,43' or '42 43'.`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (INVALID_ID.test(id) || id === "." || id === "..") {
|
|
246
|
+
throw new IssueArgumentError(`Invalid issue identifier '${raw.trim()}'.`);
|
|
247
|
+
}
|
|
248
|
+
if (seen.has(id)) continue;
|
|
249
|
+
seen.add(id);
|
|
250
|
+
ids.push(id);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (ids.length === 0) {
|
|
254
|
+
throw new IssueArgumentError("No issue was informed. Pass at least one issue number.");
|
|
255
|
+
}
|
|
256
|
+
return ids;
|
|
257
|
+
}
|
|
258
|
+
|
|
222
259
|
// src/execution/confirm.ts
|
|
223
260
|
import { createInterface } from "readline";
|
|
224
261
|
var MAX_PROMPT_ATTEMPTS = 3;
|
|
@@ -476,11 +513,43 @@ async function loadResumableQueue(planFile, requested) {
|
|
|
476
513
|
const sameRequest = plan.requested.length === requested.length && plan.requested.every((id, index) => id === requested[index]);
|
|
477
514
|
return sameRequest ? plan : null;
|
|
478
515
|
}
|
|
516
|
+
function executableGraphIds(graph, requested) {
|
|
517
|
+
const roots = new Set(requested);
|
|
518
|
+
return [...graph.nodes.values()].filter((node) => roots.has(node.id) || node.issue?.state !== "closed").map((node) => node.id);
|
|
519
|
+
}
|
|
520
|
+
async function reconcileClosedDiscoveredIssues(plan, warn) {
|
|
521
|
+
let next = plan;
|
|
522
|
+
const closed = [];
|
|
523
|
+
for (const entry of plan.issues) {
|
|
524
|
+
if (entry.origin !== "discovered" || entry.status === "completed") continue;
|
|
525
|
+
try {
|
|
526
|
+
const issue = await getProvider(entry.source).get(entry.id);
|
|
527
|
+
if (issue?.state !== "closed") continue;
|
|
528
|
+
next = markQueueIssueCompleted(next, entry.id);
|
|
529
|
+
closed.push(entry.id);
|
|
530
|
+
} catch (err) {
|
|
531
|
+
warn(
|
|
532
|
+
`Could not refresh issue #${entry.id} before resuming the queue: ${err instanceof Error ? err.message : String(err)}`
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return { plan: next, closed };
|
|
537
|
+
}
|
|
479
538
|
async function planQueue(input) {
|
|
480
539
|
const info = input.info ?? printInfo;
|
|
481
540
|
const warn = input.warn ?? printWarning;
|
|
482
541
|
const single = input.requested.length === 1;
|
|
483
|
-
|
|
542
|
+
let resumable = await loadResumableQueue(input.planFile, input.requested);
|
|
543
|
+
if (resumable !== null) {
|
|
544
|
+
const reconciled = await reconcileClosedDiscoveredIssues(resumable, warn);
|
|
545
|
+
resumable = reconciled.plan;
|
|
546
|
+
if (reconciled.closed.length > 0) {
|
|
547
|
+
await saveExecutionPlan(input.planFile, resumable);
|
|
548
|
+
info(
|
|
549
|
+
`Skipping already closed issue(s) in the resumed queue: ${reconciled.closed.map((id) => `#${id}`).join(", ")}.`
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
484
553
|
if (resumable !== null && isQueueComplete(resumable)) {
|
|
485
554
|
const pr = resumable.pullRequest?.url;
|
|
486
555
|
info(
|
|
@@ -506,10 +575,8 @@ async function planQueue(input) {
|
|
|
506
575
|
known: input.known,
|
|
507
576
|
warn
|
|
508
577
|
});
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
}
|
|
512
|
-
const suggested = computeExecutionOrder(graph);
|
|
578
|
+
const schedulable = executableGraphIds(graph, input.requested);
|
|
579
|
+
const suggested = computeExecutionOrder(graph, { include: schedulable });
|
|
513
580
|
if (!suggested.ok) {
|
|
514
581
|
if (single) {
|
|
515
582
|
warn(
|
|
@@ -532,6 +599,9 @@ async function planQueue(input) {
|
|
|
532
599
|
noBranch: input.noBranch,
|
|
533
600
|
prReview: input.prReview
|
|
534
601
|
});
|
|
602
|
+
if (single && suggestedPlan.issues.length === 1 && suggestedPlan.issues[0]?.role !== "container") {
|
|
603
|
+
return { kind: "single" };
|
|
604
|
+
}
|
|
535
605
|
let choice;
|
|
536
606
|
try {
|
|
537
607
|
choice = await confirmQueue(suggestedPlan, requestedCount, {
|
|
@@ -551,7 +621,8 @@ async function planQueue(input) {
|
|
|
551
621
|
}
|
|
552
622
|
let plan = suggestedPlan;
|
|
553
623
|
if (choice === "cascade") {
|
|
554
|
-
const
|
|
624
|
+
const eligible = new Set(schedulable);
|
|
625
|
+
const include = collectCascadeIds(graph, input.requested).filter((id) => eligible.has(id));
|
|
555
626
|
const cascadeOrder = computeExecutionOrder(graph, { include });
|
|
556
627
|
if (!cascadeOrder.ok) {
|
|
557
628
|
warn(`Dependency cycle between issues: ${describeCycles(cascadeOrder.cycles)}.`);
|
|
@@ -605,131 +676,525 @@ async function planQueue(input) {
|
|
|
605
676
|
return { kind: "queue", plan, resumed: false };
|
|
606
677
|
}
|
|
607
678
|
|
|
608
|
-
// src/
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
679
|
+
// src/commands/run/pull-request.ts
|
|
680
|
+
async function primaryPrCreated(plan) {
|
|
681
|
+
try {
|
|
682
|
+
const paths = await resolveIssuePaths(plan.id);
|
|
683
|
+
return (await loadTaskPlan(paths.tasksFile)).pipeline.prCreated;
|
|
684
|
+
} catch {
|
|
685
|
+
return false;
|
|
613
686
|
}
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
const
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
if (raw.trim() !== "" || value.includes(",")) {
|
|
624
|
-
throw new IssueArgumentError(
|
|
625
|
-
`Invalid issue list '${value}': it has an empty entry. Use '42,43' or '42 43'.`
|
|
626
|
-
);
|
|
627
|
-
}
|
|
628
|
-
continue;
|
|
629
|
-
}
|
|
630
|
-
if (INVALID_ID.test(id) || id === "." || id === "..") {
|
|
631
|
-
throw new IssueArgumentError(`Invalid issue identifier '${raw.trim()}'.`);
|
|
687
|
+
}
|
|
688
|
+
async function buildPrQueueContext(plan) {
|
|
689
|
+
const pending = [];
|
|
690
|
+
for (const entry of plan.issues) {
|
|
691
|
+
try {
|
|
692
|
+
const paths = await resolveIssuePaths(entry.id);
|
|
693
|
+
const taskPlan = await loadTaskPlan(paths.tasksFile);
|
|
694
|
+
if (taskPlan.lastReviewFindings !== null && taskPlan.lastReviewFindings !== "") {
|
|
695
|
+
pending.push(`Issue #${entry.id} has unresolved review findings`);
|
|
632
696
|
}
|
|
633
|
-
|
|
634
|
-
seen.add(id);
|
|
635
|
-
ids.push(id);
|
|
697
|
+
} catch {
|
|
636
698
|
}
|
|
637
699
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
700
|
+
return {
|
|
701
|
+
issues: plan.issues.map((entry) => ({
|
|
702
|
+
id: entry.id,
|
|
703
|
+
number: entry.number,
|
|
704
|
+
title: entry.title,
|
|
705
|
+
url: entry.url,
|
|
706
|
+
source: entry.source,
|
|
707
|
+
parent: entry.parent,
|
|
708
|
+
role: entry.role,
|
|
709
|
+
complete: entry.status === "completed"
|
|
710
|
+
})),
|
|
711
|
+
excluded: plan.excluded.map((entry) => ({
|
|
712
|
+
id: entry.id,
|
|
713
|
+
number: entry.number,
|
|
714
|
+
title: entry.title,
|
|
715
|
+
reason: entry.reason
|
|
716
|
+
})),
|
|
717
|
+
pending
|
|
718
|
+
};
|
|
642
719
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
const story = activeStory(snapshot);
|
|
651
|
-
if (story === void 0 && snapshot.currentActivity === null) return [];
|
|
652
|
-
const lines = [];
|
|
653
|
-
if (story !== void 0) {
|
|
654
|
-
const duration = story.durationSeconds !== null && story.durationSeconds !== void 0 ? ` ${formatDuration(story.durationSeconds)}` : "";
|
|
655
|
-
lines.push(` ${icons.pending} ${story.id} ${stripMarkdown(story.title)}${duration}`);
|
|
720
|
+
async function propagatePullRequest(plan) {
|
|
721
|
+
let pullRequest = null;
|
|
722
|
+
try {
|
|
723
|
+
const primaryPaths = await resolveIssuePaths(plan.id);
|
|
724
|
+
pullRequest = (await loadTaskPlan(primaryPaths.tasksFile)).pullRequest ?? null;
|
|
725
|
+
} catch {
|
|
726
|
+
return null;
|
|
656
727
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
728
|
+
if (pullRequest === null) return null;
|
|
729
|
+
for (const entry of plan.issues) {
|
|
730
|
+
if (entry.id === plan.id) continue;
|
|
731
|
+
try {
|
|
732
|
+
const paths = await resolveIssuePaths(entry.id);
|
|
733
|
+
const taskPlan = await loadTaskPlan(paths.tasksFile);
|
|
734
|
+
taskPlan.pullRequest = pullRequest;
|
|
735
|
+
taskPlan.pipeline.prCreated = true;
|
|
736
|
+
await saveTaskPlan(paths.tasksFile, taskPlan);
|
|
737
|
+
} catch {
|
|
738
|
+
printWarning(`Could not record the Pull Request on issue #${entry.id}'s task plan.`);
|
|
739
|
+
}
|
|
661
740
|
}
|
|
662
|
-
return
|
|
741
|
+
return pullRequest;
|
|
663
742
|
}
|
|
664
|
-
function
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
743
|
+
async function closeIssue(issueNumber, source) {
|
|
744
|
+
try {
|
|
745
|
+
const provider = getProvider(source);
|
|
746
|
+
if (provider.close !== void 0) {
|
|
747
|
+
printInfo("Closing issue...");
|
|
748
|
+
await provider.close(issueNumber);
|
|
749
|
+
}
|
|
750
|
+
} catch {
|
|
751
|
+
printWarning("Failed to close issue automatically");
|
|
669
752
|
}
|
|
670
|
-
return snapshot.stories.find((story) => story.stage === "executing");
|
|
671
753
|
}
|
|
672
754
|
|
|
673
|
-
// src/
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
return PHASE_LABELS[phase] ?? phase.charAt(0).toUpperCase() + phase.slice(1);
|
|
684
|
-
}
|
|
685
|
-
function selectRenderer(verbose) {
|
|
686
|
-
const isTTY = !!process.stdout.isTTY;
|
|
687
|
-
const isCI = !!process.env.CI;
|
|
688
|
-
if (!isTTY || isCI) {
|
|
689
|
-
return "simple";
|
|
690
|
-
}
|
|
691
|
-
return verbose ? "verbose" : "default";
|
|
692
|
-
}
|
|
693
|
-
function storySubtaskTitle(story, executing) {
|
|
694
|
-
const base = `${story.id}: ${story.title}`;
|
|
695
|
-
return executing ? `${base} \u2192 Executando...` : base;
|
|
696
|
-
}
|
|
697
|
-
function createActiveStoryTracker(stories, storyTasks) {
|
|
698
|
-
let activeStoryId;
|
|
699
|
-
const retitle = (storyId, executing) => {
|
|
700
|
-
const task = storyTasks.get(storyId);
|
|
701
|
-
if (!task) return;
|
|
702
|
-
task.title = storySubtaskTitle(
|
|
703
|
-
stories.find((s) => s.id === storyId) ?? { id: storyId, title: "" },
|
|
704
|
-
executing
|
|
705
|
-
);
|
|
706
|
-
};
|
|
707
|
-
return {
|
|
708
|
-
setActive(storyId) {
|
|
709
|
-
if (activeStoryId !== void 0 && activeStoryId !== storyId) {
|
|
710
|
-
retitle(activeStoryId, false);
|
|
711
|
-
}
|
|
712
|
-
activeStoryId = storyId;
|
|
713
|
-
if (storyId !== void 0) retitle(storyId, true);
|
|
714
|
-
},
|
|
715
|
-
clear() {
|
|
716
|
-
if (activeStoryId === void 0) return;
|
|
717
|
-
retitle(activeStoryId, false);
|
|
718
|
-
activeStoryId = void 0;
|
|
755
|
+
// src/commands/run/queue-failure.ts
|
|
756
|
+
async function handleQueueIssueFailure(input) {
|
|
757
|
+
let plan = input.plan;
|
|
758
|
+
const { planFile, entry, result, failureMode, maxIssueAttempts, exhausted, failedIds } = input;
|
|
759
|
+
const failure2 = {
|
|
760
|
+
phase: result.failedPhase,
|
|
761
|
+
error: {
|
|
762
|
+
category: "queue_issue_failed",
|
|
763
|
+
message: `Issue #${entry.id} failed${result.failedPhase === null ? "" : ` in phase ${result.failedPhase}`}`,
|
|
764
|
+
at: isoNow()
|
|
719
765
|
}
|
|
720
766
|
};
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
767
|
+
const where = result.failedPhase === null ? "" : ` (phase ${result.failedPhase})`;
|
|
768
|
+
if (failureMode === "stop") {
|
|
769
|
+
plan = markQueueIssueFailed(plan, entry.id, failure2);
|
|
770
|
+
await saveExecutionPlan(planFile, plan);
|
|
771
|
+
printError(
|
|
772
|
+
`Queue stopped at issue #${entry.id}${where}. The branch and every commit made so far were kept.`
|
|
773
|
+
);
|
|
774
|
+
printInfo(`Resume with: issue-flow run ${plan.requested.join(",")}`);
|
|
775
|
+
return { action: "stop", code: result.code };
|
|
776
|
+
}
|
|
777
|
+
if (failureMode === "block") {
|
|
778
|
+
plan = markQueueIssueBlocked(
|
|
779
|
+
plan,
|
|
780
|
+
entry.id,
|
|
781
|
+
`Failed${where} and --on-issue-failure block was in force`,
|
|
782
|
+
failure2
|
|
783
|
+
);
|
|
784
|
+
await saveExecutionPlan(planFile, plan);
|
|
785
|
+
exhausted.add(entry.id);
|
|
786
|
+
failedIds.add(entry.id);
|
|
787
|
+
printWarning(
|
|
788
|
+
`Issue #${entry.id} failed${where} and is blocked for review. Continuing with the rest of the queue.`
|
|
789
|
+
);
|
|
790
|
+
return { action: "continue", plan };
|
|
791
|
+
}
|
|
792
|
+
const attempts = entry.attempts + 1;
|
|
793
|
+
if (attempts >= maxIssueAttempts) {
|
|
794
|
+
plan = markQueueIssueFailed(plan, entry.id, failure2);
|
|
795
|
+
exhausted.add(entry.id);
|
|
796
|
+
failedIds.add(entry.id);
|
|
797
|
+
printWarning(
|
|
798
|
+
`Issue #${entry.id} failed${where} after ${attempts} attempt(s). Continuing with the rest of the queue.`
|
|
799
|
+
);
|
|
800
|
+
} else {
|
|
801
|
+
plan = markQueueIssueSkipped(plan, entry.id, failure2);
|
|
802
|
+
printWarning(
|
|
803
|
+
`Issue #${entry.id} failed${where}. Skipping it for now and coming back at the end of the queue.`
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
await saveExecutionPlan(planFile, plan);
|
|
807
|
+
return { action: "continue", plan };
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// src/commands/run/multi-issue.ts
|
|
811
|
+
var DEFAULT_MAX_ISSUE_ATTEMPTS = 2;
|
|
812
|
+
async function decideQueue(input) {
|
|
813
|
+
const primary = input.requested[0];
|
|
814
|
+
const single = input.requested.length === 1;
|
|
815
|
+
if (single && input.runOptions.only === true) {
|
|
816
|
+
return { kind: "single" };
|
|
817
|
+
}
|
|
818
|
+
try {
|
|
819
|
+
const [project, queuePaths] = await Promise.all([
|
|
820
|
+
resolveProjectPaths(),
|
|
821
|
+
resolveQueuePaths(primary)
|
|
822
|
+
]);
|
|
823
|
+
return await planQueue({
|
|
824
|
+
requested: input.requested,
|
|
825
|
+
source: input.resolved.source,
|
|
826
|
+
known: [input.resolved.issue],
|
|
827
|
+
projectId: project.projectId,
|
|
828
|
+
planFile: queuePaths.planFile,
|
|
829
|
+
noBranch: input.noBranch,
|
|
830
|
+
prReview: input.prReview,
|
|
831
|
+
confirm: {
|
|
832
|
+
yes: input.runOptions.yes,
|
|
833
|
+
only: input.runOptions.only,
|
|
834
|
+
cascade: input.runOptions.cascade
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
} catch (err) {
|
|
838
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
839
|
+
if (single) {
|
|
840
|
+
printWarning(
|
|
841
|
+
`Could not inspect related issues (${message}). Running issue #${primary} alone.`
|
|
842
|
+
);
|
|
843
|
+
return { kind: "single" };
|
|
844
|
+
}
|
|
845
|
+
printError(`Could not plan the execution of ${input.requested.length} issues: ${message}`);
|
|
846
|
+
return { kind: "stop", code: 1 };
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
async function detachAfterConfirm(requested, noBranch, prReview, options) {
|
|
850
|
+
const childFlags = [...process.argv.slice(2)];
|
|
851
|
+
if (options.yes !== true && options.only !== true) {
|
|
852
|
+
try {
|
|
853
|
+
const issuesConfig = await loadIssuesConfig();
|
|
854
|
+
const resolution = await resolveCommandIssue(requested[0], void 0, {
|
|
855
|
+
config: issuesConfig
|
|
856
|
+
});
|
|
857
|
+
if (resolution.ok) {
|
|
858
|
+
const decision = await decideQueue({
|
|
859
|
+
requested,
|
|
860
|
+
resolved: resolution.resolved,
|
|
861
|
+
noBranch: noBranch ?? false,
|
|
862
|
+
prReview: prReview ?? false,
|
|
863
|
+
runOptions: options
|
|
864
|
+
});
|
|
865
|
+
if (decision.kind === "stop") return decision.code;
|
|
866
|
+
if (decision.kind === "queue" && !childFlags.includes("--yes")) childFlags.push("--yes");
|
|
867
|
+
if (decision.kind === "single" && !childFlags.includes("--only")) childFlags.push("--only");
|
|
868
|
+
}
|
|
869
|
+
} catch {
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
const paths = await resolveIssuePaths(requested[0]);
|
|
873
|
+
const { pid, logFile } = await spawnDetachedRun({ paths, argv: childFlags });
|
|
874
|
+
printSuccess(`Detached run started (pid ${pid}).`);
|
|
875
|
+
printInfo(`Log: ${logFile}`);
|
|
876
|
+
printInfo("Follow with: issue-flow ps");
|
|
877
|
+
printInfo(`Stop with: kill ${pid}`);
|
|
878
|
+
return 0;
|
|
879
|
+
}
|
|
880
|
+
async function runQueue(initialPlan, options, resolvedPrimary, runIssueSession3) {
|
|
881
|
+
const planFile = (await resolveQueuePaths(initialPlan.id)).planFile;
|
|
882
|
+
const queueUsage = beginUsageScope();
|
|
883
|
+
const startedAtMs = Date.now();
|
|
884
|
+
const resilience = getActiveResilienceConfig();
|
|
885
|
+
const failureMode = options.runOptions?.onIssueFailure ?? resilience.queue?.onIssueFailure ?? "stop";
|
|
886
|
+
const maxIssueAttempts = resilience.queue?.maxIssueAttempts ?? DEFAULT_MAX_ISSUE_ATTEMPTS;
|
|
887
|
+
try {
|
|
888
|
+
return await processQueueIssues({
|
|
889
|
+
plan: initialPlan,
|
|
890
|
+
planFile,
|
|
891
|
+
initialPlan,
|
|
892
|
+
options,
|
|
893
|
+
resolvedPrimary,
|
|
894
|
+
runIssueSession: runIssueSession3,
|
|
895
|
+
failureMode,
|
|
896
|
+
maxIssueAttempts,
|
|
897
|
+
startedAtMs,
|
|
898
|
+
queueUsage
|
|
899
|
+
});
|
|
900
|
+
} finally {
|
|
901
|
+
queueUsage.end();
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
async function processQueueEntry(input) {
|
|
905
|
+
let plan = input.plan;
|
|
906
|
+
const {
|
|
907
|
+
planFile,
|
|
908
|
+
entry,
|
|
909
|
+
initialPlan,
|
|
910
|
+
options,
|
|
911
|
+
resolvedPrimary,
|
|
912
|
+
runIssueSession: runIssueSession3,
|
|
913
|
+
failureMode,
|
|
914
|
+
maxIssueAttempts,
|
|
915
|
+
exhausted,
|
|
916
|
+
failedIds,
|
|
917
|
+
summaries
|
|
918
|
+
} = input;
|
|
919
|
+
let firstOfThisRun = input.firstOfThisRun;
|
|
920
|
+
plan = markQueueIssueInProgress(plan, entry.id);
|
|
921
|
+
await saveExecutionPlan(planFile, plan);
|
|
922
|
+
printInfo(
|
|
923
|
+
`Queue ${entry.position}/${plan.issues.length}: issue #${entry.id}${entry.title === "" ? "" : ` \u2014 ${entry.title}`}`
|
|
924
|
+
);
|
|
925
|
+
const issueUsage = beginUsageScope();
|
|
926
|
+
let result;
|
|
927
|
+
try {
|
|
928
|
+
result = await runIssueSession3(entry.id, options.mode, {
|
|
929
|
+
// `--from` / `--start-us` apply only to the first issue of this invocation.
|
|
930
|
+
from: firstOfThisRun ? options.from : void 0,
|
|
931
|
+
noBranch: options.noBranch,
|
|
932
|
+
runOptions: {
|
|
933
|
+
...options.runOptions,
|
|
934
|
+
startUs: firstOfThisRun ? options.runOptions?.startUs : void 0
|
|
935
|
+
},
|
|
936
|
+
queue: {
|
|
937
|
+
plan,
|
|
938
|
+
preChecked: true,
|
|
939
|
+
resolved: entry.id === initialPlan.id ? resolvedPrimary : void 0
|
|
940
|
+
}
|
|
941
|
+
});
|
|
942
|
+
} finally {
|
|
943
|
+
firstOfThisRun = false;
|
|
944
|
+
issueUsage.end();
|
|
945
|
+
}
|
|
946
|
+
if (result.code !== 0) {
|
|
947
|
+
const handled = await handleQueueIssueFailure({
|
|
948
|
+
plan,
|
|
949
|
+
planFile,
|
|
950
|
+
entry,
|
|
951
|
+
result,
|
|
952
|
+
failureMode,
|
|
953
|
+
maxIssueAttempts,
|
|
954
|
+
exhausted,
|
|
955
|
+
failedIds
|
|
956
|
+
});
|
|
957
|
+
if (handled.action === "stop") return { action: "stop", code: handled.code };
|
|
958
|
+
return { action: "continue", plan: handled.plan, firstOfThisRun };
|
|
959
|
+
}
|
|
960
|
+
plan = await recordQueueIssueSuccess({
|
|
961
|
+
plan,
|
|
962
|
+
planFile,
|
|
963
|
+
entry,
|
|
964
|
+
result,
|
|
965
|
+
usage: issueUsage.totals(),
|
|
966
|
+
summaries
|
|
967
|
+
});
|
|
968
|
+
return { action: "continue", plan, firstOfThisRun };
|
|
969
|
+
}
|
|
970
|
+
async function recordQueueIssueSuccess(input) {
|
|
971
|
+
let { plan } = input;
|
|
972
|
+
const { planFile, entry, result, usage, summaries } = input;
|
|
973
|
+
if (plan.branchName === null && result.branchName !== null) {
|
|
974
|
+
plan = setQueueBranch(plan, result.branchName);
|
|
975
|
+
}
|
|
976
|
+
plan = markQueueIssueCompleted(plan, entry.id);
|
|
977
|
+
await saveExecutionPlan(planFile, plan);
|
|
978
|
+
summaries.push({
|
|
979
|
+
id: entry.id,
|
|
980
|
+
title: entry.title,
|
|
981
|
+
storyCount: result.storyCount,
|
|
982
|
+
elapsedSeconds: result.elapsedSeconds,
|
|
983
|
+
usage
|
|
984
|
+
});
|
|
985
|
+
return plan;
|
|
986
|
+
}
|
|
987
|
+
async function processQueueIssues(input) {
|
|
988
|
+
let plan = input.plan;
|
|
989
|
+
const {
|
|
990
|
+
planFile,
|
|
991
|
+
initialPlan,
|
|
992
|
+
options,
|
|
993
|
+
resolvedPrimary,
|
|
994
|
+
runIssueSession: runIssueSession3,
|
|
995
|
+
failureMode,
|
|
996
|
+
maxIssueAttempts,
|
|
997
|
+
startedAtMs,
|
|
998
|
+
queueUsage
|
|
999
|
+
} = input;
|
|
1000
|
+
const summaries = [];
|
|
1001
|
+
let firstOfThisRun = true;
|
|
1002
|
+
const exhausted = /* @__PURE__ */ new Set();
|
|
1003
|
+
const failedIds = /* @__PURE__ */ new Set();
|
|
1004
|
+
while (true) {
|
|
1005
|
+
const entry = nextQueueIssue(plan, { exclude: exhausted });
|
|
1006
|
+
if (entry === null) break;
|
|
1007
|
+
const processed = await processQueueEntry({
|
|
1008
|
+
plan,
|
|
1009
|
+
planFile,
|
|
1010
|
+
entry,
|
|
1011
|
+
initialPlan,
|
|
1012
|
+
options,
|
|
1013
|
+
resolvedPrimary,
|
|
1014
|
+
runIssueSession: runIssueSession3,
|
|
1015
|
+
failureMode,
|
|
1016
|
+
maxIssueAttempts,
|
|
1017
|
+
firstOfThisRun,
|
|
1018
|
+
exhausted,
|
|
1019
|
+
failedIds,
|
|
1020
|
+
summaries
|
|
1021
|
+
});
|
|
1022
|
+
if (processed.action === "stop") return processed.code;
|
|
1023
|
+
plan = processed.plan;
|
|
1024
|
+
firstOfThisRun = processed.firstOfThisRun;
|
|
1025
|
+
}
|
|
1026
|
+
const code = await finishQueue(
|
|
1027
|
+
plan,
|
|
1028
|
+
planFile,
|
|
1029
|
+
summaries,
|
|
1030
|
+
startedAtMs,
|
|
1031
|
+
queueUsage,
|
|
1032
|
+
options,
|
|
1033
|
+
resolvedPrimary,
|
|
1034
|
+
runIssueSession3
|
|
1035
|
+
);
|
|
1036
|
+
if (failedIds.size > 0) {
|
|
1037
|
+
printError(
|
|
1038
|
+
`${failedIds.size} issue(s) did not finish: ${[...failedIds].map((id) => `#${id}`).join(", ")}.`
|
|
1039
|
+
);
|
|
1040
|
+
printInfo(`Resume with: issue-flow run ${plan.requested.join(",")}`);
|
|
1041
|
+
return code === 0 ? 1 : code;
|
|
1042
|
+
}
|
|
1043
|
+
return code;
|
|
1044
|
+
}
|
|
1045
|
+
async function finishQueue(plan, planFile, summaries, startedAtMs, queueUsage, options, resolvedPrimary, runIssueSession3) {
|
|
1046
|
+
let current = plan;
|
|
1047
|
+
let review = null;
|
|
1048
|
+
const alreadyOpened = current.pullRequest !== void 0 || await primaryPrCreated(current);
|
|
1049
|
+
if (!current.noBranch && options.noBranch !== true && !alreadyOpened) {
|
|
1050
|
+
const outcome = await runIssueSession3(current.id, options.mode, {
|
|
1051
|
+
prReview: options.prReview ?? current.prReview,
|
|
1052
|
+
queue: {
|
|
1053
|
+
plan: current,
|
|
1054
|
+
preChecked: true,
|
|
1055
|
+
resolved: resolvedPrimary,
|
|
1056
|
+
finalPr: await buildPrQueueContext(current)
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
1059
|
+
review = outcome.review ?? null;
|
|
1060
|
+
if (outcome.code !== 0) {
|
|
1061
|
+
printError("The consolidated Pull Request could not be created.");
|
|
1062
|
+
await saveExecutionPlan(planFile, current);
|
|
1063
|
+
return outcome.code;
|
|
1064
|
+
}
|
|
1065
|
+
const pullRequest = await propagatePullRequest(current);
|
|
1066
|
+
if (pullRequest !== null) {
|
|
1067
|
+
current = setQueuePullRequest(current, pullRequest);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
await saveExecutionPlan(planFile, current);
|
|
1071
|
+
if (review?.requestedChanges === true) {
|
|
1072
|
+
printInfo("Issues left open until the review blockers are addressed.");
|
|
1073
|
+
} else {
|
|
1074
|
+
for (const entry of current.issues) {
|
|
1075
|
+
await closeIssue(entry.id, entry.source);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
printQueueSummary({
|
|
1079
|
+
queueId: current.id,
|
|
1080
|
+
branchName: current.branchName,
|
|
1081
|
+
issues: summaries,
|
|
1082
|
+
excluded: current.excluded.map((entry) => ({ id: entry.id, title: entry.title })),
|
|
1083
|
+
elapsedSeconds: Math.round((Date.now() - startedAtMs) / 1e3),
|
|
1084
|
+
prUrl: current.pullRequest?.url ?? null,
|
|
1085
|
+
// Read here, after the consolidated Pull Request (and the optional
|
|
1086
|
+
// pr-review) already ran: reading it before would report a queue total
|
|
1087
|
+
// that leaves out the phase that just spent tokens.
|
|
1088
|
+
usage: queueUsage.totals(),
|
|
1089
|
+
prReview: review
|
|
1090
|
+
});
|
|
1091
|
+
return 0;
|
|
1092
|
+
}
|
|
1093
|
+
async function adoptQueueBranch(tasksPath, queueBranch) {
|
|
1094
|
+
try {
|
|
1095
|
+
const plan = await loadTaskPlan(tasksPath);
|
|
1096
|
+
if (queueBranch === null || queueBranch === "") {
|
|
1097
|
+
return plan.branchName === "" ? null : plan.branchName;
|
|
1098
|
+
}
|
|
1099
|
+
if (plan.branchName !== queueBranch) {
|
|
1100
|
+
plan.branchName = queueBranch;
|
|
1101
|
+
await saveTaskPlan(tasksPath, plan);
|
|
1102
|
+
}
|
|
1103
|
+
return queueBranch;
|
|
1104
|
+
} catch {
|
|
1105
|
+
return queueBranch;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// src/ui/pipeline-renderer.ts
|
|
1110
|
+
import { Listr, PRESET_TIMER, PRESET_TIMESTAMP } from "listr2";
|
|
1111
|
+
|
|
1112
|
+
// src/ui/status-view.ts
|
|
1113
|
+
function renderExecuteFocus(snapshot, options = {}) {
|
|
1114
|
+
const icons = options.icons ?? getIcons();
|
|
1115
|
+
const story = activeStory(snapshot);
|
|
1116
|
+
if (story === void 0 && snapshot.currentActivity === null) return [];
|
|
1117
|
+
const lines = [];
|
|
1118
|
+
if (story !== void 0) {
|
|
1119
|
+
const duration = story.durationSeconds !== null && story.durationSeconds !== void 0 ? ` ${formatDuration(story.durationSeconds)}` : "";
|
|
1120
|
+
lines.push(` ${icons.pending} ${story.id} ${stripMarkdown(story.title)}${duration}`);
|
|
1121
|
+
}
|
|
1122
|
+
const activity = snapshot.currentActivity;
|
|
1123
|
+
if (activity?.tool) {
|
|
1124
|
+
const detail = activity.detail ? ` ${stripMarkdown(activity.detail)}` : "";
|
|
1125
|
+
lines.push(` ${activity.tool}${detail}`);
|
|
1126
|
+
}
|
|
1127
|
+
return lines;
|
|
1128
|
+
}
|
|
1129
|
+
function activeStory(snapshot) {
|
|
1130
|
+
const fromActivity = snapshot.currentActivity?.story;
|
|
1131
|
+
if (fromActivity) {
|
|
1132
|
+
const match = snapshot.stories.find((story) => story.id === fromActivity);
|
|
1133
|
+
if (match) return match;
|
|
1134
|
+
}
|
|
1135
|
+
return snapshot.stories.find((story) => story.stage === "executing");
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
// src/ui/pipeline-renderer.ts
|
|
1139
|
+
var PHASE_LABELS = {
|
|
1140
|
+
prd: "PRD",
|
|
1141
|
+
plan: "Plan",
|
|
1142
|
+
execute: "Execute",
|
|
1143
|
+
review: "Review",
|
|
1144
|
+
pr: "PR",
|
|
1145
|
+
"pr-review": "PR Review"
|
|
1146
|
+
};
|
|
1147
|
+
function phaseLabel(phase) {
|
|
1148
|
+
return PHASE_LABELS[phase] ?? phase.charAt(0).toUpperCase() + phase.slice(1);
|
|
1149
|
+
}
|
|
1150
|
+
function selectRenderer(verbose) {
|
|
1151
|
+
const isTTY = !!process.stdout.isTTY;
|
|
1152
|
+
const isCI = !!process.env.CI;
|
|
1153
|
+
if (!isTTY || isCI) {
|
|
1154
|
+
return "simple";
|
|
1155
|
+
}
|
|
1156
|
+
return verbose ? "verbose" : "default";
|
|
1157
|
+
}
|
|
1158
|
+
function storySubtaskTitle(story, executing) {
|
|
1159
|
+
const base = `${story.id}: ${story.title}`;
|
|
1160
|
+
return executing ? `${base} \u2192 Executando...` : base;
|
|
1161
|
+
}
|
|
1162
|
+
function createActiveStoryTracker(stories, storyTasks) {
|
|
1163
|
+
let activeStoryId;
|
|
1164
|
+
const retitle = (storyId, executing) => {
|
|
1165
|
+
const task = storyTasks.get(storyId);
|
|
1166
|
+
if (!task) return;
|
|
1167
|
+
task.title = storySubtaskTitle(
|
|
1168
|
+
stories.find((s) => s.id === storyId) ?? { id: storyId, title: "" },
|
|
1169
|
+
executing
|
|
1170
|
+
);
|
|
1171
|
+
};
|
|
1172
|
+
return {
|
|
1173
|
+
setActive(storyId) {
|
|
1174
|
+
if (activeStoryId !== void 0 && activeStoryId !== storyId) {
|
|
1175
|
+
retitle(activeStoryId, false);
|
|
1176
|
+
}
|
|
1177
|
+
activeStoryId = storyId;
|
|
1178
|
+
if (storyId !== void 0) retitle(storyId, true);
|
|
1179
|
+
},
|
|
1180
|
+
clear() {
|
|
1181
|
+
if (activeStoryId === void 0) return;
|
|
1182
|
+
retitle(activeStoryId, false);
|
|
1183
|
+
activeStoryId = void 0;
|
|
1184
|
+
}
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
function executeExpandsStories(verbose) {
|
|
1188
|
+
return verbose;
|
|
1189
|
+
}
|
|
1190
|
+
function paintExecuteFocus(task) {
|
|
1191
|
+
const lines = renderExecuteFocus(getSessionPublisher().snapshot());
|
|
1192
|
+
if (lines.length > 0) task.output = lines.join("\n");
|
|
1193
|
+
}
|
|
1194
|
+
function buildExecutePhaseTask(runner, tasksPath, verbose) {
|
|
1195
|
+
return async (_ctx, task) => {
|
|
1196
|
+
let stories;
|
|
1197
|
+
try {
|
|
733
1198
|
const plan = await loadTaskPlan(tasksPath);
|
|
734
1199
|
stories = [...plan.userStories].sort((a, b) => a.priority - b.priority);
|
|
735
1200
|
} catch {
|
|
@@ -907,7 +1372,11 @@ async function runPipelineWithRenderer(options) {
|
|
|
907
1372
|
};
|
|
908
1373
|
}
|
|
909
1374
|
|
|
910
|
-
// src/commands/run.ts
|
|
1375
|
+
// src/commands/run/phase-finalize.ts
|
|
1376
|
+
import { execa } from "execa";
|
|
1377
|
+
|
|
1378
|
+
// src/commands/run/publish.ts
|
|
1379
|
+
import { join } from "path";
|
|
911
1380
|
function toIssueNumber(id) {
|
|
912
1381
|
const parsed = Number.parseInt(id, 10);
|
|
913
1382
|
return Number.isNaN(parsed) ? null : parsed;
|
|
@@ -960,17 +1429,6 @@ function publishIssueDetails(publisher, issue, at) {
|
|
|
960
1429
|
state: issue.state
|
|
961
1430
|
});
|
|
962
1431
|
}
|
|
963
|
-
var RUNNABLE_PHASES = ["prd", "plan", "execute", "review", "pr"];
|
|
964
|
-
var RUNNABLE_PHASES_NO_BRANCH = ["prd", "plan", "execute", "review"];
|
|
965
|
-
var RUNNABLE_PHASES_WITH_PR_REVIEW = [...RUNNABLE_PHASES, "pr-review"];
|
|
966
|
-
var QUEUE_PR_PHASES = ["init", "pr"];
|
|
967
|
-
var QUEUE_PR_PHASES_WITH_REVIEW = [
|
|
968
|
-
"init",
|
|
969
|
-
"pr",
|
|
970
|
-
"pr-review"
|
|
971
|
-
];
|
|
972
|
-
var RUNNABLE_QUEUE_PR_PHASES = ["pr"];
|
|
973
|
-
var RUNNABLE_QUEUE_PR_PHASES_WITH_REVIEW = ["pr", "pr-review"];
|
|
974
1432
|
function verificationForSummary(value) {
|
|
975
1433
|
if (value === null || value.verdict === null) return null;
|
|
976
1434
|
return { verdict: value.verdict, level: value.level };
|
|
@@ -997,200 +1455,608 @@ async function readPrReviewOutcome(issue, tasksPath, requestedChanges) {
|
|
|
997
1455
|
}
|
|
998
1456
|
return outcome;
|
|
999
1457
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
}
|
|
1009
|
-
if (options.background === true && options.detachedChild !== true) {
|
|
1010
|
-
const reason = backgroundRejection(mode);
|
|
1011
|
-
if (reason !== null) {
|
|
1012
|
-
printError(reason);
|
|
1013
|
-
return 1;
|
|
1458
|
+
|
|
1459
|
+
// src/commands/run/phase-finalize.ts
|
|
1460
|
+
async function applyReviewOutcomeAndClose(input) {
|
|
1461
|
+
const { issueNumber, resolvedIssue, review, inQueue } = input;
|
|
1462
|
+
if (review?.requestedChanges) {
|
|
1463
|
+
console.log("");
|
|
1464
|
+
printWarning("PR review requested changes \u2014 the Pull Request is not ready to merge.");
|
|
1465
|
+
if (review.reportPath !== null) {
|
|
1466
|
+
console.log(` Report: ${review.reportPath}`);
|
|
1014
1467
|
}
|
|
1015
|
-
return detachAfterConfirm(requested, noBranch, prReview, options);
|
|
1016
|
-
}
|
|
1017
|
-
const ownership = await claimRunOwnership(requested[0], options.detachedChild === true);
|
|
1018
|
-
if (!ownership.ok) {
|
|
1019
|
-
printError(
|
|
1020
|
-
`Another issue-flow run owns this project: ${describeRunLockOwner(ownership.owner)}.`
|
|
1021
|
-
);
|
|
1022
|
-
printInfo("Wait for it to finish, or stop that process before running again.");
|
|
1023
|
-
return 1;
|
|
1024
1468
|
}
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
prReview,
|
|
1030
|
-
requested,
|
|
1031
|
-
runOptions: options,
|
|
1032
|
-
restartWeb: options.restartWeb,
|
|
1033
|
-
...ownership.interruptedBy === null ? {} : { interruptedBy: ownership.interruptedBy }
|
|
1034
|
-
});
|
|
1035
|
-
if (first.queue === void 0) {
|
|
1036
|
-
return first.code;
|
|
1037
|
-
}
|
|
1038
|
-
return runQueue(
|
|
1039
|
-
first.queue.plan,
|
|
1040
|
-
{ mode, from, noBranch, prReview, runOptions: options },
|
|
1041
|
-
first.queue.resolved
|
|
1042
|
-
);
|
|
1043
|
-
} finally {
|
|
1044
|
-
await ownership.release();
|
|
1469
|
+
if (review?.requestedChanges) {
|
|
1470
|
+
printInfo("Issue left open until the review blockers are addressed.");
|
|
1471
|
+
} else if (!inQueue) {
|
|
1472
|
+
await closeIssue(issueNumber, resolvedIssue.source);
|
|
1045
1473
|
}
|
|
1046
1474
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
if (!
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1475
|
+
async function resolveSummaryPrUrl(input) {
|
|
1476
|
+
let prUrl = "unknown";
|
|
1477
|
+
if (!input.effectiveNoBranch && input.source === "github") {
|
|
1478
|
+
if (input.planPrUrl !== null) {
|
|
1479
|
+
prUrl = input.planPrUrl;
|
|
1480
|
+
} else if (input.branchName !== "unknown" && input.branchName !== "") {
|
|
1481
|
+
try {
|
|
1482
|
+
const latest = mostRecent(await listPullRequests(input.branchName));
|
|
1483
|
+
if (latest !== null) {
|
|
1484
|
+
prUrl = latest.url;
|
|
1485
|
+
}
|
|
1486
|
+
} catch {
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1054
1489
|
}
|
|
1055
|
-
|
|
1490
|
+
return prUrl;
|
|
1056
1491
|
}
|
|
1057
|
-
async function
|
|
1058
|
-
|
|
1492
|
+
async function finalizeSuccessfulIssueRun(input) {
|
|
1493
|
+
const {
|
|
1494
|
+
issueNumber,
|
|
1495
|
+
tasksPath,
|
|
1496
|
+
resolvedIssue,
|
|
1497
|
+
review,
|
|
1498
|
+
inQueue,
|
|
1499
|
+
finalPr,
|
|
1500
|
+
producedBranch,
|
|
1501
|
+
effectiveNoBranch,
|
|
1502
|
+
elapsedSeconds
|
|
1503
|
+
} = input;
|
|
1504
|
+
await applyReviewOutcomeAndClose({ issueNumber, resolvedIssue, review, inQueue });
|
|
1505
|
+
let branchName = "unknown";
|
|
1059
1506
|
try {
|
|
1060
|
-
|
|
1507
|
+
const proc = await execa("git", ["branch", "--show-current"], { reject: false });
|
|
1508
|
+
branchName = proc.stdout?.toString().trim() ?? "unknown";
|
|
1061
1509
|
} catch {
|
|
1062
|
-
return { ok: true, interruptedBy: null, release: async () => {
|
|
1063
|
-
} };
|
|
1064
1510
|
}
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
return {
|
|
1068
|
-
ok: true,
|
|
1069
|
-
interruptedBy: result.handle.reclaimedFrom,
|
|
1070
|
-
release: () => result.handle.release()
|
|
1071
|
-
};
|
|
1072
|
-
}
|
|
1073
|
-
async function runIssueSession(issueNumber, mode, input) {
|
|
1074
|
-
resetAgentInvocationState();
|
|
1075
|
-
const paths = await resolveIssuePaths(issueNumber);
|
|
1511
|
+
let storyCount = 0;
|
|
1512
|
+
let planPrUrl = null;
|
|
1076
1513
|
try {
|
|
1077
|
-
const
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
harness: null,
|
|
1087
|
-
model: null
|
|
1088
|
-
});
|
|
1514
|
+
const plan = await loadTaskPlan(tasksPath);
|
|
1515
|
+
storyCount = plan.userStories.length;
|
|
1516
|
+
planPrUrl = plan.pullRequest?.url ?? null;
|
|
1517
|
+
plan.lastAttemptAt = isoNow();
|
|
1518
|
+
if (!review?.requestedChanges) {
|
|
1519
|
+
plan.issueStatus = "completed";
|
|
1520
|
+
plan.completedAt = isoNow();
|
|
1521
|
+
}
|
|
1522
|
+
await saveTaskPlan(tasksPath, plan);
|
|
1089
1523
|
} catch {
|
|
1090
1524
|
}
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
if (persistSnapshot) {
|
|
1104
|
-
surfaces.push(
|
|
1105
|
-
new FilePublisher(paths.sessionFile, {
|
|
1106
|
-
logLimit: webConfig.logLimit,
|
|
1107
|
-
includeLogs: webConfig.includeLogs
|
|
1108
|
-
})
|
|
1109
|
-
);
|
|
1110
|
-
}
|
|
1111
|
-
if (journalEnabled) {
|
|
1112
|
-
surfaces.push(
|
|
1113
|
-
new JournalPublisher(paths.eventsFile, paths.rotatedEventsFile, {
|
|
1114
|
-
logLimit: webConfig.logLimit,
|
|
1115
|
-
includeLogs: webConfig.includeLogs,
|
|
1116
|
-
...resilience.journal?.maxFileBytes === void 0 ? {} : { maxFileBytes: resilience.journal.maxFileBytes }
|
|
1117
|
-
})
|
|
1118
|
-
);
|
|
1525
|
+
if (inQueue) {
|
|
1526
|
+
if (finalPr === void 0) {
|
|
1527
|
+
printSuccess(`Issue #${issueNumber} completed (${storyCount} stories).`);
|
|
1528
|
+
}
|
|
1529
|
+
return {
|
|
1530
|
+
code: 0,
|
|
1531
|
+
failedPhase: null,
|
|
1532
|
+
branchName: producedBranch ?? (branchName === "unknown" ? null : branchName),
|
|
1533
|
+
storyCount,
|
|
1534
|
+
elapsedSeconds,
|
|
1535
|
+
review
|
|
1536
|
+
};
|
|
1119
1537
|
}
|
|
1120
|
-
const
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1538
|
+
const prUrl = await resolveSummaryPrUrl({
|
|
1539
|
+
effectiveNoBranch,
|
|
1540
|
+
source: resolvedIssue.source,
|
|
1541
|
+
planPrUrl,
|
|
1542
|
+
branchName
|
|
1543
|
+
});
|
|
1544
|
+
printRunSummary({
|
|
1545
|
+
issueNumber,
|
|
1546
|
+
branchName,
|
|
1547
|
+
noBranch: effectiveNoBranch,
|
|
1548
|
+
storyCount,
|
|
1549
|
+
elapsedSeconds,
|
|
1550
|
+
prUrl,
|
|
1551
|
+
prReview: review,
|
|
1552
|
+
// Process-owned counters: the session snapshot is empty whenever web
|
|
1553
|
+
// monitoring is off, so it cannot be the source of these totals.
|
|
1554
|
+
usage: getRunUsageTotals(),
|
|
1555
|
+
verification: verificationForSummary(getSessionPublisher().snapshot().verification)
|
|
1556
|
+
});
|
|
1557
|
+
return {
|
|
1558
|
+
code: 0,
|
|
1559
|
+
failedPhase: null,
|
|
1560
|
+
branchName: branchName === "unknown" ? null : branchName,
|
|
1561
|
+
storyCount,
|
|
1562
|
+
elapsedSeconds
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
// src/commands/run/phase-bootstrap.ts
|
|
1567
|
+
import { randomUUID } from "crypto";
|
|
1568
|
+
|
|
1569
|
+
// src/commands/run/phase-config.ts
|
|
1570
|
+
async function buildAgentConfiguration(prReview) {
|
|
1571
|
+
const agentSummary = await describeRunAgents(
|
|
1572
|
+
prReview ? ["prd", "plan", "execute", "review", "pr", "pr-review"] : ["prd", "plan", "execute", "review", "pr"]
|
|
1573
|
+
);
|
|
1574
|
+
const fallbacks = getActiveResilienceConfig().providers?.chain ?? [];
|
|
1575
|
+
const routingConfig = await loadRoutingConfig();
|
|
1576
|
+
const agentConfig = await loadAgentConfig();
|
|
1577
|
+
const displayedPhases = Object.entries(agentSummary.byPhase).map(([phase, resolved]) => {
|
|
1578
|
+
const recommended = routingConfig.mode === "active" && routingConfig.policy === "recommended" && !hasExplicitAgentSelection(
|
|
1579
|
+
agentConfig,
|
|
1580
|
+
getAgentCliOverrides(),
|
|
1581
|
+
phase
|
|
1582
|
+
) ? recommendedTarget(phase) : null;
|
|
1583
|
+
return {
|
|
1584
|
+
phase,
|
|
1585
|
+
provider: recommended?.provider ?? resolved.provider,
|
|
1586
|
+
model: recommended?.model ?? resolved.model,
|
|
1587
|
+
providerSource: recommended ? "recommended" : resolved.origin.provider,
|
|
1588
|
+
modelSource: recommended ? "recommended" : resolved.origin.model
|
|
1589
|
+
};
|
|
1590
|
+
});
|
|
1591
|
+
const configurationSnapshot = {
|
|
1592
|
+
precedence: ["default", "global", "project", "env", "cli", "step override"],
|
|
1593
|
+
defaultProvider: {
|
|
1594
|
+
value: agentSummary.defaultProvider,
|
|
1595
|
+
source: agentSummary.defaultOrigin.provider
|
|
1596
|
+
},
|
|
1597
|
+
defaultModel: {
|
|
1598
|
+
value: agentSummary.defaultModel,
|
|
1599
|
+
source: agentSummary.defaultOrigin.model
|
|
1600
|
+
},
|
|
1601
|
+
phases: displayedPhases.map((resolved) => ({
|
|
1602
|
+
phase: resolved.phase,
|
|
1603
|
+
provider: { value: resolved.provider, source: resolved.providerSource },
|
|
1604
|
+
model: { value: resolved.model, source: resolved.modelSource }
|
|
1605
|
+
})),
|
|
1606
|
+
fallbacks,
|
|
1607
|
+
overrides: displayedPhases.filter(
|
|
1608
|
+
(resolved) => resolved.provider !== agentSummary.defaultProvider || resolved.model !== agentSummary.defaultModel
|
|
1609
|
+
).map(
|
|
1610
|
+
(resolved) => `${resolved.phase}: ${resolved.provider}${resolved.model ? ` \xB7 ${resolved.model}` : ""}`
|
|
1611
|
+
)
|
|
1612
|
+
};
|
|
1613
|
+
return { agentSummary, configurationSnapshot };
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
// src/commands/run/types.ts
|
|
1617
|
+
var RUNNABLE_PHASES = ["prd", "plan", "execute", "review", "pr"];
|
|
1618
|
+
var RUNNABLE_PHASES_NO_BRANCH = ["prd", "plan", "execute", "review"];
|
|
1619
|
+
var RUNNABLE_PHASES_WITH_PR_REVIEW = [...RUNNABLE_PHASES, "pr-review"];
|
|
1620
|
+
var QUEUE_PR_PHASES = ["init", "pr"];
|
|
1621
|
+
var QUEUE_PR_PHASES_WITH_REVIEW = [
|
|
1622
|
+
"init",
|
|
1623
|
+
"pr",
|
|
1624
|
+
"pr-review"
|
|
1625
|
+
];
|
|
1626
|
+
var RUNNABLE_QUEUE_PR_PHASES = ["pr"];
|
|
1627
|
+
var RUNNABLE_QUEUE_PR_PHASES_WITH_REVIEW = ["pr", "pr-review"];
|
|
1628
|
+
function failure(code) {
|
|
1629
|
+
return { code, failedPhase: null, branchName: null, storyCount: 0, elapsedSeconds: 0 };
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
// src/commands/run/phase-options.ts
|
|
1633
|
+
function resolveExecuteRetry(runOptions) {
|
|
1634
|
+
return {
|
|
1635
|
+
retryLimit: runOptions?.retryLimit,
|
|
1636
|
+
retryForever: runOptions?.retryForever
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
function resolveStoryNumbering(runOptions) {
|
|
1640
|
+
return {
|
|
1641
|
+
continueNumbering: runOptions?.continueNumbering,
|
|
1642
|
+
startUs: runOptions?.startUs
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
function resolveBranchAndReviewModes(input) {
|
|
1646
|
+
const warnings = [];
|
|
1647
|
+
if (input.persisted === null) {
|
|
1648
|
+
const effectiveNoBranch2 = input.noBranch ?? false;
|
|
1649
|
+
let effectivePrReview2 = input.prReview ?? false;
|
|
1650
|
+
if (effectiveNoBranch2 && effectivePrReview2) {
|
|
1651
|
+
warnings.push(
|
|
1652
|
+
"This pipeline runs with --no-branch and opens no PR. Skipping the pr-review phase."
|
|
1653
|
+
);
|
|
1654
|
+
effectivePrReview2 = false;
|
|
1655
|
+
}
|
|
1656
|
+
return {
|
|
1657
|
+
effectiveNoBranch: effectiveNoBranch2,
|
|
1658
|
+
effectivePrReview: effectivePrReview2,
|
|
1659
|
+
warnings,
|
|
1660
|
+
planIssueUrl: void 0,
|
|
1661
|
+
planBranch: void 0,
|
|
1662
|
+
planStories: []
|
|
1663
|
+
};
|
|
1130
1664
|
}
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1665
|
+
const persistedNoBranch = input.persisted.noBranch;
|
|
1666
|
+
let effectivePrReview = input.prReview ?? input.persisted.prReviewEnabled;
|
|
1667
|
+
if (input.noBranch !== void 0 && input.noBranch !== persistedNoBranch) {
|
|
1668
|
+
if (persistedNoBranch) {
|
|
1669
|
+
warnings.push(
|
|
1670
|
+
"This pipeline was started with --no-branch. Ignoring current flag; using persisted mode."
|
|
1671
|
+
);
|
|
1672
|
+
} else {
|
|
1673
|
+
warnings.push(
|
|
1674
|
+
"This pipeline was started without --no-branch. Ignoring current flag; using persisted mode."
|
|
1675
|
+
);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
const effectiveNoBranch = persistedNoBranch;
|
|
1679
|
+
if (effectiveNoBranch && effectivePrReview) {
|
|
1680
|
+
warnings.push(
|
|
1681
|
+
"This pipeline runs with --no-branch and opens no PR. Skipping the pr-review phase."
|
|
1140
1682
|
);
|
|
1683
|
+
effectivePrReview = false;
|
|
1141
1684
|
}
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1685
|
+
return {
|
|
1686
|
+
effectiveNoBranch,
|
|
1687
|
+
effectivePrReview,
|
|
1688
|
+
warnings,
|
|
1689
|
+
planIssueUrl: input.persisted.issueUrl,
|
|
1690
|
+
planBranch: input.persisted.branchName,
|
|
1691
|
+
planStories: [...input.persisted.userStories]
|
|
1148
1692
|
};
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1693
|
+
}
|
|
1694
|
+
function selectPhaseLists(input) {
|
|
1695
|
+
const { finalPr, inQueue, effectiveNoBranch, effectivePrReview } = input;
|
|
1696
|
+
const activePhases = finalPr ? effectivePrReview ? QUEUE_PR_PHASES_WITH_REVIEW : QUEUE_PR_PHASES : inQueue ? PIPELINE_PHASES_NO_BRANCH : effectiveNoBranch ? PIPELINE_PHASES_NO_BRANCH : effectivePrReview ? PIPELINE_PHASES_WITH_PR_REVIEW : PIPELINE_PHASES;
|
|
1697
|
+
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;
|
|
1698
|
+
return { activePhases, phaseOrder };
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
// src/commands/run/phase-bootstrap.ts
|
|
1702
|
+
async function runInitGate(input) {
|
|
1703
|
+
const { queue, issuesConfig, publishSessionStart, publisher } = input;
|
|
1704
|
+
const sessionStartedAt = isoNow();
|
|
1705
|
+
if (queue?.preChecked !== true) {
|
|
1706
|
+
if (isVerbose()) {
|
|
1707
|
+
printInfo("Running prerequisite checks...");
|
|
1708
|
+
}
|
|
1709
|
+
const initCode = await runInit(issuesConfig.preferredProvider, { compact: !isVerbose() });
|
|
1710
|
+
if (initCode !== 0) {
|
|
1711
|
+
publishSessionStart(PIPELINE_PHASES, sessionStartedAt);
|
|
1712
|
+
publisher.publish({ type: "phase:start", at: sessionStartedAt, phase: "init" });
|
|
1153
1713
|
publisher.publish({
|
|
1154
|
-
type: "
|
|
1714
|
+
type: "phase:end",
|
|
1155
1715
|
at: isoNow(),
|
|
1156
|
-
|
|
1157
|
-
|
|
1716
|
+
phase: "init",
|
|
1717
|
+
success: false,
|
|
1718
|
+
error: "Prerequisites not met"
|
|
1158
1719
|
});
|
|
1159
|
-
|
|
1720
|
+
printError("Prerequisites not met. Fix the issues above and try again.");
|
|
1721
|
+
return { kind: "done", result: failure(1) };
|
|
1160
1722
|
}
|
|
1723
|
+
}
|
|
1724
|
+
return { kind: "ok", sessionStartedAt };
|
|
1725
|
+
}
|
|
1726
|
+
async function beginBootstrapLocals(issueNumber, paths, runOptions) {
|
|
1727
|
+
const { continueNumbering, startUs } = resolveStoryNumbering(runOptions);
|
|
1728
|
+
const executeRetry = resolveExecuteRetry(runOptions);
|
|
1729
|
+
const tasksPath = paths.tasksFile;
|
|
1730
|
+
const sessionId = randomUUID();
|
|
1731
|
+
bindDiagnosticContext({
|
|
1732
|
+
sessionId,
|
|
1733
|
+
issue: issueNumber,
|
|
1734
|
+
executionId: null,
|
|
1735
|
+
phase: null,
|
|
1736
|
+
story: null,
|
|
1737
|
+
harness: null,
|
|
1738
|
+
model: null
|
|
1161
1739
|
});
|
|
1162
|
-
const
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1740
|
+
const [initialBranch, initialCommit] = await Promise.all([
|
|
1741
|
+
getCurrentBranch().catch(() => ""),
|
|
1742
|
+
getHeadCommit()
|
|
1743
|
+
]);
|
|
1744
|
+
return {
|
|
1745
|
+
continueNumbering,
|
|
1746
|
+
startUs,
|
|
1747
|
+
executeRetry,
|
|
1748
|
+
tasksPath,
|
|
1749
|
+
sessionId,
|
|
1750
|
+
initialBranch,
|
|
1751
|
+
initialCommit
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
async function resolvePrimaryIssue(issueNumber, queue, issuesConfig) {
|
|
1755
|
+
const resolution = await resolveCommandIssue(issueNumber, queue?.resolved, {
|
|
1756
|
+
config: issuesConfig
|
|
1757
|
+
});
|
|
1758
|
+
if (!resolution.ok) {
|
|
1759
|
+
return { kind: "done", result: failure(resolution.code) };
|
|
1760
|
+
}
|
|
1761
|
+
return { kind: "ok", resolvedIssue: resolution.resolved };
|
|
1762
|
+
}
|
|
1763
|
+
async function bootstrapThroughIssueResolution(issueNumber, paths, mode, publisher, input) {
|
|
1764
|
+
const { from, noBranch, prReview, queue } = input;
|
|
1765
|
+
const {
|
|
1766
|
+
continueNumbering,
|
|
1767
|
+
startUs,
|
|
1768
|
+
executeRetry,
|
|
1769
|
+
tasksPath,
|
|
1770
|
+
sessionId,
|
|
1771
|
+
initialBranch,
|
|
1772
|
+
initialCommit
|
|
1773
|
+
} = await beginBootstrapLocals(issueNumber, paths, input.runOptions);
|
|
1774
|
+
let publishedIssueNumber = toIssueNumber(issueNumber);
|
|
1775
|
+
const publishSessionStart = (phases, at, info) => {
|
|
1776
|
+
publisher.publish({
|
|
1777
|
+
type: "session:start",
|
|
1778
|
+
at,
|
|
1779
|
+
sessionId,
|
|
1780
|
+
issueNumber: publishedIssueNumber,
|
|
1781
|
+
issueUrl: info?.issueUrl,
|
|
1782
|
+
branch: info?.branch,
|
|
1783
|
+
branchCreated: info?.branchCreated,
|
|
1784
|
+
startCommit: info?.startCommit,
|
|
1785
|
+
phases: [...phases],
|
|
1786
|
+
configuration: configurationSnapshot,
|
|
1787
|
+
environment: {
|
|
1788
|
+
node: process.version,
|
|
1789
|
+
platform: process.platform,
|
|
1790
|
+
agent: agentSummary.defaultProvider,
|
|
1791
|
+
model: agentSummary.defaultModel,
|
|
1792
|
+
cliVersion: getPackageVersion()
|
|
1793
|
+
}
|
|
1794
|
+
});
|
|
1795
|
+
};
|
|
1796
|
+
const { agentSummary, configurationSnapshot } = await buildAgentConfiguration(prReview);
|
|
1797
|
+
printInfo(
|
|
1798
|
+
`Issue Flow v${getPackageVersion()} \xB7 starting pipeline for issue #${issueNumber} (mode: ${mode}, agent: ${agentSummary.label})`
|
|
1799
|
+
);
|
|
1800
|
+
const issuesConfig = await loadIssuesConfig();
|
|
1801
|
+
const initGate = await runInitGate({
|
|
1802
|
+
queue,
|
|
1803
|
+
issuesConfig,
|
|
1804
|
+
publishSessionStart,
|
|
1805
|
+
publisher
|
|
1167
1806
|
});
|
|
1807
|
+
if (initGate.kind === "done") return initGate;
|
|
1808
|
+
const sessionStartedAt = initGate.sessionStartedAt;
|
|
1809
|
+
const resolved = await resolvePrimaryIssue(issueNumber, queue, issuesConfig);
|
|
1810
|
+
if (resolved.kind === "done") return resolved;
|
|
1811
|
+
const resolvedIssue = resolved.resolvedIssue;
|
|
1812
|
+
publishedIssueNumber = resolvedIssue.issue.number;
|
|
1813
|
+
return {
|
|
1814
|
+
kind: "continue",
|
|
1815
|
+
issueNumber,
|
|
1816
|
+
paths,
|
|
1817
|
+
mode,
|
|
1818
|
+
publisher,
|
|
1819
|
+
input,
|
|
1820
|
+
from,
|
|
1821
|
+
noBranch,
|
|
1822
|
+
prReview,
|
|
1823
|
+
queue,
|
|
1824
|
+
continueNumbering,
|
|
1825
|
+
startUs,
|
|
1826
|
+
executeRetry,
|
|
1827
|
+
tasksPath,
|
|
1828
|
+
sessionId,
|
|
1829
|
+
initialBranch,
|
|
1830
|
+
initialCommit,
|
|
1831
|
+
publishedIssueNumber,
|
|
1832
|
+
publishSessionStart,
|
|
1833
|
+
agentSummary,
|
|
1834
|
+
configurationSnapshot,
|
|
1835
|
+
sessionStartedAt,
|
|
1836
|
+
resolvedIssue
|
|
1837
|
+
};
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
// src/commands/run/phase-prepare.ts
|
|
1841
|
+
async function loadModesAndPhases(input) {
|
|
1842
|
+
const { noBranch, prReview, tasksPath, queue } = input;
|
|
1843
|
+
let persistedFlags = null;
|
|
1168
1844
|
try {
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1845
|
+
const existingPlan = await loadTaskPlan(tasksPath);
|
|
1846
|
+
persistedFlags = {
|
|
1847
|
+
noBranch: existingPlan.noBranch ?? false,
|
|
1848
|
+
prReviewEnabled: existingPlan.prReview?.enabled ?? false,
|
|
1849
|
+
issueUrl: existingPlan.issueUrl || void 0,
|
|
1850
|
+
branchName: existingPlan.branchName || void 0,
|
|
1851
|
+
userStories: existingPlan.userStories
|
|
1852
|
+
};
|
|
1853
|
+
} catch {
|
|
1854
|
+
}
|
|
1855
|
+
const modes = resolveBranchAndReviewModes({
|
|
1856
|
+
noBranch,
|
|
1857
|
+
prReview,
|
|
1858
|
+
persisted: persistedFlags
|
|
1859
|
+
});
|
|
1860
|
+
for (const warning of modes.warnings) {
|
|
1861
|
+
printWarning(warning);
|
|
1862
|
+
}
|
|
1863
|
+
const { effectiveNoBranch, effectivePrReview } = modes;
|
|
1864
|
+
const planIssueUrl = modes.planIssueUrl;
|
|
1865
|
+
const planBranch = modes.planBranch;
|
|
1866
|
+
const planStories = modes.planStories;
|
|
1867
|
+
if (effectivePrReview) {
|
|
1868
|
+
try {
|
|
1869
|
+
const plan = await loadTaskPlan(tasksPath);
|
|
1870
|
+
if (plan.prReview?.enabled !== true) {
|
|
1871
|
+
plan.prReview = { ...plan.prReview, enabled: true, rounds: plan.prReview?.rounds ?? 0 };
|
|
1872
|
+
await saveTaskPlan(tasksPath, plan);
|
|
1873
|
+
}
|
|
1874
|
+
} catch {
|
|
1172
1875
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1876
|
+
}
|
|
1877
|
+
const inQueue = queue !== void 0;
|
|
1878
|
+
const finalPr = queue?.finalPr;
|
|
1879
|
+
const { activePhases, phaseOrder } = selectPhaseLists({
|
|
1880
|
+
finalPr: finalPr !== void 0,
|
|
1881
|
+
inQueue,
|
|
1882
|
+
effectiveNoBranch,
|
|
1883
|
+
effectivePrReview
|
|
1884
|
+
});
|
|
1885
|
+
return {
|
|
1886
|
+
effectiveNoBranch,
|
|
1887
|
+
effectivePrReview,
|
|
1888
|
+
planIssueUrl,
|
|
1889
|
+
planBranch,
|
|
1890
|
+
planStories,
|
|
1891
|
+
inQueue,
|
|
1892
|
+
finalPr,
|
|
1893
|
+
activePhases,
|
|
1894
|
+
phaseOrder
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1897
|
+
async function openPhaseSession(input) {
|
|
1898
|
+
const {
|
|
1899
|
+
issueNumber,
|
|
1900
|
+
tasksPath,
|
|
1901
|
+
queue,
|
|
1902
|
+
finalPr,
|
|
1903
|
+
effectiveNoBranch,
|
|
1904
|
+
planIssueUrl,
|
|
1905
|
+
planStories,
|
|
1906
|
+
initialBranch,
|
|
1907
|
+
initialCommit,
|
|
1908
|
+
activePhases,
|
|
1909
|
+
sessionStartedAt,
|
|
1910
|
+
resolvedIssue,
|
|
1911
|
+
publisher,
|
|
1912
|
+
publishSessionStart
|
|
1913
|
+
} = input;
|
|
1914
|
+
let planBranch = input.planBranch;
|
|
1915
|
+
const queueCommitScope = queue === void 0 ? void 0 : `issue-${issueNumber}`;
|
|
1916
|
+
let producedBranch = null;
|
|
1917
|
+
const plannedExecutionBranch = planBranch ?? null;
|
|
1918
|
+
const branchExistedBeforeExecution = effectiveNoBranch || planBranch === void 0 || planBranch === null ? null : await localBranchExists(planBranch).catch(() => null);
|
|
1919
|
+
if (queue !== void 0 && finalPr === void 0 && !effectiveNoBranch) {
|
|
1920
|
+
producedBranch = await adoptQueueBranch(tasksPath, queue.plan.branchName);
|
|
1921
|
+
if (producedBranch !== null) {
|
|
1922
|
+
planBranch = producedBranch;
|
|
1183
1923
|
}
|
|
1184
|
-
await publisher.close();
|
|
1185
|
-
writeDiagnostic({
|
|
1186
|
-
level: result.code === 0 ? "info" : "error",
|
|
1187
|
-
message: `Issue Flow session ${result.code === 0 ? "completed" : "failed"}`,
|
|
1188
|
-
context: { code: result.code, failedPhase: result.failedPhase }
|
|
1189
|
-
});
|
|
1190
|
-
await flushDiagnostics();
|
|
1191
|
-
setSessionPublisher(void 0);
|
|
1192
1924
|
}
|
|
1925
|
+
publishSessionStart(activePhases, sessionStartedAt, {
|
|
1926
|
+
issueUrl: planIssueUrl ?? resolvedIssue.issue.remoteRef ?? void 0,
|
|
1927
|
+
branch: effectiveNoBranch ? initialBranch : planBranch,
|
|
1928
|
+
branchCreated: effectiveNoBranch ? false : branchExistedBeforeExecution === true ? false : null,
|
|
1929
|
+
startCommit: initialCommit
|
|
1930
|
+
});
|
|
1931
|
+
publishIssueDetails(publisher, resolvedIssue.issue, sessionStartedAt);
|
|
1932
|
+
publishStorySeed(publisher, planStories, sessionStartedAt);
|
|
1933
|
+
publisher.publish({ type: "phase:start", at: sessionStartedAt, phase: "init" });
|
|
1934
|
+
publisher.publish({ type: "phase:end", at: isoNow(), phase: "init", success: true });
|
|
1935
|
+
await publishGitState(publisher);
|
|
1936
|
+
return {
|
|
1937
|
+
queueCommitScope,
|
|
1938
|
+
producedBranch,
|
|
1939
|
+
plannedExecutionBranch,
|
|
1940
|
+
branchExistedBeforeExecution,
|
|
1941
|
+
planBranch
|
|
1942
|
+
};
|
|
1943
|
+
}
|
|
1944
|
+
async function maybeHandoffToQueue(input) {
|
|
1945
|
+
const { queue, issueNumber, requested, resolvedIssue, noBranch, prReview, runOptions } = input;
|
|
1946
|
+
if (queue !== void 0) return null;
|
|
1947
|
+
const decision = await decideQueue({
|
|
1948
|
+
requested: requested ?? [issueNumber],
|
|
1949
|
+
resolved: resolvedIssue,
|
|
1950
|
+
noBranch: noBranch ?? false,
|
|
1951
|
+
prReview: prReview ?? false,
|
|
1952
|
+
runOptions: runOptions ?? {}
|
|
1953
|
+
});
|
|
1954
|
+
if (decision.kind === "stop") {
|
|
1955
|
+
return { kind: "done", result: failure(decision.code) };
|
|
1956
|
+
}
|
|
1957
|
+
if (decision.kind === "queue") {
|
|
1958
|
+
return {
|
|
1959
|
+
kind: "done",
|
|
1960
|
+
result: {
|
|
1961
|
+
...failure(0),
|
|
1962
|
+
queue: { plan: decision.plan, resumed: decision.resumed, resolved: resolvedIssue }
|
|
1963
|
+
}
|
|
1964
|
+
};
|
|
1965
|
+
}
|
|
1966
|
+
return null;
|
|
1967
|
+
}
|
|
1968
|
+
async function preparePhaseRun(issueNumber, paths, mode, publisher, input) {
|
|
1969
|
+
const boot = await bootstrapThroughIssueResolution(issueNumber, paths, mode, publisher, input);
|
|
1970
|
+
if (boot.kind === "done") return boot;
|
|
1971
|
+
const {
|
|
1972
|
+
from,
|
|
1973
|
+
noBranch,
|
|
1974
|
+
prReview,
|
|
1975
|
+
queue,
|
|
1976
|
+
continueNumbering,
|
|
1977
|
+
startUs,
|
|
1978
|
+
executeRetry,
|
|
1979
|
+
tasksPath,
|
|
1980
|
+
initialBranch,
|
|
1981
|
+
initialCommit,
|
|
1982
|
+
publishSessionStart,
|
|
1983
|
+
agentSummary,
|
|
1984
|
+
sessionStartedAt,
|
|
1985
|
+
resolvedIssue
|
|
1986
|
+
} = boot;
|
|
1987
|
+
const _publishedIssueNumber = boot.publishedIssueNumber;
|
|
1988
|
+
const handoff = await maybeHandoffToQueue({
|
|
1989
|
+
queue,
|
|
1990
|
+
issueNumber,
|
|
1991
|
+
requested: input.requested,
|
|
1992
|
+
resolvedIssue,
|
|
1993
|
+
noBranch,
|
|
1994
|
+
prReview,
|
|
1995
|
+
runOptions: input.runOptions
|
|
1996
|
+
});
|
|
1997
|
+
if (handoff !== null) return handoff;
|
|
1998
|
+
const modes = await loadModesAndPhases({ noBranch, prReview, tasksPath, queue });
|
|
1999
|
+
const {
|
|
2000
|
+
effectiveNoBranch,
|
|
2001
|
+
effectivePrReview,
|
|
2002
|
+
planIssueUrl,
|
|
2003
|
+
planStories,
|
|
2004
|
+
inQueue,
|
|
2005
|
+
finalPr,
|
|
2006
|
+
activePhases,
|
|
2007
|
+
phaseOrder
|
|
2008
|
+
} = modes;
|
|
2009
|
+
let planBranch = modes.planBranch;
|
|
2010
|
+
const opened = await openPhaseSession({
|
|
2011
|
+
issueNumber,
|
|
2012
|
+
tasksPath,
|
|
2013
|
+
queue,
|
|
2014
|
+
finalPr,
|
|
2015
|
+
effectiveNoBranch,
|
|
2016
|
+
planBranch,
|
|
2017
|
+
planIssueUrl,
|
|
2018
|
+
planStories,
|
|
2019
|
+
initialBranch,
|
|
2020
|
+
initialCommit,
|
|
2021
|
+
activePhases,
|
|
2022
|
+
sessionStartedAt,
|
|
2023
|
+
resolvedIssue,
|
|
2024
|
+
publisher,
|
|
2025
|
+
publishSessionStart
|
|
2026
|
+
});
|
|
2027
|
+
const { queueCommitScope, producedBranch, plannedExecutionBranch, branchExistedBeforeExecution } = opened;
|
|
2028
|
+
planBranch = opened.planBranch;
|
|
2029
|
+
return {
|
|
2030
|
+
kind: "ready",
|
|
2031
|
+
issueNumber,
|
|
2032
|
+
tasksPath,
|
|
2033
|
+
publisher,
|
|
2034
|
+
from,
|
|
2035
|
+
continueNumbering,
|
|
2036
|
+
startUs,
|
|
2037
|
+
executeRetry,
|
|
2038
|
+
initialBranch,
|
|
2039
|
+
agentSummary,
|
|
2040
|
+
resolvedIssue,
|
|
2041
|
+
effectiveNoBranch,
|
|
2042
|
+
effectivePrReview,
|
|
2043
|
+
inQueue,
|
|
2044
|
+
finalPr,
|
|
2045
|
+
activePhases,
|
|
2046
|
+
phaseOrder,
|
|
2047
|
+
queueCommitScope,
|
|
2048
|
+
queue,
|
|
2049
|
+
producedBranch,
|
|
2050
|
+
plannedExecutionBranch,
|
|
2051
|
+
branchExistedBeforeExecution
|
|
2052
|
+
};
|
|
1193
2053
|
}
|
|
2054
|
+
|
|
2055
|
+
// src/commands/run/session.ts
|
|
2056
|
+
import { mkdir as mkdir3 } from "fs/promises";
|
|
2057
|
+
|
|
2058
|
+
// src/commands/run/oversized.ts
|
|
2059
|
+
import { mkdir as mkdir2, readFile, writeFile } from "fs/promises";
|
|
1194
2060
|
async function reportIfOversized(issueNumber, paths, result) {
|
|
1195
2061
|
try {
|
|
1196
2062
|
const journal = `${await readFileIfPresent(paths.rotatedEventsFile)}${await readFileIfPresent(paths.eventsFile)}`;
|
|
@@ -1249,7 +2115,7 @@ async function maybeCreateSubIssues(issueNumber, plan, reportFile) {
|
|
|
1249
2115
|
printInfo("Nothing left to split: no pending story.");
|
|
1250
2116
|
return;
|
|
1251
2117
|
}
|
|
1252
|
-
const { runGenerate } = await import("./generate-
|
|
2118
|
+
const { runGenerate } = await import("./generate-JGW5JSKB.js");
|
|
1253
2119
|
for (const proposal of proposals) {
|
|
1254
2120
|
const instruction = [
|
|
1255
2121
|
`Create a sub-issue of #${issueNumber}: ${proposal.title}.`,
|
|
@@ -1283,366 +2149,306 @@ async function readFileIfPresent(path) {
|
|
|
1283
2149
|
return "";
|
|
1284
2150
|
}
|
|
1285
2151
|
}
|
|
1286
|
-
|
|
2152
|
+
|
|
2153
|
+
// src/commands/run/session.ts
|
|
2154
|
+
var WRITING_PHASES = /* @__PURE__ */ new Set(["plan", "execute", "pr"]);
|
|
2155
|
+
async function ensureRepositoryWritable(phase) {
|
|
2156
|
+
if (!WRITING_PHASES.has(phase)) return;
|
|
2157
|
+
const preflight = await preflightRepository({ intent: "resume-same-phase" });
|
|
2158
|
+
if (preflight.ok) return;
|
|
2159
|
+
for (const line of describePreflight(preflight)) {
|
|
2160
|
+
printError(line);
|
|
2161
|
+
}
|
|
2162
|
+
throw new Error(`The repository is not in a state the ${phase} phase can write to`);
|
|
2163
|
+
}
|
|
2164
|
+
async function claimRunOwnership(target, detached = false) {
|
|
2165
|
+
let lockFile;
|
|
1287
2166
|
try {
|
|
1288
|
-
|
|
1289
|
-
await saveTaskPlan(tasksFile, {
|
|
1290
|
-
...plan,
|
|
1291
|
-
runState: {
|
|
1292
|
-
...plan.runState ?? {
|
|
1293
|
-
currentPhase: null,
|
|
1294
|
-
attempt: 0,
|
|
1295
|
-
lastHeartbeatAt: null,
|
|
1296
|
-
blockedReason: null,
|
|
1297
|
-
owner: null
|
|
1298
|
-
},
|
|
1299
|
-
status: "paused",
|
|
1300
|
-
lastHeartbeatAt: isoNow()
|
|
1301
|
-
}
|
|
1302
|
-
});
|
|
2167
|
+
lockFile = (await resolveProjectPaths()).runLockFile;
|
|
1303
2168
|
} catch {
|
|
1304
|
-
|
|
2169
|
+
return { ok: true, interruptedBy: null, release: async () => {
|
|
2170
|
+
} };
|
|
1305
2171
|
}
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
]);
|
|
1330
|
-
let publishedIssueNumber = toIssueNumber(issueNumber);
|
|
1331
|
-
const publishSessionStart = (phases, at, info) => {
|
|
1332
|
-
publisher.publish({
|
|
1333
|
-
type: "session:start",
|
|
1334
|
-
at,
|
|
1335
|
-
sessionId,
|
|
1336
|
-
issueNumber: publishedIssueNumber,
|
|
1337
|
-
issueUrl: info?.issueUrl,
|
|
1338
|
-
branch: info?.branch,
|
|
1339
|
-
branchCreated: info?.branchCreated,
|
|
1340
|
-
startCommit: info?.startCommit,
|
|
1341
|
-
phases: [...phases],
|
|
1342
|
-
configuration: configurationSnapshot,
|
|
1343
|
-
environment: {
|
|
1344
|
-
node: process.version,
|
|
1345
|
-
platform: process.platform,
|
|
1346
|
-
agent: agentSummary.defaultProvider,
|
|
1347
|
-
model: agentSummary.defaultModel,
|
|
1348
|
-
cliVersion: getPackageVersion()
|
|
2172
|
+
const result = await acquireRunLock(lockFile, { target, detached });
|
|
2173
|
+
if (!result.ok) return result;
|
|
2174
|
+
return {
|
|
2175
|
+
ok: true,
|
|
2176
|
+
interruptedBy: result.handle.reclaimedFrom,
|
|
2177
|
+
release: () => result.handle.release()
|
|
2178
|
+
};
|
|
2179
|
+
}
|
|
2180
|
+
async function pauseIssue(tasksFile, issueNumber) {
|
|
2181
|
+
try {
|
|
2182
|
+
const plan = await loadTaskPlan(tasksFile);
|
|
2183
|
+
await saveTaskPlan(tasksFile, {
|
|
2184
|
+
...plan,
|
|
2185
|
+
runState: {
|
|
2186
|
+
...plan.runState ?? {
|
|
2187
|
+
currentPhase: null,
|
|
2188
|
+
attempt: 0,
|
|
2189
|
+
lastHeartbeatAt: null,
|
|
2190
|
+
blockedReason: null,
|
|
2191
|
+
owner: null
|
|
2192
|
+
},
|
|
2193
|
+
status: "paused",
|
|
2194
|
+
lastHeartbeatAt: isoNow()
|
|
1349
2195
|
}
|
|
1350
2196
|
});
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
const
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
)
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
const
|
|
1382
|
-
const
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
}
|
|
1387
|
-
const initCode = await runInit(issuesConfig.preferredProvider, { compact: !isVerbose() });
|
|
1388
|
-
if (initCode !== 0) {
|
|
1389
|
-
publishSessionStart(PIPELINE_PHASES, sessionStartedAt);
|
|
1390
|
-
publisher.publish({ type: "phase:start", at: sessionStartedAt, phase: "init" });
|
|
2197
|
+
} catch {
|
|
2198
|
+
printWarning(`Could not write a checkpoint for issue #${issueNumber}.`);
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
async function createSessionPublisher(input) {
|
|
2202
|
+
const { paths, persistSnapshot, journalEnabled, webConfig, maxFileBytes } = input;
|
|
2203
|
+
const surfaces = [];
|
|
2204
|
+
if (persistSnapshot || journalEnabled) {
|
|
2205
|
+
await mkdir3(paths.issueDir, { recursive: true });
|
|
2206
|
+
}
|
|
2207
|
+
if (persistSnapshot) {
|
|
2208
|
+
surfaces.push(
|
|
2209
|
+
new FilePublisher(paths.sessionFile, {
|
|
2210
|
+
logLimit: webConfig.logLimit,
|
|
2211
|
+
includeLogs: webConfig.includeLogs
|
|
2212
|
+
})
|
|
2213
|
+
);
|
|
2214
|
+
}
|
|
2215
|
+
if (journalEnabled) {
|
|
2216
|
+
surfaces.push(
|
|
2217
|
+
new JournalPublisher(paths.eventsFile, paths.rotatedEventsFile, {
|
|
2218
|
+
logLimit: webConfig.logLimit,
|
|
2219
|
+
includeLogs: webConfig.includeLogs,
|
|
2220
|
+
...maxFileBytes === void 0 ? {} : { maxFileBytes }
|
|
2221
|
+
})
|
|
2222
|
+
);
|
|
2223
|
+
}
|
|
2224
|
+
return surfaces.length === 0 ? new MemoryPublisher() : surfaces.length === 1 ? surfaces[0] : new MultiPublisher(surfaces);
|
|
2225
|
+
}
|
|
2226
|
+
function registerIssueShutdownHooks(input) {
|
|
2227
|
+
const { paths, issueNumber, publisher } = input;
|
|
2228
|
+
const releaseCheckpoint = onShutdown({
|
|
2229
|
+
phase: "checkpoint",
|
|
2230
|
+
run: async () => {
|
|
2231
|
+
await pauseIssue(paths.tasksFile, issueNumber);
|
|
1391
2232
|
publisher.publish({
|
|
1392
|
-
type: "
|
|
2233
|
+
type: "log",
|
|
1393
2234
|
at: isoNow(),
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
error: "Prerequisites not met"
|
|
2235
|
+
level: "warn",
|
|
2236
|
+
message: `Interrupted during issue #${issueNumber}. A checkpoint was saved; resume with \`issue-flow resume ${issueNumber}\`.`
|
|
1397
2237
|
});
|
|
1398
|
-
|
|
1399
|
-
return failure(1);
|
|
2238
|
+
publisher.publish({ type: "session:end", at: isoNow(), status: "failed" });
|
|
1400
2239
|
}
|
|
1401
|
-
}
|
|
1402
|
-
const resolution = await resolveCommandIssue(issueNumber, queue?.resolved, {
|
|
1403
|
-
config: issuesConfig
|
|
1404
2240
|
});
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
publishedIssueNumber = resolvedIssue.issue.number;
|
|
1410
|
-
if (queue === void 0) {
|
|
1411
|
-
const decision = await decideQueue({
|
|
1412
|
-
requested: input.requested ?? [issueNumber],
|
|
1413
|
-
resolved: resolvedIssue,
|
|
1414
|
-
noBranch: noBranch ?? false,
|
|
1415
|
-
prReview: prReview ?? false,
|
|
1416
|
-
runOptions: input.runOptions ?? {}
|
|
1417
|
-
});
|
|
1418
|
-
if (decision.kind === "stop") {
|
|
1419
|
-
return failure(decision.code);
|
|
1420
|
-
}
|
|
1421
|
-
if (decision.kind === "queue") {
|
|
1422
|
-
return {
|
|
1423
|
-
...failure(0),
|
|
1424
|
-
queue: { plan: decision.plan, resumed: decision.resumed, resolved: resolvedIssue }
|
|
1425
|
-
};
|
|
2241
|
+
const releaseClose = onShutdown({
|
|
2242
|
+
phase: "close",
|
|
2243
|
+
run: async () => {
|
|
2244
|
+
await publisher.close();
|
|
1426
2245
|
}
|
|
2246
|
+
});
|
|
2247
|
+
return { releaseCheckpoint, releaseClose };
|
|
2248
|
+
}
|
|
2249
|
+
async function closeIssueSession(input) {
|
|
2250
|
+
const { releaseCheckpoint, releaseClose, result, publisher } = input;
|
|
2251
|
+
releaseCheckpoint();
|
|
2252
|
+
releaseClose();
|
|
2253
|
+
if (result.queue === void 0) {
|
|
2254
|
+
publisher.publish({
|
|
2255
|
+
type: "session:end",
|
|
2256
|
+
at: isoNow(),
|
|
2257
|
+
status: result.code === 0 ? "completed" : "failed"
|
|
2258
|
+
});
|
|
1427
2259
|
}
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
printWarning(
|
|
1447
|
-
"This pipeline was started without --no-branch. Ignoring current flag; using persisted mode."
|
|
1448
|
-
);
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
|
-
effectiveNoBranch = persistedNoBranch;
|
|
1452
|
-
} catch {
|
|
2260
|
+
await publisher.close();
|
|
2261
|
+
writeDiagnostic({
|
|
2262
|
+
level: result.code === 0 ? "info" : "error",
|
|
2263
|
+
message: `Issue Flow session ${result.code === 0 ? "completed" : "failed"}`,
|
|
2264
|
+
context: { code: result.code, failedPhase: result.failedPhase }
|
|
2265
|
+
});
|
|
2266
|
+
await flushDiagnostics();
|
|
2267
|
+
setSessionPublisher(void 0);
|
|
2268
|
+
}
|
|
2269
|
+
async function applySessionSideEffects(input) {
|
|
2270
|
+
const { publisher, interruptedBy, webConfig, restartWeb } = input;
|
|
2271
|
+
if (interruptedBy !== void 0) {
|
|
2272
|
+
publisher.publish({
|
|
2273
|
+
type: "log",
|
|
2274
|
+
at: isoNow(),
|
|
2275
|
+
level: "warn",
|
|
2276
|
+
message: `Previous run interrupted: ${describeRunLockOwner(interruptedBy)}. Its lock was stale and has been taken over.`
|
|
2277
|
+
});
|
|
1453
2278
|
}
|
|
1454
|
-
if (
|
|
1455
|
-
|
|
1456
|
-
|
|
2279
|
+
if (webConfig.enabled) {
|
|
2280
|
+
await ensureWebMonitor(
|
|
2281
|
+
{
|
|
2282
|
+
publisher,
|
|
2283
|
+
port: webConfig.port,
|
|
2284
|
+
host: webConfig.host,
|
|
2285
|
+
refreshSeconds: webConfig.refreshSeconds
|
|
2286
|
+
},
|
|
2287
|
+
{ restart: restartWeb === true }
|
|
1457
2288
|
);
|
|
1458
|
-
effectivePrReview = false;
|
|
1459
2289
|
}
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
2290
|
+
}
|
|
2291
|
+
async function runIssueSession(issueNumber, mode, input, runPipelinePhases2) {
|
|
2292
|
+
resetAgentInvocationState();
|
|
2293
|
+
const paths = await resolveIssuePaths(issueNumber);
|
|
2294
|
+
try {
|
|
2295
|
+
const project = await resolveProjectPaths();
|
|
2296
|
+
bindDiagnosticContext({
|
|
2297
|
+
project: project.projectId,
|
|
2298
|
+
projectRoot: await getProjectRoot(),
|
|
2299
|
+
issue: issueNumber,
|
|
2300
|
+
sessionId: null,
|
|
2301
|
+
executionId: null,
|
|
2302
|
+
phase: null,
|
|
2303
|
+
story: null,
|
|
2304
|
+
harness: null,
|
|
2305
|
+
model: null
|
|
2306
|
+
});
|
|
2307
|
+
} catch {
|
|
1469
2308
|
}
|
|
1470
|
-
const
|
|
1471
|
-
const
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
const queueCommitScope = queue === void 0 ? void 0 : `issue-${issueNumber}`;
|
|
1475
|
-
let producedBranch = null;
|
|
1476
|
-
let plannedExecutionBranch = planBranch ?? null;
|
|
1477
|
-
let branchExistedBeforeExecution = effectiveNoBranch || planBranch === void 0 || planBranch === null ? null : await localBranchExists(planBranch).catch(() => null);
|
|
1478
|
-
if (queue !== void 0 && finalPr === void 0 && !effectiveNoBranch) {
|
|
1479
|
-
producedBranch = await adoptQueueBranch(tasksPath, queue.plan.branchName);
|
|
1480
|
-
if (producedBranch !== null) {
|
|
1481
|
-
planBranch = producedBranch;
|
|
1482
|
-
}
|
|
2309
|
+
const resilience = await initResilienceConfig();
|
|
2310
|
+
const configuredInactivity = resilience.watchdog?.inactivityTimeoutMs;
|
|
2311
|
+
if (configuredInactivity !== void 0 && getInactivityTimeout() === void 0) {
|
|
2312
|
+
setInactivityTimeout(configuredInactivity);
|
|
1483
2313
|
}
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
2314
|
+
const webConfig = await loadWebConfig();
|
|
2315
|
+
const journalEnabled = resilience.journal?.enabled === true;
|
|
2316
|
+
const persistSnapshot = webConfig.enabled || input.runOptions?.detachedChild === true;
|
|
2317
|
+
const publisher = await createSessionPublisher({
|
|
2318
|
+
paths,
|
|
2319
|
+
persistSnapshot,
|
|
2320
|
+
journalEnabled,
|
|
2321
|
+
webConfig,
|
|
2322
|
+
maxFileBytes: resilience.journal?.maxFileBytes
|
|
1489
2323
|
});
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
const nextPhase = mgr.getNextPhase();
|
|
1514
|
-
if (nextPhase && nextPhase !== "init") {
|
|
1515
|
-
startPhase = nextPhase;
|
|
1516
|
-
printInfo(`Resuming from phase: ${startPhase}`);
|
|
1517
|
-
}
|
|
1518
|
-
} catch {
|
|
2324
|
+
setSessionPublisher(publisher);
|
|
2325
|
+
await applySessionSideEffects({
|
|
2326
|
+
publisher,
|
|
2327
|
+
interruptedBy: input.interruptedBy,
|
|
2328
|
+
webConfig,
|
|
2329
|
+
restartWeb: input.restartWeb
|
|
2330
|
+
});
|
|
2331
|
+
let result = {
|
|
2332
|
+
code: 1,
|
|
2333
|
+
failedPhase: null,
|
|
2334
|
+
branchName: null,
|
|
2335
|
+
storyCount: 0,
|
|
2336
|
+
elapsedSeconds: 0
|
|
2337
|
+
};
|
|
2338
|
+
const { releaseCheckpoint, releaseClose } = registerIssueShutdownHooks({
|
|
2339
|
+
paths,
|
|
2340
|
+
issueNumber,
|
|
2341
|
+
publisher
|
|
2342
|
+
});
|
|
2343
|
+
try {
|
|
2344
|
+
result = await runPipelinePhases2(issueNumber, paths, mode, publisher, input);
|
|
2345
|
+
if (result.code !== 0) {
|
|
2346
|
+
await reportIfOversized(issueNumber, paths, result);
|
|
1519
2347
|
}
|
|
2348
|
+
return result;
|
|
2349
|
+
} finally {
|
|
2350
|
+
await closeIssueSession({ releaseCheckpoint, releaseClose, result, publisher });
|
|
1520
2351
|
}
|
|
1521
|
-
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
// src/commands/run/phase-runners.ts
|
|
2355
|
+
function createPlanRunner(input, makeRunner) {
|
|
2356
|
+
const {
|
|
2357
|
+
issueNumber,
|
|
2358
|
+
tasksPath,
|
|
2359
|
+
publisher,
|
|
2360
|
+
resolvedIssue,
|
|
2361
|
+
queue,
|
|
2362
|
+
continueNumbering,
|
|
2363
|
+
startUs,
|
|
2364
|
+
effectiveNoBranch,
|
|
2365
|
+
effectivePrReview,
|
|
2366
|
+
initialBranch,
|
|
2367
|
+
branchState
|
|
2368
|
+
} = input;
|
|
2369
|
+
return async () => {
|
|
2370
|
+
await makeRunner(
|
|
2371
|
+
() => runPlan(issueNumber, resolvedIssue, {
|
|
2372
|
+
continueFlag: continueNumbering,
|
|
2373
|
+
startUs,
|
|
2374
|
+
...effectiveNoBranch && initialBranch ? { branchName: initialBranch } : {}
|
|
2375
|
+
}),
|
|
2376
|
+
"plan"
|
|
2377
|
+
)();
|
|
1522
2378
|
try {
|
|
1523
2379
|
const plan = await loadTaskPlan(tasksPath);
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
2380
|
+
if (!effectiveNoBranch) {
|
|
2381
|
+
branchState.plannedExecutionBranch = plan.branchName;
|
|
2382
|
+
branchState.branchExistedBeforeExecution = await localBranchExists(plan.branchName).catch(
|
|
2383
|
+
() => null
|
|
2384
|
+
);
|
|
2385
|
+
publisher.publish({
|
|
2386
|
+
type: "git:update",
|
|
2387
|
+
at: isoNow(),
|
|
2388
|
+
branchCreated: branchState.branchExistedBeforeExecution === true ? false : null
|
|
2389
|
+
});
|
|
1533
2390
|
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
2391
|
+
publishStorySeed(publisher, plan.userStories, isoNow());
|
|
2392
|
+
if (effectiveNoBranch || effectivePrReview) {
|
|
2393
|
+
if (effectiveNoBranch) plan.noBranch = true;
|
|
2394
|
+
if (effectivePrReview) {
|
|
2395
|
+
plan.prReview = { ...plan.prReview, enabled: true, rounds: plan.prReview?.rounds ?? 0 };
|
|
2396
|
+
}
|
|
2397
|
+
await saveTaskPlan(tasksPath, plan);
|
|
2398
|
+
}
|
|
2399
|
+
} catch {
|
|
2400
|
+
}
|
|
2401
|
+
if (queue !== void 0 && !effectiveNoBranch) {
|
|
2402
|
+
branchState.producedBranch = await adoptQueueBranch(tasksPath, queue.plan.branchName);
|
|
1542
2403
|
}
|
|
1543
2404
|
};
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
2405
|
+
}
|
|
2406
|
+
function createReviewRunner(input) {
|
|
2407
|
+
const { issueNumber, tasksPath, publisher, resolvedIssue, queueCommitScope, executeRetry } = input;
|
|
2408
|
+
return async () => {
|
|
2409
|
+
let maxCycles = 3;
|
|
2410
|
+
try {
|
|
2411
|
+
const plan = await loadTaskPlan(tasksPath);
|
|
2412
|
+
maxCycles = plan.maxCorrectionCycles;
|
|
2413
|
+
} catch {
|
|
2414
|
+
}
|
|
2415
|
+
let code = await runReview(issueNumber, resolvedIssue);
|
|
2416
|
+
let cycle = 0;
|
|
2417
|
+
while (code !== 0 && cycle < maxCycles) {
|
|
2418
|
+
cycle++;
|
|
2419
|
+
printWarning(`Review failed. Starting correction cycle ${cycle}/${maxCycles}...`);
|
|
2420
|
+
publisher.publish({ type: "correction:cycle", at: isoNow(), cycle, maxCycles });
|
|
1555
2421
|
try {
|
|
1556
2422
|
const plan = await loadTaskPlan(tasksPath);
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
branchExistedBeforeExecution = await localBranchExists(plan.branchName).catch(() => null);
|
|
1560
|
-
publisher.publish({
|
|
1561
|
-
type: "git:update",
|
|
1562
|
-
at: isoNow(),
|
|
1563
|
-
branchCreated: branchExistedBeforeExecution === true ? false : null
|
|
1564
|
-
});
|
|
1565
|
-
}
|
|
1566
|
-
publishStorySeed(publisher, plan.userStories, isoNow());
|
|
1567
|
-
if (effectiveNoBranch || effectivePrReview) {
|
|
1568
|
-
if (effectiveNoBranch) plan.noBranch = true;
|
|
1569
|
-
if (effectivePrReview) {
|
|
1570
|
-
plan.prReview = { ...plan.prReview, enabled: true, rounds: plan.prReview?.rounds ?? 0 };
|
|
1571
|
-
}
|
|
1572
|
-
await saveTaskPlan(tasksPath, plan);
|
|
1573
|
-
}
|
|
2423
|
+
plan.correctionCycle = cycle;
|
|
2424
|
+
await saveTaskPlan(tasksPath, plan);
|
|
1574
2425
|
} catch {
|
|
1575
2426
|
}
|
|
1576
|
-
|
|
1577
|
-
producedBranch = await adoptQueueBranch(tasksPath, queue.plan.branchName);
|
|
1578
|
-
}
|
|
1579
|
-
},
|
|
1580
|
-
execute: makeRunner(
|
|
1581
|
-
() => runExecute(void 0, {
|
|
2427
|
+
const execCode = await runExecute(void 0, {
|
|
1582
2428
|
issue: issueNumber,
|
|
1583
2429
|
commitScope: queueCommitScope,
|
|
1584
2430
|
...executeRetry
|
|
1585
|
-
})
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
review: async () => {
|
|
1589
|
-
let maxCycles = 3;
|
|
1590
|
-
try {
|
|
1591
|
-
const plan = await loadTaskPlan(tasksPath);
|
|
1592
|
-
maxCycles = plan.maxCorrectionCycles;
|
|
1593
|
-
} catch {
|
|
1594
|
-
}
|
|
1595
|
-
let code = await runReview(issueNumber, resolvedIssue);
|
|
1596
|
-
let cycle = 0;
|
|
1597
|
-
while (code !== 0 && cycle < maxCycles) {
|
|
1598
|
-
cycle++;
|
|
1599
|
-
printWarning(`Review failed. Starting correction cycle ${cycle}/${maxCycles}...`);
|
|
1600
|
-
publisher.publish({ type: "correction:cycle", at: isoNow(), cycle, maxCycles });
|
|
1601
|
-
try {
|
|
1602
|
-
const plan = await loadTaskPlan(tasksPath);
|
|
1603
|
-
plan.correctionCycle = cycle;
|
|
1604
|
-
await saveTaskPlan(tasksPath, plan);
|
|
1605
|
-
} catch {
|
|
1606
|
-
}
|
|
1607
|
-
const execCode = await runExecute(void 0, {
|
|
1608
|
-
issue: issueNumber,
|
|
1609
|
-
commitScope: queueCommitScope,
|
|
1610
|
-
...executeRetry
|
|
1611
|
-
});
|
|
1612
|
-
if (execCode !== 0) {
|
|
1613
|
-
throw new Error("Correction execution failed");
|
|
1614
|
-
}
|
|
1615
|
-
code = await runReview(issueNumber, resolvedIssue);
|
|
1616
|
-
}
|
|
1617
|
-
if (code !== 0) {
|
|
1618
|
-
throw new Error(`Review failed after ${maxCycles} correction cycles`);
|
|
2431
|
+
});
|
|
2432
|
+
if (execCode !== 0) {
|
|
2433
|
+
throw new Error("Correction execution failed");
|
|
1619
2434
|
}
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
"pr"
|
|
1626
|
-
)
|
|
2435
|
+
code = await runReview(issueNumber, resolvedIssue);
|
|
2436
|
+
}
|
|
2437
|
+
if (code !== 0) {
|
|
2438
|
+
throw new Error(`Review failed after ${maxCycles} correction cycles`);
|
|
2439
|
+
}
|
|
1627
2440
|
};
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
if (code === 1) {
|
|
1633
|
-
throw new Error("Phase pr-review failed with exit code 1");
|
|
1634
|
-
}
|
|
1635
|
-
reviewBox.outcome = await readPrReviewOutcome(issueNumber, tasksPath, code === 2);
|
|
1636
|
-
};
|
|
1637
|
-
}
|
|
1638
|
-
const instrumentedRunners = Object.fromEntries(
|
|
2441
|
+
}
|
|
2442
|
+
function instrumentPhaseRunners(runners, input) {
|
|
2443
|
+
const { publisher, issueNumber, branchState } = input;
|
|
2444
|
+
return Object.fromEntries(
|
|
1639
2445
|
Object.entries(runners).map(([phase, fn]) => [
|
|
1640
2446
|
phase,
|
|
1641
2447
|
async () => {
|
|
1642
2448
|
publisher.publish({ type: "phase:start", at: isoNow(), phase });
|
|
1643
2449
|
try {
|
|
1644
2450
|
await fn();
|
|
1645
|
-
if (phase === "execute" && branchExistedBeforeExecution === false && plannedExecutionBranch !== null && await getCurrentBranch().catch(() => "") === plannedExecutionBranch) {
|
|
2451
|
+
if (phase === "execute" && branchState.branchExistedBeforeExecution === false && branchState.plannedExecutionBranch !== null && await getCurrentBranch().catch(() => "") === branchState.plannedExecutionBranch) {
|
|
1646
2452
|
publisher.publish({ type: "git:update", at: isoNow(), branchCreated: true });
|
|
1647
2453
|
}
|
|
1648
2454
|
await publishGitState(publisher);
|
|
@@ -1661,447 +2467,257 @@ async function runPipelinePhases(issueNumber, paths, mode, publisher, input) {
|
|
|
1661
2467
|
}
|
|
1662
2468
|
])
|
|
1663
2469
|
);
|
|
1664
|
-
const phaseSuffixes = {};
|
|
1665
|
-
for (const [phase, resolved] of Object.entries(agentSummary.byPhase)) {
|
|
1666
|
-
if (resolved.provider !== agentSummary.defaultProvider) {
|
|
1667
|
-
phaseSuffixes[phase] = resolved.model ? `${resolved.provider} \xB7 ${resolved.model}` : resolved.provider;
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
|
-
const result = await runPipelineWithRenderer({
|
|
1671
|
-
phases: phaseOrder,
|
|
1672
|
-
startIndex: startIdx,
|
|
1673
|
-
verbose: isVerbose(),
|
|
1674
|
-
runners: instrumentedRunners,
|
|
1675
|
-
tasksPath,
|
|
1676
|
-
phaseSuffixes
|
|
1677
|
-
});
|
|
1678
|
-
if (!result.success) {
|
|
1679
|
-
printError(`Phase ${result.failedPhase} failed`);
|
|
1680
|
-
return {
|
|
1681
|
-
...failure(1),
|
|
1682
|
-
failedPhase: result.failedPhase ?? null,
|
|
1683
|
-
branchName: producedBranch,
|
|
1684
|
-
elapsedSeconds: result.overallElapsedSeconds
|
|
1685
|
-
};
|
|
1686
|
-
}
|
|
1687
|
-
const review = reviewBox.outcome;
|
|
1688
|
-
if (review?.requestedChanges) {
|
|
1689
|
-
console.log("");
|
|
1690
|
-
printWarning("PR review requested changes \u2014 the Pull Request is not ready to merge.");
|
|
1691
|
-
if (review.reportPath !== null) {
|
|
1692
|
-
console.log(` Report: ${review.reportPath}`);
|
|
1693
|
-
}
|
|
1694
|
-
}
|
|
1695
|
-
if (review?.requestedChanges) {
|
|
1696
|
-
printInfo("Issue left open until the review blockers are addressed.");
|
|
1697
|
-
} else if (!inQueue) {
|
|
1698
|
-
await closeIssue(issueNumber, resolvedIssue.source);
|
|
1699
|
-
}
|
|
1700
|
-
let branchName = "unknown";
|
|
1701
|
-
try {
|
|
1702
|
-
const proc = await execa("git", ["branch", "--show-current"], { reject: false });
|
|
1703
|
-
branchName = proc.stdout?.toString().trim() ?? "unknown";
|
|
1704
|
-
} catch {
|
|
1705
|
-
}
|
|
1706
|
-
let storyCount = 0;
|
|
1707
|
-
let planPrUrl = null;
|
|
1708
|
-
try {
|
|
1709
|
-
const plan = await loadTaskPlan(tasksPath);
|
|
1710
|
-
storyCount = plan.userStories.length;
|
|
1711
|
-
planPrUrl = plan.pullRequest?.url ?? null;
|
|
1712
|
-
plan.lastAttemptAt = isoNow();
|
|
1713
|
-
if (!review?.requestedChanges) {
|
|
1714
|
-
plan.issueStatus = "completed";
|
|
1715
|
-
plan.completedAt = isoNow();
|
|
1716
|
-
}
|
|
1717
|
-
await saveTaskPlan(tasksPath, plan);
|
|
1718
|
-
} catch {
|
|
1719
|
-
}
|
|
1720
|
-
if (inQueue) {
|
|
1721
|
-
if (finalPr === void 0) {
|
|
1722
|
-
printSuccess(`Issue #${issueNumber} completed (${storyCount} stories).`);
|
|
1723
|
-
}
|
|
1724
|
-
return {
|
|
1725
|
-
code: 0,
|
|
1726
|
-
failedPhase: null,
|
|
1727
|
-
branchName: producedBranch ?? (branchName === "unknown" ? null : branchName),
|
|
1728
|
-
storyCount,
|
|
1729
|
-
elapsedSeconds: result.overallElapsedSeconds,
|
|
1730
|
-
review
|
|
1731
|
-
};
|
|
1732
|
-
}
|
|
1733
|
-
let prUrl = "unknown";
|
|
1734
|
-
if (!effectiveNoBranch && resolvedIssue.source === "github") {
|
|
1735
|
-
if (planPrUrl !== null) {
|
|
1736
|
-
prUrl = planPrUrl;
|
|
1737
|
-
} else if (branchName !== "unknown" && branchName !== "") {
|
|
1738
|
-
try {
|
|
1739
|
-
const latest = mostRecent(await listPullRequests(branchName));
|
|
1740
|
-
if (latest !== null) {
|
|
1741
|
-
prUrl = latest.url;
|
|
1742
|
-
}
|
|
1743
|
-
} catch {
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
printRunSummary({
|
|
1748
|
-
issueNumber,
|
|
1749
|
-
branchName,
|
|
1750
|
-
noBranch: effectiveNoBranch,
|
|
1751
|
-
storyCount,
|
|
1752
|
-
elapsedSeconds: result.overallElapsedSeconds,
|
|
1753
|
-
prUrl,
|
|
1754
|
-
prReview: review,
|
|
1755
|
-
// Process-owned counters: the session snapshot is empty whenever web
|
|
1756
|
-
// monitoring is off, so it cannot be the source of these totals.
|
|
1757
|
-
usage: getRunUsageTotals(),
|
|
1758
|
-
verification: verificationForSummary(getSessionPublisher().snapshot().verification)
|
|
1759
|
-
});
|
|
1760
|
-
return {
|
|
1761
|
-
code: 0,
|
|
1762
|
-
failedPhase: null,
|
|
1763
|
-
branchName: branchName === "unknown" ? null : branchName,
|
|
1764
|
-
storyCount,
|
|
1765
|
-
elapsedSeconds: result.overallElapsedSeconds
|
|
1766
|
-
};
|
|
1767
|
-
}
|
|
1768
|
-
async function detachAfterConfirm(requested, noBranch, prReview, options) {
|
|
1769
|
-
const childFlags = [...process.argv.slice(2)];
|
|
1770
|
-
if (options.yes !== true && options.only !== true) {
|
|
1771
|
-
try {
|
|
1772
|
-
const issuesConfig = await loadIssuesConfig();
|
|
1773
|
-
const resolution = await resolveCommandIssue(requested[0], void 0, {
|
|
1774
|
-
config: issuesConfig
|
|
1775
|
-
});
|
|
1776
|
-
if (resolution.ok) {
|
|
1777
|
-
const decision = await decideQueue({
|
|
1778
|
-
requested,
|
|
1779
|
-
resolved: resolution.resolved,
|
|
1780
|
-
noBranch: noBranch ?? false,
|
|
1781
|
-
prReview: prReview ?? false,
|
|
1782
|
-
runOptions: options
|
|
1783
|
-
});
|
|
1784
|
-
if (decision.kind === "stop") return decision.code;
|
|
1785
|
-
if (decision.kind === "queue" && !childFlags.includes("--yes")) childFlags.push("--yes");
|
|
1786
|
-
if (decision.kind === "single" && !childFlags.includes("--only")) childFlags.push("--only");
|
|
1787
|
-
}
|
|
1788
|
-
} catch {
|
|
1789
|
-
}
|
|
1790
|
-
}
|
|
1791
|
-
const paths = await resolveIssuePaths(requested[0]);
|
|
1792
|
-
const { pid, logFile } = await spawnDetachedRun({ paths, argv: childFlags });
|
|
1793
|
-
printSuccess(`Detached run started (pid ${pid}).`);
|
|
1794
|
-
printInfo(`Log: ${logFile}`);
|
|
1795
|
-
printInfo("Follow with: issue-flow ps");
|
|
1796
|
-
printInfo(`Stop with: kill ${pid}`);
|
|
1797
|
-
return 0;
|
|
1798
|
-
}
|
|
1799
|
-
async function decideQueue(input) {
|
|
1800
|
-
const primary = input.requested[0];
|
|
1801
|
-
const single = input.requested.length === 1;
|
|
1802
|
-
if (single && input.runOptions.only === true) {
|
|
1803
|
-
return { kind: "single" };
|
|
1804
|
-
}
|
|
1805
|
-
try {
|
|
1806
|
-
const [project, queuePaths] = await Promise.all([
|
|
1807
|
-
resolveProjectPaths(),
|
|
1808
|
-
resolveQueuePaths(primary)
|
|
1809
|
-
]);
|
|
1810
|
-
return await planQueue({
|
|
1811
|
-
requested: input.requested,
|
|
1812
|
-
source: input.resolved.source,
|
|
1813
|
-
known: [input.resolved.issue],
|
|
1814
|
-
projectId: project.projectId,
|
|
1815
|
-
planFile: queuePaths.planFile,
|
|
1816
|
-
noBranch: input.noBranch,
|
|
1817
|
-
prReview: input.prReview,
|
|
1818
|
-
confirm: {
|
|
1819
|
-
yes: input.runOptions.yes,
|
|
1820
|
-
only: input.runOptions.only,
|
|
1821
|
-
cascade: input.runOptions.cascade
|
|
1822
|
-
}
|
|
1823
|
-
});
|
|
1824
|
-
} catch (err) {
|
|
1825
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1826
|
-
if (single) {
|
|
1827
|
-
printWarning(
|
|
1828
|
-
`Could not inspect related issues (${message}). Running issue #${primary} alone.`
|
|
1829
|
-
);
|
|
1830
|
-
return { kind: "single" };
|
|
1831
|
-
}
|
|
1832
|
-
printError(`Could not plan the execution of ${input.requested.length} issues: ${message}`);
|
|
1833
|
-
return { kind: "stop", code: 1 };
|
|
1834
|
-
}
|
|
1835
2470
|
}
|
|
1836
|
-
|
|
1837
|
-
const
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
await saveExecutionPlan(planFile, plan);
|
|
1854
|
-
printInfo(
|
|
1855
|
-
`Queue ${entry.position}/${plan.issues.length}: issue #${entry.id}${entry.title === "" ? "" : ` \u2014 ${entry.title}`}`
|
|
1856
|
-
);
|
|
1857
|
-
const issueUsage = beginUsageScope();
|
|
1858
|
-
let result;
|
|
1859
|
-
try {
|
|
1860
|
-
result = await runIssueSession(entry.id, options.mode, {
|
|
1861
|
-
// `--from` addresses the issue the queue is resuming, never the ones
|
|
1862
|
-
// that come after it — those start from their own beginning.
|
|
1863
|
-
from: firstOfThisRun ? options.from : void 0,
|
|
1864
|
-
noBranch: options.noBranch,
|
|
1865
|
-
// `--start-us` belongs to the first issue this invocation runs; the
|
|
1866
|
-
// ones after it continue from the history those plans just wrote.
|
|
1867
|
-
runOptions: {
|
|
1868
|
-
...options.runOptions,
|
|
1869
|
-
startUs: firstOfThisRun ? options.runOptions?.startUs : void 0
|
|
1870
|
-
},
|
|
1871
|
-
queue: {
|
|
1872
|
-
plan,
|
|
1873
|
-
preChecked: true,
|
|
1874
|
-
resolved: entry.id === initialPlan.id ? resolvedPrimary : void 0
|
|
1875
|
-
}
|
|
1876
|
-
});
|
|
1877
|
-
} finally {
|
|
1878
|
-
firstOfThisRun = false;
|
|
1879
|
-
issueUsage.end();
|
|
1880
|
-
}
|
|
1881
|
-
const usage = issueUsage.totals();
|
|
1882
|
-
if (result.code !== 0) {
|
|
1883
|
-
const failure2 = {
|
|
1884
|
-
phase: result.failedPhase,
|
|
1885
|
-
error: {
|
|
1886
|
-
category: "queue_issue_failed",
|
|
1887
|
-
message: `Issue #${entry.id} failed${result.failedPhase === null ? "" : ` in phase ${result.failedPhase}`}`,
|
|
1888
|
-
at: isoNow()
|
|
1889
|
-
}
|
|
1890
|
-
};
|
|
1891
|
-
const where = result.failedPhase === null ? "" : ` (phase ${result.failedPhase})`;
|
|
1892
|
-
if (failureMode === "stop") {
|
|
1893
|
-
plan = markQueueIssueFailed(plan, entry.id, failure2);
|
|
1894
|
-
await saveExecutionPlan(planFile, plan);
|
|
1895
|
-
printError(
|
|
1896
|
-
`Queue stopped at issue #${entry.id}${where}. The branch and every commit made so far were kept.`
|
|
1897
|
-
);
|
|
1898
|
-
printInfo(`Resume with: issue-flow run ${plan.requested.join(",")}`);
|
|
1899
|
-
return result.code;
|
|
1900
|
-
}
|
|
1901
|
-
if (failureMode === "block") {
|
|
1902
|
-
plan = markQueueIssueBlocked(
|
|
1903
|
-
plan,
|
|
1904
|
-
entry.id,
|
|
1905
|
-
`Failed${where} and --on-issue-failure block was in force`,
|
|
1906
|
-
failure2
|
|
1907
|
-
);
|
|
1908
|
-
await saveExecutionPlan(planFile, plan);
|
|
1909
|
-
exhausted.add(entry.id);
|
|
1910
|
-
failedIds.add(entry.id);
|
|
1911
|
-
printWarning(
|
|
1912
|
-
`Issue #${entry.id} failed${where} and is blocked for review. Continuing with the rest of the queue.`
|
|
1913
|
-
);
|
|
1914
|
-
continue;
|
|
1915
|
-
}
|
|
1916
|
-
const attempts = entry.attempts + 1;
|
|
1917
|
-
if (attempts >= maxIssueAttempts) {
|
|
1918
|
-
plan = markQueueIssueFailed(plan, entry.id, failure2);
|
|
1919
|
-
exhausted.add(entry.id);
|
|
1920
|
-
failedIds.add(entry.id);
|
|
1921
|
-
printWarning(
|
|
1922
|
-
`Issue #${entry.id} failed${where} after ${attempts} attempt(s). Continuing with the rest of the queue.`
|
|
1923
|
-
);
|
|
1924
|
-
} else {
|
|
1925
|
-
plan = markQueueIssueSkipped(plan, entry.id, failure2);
|
|
1926
|
-
printWarning(
|
|
1927
|
-
`Issue #${entry.id} failed${where}. Skipping it for now and coming back at the end of the queue.`
|
|
1928
|
-
);
|
|
1929
|
-
}
|
|
1930
|
-
await saveExecutionPlan(planFile, plan);
|
|
1931
|
-
continue;
|
|
1932
|
-
}
|
|
1933
|
-
if (plan.branchName === null && result.branchName !== null) {
|
|
1934
|
-
plan = setQueueBranch(plan, result.branchName);
|
|
1935
|
-
}
|
|
1936
|
-
plan = markQueueIssueCompleted(plan, entry.id);
|
|
1937
|
-
await saveExecutionPlan(planFile, plan);
|
|
1938
|
-
summaries.push({
|
|
1939
|
-
id: entry.id,
|
|
1940
|
-
title: entry.title,
|
|
1941
|
-
storyCount: result.storyCount,
|
|
1942
|
-
elapsedSeconds: result.elapsedSeconds,
|
|
1943
|
-
usage
|
|
1944
|
-
});
|
|
1945
|
-
}
|
|
1946
|
-
const code = await finishQueue(
|
|
1947
|
-
plan,
|
|
1948
|
-
planFile,
|
|
1949
|
-
summaries,
|
|
1950
|
-
startedAtMs,
|
|
1951
|
-
queueUsage,
|
|
1952
|
-
options,
|
|
1953
|
-
resolvedPrimary
|
|
1954
|
-
);
|
|
1955
|
-
if (failedIds.size > 0) {
|
|
1956
|
-
printError(
|
|
1957
|
-
`${failedIds.size} issue(s) did not finish: ${[...failedIds].map((id) => `#${id}`).join(", ")}.`
|
|
1958
|
-
);
|
|
1959
|
-
printInfo(`Resume with: issue-flow run ${plan.requested.join(",")}`);
|
|
1960
|
-
return code === 0 ? 1 : code;
|
|
2471
|
+
function buildInstrumentedPhaseRunners(input) {
|
|
2472
|
+
const {
|
|
2473
|
+
issueNumber,
|
|
2474
|
+
tasksPath,
|
|
2475
|
+
publisher,
|
|
2476
|
+
resolvedIssue,
|
|
2477
|
+
finalPr,
|
|
2478
|
+
executeRetry,
|
|
2479
|
+
effectivePrReview,
|
|
2480
|
+
queueCommitScope,
|
|
2481
|
+
branchState
|
|
2482
|
+
} = input;
|
|
2483
|
+
const makeRunner = (fn, phase) => async () => {
|
|
2484
|
+
await ensureRepositoryWritable(phase);
|
|
2485
|
+
const code = await fn();
|
|
2486
|
+
if (code !== 0) {
|
|
2487
|
+
throw new Error(`Phase ${phase} failed with exit code ${code}`);
|
|
1961
2488
|
}
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
2489
|
+
};
|
|
2490
|
+
const runners = {
|
|
2491
|
+
prd: makeRunner(() => runPrd(issueNumber, resolvedIssue), "prd"),
|
|
2492
|
+
plan: createPlanRunner(input, makeRunner),
|
|
2493
|
+
execute: makeRunner(
|
|
2494
|
+
() => runExecute(void 0, {
|
|
2495
|
+
issue: issueNumber,
|
|
2496
|
+
commitScope: queueCommitScope,
|
|
2497
|
+
...executeRetry
|
|
2498
|
+
}),
|
|
2499
|
+
"execute"
|
|
2500
|
+
),
|
|
2501
|
+
review: createReviewRunner(input),
|
|
2502
|
+
pr: makeRunner(
|
|
2503
|
+
// The options argument is omitted outside a queue so a standalone run
|
|
2504
|
+
// calls `runPr` exactly as it always did.
|
|
2505
|
+
() => finalPr === void 0 ? runPr(issueNumber, resolvedIssue) : runPr(issueNumber, resolvedIssue, { queue: finalPr }),
|
|
2506
|
+
"pr"
|
|
2507
|
+
)
|
|
2508
|
+
};
|
|
2509
|
+
const reviewBox = { outcome: null };
|
|
2510
|
+
if (effectivePrReview) {
|
|
2511
|
+
runners["pr-review"] = async () => {
|
|
2512
|
+
const code = await runPrReview(void 0, { issue: issueNumber, yes: true });
|
|
2513
|
+
if (code === 1) {
|
|
2514
|
+
throw new Error("Phase pr-review failed with exit code 1");
|
|
2515
|
+
}
|
|
2516
|
+
reviewBox.outcome = await readPrReviewOutcome(issueNumber, tasksPath, code === 2);
|
|
2517
|
+
};
|
|
1965
2518
|
}
|
|
2519
|
+
const instrumentedRunners = instrumentPhaseRunners(runners, {
|
|
2520
|
+
publisher,
|
|
2521
|
+
issueNumber,
|
|
2522
|
+
branchState
|
|
2523
|
+
});
|
|
2524
|
+
return { instrumentedRunners, reviewBox };
|
|
1966
2525
|
}
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
const
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
2526
|
+
|
|
2527
|
+
// src/commands/run/phase-start.ts
|
|
2528
|
+
async function resolveStartPhase(input) {
|
|
2529
|
+
const { from, activePhases, effectiveNoBranch, tasksPath } = input;
|
|
2530
|
+
let startPhase = "prd";
|
|
2531
|
+
if (from) {
|
|
2532
|
+
if (!activePhases.includes(from)) {
|
|
2533
|
+
const validPhases = activePhases.filter((p) => p !== "init").join(", ");
|
|
2534
|
+
if (effectiveNoBranch && PIPELINE_PHASES.includes(from)) {
|
|
2535
|
+
printError(
|
|
2536
|
+
`The '${from}' phase is not available in --no-branch mode. Valid phases: ${validPhases}`
|
|
2537
|
+
);
|
|
2538
|
+
} else {
|
|
2539
|
+
printError(`Invalid phase: ${from}. Valid phases: ${validPhases}`);
|
|
1979
2540
|
}
|
|
1980
|
-
|
|
1981
|
-
review = outcome.review ?? null;
|
|
1982
|
-
if (outcome.code !== 0) {
|
|
1983
|
-
printError("The consolidated Pull Request could not be created.");
|
|
1984
|
-
await saveExecutionPlan(planFile, current);
|
|
1985
|
-
return outcome.code;
|
|
1986
|
-
}
|
|
1987
|
-
const pullRequest = await propagatePullRequest(current);
|
|
1988
|
-
if (pullRequest !== null) {
|
|
1989
|
-
current = setQueuePullRequest(current, pullRequest);
|
|
2541
|
+
return { ok: false };
|
|
1990
2542
|
}
|
|
1991
|
-
|
|
1992
|
-
await saveExecutionPlan(planFile, current);
|
|
1993
|
-
if (review?.requestedChanges === true) {
|
|
1994
|
-
printInfo("Issues left open until the review blockers are addressed.");
|
|
2543
|
+
startPhase = from;
|
|
1995
2544
|
} else {
|
|
1996
|
-
for (const entry of current.issues) {
|
|
1997
|
-
await closeIssue(entry.id, entry.source);
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
printQueueSummary({
|
|
2001
|
-
queueId: current.id,
|
|
2002
|
-
branchName: current.branchName,
|
|
2003
|
-
issues: summaries,
|
|
2004
|
-
excluded: current.excluded.map((entry) => ({ id: entry.id, title: entry.title })),
|
|
2005
|
-
elapsedSeconds: Math.round((Date.now() - startedAtMs) / 1e3),
|
|
2006
|
-
prUrl: current.pullRequest?.url ?? null,
|
|
2007
|
-
// Read here, after the consolidated Pull Request (and the optional
|
|
2008
|
-
// pr-review) already ran: reading it before would report a queue total
|
|
2009
|
-
// that leaves out the phase that just spent tokens.
|
|
2010
|
-
usage: queueUsage.totals(),
|
|
2011
|
-
prReview: review
|
|
2012
|
-
});
|
|
2013
|
-
return 0;
|
|
2014
|
-
}
|
|
2015
|
-
async function primaryPrCreated(plan) {
|
|
2016
|
-
try {
|
|
2017
|
-
const paths = await resolveIssuePaths(plan.id);
|
|
2018
|
-
return (await loadTaskPlan(paths.tasksFile)).pipeline.prCreated;
|
|
2019
|
-
} catch {
|
|
2020
|
-
return false;
|
|
2021
|
-
}
|
|
2022
|
-
}
|
|
2023
|
-
async function buildPrQueueContext(plan) {
|
|
2024
|
-
const pending = [];
|
|
2025
|
-
for (const entry of plan.issues) {
|
|
2026
2545
|
try {
|
|
2027
|
-
const
|
|
2028
|
-
const
|
|
2029
|
-
|
|
2030
|
-
|
|
2546
|
+
const plan = await loadTaskPlan(tasksPath);
|
|
2547
|
+
const mgr = new PipelineManager(plan, tasksPath, activePhases);
|
|
2548
|
+
const nextPhase = mgr.getNextPhase();
|
|
2549
|
+
if (nextPhase && nextPhase !== "init") {
|
|
2550
|
+
startPhase = nextPhase;
|
|
2551
|
+
printInfo(`Resuming from phase: ${startPhase}`);
|
|
2031
2552
|
}
|
|
2032
2553
|
} catch {
|
|
2033
2554
|
}
|
|
2034
2555
|
}
|
|
2035
|
-
|
|
2036
|
-
issues: plan.issues.map((entry) => ({
|
|
2037
|
-
id: entry.id,
|
|
2038
|
-
number: entry.number,
|
|
2039
|
-
title: entry.title,
|
|
2040
|
-
url: entry.url,
|
|
2041
|
-
source: entry.source,
|
|
2042
|
-
parent: entry.parent,
|
|
2043
|
-
role: entry.role,
|
|
2044
|
-
complete: entry.status === "completed"
|
|
2045
|
-
})),
|
|
2046
|
-
excluded: plan.excluded.map((entry) => ({
|
|
2047
|
-
id: entry.id,
|
|
2048
|
-
number: entry.number,
|
|
2049
|
-
title: entry.title,
|
|
2050
|
-
reason: entry.reason
|
|
2051
|
-
})),
|
|
2052
|
-
pending
|
|
2053
|
-
};
|
|
2054
|
-
}
|
|
2055
|
-
async function propagatePullRequest(plan) {
|
|
2056
|
-
let pullRequest = null;
|
|
2057
|
-
try {
|
|
2058
|
-
const primaryPaths = await resolveIssuePaths(plan.id);
|
|
2059
|
-
pullRequest = (await loadTaskPlan(primaryPaths.tasksFile)).pullRequest ?? null;
|
|
2060
|
-
} catch {
|
|
2061
|
-
return null;
|
|
2062
|
-
}
|
|
2063
|
-
if (pullRequest === null) return null;
|
|
2064
|
-
for (const entry of plan.issues) {
|
|
2065
|
-
if (entry.id === plan.id) continue;
|
|
2556
|
+
if (from) {
|
|
2066
2557
|
try {
|
|
2067
|
-
const
|
|
2068
|
-
const
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2558
|
+
const plan = await loadTaskPlan(tasksPath);
|
|
2559
|
+
const mgr = new PipelineManager(plan, tasksPath, activePhases);
|
|
2560
|
+
if (!mgr.canResume(startPhase)) {
|
|
2561
|
+
printError(`Cannot resume from ${startPhase}: prerequisite phases not complete`);
|
|
2562
|
+
return { ok: false };
|
|
2563
|
+
}
|
|
2072
2564
|
} catch {
|
|
2073
|
-
|
|
2565
|
+
if (startPhase !== "prd") {
|
|
2566
|
+
printError(`Cannot resume from ${startPhase}: no pipeline state found`);
|
|
2567
|
+
return { ok: false };
|
|
2568
|
+
}
|
|
2074
2569
|
}
|
|
2075
2570
|
}
|
|
2076
|
-
return
|
|
2571
|
+
return { ok: true, startPhase };
|
|
2077
2572
|
}
|
|
2078
|
-
|
|
2079
|
-
|
|
2573
|
+
|
|
2574
|
+
// src/commands/run/phases.ts
|
|
2575
|
+
async function runPipelinePhases(issueNumber, paths, mode, publisher, input) {
|
|
2576
|
+
const prepared = await preparePhaseRun(issueNumber, paths, mode, publisher, input);
|
|
2577
|
+
if (prepared.kind === "done") {
|
|
2578
|
+
return prepared.result;
|
|
2579
|
+
}
|
|
2580
|
+
const {
|
|
2581
|
+
tasksPath,
|
|
2582
|
+
from,
|
|
2583
|
+
continueNumbering,
|
|
2584
|
+
startUs,
|
|
2585
|
+
executeRetry,
|
|
2586
|
+
initialBranch,
|
|
2587
|
+
agentSummary,
|
|
2588
|
+
resolvedIssue,
|
|
2589
|
+
effectiveNoBranch,
|
|
2590
|
+
effectivePrReview,
|
|
2591
|
+
inQueue,
|
|
2592
|
+
finalPr,
|
|
2593
|
+
activePhases,
|
|
2594
|
+
phaseOrder,
|
|
2595
|
+
queueCommitScope,
|
|
2596
|
+
queue
|
|
2597
|
+
} = prepared;
|
|
2598
|
+
let { producedBranch, plannedExecutionBranch, branchExistedBeforeExecution } = prepared;
|
|
2599
|
+
const startResolved = await resolveStartPhase({
|
|
2600
|
+
from,
|
|
2601
|
+
activePhases,
|
|
2602
|
+
effectiveNoBranch,
|
|
2603
|
+
tasksPath
|
|
2604
|
+
});
|
|
2605
|
+
if (!startResolved.ok) {
|
|
2606
|
+
return failure(1);
|
|
2607
|
+
}
|
|
2608
|
+
const startPhase = startResolved.startPhase;
|
|
2609
|
+
const startIdx = Math.max(phaseOrder.indexOf(startPhase), 0);
|
|
2610
|
+
const branchState = {
|
|
2611
|
+
producedBranch,
|
|
2612
|
+
plannedExecutionBranch,
|
|
2613
|
+
branchExistedBeforeExecution
|
|
2614
|
+
};
|
|
2615
|
+
const { instrumentedRunners, reviewBox } = buildInstrumentedPhaseRunners({
|
|
2616
|
+
issueNumber,
|
|
2617
|
+
tasksPath,
|
|
2618
|
+
publisher,
|
|
2619
|
+
resolvedIssue,
|
|
2620
|
+
queue,
|
|
2621
|
+
finalPr,
|
|
2622
|
+
continueNumbering,
|
|
2623
|
+
startUs,
|
|
2624
|
+
executeRetry,
|
|
2625
|
+
effectiveNoBranch,
|
|
2626
|
+
effectivePrReview,
|
|
2627
|
+
initialBranch,
|
|
2628
|
+
queueCommitScope,
|
|
2629
|
+
branchState
|
|
2630
|
+
});
|
|
2631
|
+
const phaseSuffixes = phaseSuffixesFor(agentSummary);
|
|
2632
|
+
const result = await runPipelineWithRenderer({
|
|
2633
|
+
phases: phaseOrder,
|
|
2634
|
+
startIndex: startIdx,
|
|
2635
|
+
verbose: isVerbose(),
|
|
2636
|
+
runners: instrumentedRunners,
|
|
2637
|
+
tasksPath,
|
|
2638
|
+
phaseSuffixes
|
|
2639
|
+
});
|
|
2640
|
+
producedBranch = branchState.producedBranch;
|
|
2641
|
+
plannedExecutionBranch = branchState.plannedExecutionBranch;
|
|
2642
|
+
branchExistedBeforeExecution = branchState.branchExistedBeforeExecution;
|
|
2643
|
+
if (!result.success) {
|
|
2644
|
+
printError(`Phase ${result.failedPhase} failed`);
|
|
2645
|
+
return {
|
|
2646
|
+
...failure(1),
|
|
2647
|
+
failedPhase: result.failedPhase ?? null,
|
|
2648
|
+
branchName: producedBranch,
|
|
2649
|
+
elapsedSeconds: result.overallElapsedSeconds
|
|
2650
|
+
};
|
|
2651
|
+
}
|
|
2652
|
+
return finalizeSuccessfulIssueRun({
|
|
2653
|
+
issueNumber,
|
|
2654
|
+
tasksPath,
|
|
2655
|
+
resolvedIssue,
|
|
2656
|
+
review: reviewBox.outcome,
|
|
2657
|
+
inQueue,
|
|
2658
|
+
finalPr,
|
|
2659
|
+
producedBranch,
|
|
2660
|
+
effectiveNoBranch,
|
|
2661
|
+
elapsedSeconds: result.overallElapsedSeconds
|
|
2662
|
+
});
|
|
2080
2663
|
}
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
if (provider
|
|
2085
|
-
|
|
2086
|
-
await provider.close(issueNumber);
|
|
2664
|
+
function phaseSuffixesFor(agentSummary) {
|
|
2665
|
+
const phaseSuffixes = {};
|
|
2666
|
+
for (const [phase, resolved] of Object.entries(agentSummary.byPhase)) {
|
|
2667
|
+
if (resolved.provider !== agentSummary.defaultProvider) {
|
|
2668
|
+
phaseSuffixes[phase] = resolved.model ? `${resolved.provider} \xB7 ${resolved.model}` : resolved.provider;
|
|
2087
2669
|
}
|
|
2088
|
-
} catch {
|
|
2089
|
-
printWarning("Failed to close issue automatically");
|
|
2090
2670
|
}
|
|
2671
|
+
return phaseSuffixes;
|
|
2091
2672
|
}
|
|
2092
|
-
|
|
2673
|
+
|
|
2674
|
+
// src/commands/run.ts
|
|
2675
|
+
var runIssueSession2 = (issueNumber, mode, input) => runIssueSession(issueNumber, mode, input, runPipelinePhases);
|
|
2676
|
+
async function runPipeline(issue, mode, from, noBranch, prReview, options = {}) {
|
|
2677
|
+
let requested;
|
|
2093
2678
|
try {
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2679
|
+
requested = parseIssueArguments(typeof issue === "string" ? [issue] : issue);
|
|
2680
|
+
} catch (err) {
|
|
2681
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
2682
|
+
return 1;
|
|
2683
|
+
}
|
|
2684
|
+
if (options.background === true && options.detachedChild !== true) {
|
|
2685
|
+
const reason = backgroundRejection(mode);
|
|
2686
|
+
if (reason !== null) {
|
|
2687
|
+
printError(reason);
|
|
2688
|
+
return 1;
|
|
2097
2689
|
}
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2690
|
+
return detachAfterConfirm(requested, noBranch, prReview, options);
|
|
2691
|
+
}
|
|
2692
|
+
const ownership = await claimRunOwnership(requested[0], options.detachedChild === true);
|
|
2693
|
+
if (!ownership.ok) {
|
|
2694
|
+
printError(
|
|
2695
|
+
`Another issue-flow run owns this project: ${describeRunLockOwner(ownership.owner)}.`
|
|
2696
|
+
);
|
|
2697
|
+
printInfo("Wait for it to finish, or stop that process before running again.");
|
|
2698
|
+
return 1;
|
|
2699
|
+
}
|
|
2700
|
+
try {
|
|
2701
|
+
const first = await runIssueSession2(requested[0], mode, {
|
|
2702
|
+
from,
|
|
2703
|
+
noBranch,
|
|
2704
|
+
prReview,
|
|
2705
|
+
requested,
|
|
2706
|
+
runOptions: options,
|
|
2707
|
+
restartWeb: options.restartWeb,
|
|
2708
|
+
...ownership.interruptedBy === null ? {} : { interruptedBy: ownership.interruptedBy }
|
|
2709
|
+
});
|
|
2710
|
+
if (first.queue === void 0) {
|
|
2711
|
+
return first.code;
|
|
2101
2712
|
}
|
|
2102
|
-
return
|
|
2103
|
-
|
|
2104
|
-
|
|
2713
|
+
return runQueue(
|
|
2714
|
+
first.queue.plan,
|
|
2715
|
+
{ mode, from, noBranch, prReview, runOptions: options },
|
|
2716
|
+
first.queue.resolved,
|
|
2717
|
+
runIssueSession2
|
|
2718
|
+
);
|
|
2719
|
+
} finally {
|
|
2720
|
+
await ownership.release();
|
|
2105
2721
|
}
|
|
2106
2722
|
}
|
|
2107
2723
|
|
|
@@ -2110,4 +2726,4 @@ export {
|
|
|
2110
2726
|
publishIssueDetails,
|
|
2111
2727
|
runPipeline
|
|
2112
2728
|
};
|
|
2113
|
-
//# sourceMappingURL=chunk-
|
|
2729
|
+
//# sourceMappingURL=chunk-CWSQVHVV.js.map
|