issue-flow 0.12.0 → 0.13.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-BXRIEKVM.js +238 -0
- package/dist/agent-BXRIEKVM.js.map +1 -0
- package/dist/{analyze-OEET7KGE.js → analyze-WS47UYGN.js} +28 -16
- package/dist/analyze-WS47UYGN.js.map +1 -0
- package/dist/{apply-ZIA3C52S.js → apply-OS62UWRN.js} +15 -7
- package/dist/{apply-ZIA3C52S.js.map → apply-OS62UWRN.js.map} +1 -1
- package/dist/bench-SA3BOME5.js +906 -0
- package/dist/bench-SA3BOME5.js.map +1 -0
- package/dist/chunk-37N2RJW2.js +120 -0
- package/dist/chunk-37N2RJW2.js.map +1 -0
- package/dist/chunk-3XLCNVMD.js +180 -0
- package/dist/chunk-3XLCNVMD.js.map +1 -0
- package/dist/chunk-5TB6KLC2.js +72 -0
- package/dist/chunk-5TB6KLC2.js.map +1 -0
- package/dist/{chunk-MJULIKQU.js → chunk-664Y3NDX.js} +23 -47
- package/dist/chunk-664Y3NDX.js.map +1 -0
- package/dist/chunk-6FDFRNXZ.js +357 -0
- package/dist/chunk-6FDFRNXZ.js.map +1 -0
- package/dist/{chunk-56KS3TFQ.js → chunk-7KD66LIM.js} +115 -160
- package/dist/chunk-7KD66LIM.js.map +1 -0
- package/dist/chunk-7Z4Y3VKM.js +26 -0
- package/dist/chunk-7Z4Y3VKM.js.map +1 -0
- package/dist/chunk-ARFVHC2F.js +1111 -0
- package/dist/chunk-ARFVHC2F.js.map +1 -0
- package/dist/{chunk-NS6R37II.js → chunk-BHJCGWQX.js} +141 -12
- package/dist/chunk-BHJCGWQX.js.map +1 -0
- package/dist/chunk-BQDQTIM7.js +74 -0
- package/dist/chunk-BQDQTIM7.js.map +1 -0
- package/dist/chunk-CNBYCMSU.js +2709 -0
- package/dist/chunk-CNBYCMSU.js.map +1 -0
- package/dist/chunk-CRGIQFUQ.js +83 -0
- package/dist/chunk-CRGIQFUQ.js.map +1 -0
- package/dist/chunk-DIG5XKQQ.js +298 -0
- package/dist/chunk-DIG5XKQQ.js.map +1 -0
- package/dist/chunk-FZDR7CGH.js +71 -0
- package/dist/chunk-FZDR7CGH.js.map +1 -0
- package/dist/chunk-HEF2RMKN.js +413 -0
- package/dist/chunk-HEF2RMKN.js.map +1 -0
- package/dist/{chunk-M5UCXTIT.js → chunk-HJGGNLWI.js} +22 -18
- package/dist/chunk-HJGGNLWI.js.map +1 -0
- package/dist/chunk-HQAHNIAR.js +283 -0
- package/dist/chunk-HQAHNIAR.js.map +1 -0
- package/dist/{chunk-FJP3UQGD.js → chunk-IHIEXUTC.js} +39 -19
- package/dist/chunk-IHIEXUTC.js.map +1 -0
- package/dist/{chunk-Y5ZDJ4PJ.js → chunk-IV5SS4JN.js} +18 -10
- package/dist/chunk-IV5SS4JN.js.map +1 -0
- package/dist/{chunk-BYZDPR7N.js → chunk-IYV6KNVF.js} +146 -13
- package/dist/chunk-IYV6KNVF.js.map +1 -0
- package/dist/{chunk-TSOLWK7Y.js → chunk-J62QSVP6.js} +8 -8
- package/dist/chunk-KQHB7ZT3.js +559 -0
- package/dist/chunk-KQHB7ZT3.js.map +1 -0
- package/dist/chunk-L6HSKTQE.js +77 -0
- package/dist/chunk-L6HSKTQE.js.map +1 -0
- package/dist/{chunk-3QHK4YQA.js → chunk-LZ6GFQAG.js} +334 -117
- package/dist/chunk-LZ6GFQAG.js.map +1 -0
- package/dist/{chunk-LAFNLLQP.js → chunk-MIVHK5YI.js} +5 -5
- package/dist/chunk-NEZZ6KDR.js +1217 -0
- package/dist/chunk-NEZZ6KDR.js.map +1 -0
- package/dist/{chunk-NTMMF5UD.js → chunk-OPNGPUKJ.js} +436 -254
- package/dist/chunk-OPNGPUKJ.js.map +1 -0
- package/dist/chunk-P3LRUNC6.js +16 -0
- package/dist/chunk-P3LRUNC6.js.map +1 -0
- package/dist/{chunk-FADTCQGM.js → chunk-PIVGJM6K.js} +94 -32
- package/dist/chunk-PIVGJM6K.js.map +1 -0
- package/dist/{chunk-73ATPIYX.js → chunk-PV4OX5TC.js} +36 -71
- package/dist/chunk-PV4OX5TC.js.map +1 -0
- package/dist/chunk-QCXPNSEL.js +141 -0
- package/dist/chunk-QCXPNSEL.js.map +1 -0
- package/dist/{chunk-XQUSIEKA.js → chunk-QXDNBYQF.js} +52 -14
- package/dist/chunk-QXDNBYQF.js.map +1 -0
- package/dist/chunk-SNTTOQAD.js +30 -0
- package/dist/chunk-SNTTOQAD.js.map +1 -0
- package/dist/{chunk-QCPGHBSZ.js → chunk-TM67EHSV.js} +63 -21
- package/dist/chunk-TM67EHSV.js.map +1 -0
- package/dist/chunk-TVN2JVOF.js +77 -0
- package/dist/chunk-TVN2JVOF.js.map +1 -0
- package/dist/chunk-TX5WIVYP.js +129 -0
- package/dist/chunk-TX5WIVYP.js.map +1 -0
- package/dist/{chunk-A4SLHOW7.js → chunk-UJ7XWK7J.js} +24 -18
- package/dist/chunk-UJ7XWK7J.js.map +1 -0
- package/dist/{chunk-EZATO3DW.js → chunk-UOCPC2Y2.js} +594 -17
- package/dist/chunk-UOCPC2Y2.js.map +1 -0
- package/dist/chunk-XEYH6ZZI.js +289 -0
- package/dist/chunk-XEYH6ZZI.js.map +1 -0
- package/dist/cli.js +264 -44
- package/dist/cli.js.map +1 -1
- package/dist/{config-PRJGWW7J.js → config-MEM2IWWN.js} +28 -5
- package/dist/contract-JH2WBIO6.js +8 -0
- package/dist/conventions-WV4NDXGH.js +143 -0
- package/dist/conventions-WV4NDXGH.js.map +1 -0
- package/dist/execute-NITIHHCK.js +31 -0
- package/dist/{generate-EGB4KV2B.js → generate-MVQI7A45.js} +34 -20
- package/dist/{generate-EGB4KV2B.js.map → generate-MVQI7A45.js.map} +1 -1
- package/dist/git-YRVRUN3C.js +31 -0
- package/dist/init-OKWEC3EV.js +21 -0
- package/dist/{operations-BYT626KQ.js → operations-LU6JA3BX.js} +24 -15
- package/dist/{operations-BYT626KQ.js.map → operations-LU6JA3BX.js.map} +1 -1
- package/dist/permissions-7NMDFU27.js +18 -0
- package/dist/plan-LEJJF3DE.js +31 -0
- package/dist/{policy-6S4GFC76.js → policy-JSYEK5B2.js} +17 -6
- package/dist/policy-JSYEK5B2.js.map +1 -0
- package/dist/pr-HPVUZ7S3.js +36 -0
- package/dist/pr-review-R2QZBT3F.js +29 -0
- package/dist/prd-5O6GYCC6.js +30 -0
- package/dist/ps-TNE44R5V.js +90 -0
- package/dist/ps-TNE44R5V.js.map +1 -0
- package/dist/recorder-XGF55WXI.js +31 -0
- package/dist/recorder-XGF55WXI.js.map +1 -0
- package/dist/registry-LGPE53QD.js +15 -0
- package/dist/registry-LGPE53QD.js.map +1 -0
- package/dist/{resume-PPCNQVVV.js → resume-QMEPMSZW.js} +51 -34
- package/dist/{resume-PPCNQVVV.js.map → resume-QMEPMSZW.js.map} +1 -1
- package/dist/review-YKWD6SNN.js +32 -0
- package/dist/review-YKWD6SNN.js.map +1 -0
- package/dist/routing-2ZUMZPJB.js +91 -0
- package/dist/routing-2ZUMZPJB.js.map +1 -0
- package/dist/run-YU4TR62R.js +50 -0
- package/dist/run-YU4TR62R.js.map +1 -0
- package/dist/{state-manager-2EDVDBAR.js → state-manager-X3PT3FGT.js} +4 -2
- package/dist/state-manager-X3PT3FGT.js.map +1 -0
- package/dist/usage-VCFLDA5Y.js +174 -0
- package/dist/usage-VCFLDA5Y.js.map +1 -0
- package/dist/{web-EF5WIIGG.js → web-XVHLX62B.js} +41 -30
- package/dist/web-XVHLX62B.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 +3 -3
- package/web/public/app.css +38 -0
- package/web/public/app.js +121 -11
- package/web/public/index.html +24 -0
- 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/{chunk-TSOLWK7Y.js.map → chunk-J62QSVP6.js.map} +0 -0
- /package/dist/{chunk-LAFNLLQP.js.map → chunk-MIVHK5YI.js.map} +0 -0
- /package/dist/{config-PRJGWW7J.js.map → config-MEM2IWWN.js.map} +0 -0
- /package/dist/{execute-XBNVL753.js.map → contract-JH2WBIO6.js.map} +0 -0
- /package/dist/{init-AJHCXJBP.js.map → execute-NITIHHCK.js.map} +0 -0
- /package/dist/{plan-QOZ3RIO6.js.map → git-YRVRUN3C.js.map} +0 -0
- /package/dist/{pr-EBBHD2ZX.js.map → init-OKWEC3EV.js.map} +0 -0
- /package/dist/{pr-review-VVMW5PO4.js.map → permissions-7NMDFU27.js.map} +0 -0
- /package/dist/{prd-JQSZF7PR.js.map → plan-LEJJF3DE.js.map} +0 -0
- /package/dist/{review-3F6MARWH.js.map → pr-HPVUZ7S3.js.map} +0 -0
- /package/dist/{run-RXE5GDQO.js.map → pr-review-R2QZBT3F.js.map} +0 -0
- /package/dist/{state-manager-2EDVDBAR.js.map → prd-5O6GYCC6.js.map} +0 -0
|
@@ -1,31 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} from "./chunk-FADTCQGM.js";
|
|
8
|
-
import {
|
|
9
|
-
mostRecent,
|
|
10
|
-
prReviewDir,
|
|
11
|
-
readPrReviewIndex,
|
|
12
|
-
runPrReview
|
|
13
|
-
} from "./chunk-A4SLHOW7.js";
|
|
14
|
-
import {
|
|
15
|
-
runPrd
|
|
16
|
-
} from "./chunk-M5UCXTIT.js";
|
|
3
|
+
formatPhaseLine,
|
|
4
|
+
loadPhaseTiming,
|
|
5
|
+
snapshotTimingFields
|
|
6
|
+
} from "./chunk-37N2RJW2.js";
|
|
17
7
|
import {
|
|
18
8
|
runReview
|
|
19
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-QXDNBYQF.js";
|
|
20
10
|
import {
|
|
21
|
-
JournalPublisher,
|
|
22
|
-
MultiPublisher,
|
|
23
11
|
PIPELINE_PHASES,
|
|
24
12
|
PIPELINE_PHASES_NO_BRANCH,
|
|
25
13
|
PIPELINE_PHASES_WITH_PR_REVIEW,
|
|
26
14
|
PipelineManager,
|
|
27
15
|
buildDependencyGraph,
|
|
28
16
|
buildExecutionPlan,
|
|
17
|
+
collectCascadeIds,
|
|
29
18
|
computeExecutionOrder,
|
|
30
19
|
describeCycles,
|
|
31
20
|
isQueueComplete,
|
|
@@ -36,250 +25,184 @@ import {
|
|
|
36
25
|
markQueueIssueInProgress,
|
|
37
26
|
markQueueIssueSkipped,
|
|
38
27
|
nextQueueIssue,
|
|
39
|
-
parseJournal,
|
|
40
28
|
saveExecutionPlan,
|
|
41
29
|
setQueueBranch,
|
|
42
30
|
setQueuePullRequest
|
|
43
|
-
} from "./chunk-
|
|
44
|
-
import {
|
|
45
|
-
acquireRunLock,
|
|
46
|
-
describeRunLockOwner
|
|
47
|
-
} from "./chunk-BAUPQXPE.js";
|
|
31
|
+
} from "./chunk-7KD66LIM.js";
|
|
48
32
|
import {
|
|
49
33
|
runInit
|
|
50
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-BHJCGWQX.js";
|
|
51
35
|
import {
|
|
52
36
|
printQueueSummary,
|
|
53
37
|
printRunSummary,
|
|
54
|
-
runExecute
|
|
55
|
-
|
|
38
|
+
runExecute,
|
|
39
|
+
stripMarkdown
|
|
40
|
+
} from "./chunk-LZ6GFQAG.js";
|
|
41
|
+
import {
|
|
42
|
+
ensureWebMonitor
|
|
43
|
+
} from "./chunk-IHIEXUTC.js";
|
|
44
|
+
import {
|
|
45
|
+
runPlan
|
|
46
|
+
} from "./chunk-TM67EHSV.js";
|
|
47
|
+
import {
|
|
48
|
+
runPr
|
|
49
|
+
} from "./chunk-PIVGJM6K.js";
|
|
50
|
+
import {
|
|
51
|
+
mostRecent,
|
|
52
|
+
prReviewDir,
|
|
53
|
+
readPrReviewIndex,
|
|
54
|
+
runPrReview
|
|
55
|
+
} from "./chunk-UJ7XWK7J.js";
|
|
56
56
|
import {
|
|
57
57
|
listPullRequests,
|
|
58
58
|
publishGitState
|
|
59
|
-
} from "./chunk-
|
|
59
|
+
} from "./chunk-J62QSVP6.js";
|
|
60
60
|
import {
|
|
61
|
-
|
|
62
|
-
} from "./chunk-
|
|
61
|
+
runPrd
|
|
62
|
+
} from "./chunk-HJGGNLWI.js";
|
|
63
63
|
import {
|
|
64
|
-
getProvider,
|
|
65
64
|
resolveCommandIssue
|
|
66
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-FZDR7CGH.js";
|
|
66
|
+
import {
|
|
67
|
+
getProvider
|
|
68
|
+
} from "./chunk-PV4OX5TC.js";
|
|
67
69
|
import {
|
|
68
70
|
beginUsageScope,
|
|
69
71
|
getRunUsageTotals
|
|
70
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-664Y3NDX.js";
|
|
71
73
|
import {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
assessDecomposition,
|
|
75
|
+
buildDecompositionReport,
|
|
76
|
+
proposeSubIssues,
|
|
77
|
+
resetAgentInvocationState
|
|
78
|
+
} from "./chunk-ARFVHC2F.js";
|
|
74
79
|
import {
|
|
80
|
+
JournalPublisher,
|
|
81
|
+
MultiPublisher
|
|
82
|
+
} from "./chunk-QCXPNSEL.js";
|
|
83
|
+
import {
|
|
84
|
+
describeRunAgents,
|
|
75
85
|
getActiveResilienceConfig,
|
|
76
86
|
initResilienceConfig,
|
|
77
87
|
loadIssuesConfig,
|
|
78
|
-
loadWebConfig
|
|
79
|
-
|
|
88
|
+
loadWebConfig,
|
|
89
|
+
onShutdown
|
|
90
|
+
} from "./chunk-CNBYCMSU.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-HQAHNIAR.js";
|
|
90
96
|
import {
|
|
91
97
|
FilePublisher,
|
|
92
|
-
|
|
98
|
+
MemoryPublisher,
|
|
99
|
+
formatDuration,
|
|
100
|
+
getIcons,
|
|
93
101
|
getInactivityTimeout,
|
|
102
|
+
getSessionPublisher,
|
|
94
103
|
isVerbose,
|
|
95
104
|
printError,
|
|
96
105
|
printInfo,
|
|
97
106
|
printSuccess,
|
|
98
107
|
printWarning,
|
|
108
|
+
setActivityCallback,
|
|
99
109
|
setInactivityTimeout,
|
|
100
110
|
setOutputCallback,
|
|
101
111
|
setSessionPublisher,
|
|
102
112
|
setStoryStageCallback,
|
|
103
113
|
setStoryUpdateCallback
|
|
104
|
-
} from "./chunk-
|
|
114
|
+
} from "./chunk-IYV6KNVF.js";
|
|
105
115
|
import {
|
|
106
116
|
isoNow,
|
|
107
117
|
loadTaskPlan,
|
|
108
118
|
saveTaskPlan
|
|
109
|
-
} from "./chunk-
|
|
119
|
+
} from "./chunk-DIG5XKQQ.js";
|
|
120
|
+
import {
|
|
121
|
+
acquireRunLock,
|
|
122
|
+
describeRunLockOwner
|
|
123
|
+
} from "./chunk-NEZZ6KDR.js";
|
|
124
|
+
import {
|
|
125
|
+
committedStoryIds,
|
|
126
|
+
describePreflight,
|
|
127
|
+
getBaseBranch,
|
|
128
|
+
preflightRepository,
|
|
129
|
+
run
|
|
130
|
+
} from "./chunk-KQHB7ZT3.js";
|
|
110
131
|
|
|
111
132
|
// src/commands/run.ts
|
|
112
133
|
import { randomUUID } from "crypto";
|
|
113
|
-
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
134
|
+
import { mkdir as mkdir2, readFile, writeFile } from "fs/promises";
|
|
114
135
|
import { join } from "path";
|
|
115
136
|
import { execa } from "execa";
|
|
116
137
|
|
|
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
|
-
});
|
|
138
|
+
// src/execution/detach.ts
|
|
139
|
+
import { spawn } from "child_process";
|
|
140
|
+
import { mkdir, open } from "fs/promises";
|
|
141
|
+
import { dirname } from "path";
|
|
142
|
+
|
|
143
|
+
// src/storage/run-log.ts
|
|
144
|
+
import { rename, stat } from "fs/promises";
|
|
145
|
+
var DEFAULT_RUN_LOG_MAX_BYTES = 5 * 1024 * 1024;
|
|
146
|
+
async function rotateRunLogIfNeeded(filePath, rotatedPath, maxBytes = DEFAULT_RUN_LOG_MAX_BYTES) {
|
|
147
|
+
if (maxBytes <= 0) return;
|
|
148
|
+
try {
|
|
149
|
+
const size = (await stat(filePath)).size;
|
|
150
|
+
if (size < maxBytes) return;
|
|
151
|
+
} catch {
|
|
152
|
+
return;
|
|
189
153
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
id: "files-touched",
|
|
194
|
-
detail: `${files} files were touched on this branch (threshold: ${DECOMPOSITION_THRESHOLDS.filesTouched}).`
|
|
195
|
-
});
|
|
154
|
+
try {
|
|
155
|
+
await rename(filePath, rotatedPath);
|
|
156
|
+
} catch {
|
|
196
157
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/execution/detach.ts
|
|
161
|
+
function backgroundRejection(mode, env = process.env) {
|
|
162
|
+
if (mode === "manual") {
|
|
163
|
+
return "--background cannot be used with --mode manual: a detached run cannot answer prompts.";
|
|
203
164
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
});
|
|
165
|
+
const ci = env.CI;
|
|
166
|
+
const inCi = ci !== void 0 && ci !== "" && ci !== "0" && ci.toLowerCase() !== "false";
|
|
167
|
+
if (inCi || !process.stdout.isTTY) {
|
|
168
|
+
return "--background needs an interactive terminal. In CI, run in the foreground so the exit code is visible.";
|
|
209
169
|
}
|
|
210
|
-
return
|
|
170
|
+
return null;
|
|
211
171
|
}
|
|
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;
|
|
172
|
+
function childArgv(argv) {
|
|
173
|
+
const next = [];
|
|
174
|
+
for (const arg of argv) {
|
|
175
|
+
if (arg === "--background" || arg === "-d") continue;
|
|
176
|
+
next.push(arg);
|
|
177
|
+
}
|
|
178
|
+
next.push("--detached-child");
|
|
179
|
+
return next;
|
|
229
180
|
}
|
|
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}`);
|
|
181
|
+
async function spawnDetachedRun(input) {
|
|
182
|
+
const entryScript = input.entryScript ?? process.argv[1];
|
|
183
|
+
if (entryScript === void 0) {
|
|
184
|
+
throw new Error("Could not determine the CLI entry point to spawn a detached run.");
|
|
185
|
+
}
|
|
186
|
+
await mkdir(dirname(input.paths.runLogFile), { recursive: true });
|
|
187
|
+
await rotateRunLogIfNeeded(input.paths.runLogFile, input.paths.rotatedRunLogFile);
|
|
188
|
+
const log = await open(input.paths.runLogFile, "a");
|
|
189
|
+
try {
|
|
190
|
+
const args = [entryScript, ...childArgv((input.argv ?? process.argv.slice(2)).slice())];
|
|
191
|
+
const spawnFn = input.spawnFn ?? spawn;
|
|
192
|
+
const child = spawnFn(input.execPath ?? process.execPath, args, {
|
|
193
|
+
detached: true,
|
|
194
|
+
stdio: ["ignore", log.fd, log.fd],
|
|
195
|
+
cwd: input.cwd ?? process.cwd(),
|
|
196
|
+
env: input.env ?? process.env
|
|
197
|
+
});
|
|
198
|
+
if (child.pid === void 0) {
|
|
199
|
+
throw new Error("The detached run started but reported no pid.");
|
|
270
200
|
}
|
|
271
|
-
|
|
201
|
+
child.unref();
|
|
202
|
+
return { pid: child.pid, logFile: input.paths.runLogFile };
|
|
203
|
+
} finally {
|
|
204
|
+
await log.close();
|
|
272
205
|
}
|
|
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
206
|
}
|
|
284
207
|
|
|
285
208
|
// src/execution/confirm.ts
|
|
@@ -321,16 +244,25 @@ function buildQueueSummaryLines(plan) {
|
|
|
321
244
|
const primary = plan.issues.find((entry) => entry.id === plan.id) ?? plan.issues[0];
|
|
322
245
|
const lines = [];
|
|
323
246
|
if (primary !== void 0) {
|
|
324
|
-
|
|
247
|
+
const role = primary.role === "container" ? " (container)" : "";
|
|
248
|
+
lines.push(
|
|
249
|
+
` Main issue: ${issueLabel(primary)}${primary.title ? ` ${primary.title}` : ""}${role}`
|
|
250
|
+
);
|
|
325
251
|
}
|
|
326
|
-
|
|
252
|
+
const executables = plan.issues.filter((entry) => entry.role !== "container").length;
|
|
253
|
+
const containers = plan.issues.length - executables;
|
|
254
|
+
lines.push(
|
|
255
|
+
containers > 0 ? ` Total issues: ${executables} executable + ${containers} container` : ` Total issues: ${plan.issues.length}`
|
|
256
|
+
);
|
|
327
257
|
lines.push(" Suggested order:");
|
|
328
258
|
for (const entry of plan.issues) {
|
|
329
259
|
const reason = reasonFor(entry);
|
|
330
260
|
const flag = entry.heuristic ? " ~" : "";
|
|
261
|
+
const external = entry.externalDependencies.length > 0 ? ` \u26A0 depends on ${entry.externalDependencies.map((id) => `#${id}`).join(", ")} (outside this scope)` : "";
|
|
262
|
+
const role = entry.role === "container" ? " (container)" : "";
|
|
331
263
|
const title = entry.title === "" ? "" : ` ${entry.title}`;
|
|
332
264
|
lines.push(
|
|
333
|
-
` ${entry.position}. ${issueLabel(entry)}${title}${reason === "" ? "" : ` (${reason})`}${flag}`
|
|
265
|
+
` ${entry.position}. ${issueLabel(entry)}${title}${role}${reason === "" ? "" : ` (${reason})`}${flag}${external}`
|
|
334
266
|
);
|
|
335
267
|
}
|
|
336
268
|
if (plan.issues.some((entry) => entry.heuristic)) {
|
|
@@ -341,6 +273,23 @@ function buildQueueSummaryLines(plan) {
|
|
|
341
273
|
}
|
|
342
274
|
return lines;
|
|
343
275
|
}
|
|
276
|
+
function buildScopeSummaryLine(plan, choice) {
|
|
277
|
+
if (choice === "cancel") return null;
|
|
278
|
+
const primary = plan.requested[0] ?? plan.id;
|
|
279
|
+
if (choice === "requested") {
|
|
280
|
+
return `Scope: ${plan.requested.length} requested issue(s).`;
|
|
281
|
+
}
|
|
282
|
+
if (choice === "cascade") {
|
|
283
|
+
const executables = plan.issues.filter((entry) => entry.role !== "container").length;
|
|
284
|
+
return `Scope: hierarchy of #${primary} (${executables} executable, container not implemented).`;
|
|
285
|
+
}
|
|
286
|
+
return `Scope: ${plan.issues.length} issues from the hierarchy of #${primary}.`;
|
|
287
|
+
}
|
|
288
|
+
function requestedIsContainer(plan) {
|
|
289
|
+
return plan.issues.some(
|
|
290
|
+
(entry) => plan.requested.includes(entry.id) && entry.role === "container"
|
|
291
|
+
);
|
|
292
|
+
}
|
|
344
293
|
async function confirmQueue(plan, requestedOnlyCount, options = {}) {
|
|
345
294
|
const info = options.info ?? printInfo;
|
|
346
295
|
const warn = options.warn ?? printWarning;
|
|
@@ -349,19 +298,41 @@ async function confirmQueue(plan, requestedOnlyCount, options = {}) {
|
|
|
349
298
|
(options.stdout ?? process.stdout).write(`${line}
|
|
350
299
|
`);
|
|
351
300
|
}
|
|
301
|
+
const container = requestedIsContainer(plan);
|
|
352
302
|
if (options.only === true) {
|
|
303
|
+
if (container) {
|
|
304
|
+
warn(
|
|
305
|
+
`${plan.requested.map((id) => `#${id}`).join(", ")} is a container. --only will run it as an executable issue.`
|
|
306
|
+
);
|
|
307
|
+
}
|
|
353
308
|
info(`--only: running just the ${requestedOnlyCount} issue(s) you asked for.`);
|
|
309
|
+
info(buildScopeSummaryLine(plan, "requested") ?? "");
|
|
354
310
|
return "requested";
|
|
355
311
|
}
|
|
312
|
+
if (options.cascade === true || options.yes === true && container) {
|
|
313
|
+
info(
|
|
314
|
+
`${options.cascade === true ? "--cascade" : "--yes"}: running the hierarchy of the container, without implementing it.`
|
|
315
|
+
);
|
|
316
|
+
info(buildScopeSummaryLine(plan, "cascade") ?? "");
|
|
317
|
+
return "cascade";
|
|
318
|
+
}
|
|
356
319
|
if (options.yes === true) {
|
|
357
320
|
info(`--yes: running the whole hierarchy (${plan.issues.length} issues).`);
|
|
321
|
+
info(buildScopeSummaryLine(plan, "all") ?? "");
|
|
358
322
|
return "all";
|
|
359
323
|
}
|
|
360
324
|
const interactive = options.interactive ?? isInteractiveByDefault();
|
|
325
|
+
if (!interactive && container) {
|
|
326
|
+
throw new QueueConfirmationError(
|
|
327
|
+
`${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.`
|
|
328
|
+
);
|
|
329
|
+
}
|
|
361
330
|
if (!interactive && options.singleRequest === true) {
|
|
362
331
|
warn(
|
|
363
332
|
`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
333
|
);
|
|
334
|
+
const summary2 = buildScopeSummaryLine(plan, "requested");
|
|
335
|
+
if (summary2) info(summary2);
|
|
365
336
|
return "requested";
|
|
366
337
|
}
|
|
367
338
|
if (!interactive) {
|
|
@@ -369,10 +340,19 @@ async function confirmQueue(plan, requestedOnlyCount, options = {}) {
|
|
|
369
340
|
"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
341
|
);
|
|
371
342
|
}
|
|
372
|
-
const query = `Which scope should run? [1] Only the issues informed (${requestedOnlyCount}) [2] The whole hierarchy (${plan.issues.length}) [3] Cancel: `;
|
|
373
|
-
|
|
343
|
+
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: `;
|
|
344
|
+
const choice = await ask(
|
|
345
|
+
query,
|
|
346
|
+
options.stdin ?? process.stdin,
|
|
347
|
+
options.stdout ?? process.stdout,
|
|
348
|
+
warn,
|
|
349
|
+
container ? "container" : "plain"
|
|
350
|
+
);
|
|
351
|
+
const summary = buildScopeSummaryLine(plan, choice);
|
|
352
|
+
if (summary) info(summary);
|
|
353
|
+
return choice;
|
|
374
354
|
}
|
|
375
|
-
async function ask(query, stdin, stdout, warn) {
|
|
355
|
+
async function ask(query, stdin, stdout, warn, mode = "plain") {
|
|
376
356
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
377
357
|
const queued = [];
|
|
378
358
|
let pending = null;
|
|
@@ -414,6 +394,14 @@ async function ask(query, stdin, stdout, warn) {
|
|
|
414
394
|
return "cancel";
|
|
415
395
|
}
|
|
416
396
|
const choice = answer.trim();
|
|
397
|
+
if (mode === "container") {
|
|
398
|
+
if (choice === "" || choice === "1") return "cascade";
|
|
399
|
+
if (choice === "2") return "all";
|
|
400
|
+
if (choice === "3") return "requested";
|
|
401
|
+
if (choice === "4") return "cancel";
|
|
402
|
+
warn(`Invalid choice: '${choice}'. Enter 1, 2, 3 or 4.`);
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
417
405
|
if (choice === "1") return "requested";
|
|
418
406
|
if (choice === "" || choice === "2") return "all";
|
|
419
407
|
if (choice === "3") return "cancel";
|
|
@@ -456,6 +444,14 @@ async function discoverIssueGraph(roots, options = {}) {
|
|
|
456
444
|
}
|
|
457
445
|
|
|
458
446
|
// src/execution/queue.ts
|
|
447
|
+
function forceRequestedExecutable(plan) {
|
|
448
|
+
return {
|
|
449
|
+
...plan,
|
|
450
|
+
issues: plan.issues.map(
|
|
451
|
+
(entry) => plan.requested.includes(entry.id) ? { ...entry, role: "executable" } : entry
|
|
452
|
+
)
|
|
453
|
+
};
|
|
454
|
+
}
|
|
459
455
|
async function loadResumableQueue(planFile, requested) {
|
|
460
456
|
let plan;
|
|
461
457
|
try {
|
|
@@ -540,7 +536,30 @@ async function planQueue(input) {
|
|
|
540
536
|
return { kind: "stop", code: 1 };
|
|
541
537
|
}
|
|
542
538
|
let plan = suggestedPlan;
|
|
543
|
-
if (choice === "
|
|
539
|
+
if (choice === "cascade") {
|
|
540
|
+
const include = collectCascadeIds(graph, input.requested);
|
|
541
|
+
const cascadeOrder = computeExecutionOrder(graph, { include });
|
|
542
|
+
if (!cascadeOrder.ok) {
|
|
543
|
+
warn(`Dependency cycle between issues: ${describeCycles(cascadeOrder.cycles)}.`);
|
|
544
|
+
return { kind: "stop", code: 1 };
|
|
545
|
+
}
|
|
546
|
+
if (cascadeOrder.order.filter(
|
|
547
|
+
(id) => suggestedPlan.issues.find((entry) => entry.id === id)?.role !== "container"
|
|
548
|
+
).length === 0) {
|
|
549
|
+
warn(
|
|
550
|
+
`Issue #${input.requested[0]} is a container with no executable children in scope. Re-run with --only if you really want to implement it.`
|
|
551
|
+
);
|
|
552
|
+
return { kind: "stop", code: 1 };
|
|
553
|
+
}
|
|
554
|
+
plan = buildExecutionPlan({
|
|
555
|
+
projectId: input.projectId,
|
|
556
|
+
requested: input.requested,
|
|
557
|
+
graph,
|
|
558
|
+
order: cascadeOrder.order,
|
|
559
|
+
noBranch: input.noBranch,
|
|
560
|
+
prReview: input.prReview
|
|
561
|
+
});
|
|
562
|
+
} else if (choice === "requested") {
|
|
544
563
|
if (!requestedOrder.ok) {
|
|
545
564
|
warn(
|
|
546
565
|
`Dependency cycle between the issues you informed: ${describeCycles(requestedOrder.cycles)}.`
|
|
@@ -557,14 +576,16 @@ async function planQueue(input) {
|
|
|
557
576
|
}
|
|
558
577
|
return { kind: "single" };
|
|
559
578
|
}
|
|
560
|
-
plan =
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
579
|
+
plan = forceRequestedExecutable(
|
|
580
|
+
buildExecutionPlan({
|
|
581
|
+
projectId: input.projectId,
|
|
582
|
+
requested: input.requested,
|
|
583
|
+
graph,
|
|
584
|
+
order: requestedOrder.order,
|
|
585
|
+
noBranch: input.noBranch,
|
|
586
|
+
prReview: input.prReview
|
|
587
|
+
})
|
|
588
|
+
);
|
|
568
589
|
}
|
|
569
590
|
await saveExecutionPlan(input.planFile, plan);
|
|
570
591
|
return { kind: "queue", plan, resumed: false };
|
|
@@ -608,6 +629,34 @@ function parseIssueArguments(values) {
|
|
|
608
629
|
|
|
609
630
|
// src/ui/pipeline-renderer.ts
|
|
610
631
|
import { Listr, PRESET_TIMER, PRESET_TIMESTAMP } from "listr2";
|
|
632
|
+
|
|
633
|
+
// src/ui/status-view.ts
|
|
634
|
+
function renderExecuteFocus(snapshot, options = {}) {
|
|
635
|
+
const icons = options.icons ?? getIcons();
|
|
636
|
+
const story = activeStory(snapshot);
|
|
637
|
+
if (story === void 0 && snapshot.currentActivity === null) return [];
|
|
638
|
+
const lines = [];
|
|
639
|
+
if (story !== void 0) {
|
|
640
|
+
const duration = story.durationSeconds !== null && story.durationSeconds !== void 0 ? ` ${formatDuration(story.durationSeconds)}` : "";
|
|
641
|
+
lines.push(` ${icons.pending} ${story.id} ${stripMarkdown(story.title)}${duration}`);
|
|
642
|
+
}
|
|
643
|
+
const activity = snapshot.currentActivity;
|
|
644
|
+
if (activity?.tool) {
|
|
645
|
+
const detail = activity.detail ? ` ${stripMarkdown(activity.detail)}` : "";
|
|
646
|
+
lines.push(` ${activity.tool}${detail}`);
|
|
647
|
+
}
|
|
648
|
+
return lines;
|
|
649
|
+
}
|
|
650
|
+
function activeStory(snapshot) {
|
|
651
|
+
const fromActivity = snapshot.currentActivity?.story;
|
|
652
|
+
if (fromActivity) {
|
|
653
|
+
const match = snapshot.stories.find((story) => story.id === fromActivity);
|
|
654
|
+
if (match) return match;
|
|
655
|
+
}
|
|
656
|
+
return snapshot.stories.find((story) => story.stage === "executing");
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// src/ui/pipeline-renderer.ts
|
|
611
660
|
var PHASE_LABELS = {
|
|
612
661
|
prd: "PRD",
|
|
613
662
|
plan: "Plan",
|
|
@@ -656,6 +705,13 @@ function createActiveStoryTracker(stories, storyTasks) {
|
|
|
656
705
|
}
|
|
657
706
|
};
|
|
658
707
|
}
|
|
708
|
+
function executeExpandsStories(verbose) {
|
|
709
|
+
return verbose;
|
|
710
|
+
}
|
|
711
|
+
function paintExecuteFocus(task) {
|
|
712
|
+
const lines = renderExecuteFocus(getSessionPublisher().snapshot());
|
|
713
|
+
if (lines.length > 0) task.output = lines.join("\n");
|
|
714
|
+
}
|
|
659
715
|
function buildExecutePhaseTask(runner, tasksPath, verbose) {
|
|
660
716
|
return async (_ctx, task) => {
|
|
661
717
|
let stories;
|
|
@@ -676,6 +732,31 @@ function buildExecutePhaseTask(runner, tasksPath, verbose) {
|
|
|
676
732
|
const totalStories = stories.length;
|
|
677
733
|
const initialPassed = stories.filter((s) => s.passes).length;
|
|
678
734
|
task.title = `Execute (${initialPassed}/${totalStories} stories passing)`;
|
|
735
|
+
if (!executeExpandsStories(verbose)) {
|
|
736
|
+
setStoryUpdateCallback((updatedStories) => {
|
|
737
|
+
const passed = updatedStories.filter((s) => s.passes).length;
|
|
738
|
+
task.title = `Execute (${passed}/${totalStories} stories passing)`;
|
|
739
|
+
paintExecuteFocus(task);
|
|
740
|
+
});
|
|
741
|
+
setStoryStageCallback(() => {
|
|
742
|
+
paintExecuteFocus(task);
|
|
743
|
+
});
|
|
744
|
+
setActivityCallback(() => {
|
|
745
|
+
paintExecuteFocus(task);
|
|
746
|
+
});
|
|
747
|
+
setOutputCallback((line) => {
|
|
748
|
+
task.output = line;
|
|
749
|
+
});
|
|
750
|
+
try {
|
|
751
|
+
await runner();
|
|
752
|
+
} finally {
|
|
753
|
+
setOutputCallback(void 0);
|
|
754
|
+
setStoryUpdateCallback(void 0);
|
|
755
|
+
setStoryStageCallback(void 0);
|
|
756
|
+
setActivityCallback(void 0);
|
|
757
|
+
}
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
679
760
|
const resolvers = /* @__PURE__ */ new Map();
|
|
680
761
|
setStoryUpdateCallback((updatedStories) => {
|
|
681
762
|
const passed = updatedStories.filter((s) => s.passes).length;
|
|
@@ -742,14 +823,14 @@ function buildExecutePhaseTask(runner, tasksPath, verbose) {
|
|
|
742
823
|
};
|
|
743
824
|
}
|
|
744
825
|
async function runPipelineWithRenderer(options) {
|
|
745
|
-
const { phases, startIndex, verbose, runners, tasksPath } = options;
|
|
826
|
+
const { phases, startIndex, verbose, runners, tasksPath, phaseSuffixes } = options;
|
|
746
827
|
const overallStart = Date.now();
|
|
747
828
|
let currentPhase;
|
|
748
829
|
const renderer = selectRenderer(verbose);
|
|
749
830
|
const isSimple = renderer === "simple";
|
|
750
831
|
const tasks = new Listr(
|
|
751
832
|
phases.map((phase, index) => {
|
|
752
|
-
const label = phaseLabel(phase);
|
|
833
|
+
const label = phaseSuffixes?.[phase] ? `${phaseLabel(phase)} (${phaseSuffixes[phase]})` : phaseLabel(phase);
|
|
753
834
|
const isExecutePhase = phase === "execute" && tasksPath;
|
|
754
835
|
return {
|
|
755
836
|
title: label,
|
|
@@ -802,6 +883,7 @@ async function runPipelineWithRenderer(options) {
|
|
|
802
883
|
setOutputCallback(void 0);
|
|
803
884
|
setStoryUpdateCallback(void 0);
|
|
804
885
|
setStoryStageCallback(void 0);
|
|
886
|
+
setActivityCallback(void 0);
|
|
805
887
|
}
|
|
806
888
|
const overallElapsedSeconds = Math.floor((Date.now() - overallStart) / 1e3);
|
|
807
889
|
return {
|
|
@@ -816,6 +898,35 @@ function toIssueNumber(id) {
|
|
|
816
898
|
const parsed = Number.parseInt(id, 10);
|
|
817
899
|
return Number.isNaN(parsed) ? null : parsed;
|
|
818
900
|
}
|
|
901
|
+
async function publishInstrumentedPhaseEnd(publisher, phase, issueNumber, success, error) {
|
|
902
|
+
const at = isoNow();
|
|
903
|
+
const startedAt = publisher.snapshot().phases.find((entry) => entry.name === phase)?.startedAt;
|
|
904
|
+
const wallMs = startedAt === null || startedAt === void 0 ? null : Math.max(0, Date.parse(at) - Date.parse(startedAt));
|
|
905
|
+
const timing = await loadPhaseTiming(phase, wallMs);
|
|
906
|
+
publisher.publish({
|
|
907
|
+
type: "phase:end",
|
|
908
|
+
at,
|
|
909
|
+
phase,
|
|
910
|
+
success,
|
|
911
|
+
...error === void 0 ? {} : { error },
|
|
912
|
+
...snapshotTimingFields(timing)
|
|
913
|
+
});
|
|
914
|
+
if (isVerbose()) {
|
|
915
|
+
printInfo(
|
|
916
|
+
formatPhaseLine({
|
|
917
|
+
issueNumber: toIssueNumber(issueNumber) ?? issueNumber,
|
|
918
|
+
phase,
|
|
919
|
+
iteration: timing.iteration,
|
|
920
|
+
wallMs,
|
|
921
|
+
cliDurationMs: timing.cliDurationMs,
|
|
922
|
+
harnessStartupMs: timing.harnessStartupMs,
|
|
923
|
+
ttftMs: timing.ttftMs,
|
|
924
|
+
numTurns: timing.numTurns,
|
|
925
|
+
outputTokens: timing.outputTokens
|
|
926
|
+
})
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
819
930
|
function publishStorySeed(publisher, stories, at) {
|
|
820
931
|
if (stories.length === 0) return false;
|
|
821
932
|
publisher.publish({ type: "stories:update", at, stories: [...stories] });
|
|
@@ -846,6 +957,10 @@ var QUEUE_PR_PHASES_WITH_REVIEW = [
|
|
|
846
957
|
];
|
|
847
958
|
var RUNNABLE_QUEUE_PR_PHASES = ["pr"];
|
|
848
959
|
var RUNNABLE_QUEUE_PR_PHASES_WITH_REVIEW = ["pr", "pr-review"];
|
|
960
|
+
function verificationForSummary(value) {
|
|
961
|
+
if (value === null || value.verdict === null) return null;
|
|
962
|
+
return { verdict: value.verdict, level: value.level };
|
|
963
|
+
}
|
|
849
964
|
async function readPrReviewOutcome(issue, tasksPath, requestedChanges) {
|
|
850
965
|
const outcome = { requestedChanges, recommendation: null, reportPath: null };
|
|
851
966
|
try {
|
|
@@ -877,7 +992,15 @@ async function runPipeline(issue, mode, from, noBranch, prReview, options = {})
|
|
|
877
992
|
printError(err instanceof Error ? err.message : String(err));
|
|
878
993
|
return 1;
|
|
879
994
|
}
|
|
880
|
-
|
|
995
|
+
if (options.background === true && options.detachedChild !== true) {
|
|
996
|
+
const reason = backgroundRejection(mode);
|
|
997
|
+
if (reason !== null) {
|
|
998
|
+
printError(reason);
|
|
999
|
+
return 1;
|
|
1000
|
+
}
|
|
1001
|
+
return detachAfterConfirm(requested, noBranch, prReview, options);
|
|
1002
|
+
}
|
|
1003
|
+
const ownership = await claimRunOwnership(requested[0], options.detachedChild === true);
|
|
881
1004
|
if (!ownership.ok) {
|
|
882
1005
|
printError(
|
|
883
1006
|
`Another issue-flow run owns this project: ${describeRunLockOwner(ownership.owner)}.`
|
|
@@ -916,7 +1039,7 @@ async function ensureRepositoryWritable(phase) {
|
|
|
916
1039
|
}
|
|
917
1040
|
throw new Error(`The repository is not in a state the ${phase} phase can write to`);
|
|
918
1041
|
}
|
|
919
|
-
async function claimRunOwnership(target) {
|
|
1042
|
+
async function claimRunOwnership(target, detached = false) {
|
|
920
1043
|
let lockFile;
|
|
921
1044
|
try {
|
|
922
1045
|
lockFile = (await resolveProjectPaths()).runLockFile;
|
|
@@ -924,7 +1047,7 @@ async function claimRunOwnership(target) {
|
|
|
924
1047
|
return { ok: true, interruptedBy: null, release: async () => {
|
|
925
1048
|
} };
|
|
926
1049
|
}
|
|
927
|
-
const result = await acquireRunLock(lockFile, { target });
|
|
1050
|
+
const result = await acquireRunLock(lockFile, { target, detached });
|
|
928
1051
|
if (!result.ok) return result;
|
|
929
1052
|
return {
|
|
930
1053
|
ok: true,
|
|
@@ -933,6 +1056,7 @@ async function claimRunOwnership(target) {
|
|
|
933
1056
|
};
|
|
934
1057
|
}
|
|
935
1058
|
async function runIssueSession(issueNumber, mode, input) {
|
|
1059
|
+
resetAgentInvocationState();
|
|
936
1060
|
const paths = await resolveIssuePaths(issueNumber);
|
|
937
1061
|
const resilience = await initResilienceConfig();
|
|
938
1062
|
const configuredInactivity = resilience.watchdog?.inactivityTimeoutMs;
|
|
@@ -942,10 +1066,11 @@ async function runIssueSession(issueNumber, mode, input) {
|
|
|
942
1066
|
const webConfig = await loadWebConfig();
|
|
943
1067
|
const surfaces = [];
|
|
944
1068
|
const journalEnabled = resilience.journal?.enabled === true;
|
|
945
|
-
|
|
946
|
-
|
|
1069
|
+
const persistSnapshot = webConfig.enabled || input.runOptions?.detachedChild === true;
|
|
1070
|
+
if (persistSnapshot || journalEnabled) {
|
|
1071
|
+
await mkdir2(paths.issueDir, { recursive: true });
|
|
947
1072
|
}
|
|
948
|
-
if (
|
|
1073
|
+
if (persistSnapshot) {
|
|
949
1074
|
surfaces.push(
|
|
950
1075
|
new FilePublisher(paths.sessionFile, {
|
|
951
1076
|
logLimit: webConfig.logLimit,
|
|
@@ -962,7 +1087,7 @@ async function runIssueSession(issueNumber, mode, input) {
|
|
|
962
1087
|
})
|
|
963
1088
|
);
|
|
964
1089
|
}
|
|
965
|
-
const publisher = surfaces.length === 0 ? new
|
|
1090
|
+
const publisher = surfaces.length === 0 ? new MemoryPublisher() : surfaces.length === 1 ? surfaces[0] : new MultiPublisher(surfaces);
|
|
966
1091
|
setSessionPublisher(publisher);
|
|
967
1092
|
if (input.interruptedBy !== void 0) {
|
|
968
1093
|
const previous = input.interruptedBy;
|
|
@@ -1045,7 +1170,7 @@ async function reportIfOversized(issueNumber, paths, result) {
|
|
|
1045
1170
|
at: isoNow()
|
|
1046
1171
|
});
|
|
1047
1172
|
const reportFile = paths.decompositionFile;
|
|
1048
|
-
await
|
|
1173
|
+
await mkdir2(paths.issueDir, { recursive: true });
|
|
1049
1174
|
await writeFile(reportFile, report, "utf-8");
|
|
1050
1175
|
printWarning(`Issue #${issueNumber} looks larger than one run. Report: ${reportFile}`);
|
|
1051
1176
|
for (const signal of assessment.signals) {
|
|
@@ -1085,7 +1210,7 @@ async function maybeCreateSubIssues(issueNumber, plan, reportFile) {
|
|
|
1085
1210
|
printInfo("Nothing left to split: no pending story.");
|
|
1086
1211
|
return;
|
|
1087
1212
|
}
|
|
1088
|
-
const { runGenerate } = await import("./generate-
|
|
1213
|
+
const { runGenerate } = await import("./generate-MVQI7A45.js");
|
|
1089
1214
|
for (const proposal of proposals) {
|
|
1090
1215
|
const instruction = [
|
|
1091
1216
|
`Create a sub-issue of #${issueNumber}: ${proposal.title}.`,
|
|
@@ -1160,15 +1285,27 @@ async function runPipelinePhases(issueNumber, paths, mode, publisher, input) {
|
|
|
1160
1285
|
issueUrl: info?.issueUrl,
|
|
1161
1286
|
branch: info?.branch,
|
|
1162
1287
|
phases: [...phases],
|
|
1163
|
-
environment: {
|
|
1288
|
+
environment: {
|
|
1289
|
+
node: process.version,
|
|
1290
|
+
platform: process.platform,
|
|
1291
|
+
agent: agentSummary.defaultProvider,
|
|
1292
|
+
model: agentSummary.defaultModel
|
|
1293
|
+
}
|
|
1164
1294
|
});
|
|
1165
1295
|
};
|
|
1166
|
-
|
|
1296
|
+
const agentSummary = await describeRunAgents(
|
|
1297
|
+
prReview ? ["prd", "plan", "execute", "review", "pr", "pr-review"] : ["prd", "plan", "execute", "review", "pr"]
|
|
1298
|
+
);
|
|
1299
|
+
printInfo(
|
|
1300
|
+
`Starting pipeline for issue #${issueNumber} (mode: ${mode}, agent: ${agentSummary.label})`
|
|
1301
|
+
);
|
|
1167
1302
|
const issuesConfig = await loadIssuesConfig();
|
|
1168
1303
|
const sessionStartedAt = isoNow();
|
|
1169
1304
|
if (queue?.preChecked !== true) {
|
|
1170
|
-
|
|
1171
|
-
|
|
1305
|
+
if (isVerbose()) {
|
|
1306
|
+
printInfo("Running prerequisite checks...");
|
|
1307
|
+
}
|
|
1308
|
+
const initCode = await runInit(issuesConfig.preferredProvider, { compact: !isVerbose() });
|
|
1172
1309
|
if (initCode !== 0) {
|
|
1173
1310
|
publishSessionStart(PIPELINE_PHASES, sessionStartedAt);
|
|
1174
1311
|
publisher.publish({ type: "phase:start", at: sessionStartedAt, phase: "init" });
|
|
@@ -1328,18 +1465,17 @@ async function runPipelinePhases(issueNumber, paths, mode, publisher, input) {
|
|
|
1328
1465
|
() => runPlan(issueNumber, resolvedIssue, { continueFlag: continueNumbering, startUs }),
|
|
1329
1466
|
"plan"
|
|
1330
1467
|
)();
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
}
|
|
1468
|
+
try {
|
|
1469
|
+
const plan = await loadTaskPlan(tasksPath);
|
|
1470
|
+
publishStorySeed(publisher, plan.userStories, isoNow());
|
|
1471
|
+
if (effectiveNoBranch || effectivePrReview) {
|
|
1472
|
+
if (effectiveNoBranch) plan.noBranch = true;
|
|
1337
1473
|
if (effectivePrReview) {
|
|
1338
1474
|
plan.prReview = { ...plan.prReview, enabled: true, rounds: plan.prReview?.rounds ?? 0 };
|
|
1339
1475
|
}
|
|
1340
1476
|
await saveTaskPlan(tasksPath, plan);
|
|
1341
|
-
} catch {
|
|
1342
1477
|
}
|
|
1478
|
+
} catch {
|
|
1343
1479
|
}
|
|
1344
1480
|
if (queue !== void 0 && !effectiveNoBranch) {
|
|
1345
1481
|
producedBranch = await adoptQueueBranch(tasksPath, queue.plan.branchName);
|
|
@@ -1411,27 +1547,34 @@ async function runPipelinePhases(issueNumber, paths, mode, publisher, input) {
|
|
|
1411
1547
|
try {
|
|
1412
1548
|
await fn();
|
|
1413
1549
|
await publishGitState(publisher);
|
|
1414
|
-
|
|
1550
|
+
await publishInstrumentedPhaseEnd(publisher, phase, issueNumber, true);
|
|
1415
1551
|
} catch (err) {
|
|
1416
1552
|
await publishGitState(publisher);
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
at: isoNow(),
|
|
1553
|
+
await publishInstrumentedPhaseEnd(
|
|
1554
|
+
publisher,
|
|
1420
1555
|
phase,
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1556
|
+
issueNumber,
|
|
1557
|
+
false,
|
|
1558
|
+
err instanceof Error ? err.message : String(err)
|
|
1559
|
+
);
|
|
1424
1560
|
throw err;
|
|
1425
1561
|
}
|
|
1426
1562
|
}
|
|
1427
1563
|
])
|
|
1428
1564
|
);
|
|
1565
|
+
const phaseSuffixes = {};
|
|
1566
|
+
for (const [phase, resolved] of Object.entries(agentSummary.byPhase)) {
|
|
1567
|
+
if (resolved.provider !== agentSummary.defaultProvider) {
|
|
1568
|
+
phaseSuffixes[phase] = resolved.model ? `${resolved.provider} \xB7 ${resolved.model}` : resolved.provider;
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1429
1571
|
const result = await runPipelineWithRenderer({
|
|
1430
1572
|
phases: phaseOrder,
|
|
1431
1573
|
startIndex: startIdx,
|
|
1432
1574
|
verbose: isVerbose(),
|
|
1433
1575
|
runners: instrumentedRunners,
|
|
1434
|
-
tasksPath
|
|
1576
|
+
tasksPath,
|
|
1577
|
+
phaseSuffixes
|
|
1435
1578
|
});
|
|
1436
1579
|
if (!result.success) {
|
|
1437
1580
|
printError(`Phase ${result.failedPhase} failed`);
|
|
@@ -1512,7 +1655,8 @@ async function runPipelinePhases(issueNumber, paths, mode, publisher, input) {
|
|
|
1512
1655
|
prReview: review,
|
|
1513
1656
|
// Process-owned counters: the session snapshot is empty whenever web
|
|
1514
1657
|
// monitoring is off, so it cannot be the source of these totals.
|
|
1515
|
-
usage: getRunUsageTotals()
|
|
1658
|
+
usage: getRunUsageTotals(),
|
|
1659
|
+
verification: verificationForSummary(getSessionPublisher().snapshot().verification)
|
|
1516
1660
|
});
|
|
1517
1661
|
return {
|
|
1518
1662
|
code: 0,
|
|
@@ -1522,6 +1666,37 @@ async function runPipelinePhases(issueNumber, paths, mode, publisher, input) {
|
|
|
1522
1666
|
elapsedSeconds: result.overallElapsedSeconds
|
|
1523
1667
|
};
|
|
1524
1668
|
}
|
|
1669
|
+
async function detachAfterConfirm(requested, noBranch, prReview, options) {
|
|
1670
|
+
const childFlags = [...process.argv.slice(2)];
|
|
1671
|
+
if (options.yes !== true && options.only !== true) {
|
|
1672
|
+
try {
|
|
1673
|
+
const issuesConfig = await loadIssuesConfig();
|
|
1674
|
+
const resolution = await resolveCommandIssue(requested[0], void 0, {
|
|
1675
|
+
config: issuesConfig
|
|
1676
|
+
});
|
|
1677
|
+
if (resolution.ok) {
|
|
1678
|
+
const decision = await decideQueue({
|
|
1679
|
+
requested,
|
|
1680
|
+
resolved: resolution.resolved,
|
|
1681
|
+
noBranch: noBranch ?? false,
|
|
1682
|
+
prReview: prReview ?? false,
|
|
1683
|
+
runOptions: options
|
|
1684
|
+
});
|
|
1685
|
+
if (decision.kind === "stop") return decision.code;
|
|
1686
|
+
if (decision.kind === "queue" && !childFlags.includes("--yes")) childFlags.push("--yes");
|
|
1687
|
+
if (decision.kind === "single" && !childFlags.includes("--only")) childFlags.push("--only");
|
|
1688
|
+
}
|
|
1689
|
+
} catch {
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
const paths = await resolveIssuePaths(requested[0]);
|
|
1693
|
+
const { pid, logFile } = await spawnDetachedRun({ paths, argv: childFlags });
|
|
1694
|
+
printSuccess(`Detached run started (pid ${pid}).`);
|
|
1695
|
+
printInfo(`Log: ${logFile}`);
|
|
1696
|
+
printInfo("Follow with: issue-flow ps");
|
|
1697
|
+
printInfo(`Stop with: kill ${pid}`);
|
|
1698
|
+
return 0;
|
|
1699
|
+
}
|
|
1525
1700
|
async function decideQueue(input) {
|
|
1526
1701
|
const primary = input.requested[0];
|
|
1527
1702
|
const single = input.requested.length === 1;
|
|
@@ -1541,7 +1716,11 @@ async function decideQueue(input) {
|
|
|
1541
1716
|
planFile: queuePaths.planFile,
|
|
1542
1717
|
noBranch: input.noBranch,
|
|
1543
1718
|
prReview: input.prReview,
|
|
1544
|
-
confirm: {
|
|
1719
|
+
confirm: {
|
|
1720
|
+
yes: input.runOptions.yes,
|
|
1721
|
+
only: input.runOptions.only,
|
|
1722
|
+
cascade: input.runOptions.cascade
|
|
1723
|
+
}
|
|
1545
1724
|
});
|
|
1546
1725
|
} catch (err) {
|
|
1547
1726
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -1760,7 +1939,10 @@ async function buildPrQueueContext(plan) {
|
|
|
1760
1939
|
number: entry.number,
|
|
1761
1940
|
title: entry.title,
|
|
1762
1941
|
url: entry.url,
|
|
1763
|
-
source: entry.source
|
|
1942
|
+
source: entry.source,
|
|
1943
|
+
parent: entry.parent,
|
|
1944
|
+
role: entry.role,
|
|
1945
|
+
complete: entry.status === "completed"
|
|
1764
1946
|
})),
|
|
1765
1947
|
excluded: plan.excluded.map((entry) => ({
|
|
1766
1948
|
id: entry.id,
|
|
@@ -1829,4 +2011,4 @@ export {
|
|
|
1829
2011
|
publishIssueDetails,
|
|
1830
2012
|
runPipeline
|
|
1831
2013
|
};
|
|
1832
|
-
//# sourceMappingURL=chunk-
|
|
2014
|
+
//# sourceMappingURL=chunk-OPNGPUKJ.js.map
|