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