issue-flow 0.7.7 → 0.9.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 +16 -0
- package/dist/{analyze-2RVGY5OK.js → analyze-VSWAEVN3.js} +6 -6
- package/dist/{chunk-WO6ERABE.js → chunk-57MH6RYJ.js} +106 -7
- package/dist/chunk-57MH6RYJ.js.map +1 -0
- package/dist/{chunk-6UTVPKNF.js → chunk-66ZGYXTH.js} +53 -9
- package/dist/chunk-66ZGYXTH.js.map +1 -0
- package/dist/{chunk-WGDLUPSF.js → chunk-DP43WJY4.js} +75 -11
- package/dist/chunk-DP43WJY4.js.map +1 -0
- package/dist/{chunk-ZYBYMEWG.js → chunk-DTNM3LTO.js} +33 -10
- package/dist/chunk-DTNM3LTO.js.map +1 -0
- package/dist/{chunk-KHTLN54U.js → chunk-JGKPSWOA.js} +2 -2
- package/dist/{chunk-EYVXLRRD.js → chunk-KXAG24DD.js} +219 -3
- package/dist/chunk-KXAG24DD.js.map +1 -0
- package/dist/{chunk-XPW46TI5.js → chunk-M37YZYDO.js} +3 -3
- package/dist/{chunk-G37JNQKB.js → chunk-M72ZEPWD.js} +2 -2
- package/dist/{chunk-RUTDK6ZF.js → chunk-MTJQOOMG.js} +105 -4
- package/dist/chunk-MTJQOOMG.js.map +1 -0
- package/dist/{chunk-OPIDR2DC.js → chunk-NB6WKTBL.js} +2 -2
- package/dist/chunk-TJMZWRS6.js +265 -0
- package/dist/chunk-TJMZWRS6.js.map +1 -0
- package/dist/{chunk-6IOPYFUM.js → chunk-TLSIK656.js} +6 -6
- package/dist/{chunk-JOK7VXRV.js → chunk-VNKSMWDN.js} +17 -3
- package/dist/chunk-VNKSMWDN.js.map +1 -0
- package/dist/{chunk-6L2BPOE3.js → chunk-WHEU55ZP.js} +7 -7
- package/dist/{chunk-EOTULFWV.js → chunk-X7XBF5TS.js} +6 -6
- package/dist/cli.js +91 -29
- package/dist/cli.js.map +1 -1
- package/dist/{config-OAZG7KIK.js → config-J5DW6HSY.js} +3 -3
- package/dist/execute-Z2DKGVLR.js +14 -0
- package/dist/{generate-BEJF3SVJ.js → generate-ERDPOTKX.js} +6 -6
- package/dist/init-UY6ZCIFT.js +9 -0
- package/dist/plan-REMAMCX4.js +16 -0
- package/dist/pr-KERP52TI.js +20 -0
- package/dist/pr-review-IU4N3TEK.js +16 -0
- package/dist/prd-SHVQW6RM.js +16 -0
- package/dist/review-L5F77QAC.js +14 -0
- package/dist/run-BRBFHVUO.js +1860 -0
- package/dist/run-BRBFHVUO.js.map +1 -0
- package/dist/{web-3SBYL7QJ.js → web-WQISLO66.js} +4 -4
- package/package.json +1 -1
- package/prompts/execute.md +2 -2
- package/prompts/plan.md +10 -1
- package/prompts/pr.md +9 -4
- package/web/CLAUDE.md +70 -8
- package/web/public/app.css +444 -0
- package/web/public/app.js +573 -17
- package/web/public/index.html +50 -0
- package/dist/chunk-3E6KASSE.js +0 -123
- package/dist/chunk-3E6KASSE.js.map +0 -1
- package/dist/chunk-6UTVPKNF.js.map +0 -1
- package/dist/chunk-EYVXLRRD.js.map +0 -1
- package/dist/chunk-JOK7VXRV.js.map +0 -1
- package/dist/chunk-RUTDK6ZF.js.map +0 -1
- package/dist/chunk-WGDLUPSF.js.map +0 -1
- package/dist/chunk-WO6ERABE.js.map +0 -1
- package/dist/chunk-ZYBYMEWG.js.map +0 -1
- package/dist/execute-AOEM52CD.js +0 -14
- package/dist/init-TBC3WI74.js +0 -9
- package/dist/plan-YDZYB2BO.js +0 -16
- package/dist/pr-SIXMJIZF.js +0 -16
- package/dist/pr-review-AY56ZPJR.js +0 -16
- package/dist/prd-MBSSBLGO.js +0 -16
- package/dist/review-2MDDHZ4X.js +0 -14
- package/dist/run-SEB6F3W4.js +0 -733
- package/dist/run-SEB6F3W4.js.map +0 -1
- /package/dist/{analyze-2RVGY5OK.js.map → analyze-VSWAEVN3.js.map} +0 -0
- /package/dist/{chunk-KHTLN54U.js.map → chunk-JGKPSWOA.js.map} +0 -0
- /package/dist/{chunk-XPW46TI5.js.map → chunk-M37YZYDO.js.map} +0 -0
- /package/dist/{chunk-G37JNQKB.js.map → chunk-M72ZEPWD.js.map} +0 -0
- /package/dist/{chunk-OPIDR2DC.js.map → chunk-NB6WKTBL.js.map} +0 -0
- /package/dist/{chunk-6IOPYFUM.js.map → chunk-TLSIK656.js.map} +0 -0
- /package/dist/{chunk-6L2BPOE3.js.map → chunk-WHEU55ZP.js.map} +0 -0
- /package/dist/{chunk-EOTULFWV.js.map → chunk-X7XBF5TS.js.map} +0 -0
- /package/dist/{config-OAZG7KIK.js.map → config-J5DW6HSY.js.map} +0 -0
- /package/dist/{execute-AOEM52CD.js.map → execute-Z2DKGVLR.js.map} +0 -0
- /package/dist/{generate-BEJF3SVJ.js.map → generate-ERDPOTKX.js.map} +0 -0
- /package/dist/{init-TBC3WI74.js.map → init-UY6ZCIFT.js.map} +0 -0
- /package/dist/{plan-YDZYB2BO.js.map → plan-REMAMCX4.js.map} +0 -0
- /package/dist/{pr-SIXMJIZF.js.map → pr-KERP52TI.js.map} +0 -0
- /package/dist/{pr-review-AY56ZPJR.js.map → pr-review-IU4N3TEK.js.map} +0 -0
- /package/dist/{prd-MBSSBLGO.js.map → prd-SHVQW6RM.js.map} +0 -0
- /package/dist/{review-2MDDHZ4X.js.map → review-L5F77QAC.js.map} +0 -0
- /package/dist/{web-3SBYL7QJ.js.map → web-WQISLO66.js.map} +0 -0
package/dist/run-SEB6F3W4.js
DELETED
|
@@ -1,733 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
runPrd
|
|
4
|
-
} from "./chunk-6L2BPOE3.js";
|
|
5
|
-
import {
|
|
6
|
-
runReview
|
|
7
|
-
} from "./chunk-EOTULFWV.js";
|
|
8
|
-
import {
|
|
9
|
-
ensureWebMonitor
|
|
10
|
-
} from "./chunk-JOK7VXRV.js";
|
|
11
|
-
import {
|
|
12
|
-
runInit
|
|
13
|
-
} from "./chunk-G37JNQKB.js";
|
|
14
|
-
import {
|
|
15
|
-
printRunSummary,
|
|
16
|
-
runExecute
|
|
17
|
-
} from "./chunk-WGDLUPSF.js";
|
|
18
|
-
import {
|
|
19
|
-
runPlan
|
|
20
|
-
} from "./chunk-3E6KASSE.js";
|
|
21
|
-
import {
|
|
22
|
-
runPr
|
|
23
|
-
} from "./chunk-6UTVPKNF.js";
|
|
24
|
-
import "./chunk-OPIDR2DC.js";
|
|
25
|
-
import {
|
|
26
|
-
getProvider,
|
|
27
|
-
resolveCommandIssue
|
|
28
|
-
} from "./chunk-EYVXLRRD.js";
|
|
29
|
-
import "./chunk-HDV3YDTG.js";
|
|
30
|
-
import {
|
|
31
|
-
mostRecent,
|
|
32
|
-
prReviewDir,
|
|
33
|
-
readPrReviewIndex,
|
|
34
|
-
runPrReview
|
|
35
|
-
} from "./chunk-6IOPYFUM.js";
|
|
36
|
-
import {
|
|
37
|
-
listPullRequests,
|
|
38
|
-
publishGitState
|
|
39
|
-
} from "./chunk-XPW46TI5.js";
|
|
40
|
-
import "./chunk-KHTLN54U.js";
|
|
41
|
-
import {
|
|
42
|
-
getRunUsageTotals
|
|
43
|
-
} from "./chunk-ZYBYMEWG.js";
|
|
44
|
-
import "./chunk-SLHZPB3C.js";
|
|
45
|
-
import {
|
|
46
|
-
loadIssuesConfig,
|
|
47
|
-
loadWebConfig,
|
|
48
|
-
resolveIssuePaths
|
|
49
|
-
} from "./chunk-RUTDK6ZF.js";
|
|
50
|
-
import {
|
|
51
|
-
FilePublisher,
|
|
52
|
-
NullPublisher,
|
|
53
|
-
isVerbose,
|
|
54
|
-
isoNow,
|
|
55
|
-
loadTaskPlan,
|
|
56
|
-
printError,
|
|
57
|
-
printInfo,
|
|
58
|
-
printWarning,
|
|
59
|
-
saveTaskPlan,
|
|
60
|
-
setOutputCallback,
|
|
61
|
-
setSessionPublisher,
|
|
62
|
-
setStoryUpdateCallback
|
|
63
|
-
} from "./chunk-WO6ERABE.js";
|
|
64
|
-
|
|
65
|
-
// src/commands/run.ts
|
|
66
|
-
import { randomUUID } from "crypto";
|
|
67
|
-
import { mkdir } from "fs/promises";
|
|
68
|
-
import { join } from "path";
|
|
69
|
-
import { execa } from "execa";
|
|
70
|
-
|
|
71
|
-
// src/core/pipeline.ts
|
|
72
|
-
var PIPELINE_PHASES = ["init", "prd", "plan", "execute", "review", "pr"];
|
|
73
|
-
var PIPELINE_PHASES_WITH_PR_REVIEW = [...PIPELINE_PHASES, "pr-review"];
|
|
74
|
-
var PIPELINE_PHASES_NO_BRANCH = PIPELINE_PHASES.filter(
|
|
75
|
-
(p) => p !== "pr"
|
|
76
|
-
);
|
|
77
|
-
var PHASE_TO_FIELD = {
|
|
78
|
-
init: null,
|
|
79
|
-
prd: "prdCompleted",
|
|
80
|
-
plan: "jsonCompleted",
|
|
81
|
-
execute: "executionCompleted",
|
|
82
|
-
review: "reviewCompleted",
|
|
83
|
-
pr: "prCreated",
|
|
84
|
-
"pr-review": "prReviewCompleted"
|
|
85
|
-
};
|
|
86
|
-
var PipelineManager = class {
|
|
87
|
-
tasksJsonPath;
|
|
88
|
-
plan;
|
|
89
|
-
activePhases;
|
|
90
|
-
constructor(plan, tasksJsonPath, activePhases) {
|
|
91
|
-
this.plan = plan;
|
|
92
|
-
this.tasksJsonPath = tasksJsonPath;
|
|
93
|
-
this.activePhases = activePhases ?? PIPELINE_PHASES;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Reload state from disk.
|
|
97
|
-
*/
|
|
98
|
-
async reload() {
|
|
99
|
-
this.plan = await loadTaskPlan(this.tasksJsonPath);
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Check if a phase is complete.
|
|
103
|
-
*/
|
|
104
|
-
isPhaseComplete(phase) {
|
|
105
|
-
const field = PHASE_TO_FIELD[phase];
|
|
106
|
-
if (field === null) return true;
|
|
107
|
-
return this.plan.pipeline?.[field] ?? false;
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Get the first incomplete phase in the pipeline.
|
|
111
|
-
* Returns null if all phases are complete.
|
|
112
|
-
*/
|
|
113
|
-
getNextPhase() {
|
|
114
|
-
for (const phase of this.activePhases) {
|
|
115
|
-
if (!this.isPhaseComplete(phase)) {
|
|
116
|
-
return phase;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Get the current phase (alias for getNextPhase).
|
|
123
|
-
*/
|
|
124
|
-
getCurrentPhase() {
|
|
125
|
-
return this.getNextPhase();
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Check whether we can resume from a specific phase.
|
|
129
|
-
* All prerequisite phases must be complete.
|
|
130
|
-
*/
|
|
131
|
-
canResume(fromPhase) {
|
|
132
|
-
const idx = this.activePhases.indexOf(fromPhase);
|
|
133
|
-
if (idx < 0) return false;
|
|
134
|
-
for (let i = 0; i < idx; i++) {
|
|
135
|
-
if (!this.isPhaseComplete(this.activePhases[i])) {
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return true;
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Mark a phase as complete and persist to disk.
|
|
143
|
-
*/
|
|
144
|
-
async markPhaseComplete(phase) {
|
|
145
|
-
const field = PHASE_TO_FIELD[phase];
|
|
146
|
-
if (field === null) return;
|
|
147
|
-
this.plan = {
|
|
148
|
-
...this.plan,
|
|
149
|
-
pipeline: {
|
|
150
|
-
...this.plan.pipeline ?? {
|
|
151
|
-
prdCompleted: false,
|
|
152
|
-
jsonCompleted: false,
|
|
153
|
-
executionCompleted: false,
|
|
154
|
-
reviewCompleted: false,
|
|
155
|
-
prCreated: false
|
|
156
|
-
},
|
|
157
|
-
[field]: true
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
await saveTaskPlan(this.tasksJsonPath, this.plan);
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
// src/ui/pipeline-renderer.ts
|
|
165
|
-
import { Listr, PRESET_TIMER, PRESET_TIMESTAMP } from "listr2";
|
|
166
|
-
var PHASE_LABELS = {
|
|
167
|
-
prd: "PRD",
|
|
168
|
-
plan: "Plan",
|
|
169
|
-
execute: "Execute",
|
|
170
|
-
review: "Review",
|
|
171
|
-
pr: "PR",
|
|
172
|
-
"pr-review": "PR Review"
|
|
173
|
-
};
|
|
174
|
-
function phaseLabel(phase) {
|
|
175
|
-
return PHASE_LABELS[phase] ?? phase.charAt(0).toUpperCase() + phase.slice(1);
|
|
176
|
-
}
|
|
177
|
-
function selectRenderer(verbose) {
|
|
178
|
-
const isTTY = !!process.stdout.isTTY;
|
|
179
|
-
const isCI = !!process.env.CI;
|
|
180
|
-
if (!isTTY || isCI) {
|
|
181
|
-
return "simple";
|
|
182
|
-
}
|
|
183
|
-
return verbose ? "verbose" : "default";
|
|
184
|
-
}
|
|
185
|
-
function buildExecutePhaseTask(runner, tasksPath, verbose) {
|
|
186
|
-
return async (_ctx, task) => {
|
|
187
|
-
let stories;
|
|
188
|
-
try {
|
|
189
|
-
const plan = await loadTaskPlan(tasksPath);
|
|
190
|
-
stories = [...plan.userStories].sort((a, b) => a.priority - b.priority);
|
|
191
|
-
} catch {
|
|
192
|
-
setOutputCallback((line) => {
|
|
193
|
-
task.output = line;
|
|
194
|
-
});
|
|
195
|
-
try {
|
|
196
|
-
await runner();
|
|
197
|
-
} finally {
|
|
198
|
-
setOutputCallback(void 0);
|
|
199
|
-
}
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
const totalStories = stories.length;
|
|
203
|
-
const initialPassed = stories.filter((s) => s.passes).length;
|
|
204
|
-
task.title = `Execute (${initialPassed}/${totalStories} stories passing)`;
|
|
205
|
-
const resolvers = /* @__PURE__ */ new Map();
|
|
206
|
-
setStoryUpdateCallback((updatedStories) => {
|
|
207
|
-
const passed = updatedStories.filter((s) => s.passes).length;
|
|
208
|
-
task.title = `Execute (${passed}/${totalStories} stories passing)`;
|
|
209
|
-
for (const s of updatedStories) {
|
|
210
|
-
if (s.passes && resolvers.has(s.id)) {
|
|
211
|
-
resolvers.get(s.id).resolve();
|
|
212
|
-
resolvers.delete(s.id);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
const subtaskDefs = stories.map((story) => ({
|
|
217
|
-
title: `${story.id}: ${story.title}`,
|
|
218
|
-
skip: story.passes ? "already passing" : false,
|
|
219
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
220
|
-
task: async () => {
|
|
221
|
-
await new Promise((resolve, reject) => {
|
|
222
|
-
resolvers.set(story.id, { resolve, reject });
|
|
223
|
-
});
|
|
224
|
-
},
|
|
225
|
-
rendererOptions: {
|
|
226
|
-
timer: PRESET_TIMER
|
|
227
|
-
}
|
|
228
|
-
}));
|
|
229
|
-
const engineSubtask = {
|
|
230
|
-
title: "Running engine",
|
|
231
|
-
task: async (_c, engineTask) => {
|
|
232
|
-
setOutputCallback((line) => {
|
|
233
|
-
engineTask.output = line;
|
|
234
|
-
});
|
|
235
|
-
try {
|
|
236
|
-
await runner();
|
|
237
|
-
} finally {
|
|
238
|
-
setOutputCallback(void 0);
|
|
239
|
-
setStoryUpdateCallback(void 0);
|
|
240
|
-
for (const [, { resolve }] of resolvers) {
|
|
241
|
-
resolve();
|
|
242
|
-
}
|
|
243
|
-
resolvers.clear();
|
|
244
|
-
}
|
|
245
|
-
},
|
|
246
|
-
rendererOptions: {
|
|
247
|
-
timer: PRESET_TIMER,
|
|
248
|
-
outputBar: verbose ? Infinity : false,
|
|
249
|
-
persistentOutput: false,
|
|
250
|
-
bottomBar: verbose ? Infinity : 3
|
|
251
|
-
}
|
|
252
|
-
};
|
|
253
|
-
return task.newListr([engineSubtask, ...subtaskDefs], {
|
|
254
|
-
concurrent: true,
|
|
255
|
-
exitOnError: true,
|
|
256
|
-
rendererOptions: {
|
|
257
|
-
timer: PRESET_TIMER,
|
|
258
|
-
collapseSkips: false
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
async function runPipelineWithRenderer(options) {
|
|
264
|
-
const { phases, startIndex, verbose, runners, tasksPath } = options;
|
|
265
|
-
const overallStart = Date.now();
|
|
266
|
-
let currentPhase;
|
|
267
|
-
const renderer = selectRenderer(verbose);
|
|
268
|
-
const isSimple = renderer === "simple";
|
|
269
|
-
const tasks = new Listr(
|
|
270
|
-
phases.map((phase, index) => {
|
|
271
|
-
const label = phaseLabel(phase);
|
|
272
|
-
const isExecutePhase = phase === "execute" && tasksPath;
|
|
273
|
-
return {
|
|
274
|
-
title: label,
|
|
275
|
-
skip: index < startIndex ? "skipped" : false,
|
|
276
|
-
task: isExecutePhase ? async (_ctx, task) => {
|
|
277
|
-
currentPhase = phase;
|
|
278
|
-
const runner = runners[phase];
|
|
279
|
-
if (!runner) {
|
|
280
|
-
throw new Error(`No runner defined for phase: ${phase}`);
|
|
281
|
-
}
|
|
282
|
-
return buildExecutePhaseTask(runner, tasksPath, verbose)(_ctx, task);
|
|
283
|
-
} : async (_ctx, task) => {
|
|
284
|
-
currentPhase = phase;
|
|
285
|
-
const runner = runners[phase];
|
|
286
|
-
if (!runner) {
|
|
287
|
-
throw new Error(`No runner defined for phase: ${phase}`);
|
|
288
|
-
}
|
|
289
|
-
setOutputCallback((line) => {
|
|
290
|
-
task.output = line;
|
|
291
|
-
});
|
|
292
|
-
try {
|
|
293
|
-
await runner();
|
|
294
|
-
} finally {
|
|
295
|
-
setOutputCallback(void 0);
|
|
296
|
-
}
|
|
297
|
-
},
|
|
298
|
-
rendererOptions: {
|
|
299
|
-
timer: PRESET_TIMER,
|
|
300
|
-
outputBar: verbose ? Infinity : false,
|
|
301
|
-
persistentOutput: false
|
|
302
|
-
}
|
|
303
|
-
};
|
|
304
|
-
}),
|
|
305
|
-
{
|
|
306
|
-
renderer,
|
|
307
|
-
rendererOptions: {
|
|
308
|
-
timer: PRESET_TIMER,
|
|
309
|
-
collapseSkips: false,
|
|
310
|
-
...isSimple ? { timestamp: PRESET_TIMESTAMP } : {}
|
|
311
|
-
},
|
|
312
|
-
exitOnError: true
|
|
313
|
-
}
|
|
314
|
-
);
|
|
315
|
-
let failedPhase;
|
|
316
|
-
try {
|
|
317
|
-
await tasks.run();
|
|
318
|
-
} catch {
|
|
319
|
-
failedPhase = currentPhase ?? phases[startIndex];
|
|
320
|
-
} finally {
|
|
321
|
-
setOutputCallback(void 0);
|
|
322
|
-
setStoryUpdateCallback(void 0);
|
|
323
|
-
}
|
|
324
|
-
const overallElapsedSeconds = Math.floor((Date.now() - overallStart) / 1e3);
|
|
325
|
-
return {
|
|
326
|
-
success: !failedPhase,
|
|
327
|
-
failedPhase,
|
|
328
|
-
overallElapsedSeconds
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// src/commands/run.ts
|
|
333
|
-
function toIssueNumber(id) {
|
|
334
|
-
const parsed = Number.parseInt(id, 10);
|
|
335
|
-
return Number.isNaN(parsed) ? null : parsed;
|
|
336
|
-
}
|
|
337
|
-
function publishStorySeed(publisher, stories, at) {
|
|
338
|
-
if (stories.length === 0) return false;
|
|
339
|
-
publisher.publish({ type: "stories:update", at, stories: [...stories] });
|
|
340
|
-
return true;
|
|
341
|
-
}
|
|
342
|
-
function publishIssueDetails(publisher, issue, at) {
|
|
343
|
-
publisher.publish({
|
|
344
|
-
type: "issue:update",
|
|
345
|
-
at,
|
|
346
|
-
number: issue.number,
|
|
347
|
-
// Left undefined (rather than null) when the origin has no remote, so the
|
|
348
|
-
// reducer keeps whatever URL session:start already published.
|
|
349
|
-
url: issue.remoteRef ?? void 0,
|
|
350
|
-
title: issue.title,
|
|
351
|
-
description: issue.body,
|
|
352
|
-
labels: issue.labels,
|
|
353
|
-
state: issue.state
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
var RUNNABLE_PHASES = ["prd", "plan", "execute", "review", "pr"];
|
|
357
|
-
var RUNNABLE_PHASES_NO_BRANCH = ["prd", "plan", "execute", "review"];
|
|
358
|
-
var RUNNABLE_PHASES_WITH_PR_REVIEW = [...RUNNABLE_PHASES, "pr-review"];
|
|
359
|
-
async function readPrReviewOutcome(issue, tasksPath, requestedChanges) {
|
|
360
|
-
const outcome = { requestedChanges, recommendation: null, reportPath: null };
|
|
361
|
-
try {
|
|
362
|
-
const plan = await loadTaskPlan(tasksPath);
|
|
363
|
-
outcome.recommendation = plan.prReview?.lastRecommendation ?? null;
|
|
364
|
-
const pullRequest = plan.prReview?.pullRequestNumber;
|
|
365
|
-
if (pullRequest !== void 0) {
|
|
366
|
-
const dir = await prReviewDir({ issue, pullRequest });
|
|
367
|
-
const index = await readPrReviewIndex(dir);
|
|
368
|
-
const last = index?.rounds.reduce(
|
|
369
|
-
(latest, entry) => latest === null || entry.round > latest.round ? entry : latest,
|
|
370
|
-
null
|
|
371
|
-
);
|
|
372
|
-
if (last) {
|
|
373
|
-
outcome.reportPath = join(dir, last.reportPath);
|
|
374
|
-
outcome.recommendation ??= last.recommendation;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
} catch {
|
|
378
|
-
}
|
|
379
|
-
return outcome;
|
|
380
|
-
}
|
|
381
|
-
async function runPipeline(issue, mode, from, noBranch, prReview) {
|
|
382
|
-
const issueNumber = issue.replace(/^#/, "");
|
|
383
|
-
const paths = await resolveIssuePaths(issueNumber);
|
|
384
|
-
const webConfig = await loadWebConfig();
|
|
385
|
-
let publisher = new NullPublisher();
|
|
386
|
-
if (webConfig.enabled) {
|
|
387
|
-
await mkdir(paths.issueDir, { recursive: true });
|
|
388
|
-
publisher = new FilePublisher(paths.sessionFile, {
|
|
389
|
-
logLimit: webConfig.logLimit,
|
|
390
|
-
includeLogs: webConfig.includeLogs
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
setSessionPublisher(publisher);
|
|
394
|
-
if (webConfig.enabled) {
|
|
395
|
-
await ensureWebMonitor({
|
|
396
|
-
publisher,
|
|
397
|
-
port: webConfig.port,
|
|
398
|
-
host: webConfig.host,
|
|
399
|
-
refreshSeconds: webConfig.refreshSeconds
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
let exitCode = 1;
|
|
403
|
-
try {
|
|
404
|
-
exitCode = await runPipelinePhases(
|
|
405
|
-
issueNumber,
|
|
406
|
-
paths,
|
|
407
|
-
mode,
|
|
408
|
-
publisher,
|
|
409
|
-
from,
|
|
410
|
-
noBranch,
|
|
411
|
-
prReview
|
|
412
|
-
);
|
|
413
|
-
return exitCode;
|
|
414
|
-
} finally {
|
|
415
|
-
publisher.publish({
|
|
416
|
-
type: "session:end",
|
|
417
|
-
at: isoNow(),
|
|
418
|
-
status: exitCode === 0 ? "completed" : "failed"
|
|
419
|
-
});
|
|
420
|
-
await publisher.close();
|
|
421
|
-
setSessionPublisher(void 0);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
async function runPipelinePhases(issueNumber, paths, mode, publisher, from, noBranch, prReview) {
|
|
425
|
-
const tasksPath = paths.tasksFile;
|
|
426
|
-
const sessionId = randomUUID();
|
|
427
|
-
let publishedIssueNumber = toIssueNumber(issueNumber);
|
|
428
|
-
const publishSessionStart = (phases, at, info) => {
|
|
429
|
-
publisher.publish({
|
|
430
|
-
type: "session:start",
|
|
431
|
-
at,
|
|
432
|
-
sessionId,
|
|
433
|
-
issueNumber: publishedIssueNumber,
|
|
434
|
-
issueUrl: info?.issueUrl,
|
|
435
|
-
branch: info?.branch,
|
|
436
|
-
phases: [...phases],
|
|
437
|
-
environment: { node: process.version, platform: process.platform }
|
|
438
|
-
});
|
|
439
|
-
};
|
|
440
|
-
printInfo(`Starting pipeline for issue #${issueNumber} (mode: ${mode})`);
|
|
441
|
-
const issuesConfig = await loadIssuesConfig();
|
|
442
|
-
printInfo("Running prerequisite checks...");
|
|
443
|
-
const sessionStartedAt = isoNow();
|
|
444
|
-
const initCode = await runInit(issuesConfig.preferredProvider);
|
|
445
|
-
if (initCode !== 0) {
|
|
446
|
-
publishSessionStart(PIPELINE_PHASES, sessionStartedAt);
|
|
447
|
-
publisher.publish({ type: "phase:start", at: sessionStartedAt, phase: "init" });
|
|
448
|
-
publisher.publish({
|
|
449
|
-
type: "phase:end",
|
|
450
|
-
at: isoNow(),
|
|
451
|
-
phase: "init",
|
|
452
|
-
success: false,
|
|
453
|
-
error: "Prerequisites not met"
|
|
454
|
-
});
|
|
455
|
-
printError("Prerequisites not met. Fix the issues above and try again.");
|
|
456
|
-
return 1;
|
|
457
|
-
}
|
|
458
|
-
const resolution = await resolveCommandIssue(issueNumber, void 0, { config: issuesConfig });
|
|
459
|
-
if (!resolution.ok) {
|
|
460
|
-
return resolution.code;
|
|
461
|
-
}
|
|
462
|
-
const resolvedIssue = resolution.resolved;
|
|
463
|
-
publishedIssueNumber = resolvedIssue.issue.number;
|
|
464
|
-
let effectiveNoBranch = noBranch ?? false;
|
|
465
|
-
let effectivePrReview = prReview ?? false;
|
|
466
|
-
let planIssueUrl;
|
|
467
|
-
let planBranch;
|
|
468
|
-
let planStories = [];
|
|
469
|
-
try {
|
|
470
|
-
const existingPlan = await loadTaskPlan(tasksPath);
|
|
471
|
-
const persistedNoBranch = existingPlan.noBranch ?? false;
|
|
472
|
-
planIssueUrl = existingPlan.issueUrl || void 0;
|
|
473
|
-
planBranch = existingPlan.branchName || void 0;
|
|
474
|
-
planStories = existingPlan.userStories;
|
|
475
|
-
effectivePrReview = prReview ?? existingPlan.prReview?.enabled ?? false;
|
|
476
|
-
if (noBranch !== void 0 && noBranch !== persistedNoBranch) {
|
|
477
|
-
if (persistedNoBranch) {
|
|
478
|
-
printWarning(
|
|
479
|
-
"This pipeline was started with --no-branch. Ignoring current flag; using persisted mode."
|
|
480
|
-
);
|
|
481
|
-
} else {
|
|
482
|
-
printWarning(
|
|
483
|
-
"This pipeline was started without --no-branch. Ignoring current flag; using persisted mode."
|
|
484
|
-
);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
effectiveNoBranch = persistedNoBranch;
|
|
488
|
-
} catch {
|
|
489
|
-
}
|
|
490
|
-
if (effectiveNoBranch && effectivePrReview) {
|
|
491
|
-
printWarning(
|
|
492
|
-
"This pipeline runs with --no-branch and opens no PR. Skipping the pr-review phase."
|
|
493
|
-
);
|
|
494
|
-
effectivePrReview = false;
|
|
495
|
-
}
|
|
496
|
-
if (effectivePrReview) {
|
|
497
|
-
try {
|
|
498
|
-
const plan = await loadTaskPlan(tasksPath);
|
|
499
|
-
if (plan.prReview?.enabled !== true) {
|
|
500
|
-
plan.prReview = { ...plan.prReview, enabled: true, rounds: plan.prReview?.rounds ?? 0 };
|
|
501
|
-
await saveTaskPlan(tasksPath, plan);
|
|
502
|
-
}
|
|
503
|
-
} catch {
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
const activePhases = effectiveNoBranch ? PIPELINE_PHASES_NO_BRANCH : effectivePrReview ? PIPELINE_PHASES_WITH_PR_REVIEW : PIPELINE_PHASES;
|
|
507
|
-
const phaseOrder = effectiveNoBranch ? RUNNABLE_PHASES_NO_BRANCH : effectivePrReview ? RUNNABLE_PHASES_WITH_PR_REVIEW : RUNNABLE_PHASES;
|
|
508
|
-
publishSessionStart(activePhases, sessionStartedAt, {
|
|
509
|
-
issueUrl: planIssueUrl ?? resolvedIssue.issue.remoteRef ?? void 0,
|
|
510
|
-
branch: planBranch
|
|
511
|
-
});
|
|
512
|
-
publishIssueDetails(publisher, resolvedIssue.issue, sessionStartedAt);
|
|
513
|
-
publishStorySeed(publisher, planStories, sessionStartedAt);
|
|
514
|
-
publisher.publish({ type: "phase:start", at: sessionStartedAt, phase: "init" });
|
|
515
|
-
publisher.publish({ type: "phase:end", at: isoNow(), phase: "init", success: true });
|
|
516
|
-
await publishGitState(publisher);
|
|
517
|
-
let startPhase = "prd";
|
|
518
|
-
if (from) {
|
|
519
|
-
if (!activePhases.includes(from)) {
|
|
520
|
-
const validPhases = activePhases.filter((p) => p !== "init").join(", ");
|
|
521
|
-
if (effectiveNoBranch && PIPELINE_PHASES.includes(from)) {
|
|
522
|
-
printError(
|
|
523
|
-
`The '${from}' phase is not available in --no-branch mode. Valid phases: ${validPhases}`
|
|
524
|
-
);
|
|
525
|
-
} else {
|
|
526
|
-
printError(`Invalid phase: ${from}. Valid phases: ${validPhases}`);
|
|
527
|
-
}
|
|
528
|
-
return 1;
|
|
529
|
-
}
|
|
530
|
-
startPhase = from;
|
|
531
|
-
} else {
|
|
532
|
-
try {
|
|
533
|
-
const plan = await loadTaskPlan(tasksPath);
|
|
534
|
-
const mgr = new PipelineManager(plan, tasksPath, activePhases);
|
|
535
|
-
const nextPhase = mgr.getNextPhase();
|
|
536
|
-
if (nextPhase && nextPhase !== "init") {
|
|
537
|
-
startPhase = nextPhase;
|
|
538
|
-
printInfo(`Resuming from phase: ${startPhase}`);
|
|
539
|
-
}
|
|
540
|
-
} catch {
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
if (from) {
|
|
544
|
-
try {
|
|
545
|
-
const plan = await loadTaskPlan(tasksPath);
|
|
546
|
-
const mgr = new PipelineManager(plan, tasksPath, activePhases);
|
|
547
|
-
if (!mgr.canResume(startPhase)) {
|
|
548
|
-
printError(`Cannot resume from ${startPhase}: prerequisite phases not complete`);
|
|
549
|
-
return 1;
|
|
550
|
-
}
|
|
551
|
-
} catch {
|
|
552
|
-
if (startPhase !== "prd") {
|
|
553
|
-
printError(`Cannot resume from ${startPhase}: no pipeline state found`);
|
|
554
|
-
return 1;
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
const startIdx = phaseOrder.indexOf(startPhase);
|
|
559
|
-
const makeRunner = (fn, phase) => async () => {
|
|
560
|
-
const code = await fn();
|
|
561
|
-
if (code !== 0) {
|
|
562
|
-
throw new Error(`Phase ${phase} failed with exit code ${code}`);
|
|
563
|
-
}
|
|
564
|
-
};
|
|
565
|
-
const runners = {
|
|
566
|
-
prd: makeRunner(() => runPrd(issueNumber, resolvedIssue), "prd"),
|
|
567
|
-
plan: async () => {
|
|
568
|
-
await makeRunner(() => runPlan(issueNumber, resolvedIssue), "plan")();
|
|
569
|
-
if (effectiveNoBranch || effectivePrReview) {
|
|
570
|
-
try {
|
|
571
|
-
const plan = await loadTaskPlan(tasksPath);
|
|
572
|
-
if (effectiveNoBranch) {
|
|
573
|
-
plan.noBranch = true;
|
|
574
|
-
}
|
|
575
|
-
if (effectivePrReview) {
|
|
576
|
-
plan.prReview = { ...plan.prReview, enabled: true, rounds: plan.prReview?.rounds ?? 0 };
|
|
577
|
-
}
|
|
578
|
-
await saveTaskPlan(tasksPath, plan);
|
|
579
|
-
} catch {
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
},
|
|
583
|
-
execute: makeRunner(() => runExecute(void 0, { issue: issueNumber }), "execute"),
|
|
584
|
-
review: async () => {
|
|
585
|
-
let maxCycles = 3;
|
|
586
|
-
try {
|
|
587
|
-
const plan = await loadTaskPlan(tasksPath);
|
|
588
|
-
maxCycles = plan.maxCorrectionCycles;
|
|
589
|
-
} catch {
|
|
590
|
-
}
|
|
591
|
-
let code = await runReview(issueNumber, resolvedIssue);
|
|
592
|
-
let cycle = 0;
|
|
593
|
-
while (code !== 0 && cycle < maxCycles) {
|
|
594
|
-
cycle++;
|
|
595
|
-
printWarning(`Review failed. Starting correction cycle ${cycle}/${maxCycles}...`);
|
|
596
|
-
publisher.publish({ type: "correction:cycle", at: isoNow(), cycle, maxCycles });
|
|
597
|
-
try {
|
|
598
|
-
const plan = await loadTaskPlan(tasksPath);
|
|
599
|
-
plan.correctionCycle = cycle;
|
|
600
|
-
await saveTaskPlan(tasksPath, plan);
|
|
601
|
-
} catch {
|
|
602
|
-
}
|
|
603
|
-
const execCode = await runExecute(void 0, { issue: issueNumber });
|
|
604
|
-
if (execCode !== 0) {
|
|
605
|
-
throw new Error("Correction execution failed");
|
|
606
|
-
}
|
|
607
|
-
code = await runReview(issueNumber, resolvedIssue);
|
|
608
|
-
}
|
|
609
|
-
if (code !== 0) {
|
|
610
|
-
throw new Error(`Review failed after ${maxCycles} correction cycles`);
|
|
611
|
-
}
|
|
612
|
-
},
|
|
613
|
-
pr: makeRunner(() => runPr(issueNumber, resolvedIssue), "pr")
|
|
614
|
-
};
|
|
615
|
-
const reviewBox = { outcome: null };
|
|
616
|
-
if (effectivePrReview) {
|
|
617
|
-
runners["pr-review"] = async () => {
|
|
618
|
-
const code = await runPrReview(void 0, { issue: issueNumber, yes: true });
|
|
619
|
-
if (code === 1) {
|
|
620
|
-
throw new Error("Phase pr-review failed with exit code 1");
|
|
621
|
-
}
|
|
622
|
-
reviewBox.outcome = await readPrReviewOutcome(issueNumber, tasksPath, code === 2);
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
const instrumentedRunners = Object.fromEntries(
|
|
626
|
-
Object.entries(runners).map(([phase, fn]) => [
|
|
627
|
-
phase,
|
|
628
|
-
async () => {
|
|
629
|
-
publisher.publish({ type: "phase:start", at: isoNow(), phase });
|
|
630
|
-
try {
|
|
631
|
-
await fn();
|
|
632
|
-
await publishGitState(publisher);
|
|
633
|
-
publisher.publish({ type: "phase:end", at: isoNow(), phase, success: true });
|
|
634
|
-
} catch (err) {
|
|
635
|
-
await publishGitState(publisher);
|
|
636
|
-
publisher.publish({
|
|
637
|
-
type: "phase:end",
|
|
638
|
-
at: isoNow(),
|
|
639
|
-
phase,
|
|
640
|
-
success: false,
|
|
641
|
-
error: err instanceof Error ? err.message : String(err)
|
|
642
|
-
});
|
|
643
|
-
throw err;
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
])
|
|
647
|
-
);
|
|
648
|
-
const result = await runPipelineWithRenderer({
|
|
649
|
-
phases: phaseOrder,
|
|
650
|
-
startIndex: startIdx,
|
|
651
|
-
verbose: isVerbose(),
|
|
652
|
-
runners: instrumentedRunners,
|
|
653
|
-
tasksPath
|
|
654
|
-
});
|
|
655
|
-
if (!result.success) {
|
|
656
|
-
printError(`Phase ${result.failedPhase} failed`);
|
|
657
|
-
return 1;
|
|
658
|
-
}
|
|
659
|
-
const review = reviewBox.outcome;
|
|
660
|
-
if (review?.requestedChanges) {
|
|
661
|
-
console.log("");
|
|
662
|
-
printWarning("PR review requested changes \u2014 the Pull Request is not ready to merge.");
|
|
663
|
-
if (review.reportPath !== null) {
|
|
664
|
-
console.log(` Report: ${review.reportPath}`);
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
if (review?.requestedChanges) {
|
|
668
|
-
printInfo("Issue left open until the review blockers are addressed.");
|
|
669
|
-
} else {
|
|
670
|
-
try {
|
|
671
|
-
const provider = getProvider(resolvedIssue.source);
|
|
672
|
-
if (provider.close !== void 0) {
|
|
673
|
-
printInfo("Closing issue...");
|
|
674
|
-
await provider.close(issueNumber);
|
|
675
|
-
}
|
|
676
|
-
} catch {
|
|
677
|
-
printWarning("Failed to close issue automatically");
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
let branchName = "unknown";
|
|
681
|
-
try {
|
|
682
|
-
const proc = await execa("git", ["branch", "--show-current"], { reject: false });
|
|
683
|
-
branchName = proc.stdout?.toString().trim() ?? "unknown";
|
|
684
|
-
} catch {
|
|
685
|
-
}
|
|
686
|
-
let storyCount = 0;
|
|
687
|
-
let planPrUrl = null;
|
|
688
|
-
try {
|
|
689
|
-
const plan = await loadTaskPlan(tasksPath);
|
|
690
|
-
storyCount = plan.userStories.length;
|
|
691
|
-
planPrUrl = plan.pullRequest?.url ?? null;
|
|
692
|
-
plan.lastAttemptAt = isoNow();
|
|
693
|
-
if (!review?.requestedChanges) {
|
|
694
|
-
plan.issueStatus = "completed";
|
|
695
|
-
plan.completedAt = isoNow();
|
|
696
|
-
}
|
|
697
|
-
await saveTaskPlan(tasksPath, plan);
|
|
698
|
-
} catch {
|
|
699
|
-
}
|
|
700
|
-
let prUrl = "unknown";
|
|
701
|
-
if (!effectiveNoBranch && resolvedIssue.source === "github") {
|
|
702
|
-
if (planPrUrl !== null) {
|
|
703
|
-
prUrl = planPrUrl;
|
|
704
|
-
} else if (branchName !== "unknown" && branchName !== "") {
|
|
705
|
-
try {
|
|
706
|
-
const latest = mostRecent(await listPullRequests(branchName));
|
|
707
|
-
if (latest !== null) {
|
|
708
|
-
prUrl = latest.url;
|
|
709
|
-
}
|
|
710
|
-
} catch {
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
printRunSummary({
|
|
715
|
-
issueNumber,
|
|
716
|
-
branchName,
|
|
717
|
-
noBranch: effectiveNoBranch,
|
|
718
|
-
storyCount,
|
|
719
|
-
elapsedSeconds: result.overallElapsedSeconds,
|
|
720
|
-
prUrl,
|
|
721
|
-
prReview: review,
|
|
722
|
-
// Process-owned counters: the session snapshot is empty whenever web
|
|
723
|
-
// monitoring is off, so it cannot be the source of these totals.
|
|
724
|
-
usage: getRunUsageTotals()
|
|
725
|
-
});
|
|
726
|
-
return 0;
|
|
727
|
-
}
|
|
728
|
-
export {
|
|
729
|
-
publishIssueDetails,
|
|
730
|
-
publishStorySeed,
|
|
731
|
-
runPipeline
|
|
732
|
-
};
|
|
733
|
-
//# sourceMappingURL=run-SEB6F3W4.js.map
|