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