issue-flow 0.15.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-O2PYDIFK.js → analyze-6M3KEOX7.js} +22 -22
- package/dist/{apply-SSU6QCD4.js → apply-ADB3ZK7W.js} +14 -14
- package/dist/{bench-ODFPAGW6.js → bench-FM7I7EIV.js} +23 -23
- package/dist/{chunk-BMHGRYL6.js → chunk-2SUFAE6X.js} +67 -24
- package/dist/chunk-2SUFAE6X.js.map +1 -0
- package/dist/{chunk-QRCGW2ZO.js → chunk-32JSYXEK.js} +8 -8
- package/dist/chunk-3R27MJ5R.js +25 -0
- package/dist/chunk-3R27MJ5R.js.map +1 -0
- package/dist/{chunk-MGCOQU6N.js → chunk-6YQT3SSM.js} +4 -4
- package/dist/chunk-6YQT3SSM.js.map +1 -0
- package/dist/{chunk-A35HE4V7.js → chunk-7F7LGMVO.js} +3 -3
- package/dist/{chunk-RPXDBX6R.js → chunk-A4H7P7T7.js} +102 -102
- package/dist/chunk-A4H7P7T7.js.map +1 -0
- package/dist/{chunk-2ZAM45PZ.js → chunk-ANI2NTR4.js} +9 -9
- package/dist/{chunk-ZPIVCM7Q.js → chunk-B3TSS7BT.js} +3 -3
- package/dist/chunk-C5TL7T4V.js +27 -0
- package/dist/chunk-C5TL7T4V.js.map +1 -0
- package/dist/chunk-D6ZBGJ3L.js +321 -0
- package/dist/chunk-D6ZBGJ3L.js.map +1 -0
- package/dist/{chunk-ZXPASFHG.js → chunk-DGRTJD5R.js} +3 -3
- package/dist/{chunk-6QYMSNVA.js → chunk-DUS5ZNKL.js} +476 -362
- package/dist/chunk-DUS5ZNKL.js.map +1 -0
- package/dist/{chunk-VR34TXHF.js → chunk-DV7HUC4Z.js} +7 -7
- package/dist/{chunk-6SRE4KD7.js → chunk-E6KXCV3T.js} +4 -4
- package/dist/{chunk-HGSYD57B.js → chunk-HJBF4WD2.js} +1656 -1070
- package/dist/chunk-HJBF4WD2.js.map +1 -0
- package/dist/{chunk-PCN7ZQND.js → chunk-HLAPOUEM.js} +8 -8
- package/dist/{chunk-WM3Y7Q4A.js → chunk-HY5HLFDU.js} +630 -571
- package/dist/chunk-HY5HLFDU.js.map +1 -0
- package/dist/{chunk-XDKGA6UX.js → chunk-ICM463EO.js} +64 -14
- package/dist/chunk-ICM463EO.js.map +1 -0
- package/dist/{chunk-MEFY4B7T.js → chunk-ISZ54SVE.js} +9 -9
- package/dist/{chunk-ARWNYXFL.js → chunk-KZ5EY5XX.js} +12 -12
- package/dist/{chunk-ZFIXEYUJ.js → chunk-KZXQWC7U.js} +32 -20
- package/dist/chunk-KZXQWC7U.js.map +1 -0
- package/dist/{chunk-KAHQ6F5C.js → chunk-LPWPFIIB.js} +11 -11
- package/dist/{chunk-FK4IDY6P.js → chunk-MI5TREN3.js} +9 -9
- package/dist/{chunk-UNQYMR3H.js → chunk-PPZ3K4YM.js} +2 -2
- package/dist/{chunk-F4OK7KEP.js → chunk-QMIQR7EE.js} +10 -10
- package/dist/{chunk-AYYMW33T.js → chunk-QPGEFLMY.js} +2 -2
- package/dist/chunk-RCZJQUTC.js +196 -0
- package/dist/chunk-RCZJQUTC.js.map +1 -0
- package/dist/{chunk-MROS3SKL.js → chunk-RNFUVKC3.js} +3 -3
- package/dist/{chunk-RWEXK55D.js → chunk-RYOUZ6EK.js} +2 -2
- package/dist/{chunk-6E2PEBFT.js → chunk-SGTTR4XW.js} +10 -10
- package/dist/{chunk-XQHFC62U.js → chunk-SOM4QA6C.js} +3 -3
- package/dist/{chunk-5ZKBIXET.js → chunk-UK4E2FUA.js} +132 -34
- package/dist/chunk-UK4E2FUA.js.map +1 -0
- package/dist/{chunk-6FXOU2KR.js → chunk-UWDCIFTP.js} +7 -7
- package/dist/{chunk-XG2NYDRA.js → chunk-VE3IYNDN.js} +3 -3
- package/dist/{chunk-3BWZO4YI.js → chunk-WCCZJL7W.js} +2 -2
- package/dist/{chunk-NVZMBXTE.js → chunk-XZUEFTUP.js} +6 -6
- package/dist/{chunk-TBDP6QPJ.js → chunk-YII4U7ZD.js} +10 -10
- package/dist/{chunk-TBDP6QPJ.js.map → chunk-YII4U7ZD.js.map} +1 -1
- package/dist/{chunk-4RXYFWP7.js → chunk-YUNBOUQH.js} +6 -6
- package/dist/{chunk-GSZCQUCY.js → chunk-YVPZUN6X.js} +2 -2
- package/dist/{chunk-XRLRKTQA.js → chunk-Z5O2XBWA.js} +14 -14
- package/dist/cli.js +61 -52
- package/dist/cli.js.map +1 -1
- package/dist/{config-UMP6DBJB.js → config-KGZUPAUP.js} +13 -13
- package/dist/{conventions-DKH7AE4T.js → conventions-RYN7OXJN.js} +15 -15
- package/dist/{diagnostics-GOGCLTRS.js → diagnostics-HTCWF7F4.js} +7 -7
- package/dist/execute-QL3ZQIAP.js +37 -0
- package/dist/{generate-AGBT7EHX.js → generate-CX6KNHZS.js} +22 -22
- package/dist/{git-AC542GU2.js → git-A2IP32PQ.js} +2 -2
- package/dist/init-PDVQDRS6.js +24 -0
- package/dist/{operations-JQYK2EMT.js → operations-CAKIT2BO.js} +15 -15
- package/dist/{permissions-6HL52K54.js → permissions-G4XCAESF.js} +4 -4
- package/dist/plan-PRORGS3L.js +34 -0
- package/dist/{policy-IMRXGG7X.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-KUUZ6TUL.js → ps-76L6ZAZL.js} +12 -12
- package/dist/{recorder-BJ2KIAWT.js → recorder-N7U66HCO.js} +7 -7
- package/dist/{registry-SB7XTMGE.js → registry-XLHHYLTR.js} +5 -5
- package/dist/{resume-QNLQSCR5.js → resume-VLWIE264.js} +40 -38
- package/dist/{resume-QNLQSCR5.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-T7EI3CIP.js → session-publisher-KAJQRHNZ.js} +3 -2
- package/dist/{state-manager-ZEPWTCA6.js → state-manager-UPHO2EA2.js} +4 -4
- package/dist/state-manager-UPHO2EA2.js.map +1 -0
- package/dist/{usage-QL563I7F.js → usage-U7QNGCPK.js} +12 -12
- package/dist/{web-PPSMNTQ6.js → web-HUNDHCJ4.js} +21 -18
- package/dist/{web-PPSMNTQ6.js.map → web-HUNDHCJ4.js.map} +1 -1
- package/package.json +6 -5
- package/web/AGENTS.md +15 -11
- package/web/public/app.css +51 -0
- package/web/public/app.js +325 -56
- package/web/public/index.html +6 -2
- package/dist/agent-AT64RXTA.js +0 -30
- package/dist/chunk-5ZKBIXET.js.map +0 -1
- package/dist/chunk-6QYMSNVA.js.map +0 -1
- package/dist/chunk-7Z4Y3VKM.js +0 -26
- package/dist/chunk-7Z4Y3VKM.js.map +0 -1
- package/dist/chunk-BMHGRYL6.js.map +0 -1
- package/dist/chunk-HGSYD57B.js.map +0 -1
- package/dist/chunk-MGCOQU6N.js.map +0 -1
- package/dist/chunk-RPXDBX6R.js.map +0 -1
- package/dist/chunk-WM3Y7Q4A.js.map +0 -1
- package/dist/chunk-XDKGA6UX.js.map +0 -1
- package/dist/chunk-XLVGPXNL.js +0 -180
- package/dist/chunk-XLVGPXNL.js.map +0 -1
- package/dist/chunk-ZFIXEYUJ.js.map +0 -1
- package/dist/execute-E3BRMDAQ.js +0 -35
- package/dist/init-LLBVJNPE.js +0 -24
- package/dist/plan-HSLWDJEZ.js +0 -34
- package/dist/pr-6TCZXG6K.js +0 -39
- package/dist/pr-review-6MWJO2NP.js +0 -32
- package/dist/prd-D6YDYAFX.js +0 -33
- package/dist/review-3KJ4BH2V.js +0 -35
- package/dist/routing-TN7GLMGU.js +0 -94
- package/dist/routing-TN7GLMGU.js.map +0 -1
- package/dist/run-5EHIFF2V.js +0 -54
- /package/dist/{agent-AT64RXTA.js.map → agent-TB6W7PT7.js.map} +0 -0
- /package/dist/{analyze-O2PYDIFK.js.map → analyze-6M3KEOX7.js.map} +0 -0
- /package/dist/{apply-SSU6QCD4.js.map → apply-ADB3ZK7W.js.map} +0 -0
- /package/dist/{bench-ODFPAGW6.js.map → bench-FM7I7EIV.js.map} +0 -0
- /package/dist/{chunk-QRCGW2ZO.js.map → chunk-32JSYXEK.js.map} +0 -0
- /package/dist/{chunk-A35HE4V7.js.map → chunk-7F7LGMVO.js.map} +0 -0
- /package/dist/{chunk-2ZAM45PZ.js.map → chunk-ANI2NTR4.js.map} +0 -0
- /package/dist/{chunk-ZPIVCM7Q.js.map → chunk-B3TSS7BT.js.map} +0 -0
- /package/dist/{chunk-ZXPASFHG.js.map → chunk-DGRTJD5R.js.map} +0 -0
- /package/dist/{chunk-VR34TXHF.js.map → chunk-DV7HUC4Z.js.map} +0 -0
- /package/dist/{chunk-6SRE4KD7.js.map → chunk-E6KXCV3T.js.map} +0 -0
- /package/dist/{chunk-PCN7ZQND.js.map → chunk-HLAPOUEM.js.map} +0 -0
- /package/dist/{chunk-MEFY4B7T.js.map → chunk-ISZ54SVE.js.map} +0 -0
- /package/dist/{chunk-ARWNYXFL.js.map → chunk-KZ5EY5XX.js.map} +0 -0
- /package/dist/{chunk-KAHQ6F5C.js.map → chunk-LPWPFIIB.js.map} +0 -0
- /package/dist/{chunk-FK4IDY6P.js.map → chunk-MI5TREN3.js.map} +0 -0
- /package/dist/{chunk-UNQYMR3H.js.map → chunk-PPZ3K4YM.js.map} +0 -0
- /package/dist/{chunk-F4OK7KEP.js.map → chunk-QMIQR7EE.js.map} +0 -0
- /package/dist/{chunk-AYYMW33T.js.map → chunk-QPGEFLMY.js.map} +0 -0
- /package/dist/{chunk-MROS3SKL.js.map → chunk-RNFUVKC3.js.map} +0 -0
- /package/dist/{chunk-RWEXK55D.js.map → chunk-RYOUZ6EK.js.map} +0 -0
- /package/dist/{chunk-6E2PEBFT.js.map → chunk-SGTTR4XW.js.map} +0 -0
- /package/dist/{chunk-XQHFC62U.js.map → chunk-SOM4QA6C.js.map} +0 -0
- /package/dist/{chunk-6FXOU2KR.js.map → chunk-UWDCIFTP.js.map} +0 -0
- /package/dist/{chunk-XG2NYDRA.js.map → chunk-VE3IYNDN.js.map} +0 -0
- /package/dist/{chunk-3BWZO4YI.js.map → chunk-WCCZJL7W.js.map} +0 -0
- /package/dist/{chunk-NVZMBXTE.js.map → chunk-XZUEFTUP.js.map} +0 -0
- /package/dist/{chunk-4RXYFWP7.js.map → chunk-YUNBOUQH.js.map} +0 -0
- /package/dist/{chunk-GSZCQUCY.js.map → chunk-YVPZUN6X.js.map} +0 -0
- /package/dist/{chunk-XRLRKTQA.js.map → chunk-Z5O2XBWA.js.map} +0 -0
- /package/dist/{config-UMP6DBJB.js.map → config-KGZUPAUP.js.map} +0 -0
- /package/dist/{conventions-DKH7AE4T.js.map → conventions-RYN7OXJN.js.map} +0 -0
- /package/dist/{diagnostics-GOGCLTRS.js.map → diagnostics-HTCWF7F4.js.map} +0 -0
- /package/dist/{execute-E3BRMDAQ.js.map → execute-QL3ZQIAP.js.map} +0 -0
- /package/dist/{generate-AGBT7EHX.js.map → generate-CX6KNHZS.js.map} +0 -0
- /package/dist/{git-AC542GU2.js.map → git-A2IP32PQ.js.map} +0 -0
- /package/dist/{init-LLBVJNPE.js.map → init-PDVQDRS6.js.map} +0 -0
- /package/dist/{operations-JQYK2EMT.js.map → operations-CAKIT2BO.js.map} +0 -0
- /package/dist/{permissions-6HL52K54.js.map → permissions-G4XCAESF.js.map} +0 -0
- /package/dist/{plan-HSLWDJEZ.js.map → plan-PRORGS3L.js.map} +0 -0
- /package/dist/{policy-IMRXGG7X.js.map → policy-WX6O2JID.js.map} +0 -0
- /package/dist/{pr-6TCZXG6K.js.map → pr-27RMQI5F.js.map} +0 -0
- /package/dist/{pr-review-6MWJO2NP.js.map → pr-review-VNESMAMT.js.map} +0 -0
- /package/dist/{prd-D6YDYAFX.js.map → prd-ZNC4T5BS.js.map} +0 -0
- /package/dist/{ps-KUUZ6TUL.js.map → ps-76L6ZAZL.js.map} +0 -0
- /package/dist/{recorder-BJ2KIAWT.js.map → recorder-N7U66HCO.js.map} +0 -0
- /package/dist/{registry-SB7XTMGE.js.map → registry-XLHHYLTR.js.map} +0 -0
- /package/dist/{review-3KJ4BH2V.js.map → review-7CREJDKB.js.map} +0 -0
- /package/dist/{run-5EHIFF2V.js.map → routing-VCDYHSXA.js.map} +0 -0
- /package/dist/{session-publisher-T7EI3CIP.js.map → run-3MPORLF5.js.map} +0 -0
- /package/dist/{state-manager-ZEPWTCA6.js.map → session-publisher-KAJQRHNZ.js.map} +0 -0
- /package/dist/{usage-QL563I7F.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,62 +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-KAHQ6F5C.js";
|
|
59
|
-
import {
|
|
60
|
-
listPullRequests,
|
|
61
|
-
publishGitState
|
|
62
|
-
} from "./chunk-4RXYFWP7.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";
|
|
86
|
+
import {
|
|
87
|
+
getPackageVersion
|
|
88
|
+
} from "./chunk-C5TL7T4V.js";
|
|
83
89
|
import {
|
|
84
90
|
describeRunAgents,
|
|
85
91
|
getActiveResilienceConfig,
|
|
92
|
+
getAgentCliOverrides,
|
|
93
|
+
hasExplicitAgentSelection,
|
|
86
94
|
initResilienceConfig,
|
|
95
|
+
loadAgentConfig,
|
|
87
96
|
loadIssuesConfig,
|
|
97
|
+
loadRoutingConfig,
|
|
88
98
|
loadWebConfig,
|
|
89
99
|
onShutdown
|
|
90
|
-
} from "./chunk-
|
|
100
|
+
} from "./chunk-HY5HLFDU.js";
|
|
91
101
|
import {
|
|
92
102
|
resolveIssuePaths,
|
|
93
103
|
resolveProjectPaths,
|
|
94
104
|
resolveQueuePaths
|
|
95
|
-
} from "./chunk-
|
|
105
|
+
} from "./chunk-DV7HUC4Z.js";
|
|
96
106
|
import {
|
|
97
107
|
formatDuration,
|
|
98
108
|
getIcons,
|
|
@@ -107,18 +117,12 @@ import {
|
|
|
107
117
|
setOutputCallback,
|
|
108
118
|
setStoryStageCallback,
|
|
109
119
|
setStoryUpdateCallback
|
|
110
|
-
} from "./chunk-
|
|
111
|
-
import {
|
|
112
|
-
FilePublisher,
|
|
113
|
-
MemoryPublisher,
|
|
114
|
-
getSessionPublisher,
|
|
115
|
-
setSessionPublisher
|
|
116
|
-
} from "./chunk-6QYMSNVA.js";
|
|
120
|
+
} from "./chunk-XZUEFTUP.js";
|
|
117
121
|
import {
|
|
118
122
|
bindDiagnosticContext,
|
|
119
123
|
flushDiagnostics,
|
|
120
124
|
writeDiagnostic
|
|
121
|
-
} from "./chunk-
|
|
125
|
+
} from "./chunk-QPGEFLMY.js";
|
|
122
126
|
import {
|
|
123
127
|
committedStoryIds,
|
|
124
128
|
describePreflight,
|
|
@@ -129,22 +133,22 @@ import {
|
|
|
129
133
|
localBranchExists,
|
|
130
134
|
preflightRepository,
|
|
131
135
|
run
|
|
132
|
-
} from "./chunk-
|
|
136
|
+
} from "./chunk-YVPZUN6X.js";
|
|
137
|
+
import {
|
|
138
|
+
FilePublisher,
|
|
139
|
+
MemoryPublisher,
|
|
140
|
+
getSessionPublisher,
|
|
141
|
+
setSessionPublisher
|
|
142
|
+
} from "./chunk-DUS5ZNKL.js";
|
|
133
143
|
import {
|
|
134
144
|
isoNow,
|
|
135
145
|
loadTaskPlan,
|
|
136
146
|
saveTaskPlan
|
|
137
|
-
} from "./chunk-
|
|
147
|
+
} from "./chunk-SOM4QA6C.js";
|
|
138
148
|
import {
|
|
139
149
|
acquireRunLock,
|
|
140
150
|
describeRunLockOwner
|
|
141
|
-
} from "./chunk-
|
|
142
|
-
|
|
143
|
-
// src/commands/run.ts
|
|
144
|
-
import { randomUUID } from "crypto";
|
|
145
|
-
import { mkdir as mkdir2, readFile, writeFile } from "fs/promises";
|
|
146
|
-
import { join } from "path";
|
|
147
|
-
import { execa } from "execa";
|
|
151
|
+
} from "./chunk-ICM463EO.js";
|
|
148
152
|
|
|
149
153
|
// src/execution/detach.ts
|
|
150
154
|
import { spawn } from "child_process";
|
|
@@ -216,6 +220,42 @@ async function spawnDetachedRun(input) {
|
|
|
216
220
|
}
|
|
217
221
|
}
|
|
218
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
|
+
|
|
219
259
|
// src/execution/confirm.ts
|
|
220
260
|
import { createInterface } from "readline";
|
|
221
261
|
var MAX_PROMPT_ATTEMPTS = 3;
|
|
@@ -602,133 +642,527 @@ async function planQueue(input) {
|
|
|
602
642
|
return { kind: "queue", plan, resumed: false };
|
|
603
643
|
}
|
|
604
644
|
|
|
605
|
-
// src/
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
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;
|
|
610
652
|
}
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if (raw.trim() !== "" || value.includes(",")) {
|
|
621
|
-
throw new IssueArgumentError(
|
|
622
|
-
`Invalid issue list '${value}': it has an empty entry. Use '42,43' or '42 43'.`
|
|
623
|
-
);
|
|
624
|
-
}
|
|
625
|
-
continue;
|
|
626
|
-
}
|
|
627
|
-
if (INVALID_ID.test(id) || id === "." || id === "..") {
|
|
628
|
-
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`);
|
|
629
662
|
}
|
|
630
|
-
|
|
631
|
-
seen.add(id);
|
|
632
|
-
ids.push(id);
|
|
663
|
+
} catch {
|
|
633
664
|
}
|
|
634
665
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
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
|
+
};
|
|
639
685
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
const story = activeStory(snapshot);
|
|
648
|
-
if (story === void 0 && snapshot.currentActivity === null) return [];
|
|
649
|
-
const lines = [];
|
|
650
|
-
if (story !== void 0) {
|
|
651
|
-
const duration = story.durationSeconds !== null && story.durationSeconds !== void 0 ? ` ${formatDuration(story.durationSeconds)}` : "";
|
|
652
|
-
lines.push(` ${icons.pending} ${story.id} ${stripMarkdown(story.title)}${duration}`);
|
|
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;
|
|
653
693
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
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
|
+
}
|
|
658
706
|
}
|
|
659
|
-
return
|
|
707
|
+
return pullRequest;
|
|
660
708
|
}
|
|
661
|
-
function
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
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");
|
|
666
718
|
}
|
|
667
|
-
return snapshot.stories.find((story) => story.stage === "executing");
|
|
668
719
|
}
|
|
669
720
|
|
|
670
|
-
// src/
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
return PHASE_LABELS[phase] ?? phase.charAt(0).toUpperCase() + phase.slice(1);
|
|
681
|
-
}
|
|
682
|
-
function selectRenderer(verbose) {
|
|
683
|
-
const isTTY = !!process.stdout.isTTY;
|
|
684
|
-
const isCI = !!process.env.CI;
|
|
685
|
-
if (!isTTY || isCI) {
|
|
686
|
-
return "simple";
|
|
687
|
-
}
|
|
688
|
-
return verbose ? "verbose" : "default";
|
|
689
|
-
}
|
|
690
|
-
function storySubtaskTitle(story, executing) {
|
|
691
|
-
const base = `${story.id}: ${story.title}`;
|
|
692
|
-
return executing ? `${base} \u2192 Executando...` : base;
|
|
693
|
-
}
|
|
694
|
-
function createActiveStoryTracker(stories, storyTasks) {
|
|
695
|
-
let activeStoryId;
|
|
696
|
-
const retitle = (storyId, executing) => {
|
|
697
|
-
const task = storyTasks.get(storyId);
|
|
698
|
-
if (!task) return;
|
|
699
|
-
task.title = storySubtaskTitle(
|
|
700
|
-
stories.find((s) => s.id === storyId) ?? { id: storyId, title: "" },
|
|
701
|
-
executing
|
|
702
|
-
);
|
|
703
|
-
};
|
|
704
|
-
return {
|
|
705
|
-
setActive(storyId) {
|
|
706
|
-
if (activeStoryId !== void 0 && activeStoryId !== storyId) {
|
|
707
|
-
retitle(activeStoryId, false);
|
|
708
|
-
}
|
|
709
|
-
activeStoryId = storyId;
|
|
710
|
-
if (storyId !== void 0) retitle(storyId, true);
|
|
711
|
-
},
|
|
712
|
-
clear() {
|
|
713
|
-
if (activeStoryId === void 0) return;
|
|
714
|
-
retitle(activeStoryId, false);
|
|
715
|
-
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()
|
|
716
731
|
}
|
|
717
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 };
|
|
718
774
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
function
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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);
|
|
732
1166
|
} catch {
|
|
733
1167
|
setOutputCallback((line) => {
|
|
734
1168
|
task.output = line;
|
|
@@ -904,7 +1338,11 @@ async function runPipelineWithRenderer(options) {
|
|
|
904
1338
|
};
|
|
905
1339
|
}
|
|
906
1340
|
|
|
907
|
-
// 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";
|
|
908
1346
|
function toIssueNumber(id) {
|
|
909
1347
|
const parsed = Number.parseInt(id, 10);
|
|
910
1348
|
return Number.isNaN(parsed) ? null : parsed;
|
|
@@ -957,17 +1395,6 @@ function publishIssueDetails(publisher, issue, at) {
|
|
|
957
1395
|
state: issue.state
|
|
958
1396
|
});
|
|
959
1397
|
}
|
|
960
|
-
var RUNNABLE_PHASES = ["prd", "plan", "execute", "review", "pr"];
|
|
961
|
-
var RUNNABLE_PHASES_NO_BRANCH = ["prd", "plan", "execute", "review"];
|
|
962
|
-
var RUNNABLE_PHASES_WITH_PR_REVIEW = [...RUNNABLE_PHASES, "pr-review"];
|
|
963
|
-
var QUEUE_PR_PHASES = ["init", "pr"];
|
|
964
|
-
var QUEUE_PR_PHASES_WITH_REVIEW = [
|
|
965
|
-
"init",
|
|
966
|
-
"pr",
|
|
967
|
-
"pr-review"
|
|
968
|
-
];
|
|
969
|
-
var RUNNABLE_QUEUE_PR_PHASES = ["pr"];
|
|
970
|
-
var RUNNABLE_QUEUE_PR_PHASES_WITH_REVIEW = ["pr", "pr-review"];
|
|
971
1398
|
function verificationForSummary(value) {
|
|
972
1399
|
if (value === null || value.verdict === null) return null;
|
|
973
1400
|
return { verdict: value.verdict, level: value.level };
|
|
@@ -994,200 +1421,608 @@ async function readPrReviewOutcome(issue, tasksPath, requestedChanges) {
|
|
|
994
1421
|
}
|
|
995
1422
|
return outcome;
|
|
996
1423
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
}
|
|
1006
|
-
if (options.background === true && options.detachedChild !== true) {
|
|
1007
|
-
const reason = backgroundRejection(mode);
|
|
1008
|
-
if (reason !== null) {
|
|
1009
|
-
printError(reason);
|
|
1010
|
-
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}`);
|
|
1011
1433
|
}
|
|
1012
|
-
return detachAfterConfirm(requested, noBranch, prReview, options);
|
|
1013
|
-
}
|
|
1014
|
-
const ownership = await claimRunOwnership(requested[0], options.detachedChild === true);
|
|
1015
|
-
if (!ownership.ok) {
|
|
1016
|
-
printError(
|
|
1017
|
-
`Another issue-flow run owns this project: ${describeRunLockOwner(ownership.owner)}.`
|
|
1018
|
-
);
|
|
1019
|
-
printInfo("Wait for it to finish, or stop that process before running again.");
|
|
1020
|
-
return 1;
|
|
1021
1434
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
prReview,
|
|
1027
|
-
requested,
|
|
1028
|
-
runOptions: options,
|
|
1029
|
-
restartWeb: options.restartWeb,
|
|
1030
|
-
...ownership.interruptedBy === null ? {} : { interruptedBy: ownership.interruptedBy }
|
|
1031
|
-
});
|
|
1032
|
-
if (first.queue === void 0) {
|
|
1033
|
-
return first.code;
|
|
1034
|
-
}
|
|
1035
|
-
return runQueue(
|
|
1036
|
-
first.queue.plan,
|
|
1037
|
-
{ mode, from, noBranch, prReview, runOptions: options },
|
|
1038
|
-
first.queue.resolved
|
|
1039
|
-
);
|
|
1040
|
-
} finally {
|
|
1041
|
-
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);
|
|
1042
1439
|
}
|
|
1043
1440
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
if (!
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
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
|
+
}
|
|
1051
1455
|
}
|
|
1052
|
-
|
|
1456
|
+
return prUrl;
|
|
1053
1457
|
}
|
|
1054
|
-
async function
|
|
1055
|
-
|
|
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";
|
|
1056
1472
|
try {
|
|
1057
|
-
|
|
1473
|
+
const proc = await execa("git", ["branch", "--show-current"], { reject: false });
|
|
1474
|
+
branchName = proc.stdout?.toString().trim() ?? "unknown";
|
|
1058
1475
|
} catch {
|
|
1059
|
-
return { ok: true, interruptedBy: null, release: async () => {
|
|
1060
|
-
} };
|
|
1061
1476
|
}
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
return {
|
|
1065
|
-
ok: true,
|
|
1066
|
-
interruptedBy: result.handle.reclaimedFrom,
|
|
1067
|
-
release: () => result.handle.release()
|
|
1068
|
-
};
|
|
1069
|
-
}
|
|
1070
|
-
async function runIssueSession(issueNumber, mode, input) {
|
|
1071
|
-
resetAgentInvocationState();
|
|
1072
|
-
const paths = await resolveIssuePaths(issueNumber);
|
|
1477
|
+
let storyCount = 0;
|
|
1478
|
+
let planPrUrl = null;
|
|
1073
1479
|
try {
|
|
1074
|
-
const
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
harness: null,
|
|
1084
|
-
model: null
|
|
1085
|
-
});
|
|
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);
|
|
1086
1489
|
} catch {
|
|
1087
1490
|
}
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
if (persistSnapshot) {
|
|
1101
|
-
surfaces.push(
|
|
1102
|
-
new FilePublisher(paths.sessionFile, {
|
|
1103
|
-
logLimit: webConfig.logLimit,
|
|
1104
|
-
includeLogs: webConfig.includeLogs
|
|
1105
|
-
})
|
|
1106
|
-
);
|
|
1107
|
-
}
|
|
1108
|
-
if (journalEnabled) {
|
|
1109
|
-
surfaces.push(
|
|
1110
|
-
new JournalPublisher(paths.eventsFile, paths.rotatedEventsFile, {
|
|
1111
|
-
logLimit: webConfig.logLimit,
|
|
1112
|
-
includeLogs: webConfig.includeLogs,
|
|
1113
|
-
...resilience.journal?.maxFileBytes === void 0 ? {} : { maxFileBytes: resilience.journal.maxFileBytes }
|
|
1114
|
-
})
|
|
1115
|
-
);
|
|
1116
|
-
}
|
|
1117
|
-
const publisher = surfaces.length === 0 ? new MemoryPublisher() : surfaces.length === 1 ? surfaces[0] : new MultiPublisher(surfaces);
|
|
1118
|
-
setSessionPublisher(publisher);
|
|
1119
|
-
if (input.interruptedBy !== void 0) {
|
|
1120
|
-
const previous = input.interruptedBy;
|
|
1121
|
-
publisher.publish({
|
|
1122
|
-
type: "log",
|
|
1123
|
-
at: isoNow(),
|
|
1124
|
-
level: "warn",
|
|
1125
|
-
message: `Previous run interrupted: ${describeRunLockOwner(previous)}. Its lock was stale and has been taken over.`
|
|
1126
|
-
});
|
|
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
|
+
};
|
|
1127
1503
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
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
|
+
};
|
|
1630
|
+
}
|
|
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."
|
|
1137
1648
|
);
|
|
1649
|
+
effectivePrReview = false;
|
|
1138
1650
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1651
|
+
return {
|
|
1652
|
+
effectiveNoBranch,
|
|
1653
|
+
effectivePrReview,
|
|
1654
|
+
warnings,
|
|
1655
|
+
planIssueUrl: input.persisted.issueUrl,
|
|
1656
|
+
planBranch: input.persisted.branchName,
|
|
1657
|
+
planStories: [...input.persisted.userStories]
|
|
1145
1658
|
};
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
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" });
|
|
1150
1679
|
publisher.publish({
|
|
1151
|
-
type: "
|
|
1680
|
+
type: "phase:end",
|
|
1152
1681
|
at: isoNow(),
|
|
1153
|
-
|
|
1154
|
-
|
|
1682
|
+
phase: "init",
|
|
1683
|
+
success: false,
|
|
1684
|
+
error: "Prerequisites not met"
|
|
1155
1685
|
});
|
|
1156
|
-
|
|
1686
|
+
printError("Prerequisites not met. Fix the issues above and try again.");
|
|
1687
|
+
return { kind: "done", result: failure(1) };
|
|
1157
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
|
|
1158
1705
|
});
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
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
|
|
1164
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;
|
|
1165
1810
|
try {
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
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 {
|
|
1169
1841
|
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
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;
|
|
1180
1889
|
}
|
|
1181
|
-
await publisher.close();
|
|
1182
|
-
writeDiagnostic({
|
|
1183
|
-
level: result.code === 0 ? "info" : "error",
|
|
1184
|
-
message: `Issue Flow session ${result.code === 0 ? "completed" : "failed"}`,
|
|
1185
|
-
context: { code: result.code, failedPhase: result.failedPhase }
|
|
1186
|
-
});
|
|
1187
|
-
await flushDiagnostics();
|
|
1188
|
-
setSessionPublisher(void 0);
|
|
1189
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
|
+
};
|
|
1190
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";
|
|
1191
2026
|
async function reportIfOversized(issueNumber, paths, result) {
|
|
1192
2027
|
try {
|
|
1193
2028
|
const journal = `${await readFileIfPresent(paths.rotatedEventsFile)}${await readFileIfPresent(paths.eventsFile)}`;
|
|
@@ -1246,7 +2081,7 @@ async function maybeCreateSubIssues(issueNumber, plan, reportFile) {
|
|
|
1246
2081
|
printInfo("Nothing left to split: no pending story.");
|
|
1247
2082
|
return;
|
|
1248
2083
|
}
|
|
1249
|
-
const { runGenerate } = await import("./generate-
|
|
2084
|
+
const { runGenerate } = await import("./generate-CX6KNHZS.js");
|
|
1250
2085
|
for (const proposal of proposals) {
|
|
1251
2086
|
const instruction = [
|
|
1252
2087
|
`Create a sub-issue of #${issueNumber}: ${proposal.title}.`,
|
|
@@ -1280,365 +2115,306 @@ async function readFileIfPresent(path) {
|
|
|
1280
2115
|
return "";
|
|
1281
2116
|
}
|
|
1282
2117
|
}
|
|
1283
|
-
|
|
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;
|
|
1284
2132
|
try {
|
|
1285
|
-
|
|
1286
|
-
await saveTaskPlan(tasksFile, {
|
|
1287
|
-
...plan,
|
|
1288
|
-
runState: {
|
|
1289
|
-
...plan.runState ?? {
|
|
1290
|
-
currentPhase: null,
|
|
1291
|
-
attempt: 0,
|
|
1292
|
-
lastHeartbeatAt: null,
|
|
1293
|
-
blockedReason: null,
|
|
1294
|
-
owner: null
|
|
1295
|
-
},
|
|
1296
|
-
status: "paused",
|
|
1297
|
-
lastHeartbeatAt: isoNow()
|
|
1298
|
-
}
|
|
1299
|
-
});
|
|
2133
|
+
lockFile = (await resolveProjectPaths()).runLockFile;
|
|
1300
2134
|
} catch {
|
|
1301
|
-
|
|
2135
|
+
return { ok: true, interruptedBy: null, release: async () => {
|
|
2136
|
+
} };
|
|
1302
2137
|
}
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
]);
|
|
1327
|
-
let publishedIssueNumber = toIssueNumber(issueNumber);
|
|
1328
|
-
const publishSessionStart = (phases, at, info) => {
|
|
1329
|
-
publisher.publish({
|
|
1330
|
-
type: "session:start",
|
|
1331
|
-
at,
|
|
1332
|
-
sessionId,
|
|
1333
|
-
issueNumber: publishedIssueNumber,
|
|
1334
|
-
issueUrl: info?.issueUrl,
|
|
1335
|
-
branch: info?.branch,
|
|
1336
|
-
branchCreated: info?.branchCreated,
|
|
1337
|
-
startCommit: info?.startCommit,
|
|
1338
|
-
phases: [...phases],
|
|
1339
|
-
configuration: configurationSnapshot,
|
|
1340
|
-
environment: {
|
|
1341
|
-
node: process.version,
|
|
1342
|
-
platform: process.platform,
|
|
1343
|
-
agent: agentSummary.defaultProvider,
|
|
1344
|
-
model: agentSummary.defaultModel
|
|
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()
|
|
1345
2161
|
}
|
|
1346
2162
|
});
|
|
1347
|
-
}
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
const
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
)
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
const
|
|
1378
|
-
const
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
}
|
|
1383
|
-
const initCode = await runInit(issuesConfig.preferredProvider, { compact: !isVerbose() });
|
|
1384
|
-
if (initCode !== 0) {
|
|
1385
|
-
publishSessionStart(PIPELINE_PHASES, sessionStartedAt);
|
|
1386
|
-
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);
|
|
1387
2198
|
publisher.publish({
|
|
1388
|
-
type: "
|
|
2199
|
+
type: "log",
|
|
1389
2200
|
at: isoNow(),
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
error: "Prerequisites not met"
|
|
2201
|
+
level: "warn",
|
|
2202
|
+
message: `Interrupted during issue #${issueNumber}. A checkpoint was saved; resume with \`issue-flow resume ${issueNumber}\`.`
|
|
1393
2203
|
});
|
|
1394
|
-
|
|
1395
|
-
return failure(1);
|
|
2204
|
+
publisher.publish({ type: "session:end", at: isoNow(), status: "failed" });
|
|
1396
2205
|
}
|
|
1397
|
-
}
|
|
1398
|
-
const resolution = await resolveCommandIssue(issueNumber, queue?.resolved, {
|
|
1399
|
-
config: issuesConfig
|
|
1400
2206
|
});
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
publishedIssueNumber = resolvedIssue.issue.number;
|
|
1406
|
-
if (queue === void 0) {
|
|
1407
|
-
const decision = await decideQueue({
|
|
1408
|
-
requested: input.requested ?? [issueNumber],
|
|
1409
|
-
resolved: resolvedIssue,
|
|
1410
|
-
noBranch: noBranch ?? false,
|
|
1411
|
-
prReview: prReview ?? false,
|
|
1412
|
-
runOptions: input.runOptions ?? {}
|
|
1413
|
-
});
|
|
1414
|
-
if (decision.kind === "stop") {
|
|
1415
|
-
return failure(decision.code);
|
|
1416
|
-
}
|
|
1417
|
-
if (decision.kind === "queue") {
|
|
1418
|
-
return {
|
|
1419
|
-
...failure(0),
|
|
1420
|
-
queue: { plan: decision.plan, resumed: decision.resumed, resolved: resolvedIssue }
|
|
1421
|
-
};
|
|
2207
|
+
const releaseClose = onShutdown({
|
|
2208
|
+
phase: "close",
|
|
2209
|
+
run: async () => {
|
|
2210
|
+
await publisher.close();
|
|
1422
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
|
+
});
|
|
1423
2225
|
}
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
printWarning(
|
|
1443
|
-
"This pipeline was started without --no-branch. Ignoring current flag; using persisted mode."
|
|
1444
|
-
);
|
|
1445
|
-
}
|
|
1446
|
-
}
|
|
1447
|
-
effectiveNoBranch = persistedNoBranch;
|
|
1448
|
-
} 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
|
+
});
|
|
1449
2244
|
}
|
|
1450
|
-
if (
|
|
1451
|
-
|
|
1452
|
-
|
|
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 }
|
|
1453
2254
|
);
|
|
1454
|
-
effectivePrReview = false;
|
|
1455
2255
|
}
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
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 {
|
|
1465
2274
|
}
|
|
1466
|
-
const
|
|
1467
|
-
const
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
const queueCommitScope = queue === void 0 ? void 0 : `issue-${issueNumber}`;
|
|
1471
|
-
let producedBranch = null;
|
|
1472
|
-
let plannedExecutionBranch = planBranch ?? null;
|
|
1473
|
-
let branchExistedBeforeExecution = effectiveNoBranch || planBranch === void 0 || planBranch === null ? null : await localBranchExists(planBranch).catch(() => null);
|
|
1474
|
-
if (queue !== void 0 && finalPr === void 0 && !effectiveNoBranch) {
|
|
1475
|
-
producedBranch = await adoptQueueBranch(tasksPath, queue.plan.branchName);
|
|
1476
|
-
if (producedBranch !== null) {
|
|
1477
|
-
planBranch = producedBranch;
|
|
1478
|
-
}
|
|
2275
|
+
const resilience = await initResilienceConfig();
|
|
2276
|
+
const configuredInactivity = resilience.watchdog?.inactivityTimeoutMs;
|
|
2277
|
+
if (configuredInactivity !== void 0 && getInactivityTimeout() === void 0) {
|
|
2278
|
+
setInactivityTimeout(configuredInactivity);
|
|
1479
2279
|
}
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
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
|
|
1485
2289
|
});
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
const nextPhase = mgr.getNextPhase();
|
|
1510
|
-
if (nextPhase && nextPhase !== "init") {
|
|
1511
|
-
startPhase = nextPhase;
|
|
1512
|
-
printInfo(`Resuming from phase: ${startPhase}`);
|
|
1513
|
-
}
|
|
1514
|
-
} 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);
|
|
1515
2313
|
}
|
|
2314
|
+
return result;
|
|
2315
|
+
} finally {
|
|
2316
|
+
await closeIssueSession({ releaseCheckpoint, releaseClose, result, publisher });
|
|
1516
2317
|
}
|
|
1517
|
-
|
|
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
|
+
)();
|
|
1518
2344
|
try {
|
|
1519
2345
|
const plan = await loadTaskPlan(tasksPath);
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
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
|
+
});
|
|
1529
2356
|
}
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
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);
|
|
1538
2369
|
}
|
|
1539
2370
|
};
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
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 });
|
|
1551
2387
|
try {
|
|
1552
2388
|
const plan = await loadTaskPlan(tasksPath);
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
branchExistedBeforeExecution = await localBranchExists(plan.branchName).catch(() => null);
|
|
1556
|
-
publisher.publish({
|
|
1557
|
-
type: "git:update",
|
|
1558
|
-
at: isoNow(),
|
|
1559
|
-
branchCreated: branchExistedBeforeExecution === true ? false : null
|
|
1560
|
-
});
|
|
1561
|
-
}
|
|
1562
|
-
publishStorySeed(publisher, plan.userStories, isoNow());
|
|
1563
|
-
if (effectiveNoBranch || effectivePrReview) {
|
|
1564
|
-
if (effectiveNoBranch) plan.noBranch = true;
|
|
1565
|
-
if (effectivePrReview) {
|
|
1566
|
-
plan.prReview = { ...plan.prReview, enabled: true, rounds: plan.prReview?.rounds ?? 0 };
|
|
1567
|
-
}
|
|
1568
|
-
await saveTaskPlan(tasksPath, plan);
|
|
1569
|
-
}
|
|
2389
|
+
plan.correctionCycle = cycle;
|
|
2390
|
+
await saveTaskPlan(tasksPath, plan);
|
|
1570
2391
|
} catch {
|
|
1571
2392
|
}
|
|
1572
|
-
|
|
1573
|
-
producedBranch = await adoptQueueBranch(tasksPath, queue.plan.branchName);
|
|
1574
|
-
}
|
|
1575
|
-
},
|
|
1576
|
-
execute: makeRunner(
|
|
1577
|
-
() => runExecute(void 0, {
|
|
2393
|
+
const execCode = await runExecute(void 0, {
|
|
1578
2394
|
issue: issueNumber,
|
|
1579
2395
|
commitScope: queueCommitScope,
|
|
1580
2396
|
...executeRetry
|
|
1581
|
-
})
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
review: async () => {
|
|
1585
|
-
let maxCycles = 3;
|
|
1586
|
-
try {
|
|
1587
|
-
const plan = await loadTaskPlan(tasksPath);
|
|
1588
|
-
maxCycles = plan.maxCorrectionCycles;
|
|
1589
|
-
} catch {
|
|
1590
|
-
}
|
|
1591
|
-
let code = await runReview(issueNumber, resolvedIssue);
|
|
1592
|
-
let cycle = 0;
|
|
1593
|
-
while (code !== 0 && cycle < maxCycles) {
|
|
1594
|
-
cycle++;
|
|
1595
|
-
printWarning(`Review failed. Starting correction cycle ${cycle}/${maxCycles}...`);
|
|
1596
|
-
publisher.publish({ type: "correction:cycle", at: isoNow(), cycle, maxCycles });
|
|
1597
|
-
try {
|
|
1598
|
-
const plan = await loadTaskPlan(tasksPath);
|
|
1599
|
-
plan.correctionCycle = cycle;
|
|
1600
|
-
await saveTaskPlan(tasksPath, plan);
|
|
1601
|
-
} catch {
|
|
1602
|
-
}
|
|
1603
|
-
const execCode = await runExecute(void 0, {
|
|
1604
|
-
issue: issueNumber,
|
|
1605
|
-
commitScope: queueCommitScope,
|
|
1606
|
-
...executeRetry
|
|
1607
|
-
});
|
|
1608
|
-
if (execCode !== 0) {
|
|
1609
|
-
throw new Error("Correction execution failed");
|
|
1610
|
-
}
|
|
1611
|
-
code = await runReview(issueNumber, resolvedIssue);
|
|
1612
|
-
}
|
|
1613
|
-
if (code !== 0) {
|
|
1614
|
-
throw new Error(`Review failed after ${maxCycles} correction cycles`);
|
|
2397
|
+
});
|
|
2398
|
+
if (execCode !== 0) {
|
|
2399
|
+
throw new Error("Correction execution failed");
|
|
1615
2400
|
}
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
"pr"
|
|
1622
|
-
)
|
|
2401
|
+
code = await runReview(issueNumber, resolvedIssue);
|
|
2402
|
+
}
|
|
2403
|
+
if (code !== 0) {
|
|
2404
|
+
throw new Error(`Review failed after ${maxCycles} correction cycles`);
|
|
2405
|
+
}
|
|
1623
2406
|
};
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
if (code === 1) {
|
|
1629
|
-
throw new Error("Phase pr-review failed with exit code 1");
|
|
1630
|
-
}
|
|
1631
|
-
reviewBox.outcome = await readPrReviewOutcome(issueNumber, tasksPath, code === 2);
|
|
1632
|
-
};
|
|
1633
|
-
}
|
|
1634
|
-
const instrumentedRunners = Object.fromEntries(
|
|
2407
|
+
}
|
|
2408
|
+
function instrumentPhaseRunners(runners, input) {
|
|
2409
|
+
const { publisher, issueNumber, branchState } = input;
|
|
2410
|
+
return Object.fromEntries(
|
|
1635
2411
|
Object.entries(runners).map(([phase, fn]) => [
|
|
1636
2412
|
phase,
|
|
1637
2413
|
async () => {
|
|
1638
2414
|
publisher.publish({ type: "phase:start", at: isoNow(), phase });
|
|
1639
2415
|
try {
|
|
1640
2416
|
await fn();
|
|
1641
|
-
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) {
|
|
1642
2418
|
publisher.publish({ type: "git:update", at: isoNow(), branchCreated: true });
|
|
1643
2419
|
}
|
|
1644
2420
|
await publishGitState(publisher);
|
|
@@ -1657,447 +2433,257 @@ async function runPipelinePhases(issueNumber, paths, mode, publisher, input) {
|
|
|
1657
2433
|
}
|
|
1658
2434
|
])
|
|
1659
2435
|
);
|
|
1660
|
-
const phaseSuffixes = {};
|
|
1661
|
-
for (const [phase, resolved] of Object.entries(agentSummary.byPhase)) {
|
|
1662
|
-
if (resolved.provider !== agentSummary.defaultProvider) {
|
|
1663
|
-
phaseSuffixes[phase] = resolved.model ? `${resolved.provider} \xB7 ${resolved.model}` : resolved.provider;
|
|
1664
|
-
}
|
|
1665
|
-
}
|
|
1666
|
-
const result = await runPipelineWithRenderer({
|
|
1667
|
-
phases: phaseOrder,
|
|
1668
|
-
startIndex: startIdx,
|
|
1669
|
-
verbose: isVerbose(),
|
|
1670
|
-
runners: instrumentedRunners,
|
|
1671
|
-
tasksPath,
|
|
1672
|
-
phaseSuffixes
|
|
1673
|
-
});
|
|
1674
|
-
if (!result.success) {
|
|
1675
|
-
printError(`Phase ${result.failedPhase} failed`);
|
|
1676
|
-
return {
|
|
1677
|
-
...failure(1),
|
|
1678
|
-
failedPhase: result.failedPhase ?? null,
|
|
1679
|
-
branchName: producedBranch,
|
|
1680
|
-
elapsedSeconds: result.overallElapsedSeconds
|
|
1681
|
-
};
|
|
1682
|
-
}
|
|
1683
|
-
const review = reviewBox.outcome;
|
|
1684
|
-
if (review?.requestedChanges) {
|
|
1685
|
-
console.log("");
|
|
1686
|
-
printWarning("PR review requested changes \u2014 the Pull Request is not ready to merge.");
|
|
1687
|
-
if (review.reportPath !== null) {
|
|
1688
|
-
console.log(` Report: ${review.reportPath}`);
|
|
1689
|
-
}
|
|
1690
|
-
}
|
|
1691
|
-
if (review?.requestedChanges) {
|
|
1692
|
-
printInfo("Issue left open until the review blockers are addressed.");
|
|
1693
|
-
} else if (!inQueue) {
|
|
1694
|
-
await closeIssue(issueNumber, resolvedIssue.source);
|
|
1695
|
-
}
|
|
1696
|
-
let branchName = "unknown";
|
|
1697
|
-
try {
|
|
1698
|
-
const proc = await execa("git", ["branch", "--show-current"], { reject: false });
|
|
1699
|
-
branchName = proc.stdout?.toString().trim() ?? "unknown";
|
|
1700
|
-
} catch {
|
|
1701
|
-
}
|
|
1702
|
-
let storyCount = 0;
|
|
1703
|
-
let planPrUrl = null;
|
|
1704
|
-
try {
|
|
1705
|
-
const plan = await loadTaskPlan(tasksPath);
|
|
1706
|
-
storyCount = plan.userStories.length;
|
|
1707
|
-
planPrUrl = plan.pullRequest?.url ?? null;
|
|
1708
|
-
plan.lastAttemptAt = isoNow();
|
|
1709
|
-
if (!review?.requestedChanges) {
|
|
1710
|
-
plan.issueStatus = "completed";
|
|
1711
|
-
plan.completedAt = isoNow();
|
|
1712
|
-
}
|
|
1713
|
-
await saveTaskPlan(tasksPath, plan);
|
|
1714
|
-
} catch {
|
|
1715
|
-
}
|
|
1716
|
-
if (inQueue) {
|
|
1717
|
-
if (finalPr === void 0) {
|
|
1718
|
-
printSuccess(`Issue #${issueNumber} completed (${storyCount} stories).`);
|
|
1719
|
-
}
|
|
1720
|
-
return {
|
|
1721
|
-
code: 0,
|
|
1722
|
-
failedPhase: null,
|
|
1723
|
-
branchName: producedBranch ?? (branchName === "unknown" ? null : branchName),
|
|
1724
|
-
storyCount,
|
|
1725
|
-
elapsedSeconds: result.overallElapsedSeconds,
|
|
1726
|
-
review
|
|
1727
|
-
};
|
|
1728
|
-
}
|
|
1729
|
-
let prUrl = "unknown";
|
|
1730
|
-
if (!effectiveNoBranch && resolvedIssue.source === "github") {
|
|
1731
|
-
if (planPrUrl !== null) {
|
|
1732
|
-
prUrl = planPrUrl;
|
|
1733
|
-
} else if (branchName !== "unknown" && branchName !== "") {
|
|
1734
|
-
try {
|
|
1735
|
-
const latest = mostRecent(await listPullRequests(branchName));
|
|
1736
|
-
if (latest !== null) {
|
|
1737
|
-
prUrl = latest.url;
|
|
1738
|
-
}
|
|
1739
|
-
} catch {
|
|
1740
|
-
}
|
|
1741
|
-
}
|
|
1742
|
-
}
|
|
1743
|
-
printRunSummary({
|
|
1744
|
-
issueNumber,
|
|
1745
|
-
branchName,
|
|
1746
|
-
noBranch: effectiveNoBranch,
|
|
1747
|
-
storyCount,
|
|
1748
|
-
elapsedSeconds: result.overallElapsedSeconds,
|
|
1749
|
-
prUrl,
|
|
1750
|
-
prReview: review,
|
|
1751
|
-
// Process-owned counters: the session snapshot is empty whenever web
|
|
1752
|
-
// monitoring is off, so it cannot be the source of these totals.
|
|
1753
|
-
usage: getRunUsageTotals(),
|
|
1754
|
-
verification: verificationForSummary(getSessionPublisher().snapshot().verification)
|
|
1755
|
-
});
|
|
1756
|
-
return {
|
|
1757
|
-
code: 0,
|
|
1758
|
-
failedPhase: null,
|
|
1759
|
-
branchName: branchName === "unknown" ? null : branchName,
|
|
1760
|
-
storyCount,
|
|
1761
|
-
elapsedSeconds: result.overallElapsedSeconds
|
|
1762
|
-
};
|
|
1763
|
-
}
|
|
1764
|
-
async function detachAfterConfirm(requested, noBranch, prReview, options) {
|
|
1765
|
-
const childFlags = [...process.argv.slice(2)];
|
|
1766
|
-
if (options.yes !== true && options.only !== true) {
|
|
1767
|
-
try {
|
|
1768
|
-
const issuesConfig = await loadIssuesConfig();
|
|
1769
|
-
const resolution = await resolveCommandIssue(requested[0], void 0, {
|
|
1770
|
-
config: issuesConfig
|
|
1771
|
-
});
|
|
1772
|
-
if (resolution.ok) {
|
|
1773
|
-
const decision = await decideQueue({
|
|
1774
|
-
requested,
|
|
1775
|
-
resolved: resolution.resolved,
|
|
1776
|
-
noBranch: noBranch ?? false,
|
|
1777
|
-
prReview: prReview ?? false,
|
|
1778
|
-
runOptions: options
|
|
1779
|
-
});
|
|
1780
|
-
if (decision.kind === "stop") return decision.code;
|
|
1781
|
-
if (decision.kind === "queue" && !childFlags.includes("--yes")) childFlags.push("--yes");
|
|
1782
|
-
if (decision.kind === "single" && !childFlags.includes("--only")) childFlags.push("--only");
|
|
1783
|
-
}
|
|
1784
|
-
} catch {
|
|
1785
|
-
}
|
|
1786
|
-
}
|
|
1787
|
-
const paths = await resolveIssuePaths(requested[0]);
|
|
1788
|
-
const { pid, logFile } = await spawnDetachedRun({ paths, argv: childFlags });
|
|
1789
|
-
printSuccess(`Detached run started (pid ${pid}).`);
|
|
1790
|
-
printInfo(`Log: ${logFile}`);
|
|
1791
|
-
printInfo("Follow with: issue-flow ps");
|
|
1792
|
-
printInfo(`Stop with: kill ${pid}`);
|
|
1793
|
-
return 0;
|
|
1794
|
-
}
|
|
1795
|
-
async function decideQueue(input) {
|
|
1796
|
-
const primary = input.requested[0];
|
|
1797
|
-
const single = input.requested.length === 1;
|
|
1798
|
-
if (single && input.runOptions.only === true) {
|
|
1799
|
-
return { kind: "single" };
|
|
1800
|
-
}
|
|
1801
|
-
try {
|
|
1802
|
-
const [project, queuePaths] = await Promise.all([
|
|
1803
|
-
resolveProjectPaths(),
|
|
1804
|
-
resolveQueuePaths(primary)
|
|
1805
|
-
]);
|
|
1806
|
-
return await planQueue({
|
|
1807
|
-
requested: input.requested,
|
|
1808
|
-
source: input.resolved.source,
|
|
1809
|
-
known: [input.resolved.issue],
|
|
1810
|
-
projectId: project.projectId,
|
|
1811
|
-
planFile: queuePaths.planFile,
|
|
1812
|
-
noBranch: input.noBranch,
|
|
1813
|
-
prReview: input.prReview,
|
|
1814
|
-
confirm: {
|
|
1815
|
-
yes: input.runOptions.yes,
|
|
1816
|
-
only: input.runOptions.only,
|
|
1817
|
-
cascade: input.runOptions.cascade
|
|
1818
|
-
}
|
|
1819
|
-
});
|
|
1820
|
-
} catch (err) {
|
|
1821
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1822
|
-
if (single) {
|
|
1823
|
-
printWarning(
|
|
1824
|
-
`Could not inspect related issues (${message}). Running issue #${primary} alone.`
|
|
1825
|
-
);
|
|
1826
|
-
return { kind: "single" };
|
|
1827
|
-
}
|
|
1828
|
-
printError(`Could not plan the execution of ${input.requested.length} issues: ${message}`);
|
|
1829
|
-
return { kind: "stop", code: 1 };
|
|
1830
|
-
}
|
|
1831
2436
|
}
|
|
1832
|
-
|
|
1833
|
-
const
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
await saveExecutionPlan(planFile, plan);
|
|
1850
|
-
printInfo(
|
|
1851
|
-
`Queue ${entry.position}/${plan.issues.length}: issue #${entry.id}${entry.title === "" ? "" : ` \u2014 ${entry.title}`}`
|
|
1852
|
-
);
|
|
1853
|
-
const issueUsage = beginUsageScope();
|
|
1854
|
-
let result;
|
|
1855
|
-
try {
|
|
1856
|
-
result = await runIssueSession(entry.id, options.mode, {
|
|
1857
|
-
// `--from` addresses the issue the queue is resuming, never the ones
|
|
1858
|
-
// that come after it — those start from their own beginning.
|
|
1859
|
-
from: firstOfThisRun ? options.from : void 0,
|
|
1860
|
-
noBranch: options.noBranch,
|
|
1861
|
-
// `--start-us` belongs to the first issue this invocation runs; the
|
|
1862
|
-
// ones after it continue from the history those plans just wrote.
|
|
1863
|
-
runOptions: {
|
|
1864
|
-
...options.runOptions,
|
|
1865
|
-
startUs: firstOfThisRun ? options.runOptions?.startUs : void 0
|
|
1866
|
-
},
|
|
1867
|
-
queue: {
|
|
1868
|
-
plan,
|
|
1869
|
-
preChecked: true,
|
|
1870
|
-
resolved: entry.id === initialPlan.id ? resolvedPrimary : void 0
|
|
1871
|
-
}
|
|
1872
|
-
});
|
|
1873
|
-
} finally {
|
|
1874
|
-
firstOfThisRun = false;
|
|
1875
|
-
issueUsage.end();
|
|
1876
|
-
}
|
|
1877
|
-
const usage = issueUsage.totals();
|
|
1878
|
-
if (result.code !== 0) {
|
|
1879
|
-
const failure2 = {
|
|
1880
|
-
phase: result.failedPhase,
|
|
1881
|
-
error: {
|
|
1882
|
-
category: "queue_issue_failed",
|
|
1883
|
-
message: `Issue #${entry.id} failed${result.failedPhase === null ? "" : ` in phase ${result.failedPhase}`}`,
|
|
1884
|
-
at: isoNow()
|
|
1885
|
-
}
|
|
1886
|
-
};
|
|
1887
|
-
const where = result.failedPhase === null ? "" : ` (phase ${result.failedPhase})`;
|
|
1888
|
-
if (failureMode === "stop") {
|
|
1889
|
-
plan = markQueueIssueFailed(plan, entry.id, failure2);
|
|
1890
|
-
await saveExecutionPlan(planFile, plan);
|
|
1891
|
-
printError(
|
|
1892
|
-
`Queue stopped at issue #${entry.id}${where}. The branch and every commit made so far were kept.`
|
|
1893
|
-
);
|
|
1894
|
-
printInfo(`Resume with: issue-flow run ${plan.requested.join(",")}`);
|
|
1895
|
-
return result.code;
|
|
1896
|
-
}
|
|
1897
|
-
if (failureMode === "block") {
|
|
1898
|
-
plan = markQueueIssueBlocked(
|
|
1899
|
-
plan,
|
|
1900
|
-
entry.id,
|
|
1901
|
-
`Failed${where} and --on-issue-failure block was in force`,
|
|
1902
|
-
failure2
|
|
1903
|
-
);
|
|
1904
|
-
await saveExecutionPlan(planFile, plan);
|
|
1905
|
-
exhausted.add(entry.id);
|
|
1906
|
-
failedIds.add(entry.id);
|
|
1907
|
-
printWarning(
|
|
1908
|
-
`Issue #${entry.id} failed${where} and is blocked for review. Continuing with the rest of the queue.`
|
|
1909
|
-
);
|
|
1910
|
-
continue;
|
|
1911
|
-
}
|
|
1912
|
-
const attempts = entry.attempts + 1;
|
|
1913
|
-
if (attempts >= maxIssueAttempts) {
|
|
1914
|
-
plan = markQueueIssueFailed(plan, entry.id, failure2);
|
|
1915
|
-
exhausted.add(entry.id);
|
|
1916
|
-
failedIds.add(entry.id);
|
|
1917
|
-
printWarning(
|
|
1918
|
-
`Issue #${entry.id} failed${where} after ${attempts} attempt(s). Continuing with the rest of the queue.`
|
|
1919
|
-
);
|
|
1920
|
-
} else {
|
|
1921
|
-
plan = markQueueIssueSkipped(plan, entry.id, failure2);
|
|
1922
|
-
printWarning(
|
|
1923
|
-
`Issue #${entry.id} failed${where}. Skipping it for now and coming back at the end of the queue.`
|
|
1924
|
-
);
|
|
1925
|
-
}
|
|
1926
|
-
await saveExecutionPlan(planFile, plan);
|
|
1927
|
-
continue;
|
|
1928
|
-
}
|
|
1929
|
-
if (plan.branchName === null && result.branchName !== null) {
|
|
1930
|
-
plan = setQueueBranch(plan, result.branchName);
|
|
1931
|
-
}
|
|
1932
|
-
plan = markQueueIssueCompleted(plan, entry.id);
|
|
1933
|
-
await saveExecutionPlan(planFile, plan);
|
|
1934
|
-
summaries.push({
|
|
1935
|
-
id: entry.id,
|
|
1936
|
-
title: entry.title,
|
|
1937
|
-
storyCount: result.storyCount,
|
|
1938
|
-
elapsedSeconds: result.elapsedSeconds,
|
|
1939
|
-
usage
|
|
1940
|
-
});
|
|
1941
|
-
}
|
|
1942
|
-
const code = await finishQueue(
|
|
1943
|
-
plan,
|
|
1944
|
-
planFile,
|
|
1945
|
-
summaries,
|
|
1946
|
-
startedAtMs,
|
|
1947
|
-
queueUsage,
|
|
1948
|
-
options,
|
|
1949
|
-
resolvedPrimary
|
|
1950
|
-
);
|
|
1951
|
-
if (failedIds.size > 0) {
|
|
1952
|
-
printError(
|
|
1953
|
-
`${failedIds.size} issue(s) did not finish: ${[...failedIds].map((id) => `#${id}`).join(", ")}.`
|
|
1954
|
-
);
|
|
1955
|
-
printInfo(`Resume with: issue-flow run ${plan.requested.join(",")}`);
|
|
1956
|
-
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}`);
|
|
1957
2454
|
}
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
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
|
+
};
|
|
1961
2484
|
}
|
|
2485
|
+
const instrumentedRunners = instrumentPhaseRunners(runners, {
|
|
2486
|
+
publisher,
|
|
2487
|
+
issueNumber,
|
|
2488
|
+
branchState
|
|
2489
|
+
});
|
|
2490
|
+
return { instrumentedRunners, reviewBox };
|
|
1962
2491
|
}
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
const
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
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}`);
|
|
1975
2506
|
}
|
|
1976
|
-
|
|
1977
|
-
review = outcome.review ?? null;
|
|
1978
|
-
if (outcome.code !== 0) {
|
|
1979
|
-
printError("The consolidated Pull Request could not be created.");
|
|
1980
|
-
await saveExecutionPlan(planFile, current);
|
|
1981
|
-
return outcome.code;
|
|
1982
|
-
}
|
|
1983
|
-
const pullRequest = await propagatePullRequest(current);
|
|
1984
|
-
if (pullRequest !== null) {
|
|
1985
|
-
current = setQueuePullRequest(current, pullRequest);
|
|
2507
|
+
return { ok: false };
|
|
1986
2508
|
}
|
|
1987
|
-
|
|
1988
|
-
await saveExecutionPlan(planFile, current);
|
|
1989
|
-
if (review?.requestedChanges === true) {
|
|
1990
|
-
printInfo("Issues left open until the review blockers are addressed.");
|
|
2509
|
+
startPhase = from;
|
|
1991
2510
|
} else {
|
|
1992
|
-
for (const entry of current.issues) {
|
|
1993
|
-
await closeIssue(entry.id, entry.source);
|
|
1994
|
-
}
|
|
1995
|
-
}
|
|
1996
|
-
printQueueSummary({
|
|
1997
|
-
queueId: current.id,
|
|
1998
|
-
branchName: current.branchName,
|
|
1999
|
-
issues: summaries,
|
|
2000
|
-
excluded: current.excluded.map((entry) => ({ id: entry.id, title: entry.title })),
|
|
2001
|
-
elapsedSeconds: Math.round((Date.now() - startedAtMs) / 1e3),
|
|
2002
|
-
prUrl: current.pullRequest?.url ?? null,
|
|
2003
|
-
// Read here, after the consolidated Pull Request (and the optional
|
|
2004
|
-
// pr-review) already ran: reading it before would report a queue total
|
|
2005
|
-
// that leaves out the phase that just spent tokens.
|
|
2006
|
-
usage: queueUsage.totals(),
|
|
2007
|
-
prReview: review
|
|
2008
|
-
});
|
|
2009
|
-
return 0;
|
|
2010
|
-
}
|
|
2011
|
-
async function primaryPrCreated(plan) {
|
|
2012
|
-
try {
|
|
2013
|
-
const paths = await resolveIssuePaths(plan.id);
|
|
2014
|
-
return (await loadTaskPlan(paths.tasksFile)).pipeline.prCreated;
|
|
2015
|
-
} catch {
|
|
2016
|
-
return false;
|
|
2017
|
-
}
|
|
2018
|
-
}
|
|
2019
|
-
async function buildPrQueueContext(plan) {
|
|
2020
|
-
const pending = [];
|
|
2021
|
-
for (const entry of plan.issues) {
|
|
2022
2511
|
try {
|
|
2023
|
-
const
|
|
2024
|
-
const
|
|
2025
|
-
|
|
2026
|
-
|
|
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}`);
|
|
2027
2518
|
}
|
|
2028
2519
|
} catch {
|
|
2029
2520
|
}
|
|
2030
2521
|
}
|
|
2031
|
-
|
|
2032
|
-
issues: plan.issues.map((entry) => ({
|
|
2033
|
-
id: entry.id,
|
|
2034
|
-
number: entry.number,
|
|
2035
|
-
title: entry.title,
|
|
2036
|
-
url: entry.url,
|
|
2037
|
-
source: entry.source,
|
|
2038
|
-
parent: entry.parent,
|
|
2039
|
-
role: entry.role,
|
|
2040
|
-
complete: entry.status === "completed"
|
|
2041
|
-
})),
|
|
2042
|
-
excluded: plan.excluded.map((entry) => ({
|
|
2043
|
-
id: entry.id,
|
|
2044
|
-
number: entry.number,
|
|
2045
|
-
title: entry.title,
|
|
2046
|
-
reason: entry.reason
|
|
2047
|
-
})),
|
|
2048
|
-
pending
|
|
2049
|
-
};
|
|
2050
|
-
}
|
|
2051
|
-
async function propagatePullRequest(plan) {
|
|
2052
|
-
let pullRequest = null;
|
|
2053
|
-
try {
|
|
2054
|
-
const primaryPaths = await resolveIssuePaths(plan.id);
|
|
2055
|
-
pullRequest = (await loadTaskPlan(primaryPaths.tasksFile)).pullRequest ?? null;
|
|
2056
|
-
} catch {
|
|
2057
|
-
return null;
|
|
2058
|
-
}
|
|
2059
|
-
if (pullRequest === null) return null;
|
|
2060
|
-
for (const entry of plan.issues) {
|
|
2061
|
-
if (entry.id === plan.id) continue;
|
|
2522
|
+
if (from) {
|
|
2062
2523
|
try {
|
|
2063
|
-
const
|
|
2064
|
-
const
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
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
|
+
}
|
|
2068
2530
|
} catch {
|
|
2069
|
-
|
|
2531
|
+
if (startPhase !== "prd") {
|
|
2532
|
+
printError(`Cannot resume from ${startPhase}: no pipeline state found`);
|
|
2533
|
+
return { ok: false };
|
|
2534
|
+
}
|
|
2070
2535
|
}
|
|
2071
2536
|
}
|
|
2072
|
-
return
|
|
2537
|
+
return { ok: true, startPhase };
|
|
2073
2538
|
}
|
|
2074
|
-
|
|
2075
|
-
|
|
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
|
+
});
|
|
2076
2629
|
}
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
if (provider
|
|
2081
|
-
|
|
2082
|
-
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;
|
|
2083
2635
|
}
|
|
2084
|
-
} catch {
|
|
2085
|
-
printWarning("Failed to close issue automatically");
|
|
2086
2636
|
}
|
|
2637
|
+
return phaseSuffixes;
|
|
2087
2638
|
}
|
|
2088
|
-
|
|
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;
|
|
2089
2644
|
try {
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
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;
|
|
2093
2655
|
}
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
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;
|
|
2097
2678
|
}
|
|
2098
|
-
return
|
|
2099
|
-
|
|
2100
|
-
|
|
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();
|
|
2101
2687
|
}
|
|
2102
2688
|
}
|
|
2103
2689
|
|
|
@@ -2106,4 +2692,4 @@ export {
|
|
|
2106
2692
|
publishIssueDetails,
|
|
2107
2693
|
runPipeline
|
|
2108
2694
|
};
|
|
2109
|
-
//# sourceMappingURL=chunk-
|
|
2695
|
+
//# sourceMappingURL=chunk-HJBF4WD2.js.map
|