issue-flow 0.4.3 → 0.5.1
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/LICENSE +21 -0
- package/README.md +38 -1
- package/dist/{chunk-MEB3B2FI.js → analyze-IJ2SYXVM.js} +7 -10
- package/dist/{chunk-MEB3B2FI.js.map → analyze-IJ2SYXVM.js.map} +1 -1
- package/dist/{chunk-4OQ2ARLP.js → chunk-2XWXVWVD.js} +10 -2
- package/dist/chunk-2XWXVWVD.js.map +1 -0
- package/dist/{chunk-MWUIIUAS.js → chunk-AY7UZAGX.js} +8 -10
- package/dist/{chunk-MWUIIUAS.js.map → chunk-AY7UZAGX.js.map} +1 -1
- package/dist/chunk-FNLXZXWP.js +846 -0
- package/dist/chunk-FNLXZXWP.js.map +1 -0
- package/dist/{chunk-HYP64MZI.js → chunk-GYR4YHEB.js} +110 -116
- package/dist/chunk-GYR4YHEB.js.map +1 -0
- package/dist/{chunk-CYWHYPTU.js → chunk-MKQIP7YW.js} +7 -9
- package/dist/{chunk-CYWHYPTU.js.map → chunk-MKQIP7YW.js.map} +1 -1
- package/dist/{chunk-CSSXTPWE.js → chunk-Q2TQCJDN.js} +2 -2
- package/dist/{chunk-2JZCWJEQ.js → chunk-SI4ALORB.js} +7 -21
- package/dist/chunk-SI4ALORB.js.map +1 -0
- package/dist/{chunk-OZVHOVDT.js → chunk-SLHZPB3C.js} +15 -8
- package/dist/chunk-SLHZPB3C.js.map +1 -0
- package/dist/chunk-TYOFZ6DD.js +240 -0
- package/dist/chunk-TYOFZ6DD.js.map +1 -0
- package/dist/{chunk-4PZXIOXD.js → chunk-XJJP4TSK.js} +7 -9
- package/dist/{chunk-4PZXIOXD.js.map → chunk-XJJP4TSK.js.map} +1 -1
- package/dist/cli.js +56 -19
- package/dist/cli.js.map +1 -1
- package/dist/execute-DALVYZIH.js +11 -0
- package/dist/{generate-HP4CPMJG.js → generate-5OWWNRIB.js} +4 -4
- package/dist/init-6AWQI3IE.js +9 -0
- package/dist/plan-RQXK2OCP.js +11 -0
- package/dist/pr-UU4H7ZZR.js +11 -0
- package/dist/prd-WB3QH6QF.js +11 -0
- package/dist/review-2WHYJG7M.js +11 -0
- package/dist/run-UCSNYSQO.js +756 -0
- package/dist/run-UCSNYSQO.js.map +1 -0
- package/package.json +13 -3
- package/prompts/prd.md +5 -4
- package/web/public/app.css +481 -0
- package/web/public/app.js +522 -0
- package/web/public/index.html +113 -0
- package/dist/analyze-3SN7ZAZU.js +0 -12
- package/dist/chunk-2JZCWJEQ.js.map +0 -1
- package/dist/chunk-4OQ2ARLP.js.map +0 -1
- package/dist/chunk-64FU2L2T.js +0 -180
- package/dist/chunk-64FU2L2T.js.map +0 -1
- package/dist/chunk-HYP64MZI.js.map +0 -1
- package/dist/chunk-OZVHOVDT.js.map +0 -1
- package/dist/chunk-YAFHVFV5.js +0 -199
- package/dist/chunk-YAFHVFV5.js.map +0 -1
- package/dist/execute-GIA4IKS5.js +0 -11
- package/dist/init-SXFGOCML.js +0 -9
- package/dist/plan-HWZRG5GP.js +0 -12
- package/dist/pr-TTOFYUCV.js +0 -12
- package/dist/prd-ET2QN7GB.js +0 -12
- package/dist/review-3QDZCOMT.js +0 -12
- package/dist/review-3QDZCOMT.js.map +0 -1
- package/dist/run-PKHPULYL.js +0 -451
- package/dist/run-PKHPULYL.js.map +0 -1
- /package/dist/{chunk-CSSXTPWE.js.map → chunk-Q2TQCJDN.js.map} +0 -0
- /package/dist/{analyze-3SN7ZAZU.js.map → execute-DALVYZIH.js.map} +0 -0
- /package/dist/{generate-HP4CPMJG.js.map → generate-5OWWNRIB.js.map} +0 -0
- /package/dist/{execute-GIA4IKS5.js.map → init-6AWQI3IE.js.map} +0 -0
- /package/dist/{init-SXFGOCML.js.map → plan-RQXK2OCP.js.map} +0 -0
- /package/dist/{plan-HWZRG5GP.js.map → pr-UU4H7ZZR.js.map} +0 -0
- /package/dist/{pr-TTOFYUCV.js.map → prd-WB3QH6QF.js.map} +0 -0
- /package/dist/{prd-ET2QN7GB.js.map → review-2WHYJG7M.js.map} +0 -0
|
@@ -0,0 +1,846 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/schemas.ts
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
var userStorySchema = z.object({
|
|
6
|
+
id: z.string(),
|
|
7
|
+
title: z.string(),
|
|
8
|
+
description: z.string(),
|
|
9
|
+
acceptanceCriteria: z.array(z.string()),
|
|
10
|
+
priority: z.number().int().positive(),
|
|
11
|
+
passes: z.boolean(),
|
|
12
|
+
notes: z.string()
|
|
13
|
+
});
|
|
14
|
+
var pipelineStateSchema = z.object({
|
|
15
|
+
analyzeCompleted: z.boolean().optional(),
|
|
16
|
+
prdCompleted: z.boolean(),
|
|
17
|
+
jsonCompleted: z.boolean(),
|
|
18
|
+
executionCompleted: z.boolean(),
|
|
19
|
+
reviewCompleted: z.boolean(),
|
|
20
|
+
prCreated: z.boolean()
|
|
21
|
+
});
|
|
22
|
+
var lastErrorSchema = z.object({
|
|
23
|
+
category: z.string(),
|
|
24
|
+
message: z.string(),
|
|
25
|
+
at: z.string()
|
|
26
|
+
});
|
|
27
|
+
var taskPlanSchema = z.object({
|
|
28
|
+
project: z.string(),
|
|
29
|
+
issueNumber: z.number().int().positive(),
|
|
30
|
+
issueUrl: z.string(),
|
|
31
|
+
branchName: z.string(),
|
|
32
|
+
noBranch: z.boolean().optional().default(false),
|
|
33
|
+
description: z.string(),
|
|
34
|
+
issueStatus: z.enum(["pending", "in_progress", "completed"]),
|
|
35
|
+
completedAt: z.string().nullable(),
|
|
36
|
+
lastAttemptAt: z.string().nullable(),
|
|
37
|
+
lastError: lastErrorSchema.nullable(),
|
|
38
|
+
correctionCycle: z.number().int().min(0),
|
|
39
|
+
maxCorrectionCycles: z.number().int().min(0),
|
|
40
|
+
pipeline: pipelineStateSchema,
|
|
41
|
+
userStories: z.array(userStorySchema)
|
|
42
|
+
});
|
|
43
|
+
var headlessResultSchema = z.object({
|
|
44
|
+
success: z.boolean(),
|
|
45
|
+
result: z.string(),
|
|
46
|
+
cost: z.object({
|
|
47
|
+
inputTokens: z.number().optional(),
|
|
48
|
+
outputTokens: z.number().optional()
|
|
49
|
+
}).nullable(),
|
|
50
|
+
error: z.string().nullable()
|
|
51
|
+
});
|
|
52
|
+
var sessionLogEntrySchema = z.object({
|
|
53
|
+
at: z.string(),
|
|
54
|
+
level: z.enum(["info", "warn", "error"]),
|
|
55
|
+
message: z.string()
|
|
56
|
+
});
|
|
57
|
+
var sessionPhaseSchema = z.object({
|
|
58
|
+
name: z.string(),
|
|
59
|
+
status: z.enum(["pending", "running", "completed", "failed"]),
|
|
60
|
+
startedAt: z.string().nullable(),
|
|
61
|
+
endedAt: z.string().nullable(),
|
|
62
|
+
durationSeconds: z.number().nullable(),
|
|
63
|
+
error: z.string().nullable()
|
|
64
|
+
});
|
|
65
|
+
var sessionStorySchema = z.object({
|
|
66
|
+
id: z.string(),
|
|
67
|
+
title: z.string(),
|
|
68
|
+
priority: z.number(),
|
|
69
|
+
passes: z.boolean(),
|
|
70
|
+
completedAt: z.string().nullable()
|
|
71
|
+
});
|
|
72
|
+
var sessionSnapshotSchema = z.object({
|
|
73
|
+
schemaVersion: z.literal(1),
|
|
74
|
+
sessionId: z.string().nullable(),
|
|
75
|
+
readOnly: z.literal(true),
|
|
76
|
+
capabilities: z.array(z.string()),
|
|
77
|
+
issue: z.object({ number: z.number().nullable(), url: z.string().nullable() }),
|
|
78
|
+
status: z.enum(["idle", "running", "completed", "failed"]),
|
|
79
|
+
startedAt: z.string().nullable(),
|
|
80
|
+
updatedAt: z.string().nullable(),
|
|
81
|
+
endedAt: z.string().nullable(),
|
|
82
|
+
elapsedSeconds: z.number().nullable(),
|
|
83
|
+
estimatedRemainingSeconds: z.number().nullable(),
|
|
84
|
+
progress: z.object({
|
|
85
|
+
percent: z.number(),
|
|
86
|
+
phasesCompleted: z.number(),
|
|
87
|
+
phasesTotal: z.number(),
|
|
88
|
+
storiesCompleted: z.number(),
|
|
89
|
+
storiesTotal: z.number()
|
|
90
|
+
}),
|
|
91
|
+
currentPhase: z.string().nullable(),
|
|
92
|
+
currentActivity: z.object({
|
|
93
|
+
story: z.string().nullable(),
|
|
94
|
+
tool: z.string().nullable(),
|
|
95
|
+
detail: z.string().nullable(),
|
|
96
|
+
since: z.string()
|
|
97
|
+
}).nullable(),
|
|
98
|
+
phases: z.array(sessionPhaseSchema),
|
|
99
|
+
stories: z.array(sessionStorySchema),
|
|
100
|
+
execution: z.object({
|
|
101
|
+
iteration: z.number(),
|
|
102
|
+
retries: z.number(),
|
|
103
|
+
correctionCycle: z.number(),
|
|
104
|
+
maxCorrectionCycles: z.number().nullable()
|
|
105
|
+
}),
|
|
106
|
+
git: z.object({
|
|
107
|
+
branch: z.string().nullable(),
|
|
108
|
+
baseBranch: z.string().nullable(),
|
|
109
|
+
commits: z.array(z.object({ hash: z.string(), subject: z.string() }))
|
|
110
|
+
}),
|
|
111
|
+
pullRequests: z.array(z.object({ number: z.number(), url: z.string(), title: z.string() })),
|
|
112
|
+
logs: z.array(sessionLogEntrySchema),
|
|
113
|
+
errors: z.array(sessionLogEntrySchema),
|
|
114
|
+
warnings: z.array(sessionLogEntrySchema),
|
|
115
|
+
lastError: z.object({ message: z.string(), at: z.string() }).nullable(),
|
|
116
|
+
nextSteps: z.array(z.string()),
|
|
117
|
+
environment: z.object({ node: z.string(), platform: z.string() }).nullable()
|
|
118
|
+
});
|
|
119
|
+
var webConfigSchema = z.object({
|
|
120
|
+
enabled: z.boolean().default(false),
|
|
121
|
+
port: z.number().int().min(1).max(65535).default(3737),
|
|
122
|
+
host: z.string().min(1).default("127.0.0.1"),
|
|
123
|
+
refreshSeconds: z.number().positive().default(5),
|
|
124
|
+
logLimit: z.number().int().positive().default(200),
|
|
125
|
+
includeLogs: z.boolean().default(true)
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// src/core/session-state.ts
|
|
129
|
+
import { rename, writeFile } from "fs/promises";
|
|
130
|
+
import { stripVTControlCharacters } from "util";
|
|
131
|
+
var DEFAULT_LOG_LIMIT = 200;
|
|
132
|
+
var DEFAULT_THROTTLE_MS = 1e3;
|
|
133
|
+
function createInitialSnapshot() {
|
|
134
|
+
return {
|
|
135
|
+
schemaVersion: 1,
|
|
136
|
+
sessionId: null,
|
|
137
|
+
readOnly: true,
|
|
138
|
+
capabilities: [],
|
|
139
|
+
issue: { number: null, url: null },
|
|
140
|
+
status: "idle",
|
|
141
|
+
startedAt: null,
|
|
142
|
+
updatedAt: null,
|
|
143
|
+
endedAt: null,
|
|
144
|
+
elapsedSeconds: null,
|
|
145
|
+
estimatedRemainingSeconds: null,
|
|
146
|
+
progress: {
|
|
147
|
+
percent: 0,
|
|
148
|
+
phasesCompleted: 0,
|
|
149
|
+
phasesTotal: 0,
|
|
150
|
+
storiesCompleted: 0,
|
|
151
|
+
storiesTotal: 0
|
|
152
|
+
},
|
|
153
|
+
currentPhase: null,
|
|
154
|
+
currentActivity: null,
|
|
155
|
+
phases: [],
|
|
156
|
+
stories: [],
|
|
157
|
+
execution: { iteration: 0, retries: 0, correctionCycle: 0, maxCorrectionCycles: null },
|
|
158
|
+
git: { branch: null, baseBranch: null, commits: [] },
|
|
159
|
+
pullRequests: [],
|
|
160
|
+
logs: [],
|
|
161
|
+
errors: [],
|
|
162
|
+
warnings: [],
|
|
163
|
+
lastError: null,
|
|
164
|
+
nextSteps: [],
|
|
165
|
+
environment: null
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function secondsBetween(from, to) {
|
|
169
|
+
if (from === null) return null;
|
|
170
|
+
const fromMs = Date.parse(from);
|
|
171
|
+
const toMs = Date.parse(to);
|
|
172
|
+
if (Number.isNaN(fromMs) || Number.isNaN(toMs)) return null;
|
|
173
|
+
return Math.max(0, Math.round((toMs - fromMs) / 1e3));
|
|
174
|
+
}
|
|
175
|
+
function computePercent(phasesCompleted, phasesTotal) {
|
|
176
|
+
if (phasesTotal === 0) return 0;
|
|
177
|
+
return Math.round(phasesCompleted / phasesTotal * 100);
|
|
178
|
+
}
|
|
179
|
+
function estimateRemainingSeconds(snapshot) {
|
|
180
|
+
const completions = snapshot.stories.map((story) => story.completedAt === null ? Number.NaN : Date.parse(story.completedAt)).filter((ms) => !Number.isNaN(ms)).sort((a, b) => a - b);
|
|
181
|
+
if (completions.length < 2) return null;
|
|
182
|
+
const startMs = snapshot.startedAt === null ? Number.NaN : Date.parse(snapshot.startedAt);
|
|
183
|
+
const boundaries = Number.isNaN(startMs) ? completions : [startMs, ...completions];
|
|
184
|
+
const durations = [];
|
|
185
|
+
for (let i = 1; i < boundaries.length; i++) {
|
|
186
|
+
durations.push(Math.max(0, boundaries[i] - boundaries[i - 1]));
|
|
187
|
+
}
|
|
188
|
+
if (durations.length === 0) return null;
|
|
189
|
+
const averageMs = durations.reduce((sum, d) => sum + d, 0) / durations.length;
|
|
190
|
+
const pending = snapshot.stories.filter((story) => !story.passes).length;
|
|
191
|
+
return Math.round(averageMs * pending / 1e3);
|
|
192
|
+
}
|
|
193
|
+
function deriveNextSteps(snapshot) {
|
|
194
|
+
if (snapshot.status === "completed") return [];
|
|
195
|
+
return snapshot.phases.filter((phase) => phase.status === "pending").map((phase) => phase.name);
|
|
196
|
+
}
|
|
197
|
+
function reduceSessionEvent(snapshot, event, options) {
|
|
198
|
+
const next = applyEvent(snapshot, event, options);
|
|
199
|
+
if (next === snapshot) return snapshot;
|
|
200
|
+
const elapsedSeconds = secondsBetween(next.startedAt, event.at) ?? next.elapsedSeconds;
|
|
201
|
+
return {
|
|
202
|
+
...next,
|
|
203
|
+
updatedAt: event.at,
|
|
204
|
+
elapsedSeconds,
|
|
205
|
+
estimatedRemainingSeconds: estimateRemainingSeconds(next),
|
|
206
|
+
errors: next.logs.filter((entry) => entry.level === "error"),
|
|
207
|
+
warnings: next.logs.filter((entry) => entry.level === "warn"),
|
|
208
|
+
nextSteps: deriveNextSteps(next)
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function applyEvent(snapshot, event, options) {
|
|
212
|
+
switch (event.type) {
|
|
213
|
+
case "session:start": {
|
|
214
|
+
const initial = createInitialSnapshot();
|
|
215
|
+
return {
|
|
216
|
+
...initial,
|
|
217
|
+
sessionId: event.sessionId,
|
|
218
|
+
issue: { number: event.issueNumber, url: event.issueUrl ?? null },
|
|
219
|
+
status: "running",
|
|
220
|
+
startedAt: event.at,
|
|
221
|
+
progress: {
|
|
222
|
+
...initial.progress,
|
|
223
|
+
phasesTotal: event.phases.length
|
|
224
|
+
},
|
|
225
|
+
phases: event.phases.map((name) => ({
|
|
226
|
+
name,
|
|
227
|
+
status: "pending",
|
|
228
|
+
startedAt: null,
|
|
229
|
+
endedAt: null,
|
|
230
|
+
durationSeconds: null,
|
|
231
|
+
error: null
|
|
232
|
+
})),
|
|
233
|
+
git: { branch: event.branch ?? null, baseBranch: event.baseBranch ?? null, commits: [] },
|
|
234
|
+
environment: event.environment ?? null
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
case "phase:start": {
|
|
238
|
+
const known = snapshot.phases.some((p) => p.name === event.phase);
|
|
239
|
+
const phases = known ? snapshot.phases.map(
|
|
240
|
+
(p) => p.name === event.phase ? {
|
|
241
|
+
...p,
|
|
242
|
+
status: "running",
|
|
243
|
+
startedAt: event.at,
|
|
244
|
+
endedAt: null,
|
|
245
|
+
error: null
|
|
246
|
+
} : p
|
|
247
|
+
) : [
|
|
248
|
+
...snapshot.phases,
|
|
249
|
+
{
|
|
250
|
+
name: event.phase,
|
|
251
|
+
status: "running",
|
|
252
|
+
startedAt: event.at,
|
|
253
|
+
endedAt: null,
|
|
254
|
+
durationSeconds: null,
|
|
255
|
+
error: null
|
|
256
|
+
}
|
|
257
|
+
];
|
|
258
|
+
return {
|
|
259
|
+
...snapshot,
|
|
260
|
+
currentPhase: event.phase,
|
|
261
|
+
phases,
|
|
262
|
+
progress: known ? snapshot.progress : { ...snapshot.progress, phasesTotal: phases.length }
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
case "phase:end": {
|
|
266
|
+
const phases = snapshot.phases.map(
|
|
267
|
+
(p) => p.name === event.phase ? {
|
|
268
|
+
...p,
|
|
269
|
+
status: event.success ? "completed" : "failed",
|
|
270
|
+
endedAt: event.at,
|
|
271
|
+
durationSeconds: secondsBetween(p.startedAt, event.at),
|
|
272
|
+
error: event.error ? stripVTControlCharacters(event.error) : null
|
|
273
|
+
} : p
|
|
274
|
+
);
|
|
275
|
+
const phasesCompleted = phases.filter((p) => p.status === "completed").length;
|
|
276
|
+
return {
|
|
277
|
+
...snapshot,
|
|
278
|
+
currentPhase: snapshot.currentPhase === event.phase ? null : snapshot.currentPhase,
|
|
279
|
+
currentActivity: null,
|
|
280
|
+
phases,
|
|
281
|
+
progress: {
|
|
282
|
+
...snapshot.progress,
|
|
283
|
+
phasesCompleted,
|
|
284
|
+
percent: computePercent(phasesCompleted, snapshot.progress.phasesTotal)
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
case "iteration:start":
|
|
289
|
+
return {
|
|
290
|
+
...snapshot,
|
|
291
|
+
execution: { ...snapshot.execution, iteration: event.iteration }
|
|
292
|
+
};
|
|
293
|
+
case "iteration:end":
|
|
294
|
+
return { ...snapshot, currentActivity: null };
|
|
295
|
+
case "retry":
|
|
296
|
+
return {
|
|
297
|
+
...snapshot,
|
|
298
|
+
execution: { ...snapshot.execution, retries: snapshot.execution.retries + 1 }
|
|
299
|
+
};
|
|
300
|
+
case "stories:update": {
|
|
301
|
+
const previous = new Map(snapshot.stories.map((story) => [story.id, story]));
|
|
302
|
+
const stories = event.stories.map((story) => {
|
|
303
|
+
const before = previous.get(story.id);
|
|
304
|
+
const completedAt = !story.passes ? null : before && !before.passes ? event.at : before?.completedAt ?? null;
|
|
305
|
+
return {
|
|
306
|
+
id: story.id,
|
|
307
|
+
title: story.title,
|
|
308
|
+
priority: story.priority,
|
|
309
|
+
passes: story.passes,
|
|
310
|
+
completedAt
|
|
311
|
+
};
|
|
312
|
+
});
|
|
313
|
+
return {
|
|
314
|
+
...snapshot,
|
|
315
|
+
stories,
|
|
316
|
+
progress: {
|
|
317
|
+
...snapshot.progress,
|
|
318
|
+
storiesCompleted: stories.filter((s) => s.passes).length,
|
|
319
|
+
storiesTotal: stories.length
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
case "activity": {
|
|
324
|
+
const story = event.story ?? null;
|
|
325
|
+
const tool = event.tool ?? null;
|
|
326
|
+
const detail = event.detail ?? null;
|
|
327
|
+
const current = snapshot.currentActivity;
|
|
328
|
+
const since = current && current.story === story && current.tool === tool && current.detail === detail ? current.since : event.at;
|
|
329
|
+
return { ...snapshot, currentActivity: { story, tool, detail, since } };
|
|
330
|
+
}
|
|
331
|
+
case "log": {
|
|
332
|
+
const limit = options?.logLimit ?? DEFAULT_LOG_LIMIT;
|
|
333
|
+
const entry = {
|
|
334
|
+
at: event.at,
|
|
335
|
+
level: event.level,
|
|
336
|
+
message: stripVTControlCharacters(event.message)
|
|
337
|
+
};
|
|
338
|
+
const logs = [...snapshot.logs, entry].slice(-Math.max(1, limit));
|
|
339
|
+
return { ...snapshot, logs };
|
|
340
|
+
}
|
|
341
|
+
case "git:update":
|
|
342
|
+
return {
|
|
343
|
+
...snapshot,
|
|
344
|
+
git: {
|
|
345
|
+
branch: event.branch ?? snapshot.git.branch,
|
|
346
|
+
baseBranch: event.baseBranch ?? snapshot.git.baseBranch,
|
|
347
|
+
commits: event.commits ?? snapshot.git.commits
|
|
348
|
+
},
|
|
349
|
+
pullRequests: event.pullRequests ?? snapshot.pullRequests
|
|
350
|
+
};
|
|
351
|
+
case "correction:cycle":
|
|
352
|
+
return {
|
|
353
|
+
...snapshot,
|
|
354
|
+
execution: {
|
|
355
|
+
...snapshot.execution,
|
|
356
|
+
correctionCycle: event.cycle,
|
|
357
|
+
maxCorrectionCycles: event.maxCycles
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
case "session:end":
|
|
361
|
+
return {
|
|
362
|
+
...snapshot,
|
|
363
|
+
status: event.status,
|
|
364
|
+
endedAt: event.at,
|
|
365
|
+
currentPhase: null,
|
|
366
|
+
currentActivity: null,
|
|
367
|
+
lastError: event.error ? { message: stripVTControlCharacters(event.error), at: event.at } : snapshot.lastError
|
|
368
|
+
};
|
|
369
|
+
default:
|
|
370
|
+
return snapshot;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
var NullPublisher = class {
|
|
374
|
+
empty = createInitialSnapshot();
|
|
375
|
+
publish(_event) {
|
|
376
|
+
}
|
|
377
|
+
snapshot() {
|
|
378
|
+
return this.empty;
|
|
379
|
+
}
|
|
380
|
+
version() {
|
|
381
|
+
return 0;
|
|
382
|
+
}
|
|
383
|
+
async flush() {
|
|
384
|
+
}
|
|
385
|
+
async close() {
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
var MemoryPublisher = class {
|
|
389
|
+
state = createInitialSnapshot();
|
|
390
|
+
versionCounter = 0;
|
|
391
|
+
warned = false;
|
|
392
|
+
onWarn;
|
|
393
|
+
reducerOptions;
|
|
394
|
+
includeLogs;
|
|
395
|
+
constructor(options = {}) {
|
|
396
|
+
this.onWarn = options.onWarn ?? ((message) => process.stderr.write(`${message}
|
|
397
|
+
`));
|
|
398
|
+
this.reducerOptions = { logLimit: options.logLimit };
|
|
399
|
+
this.includeLogs = options.includeLogs ?? true;
|
|
400
|
+
}
|
|
401
|
+
publish(event) {
|
|
402
|
+
if (event.type === "log" && !this.includeLogs) return;
|
|
403
|
+
try {
|
|
404
|
+
this.state = reduceSessionEvent(this.state, event, this.reducerOptions);
|
|
405
|
+
this.versionCounter++;
|
|
406
|
+
this.afterPublish(event);
|
|
407
|
+
} catch (err) {
|
|
408
|
+
this.warnOnce(err);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
/** Hook for subclasses; runs inside publish()'s try/catch. */
|
|
412
|
+
afterPublish(_event) {
|
|
413
|
+
}
|
|
414
|
+
snapshot() {
|
|
415
|
+
return this.state;
|
|
416
|
+
}
|
|
417
|
+
version() {
|
|
418
|
+
return this.versionCounter;
|
|
419
|
+
}
|
|
420
|
+
async flush() {
|
|
421
|
+
}
|
|
422
|
+
async close() {
|
|
423
|
+
}
|
|
424
|
+
warnOnce(err) {
|
|
425
|
+
if (this.warned) return;
|
|
426
|
+
this.warned = true;
|
|
427
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
428
|
+
try {
|
|
429
|
+
this.onWarn(`issue-flow: web monitoring disabled itself after an error: ${message}`);
|
|
430
|
+
} catch {
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
var FilePublisher = class extends MemoryPublisher {
|
|
435
|
+
filePath;
|
|
436
|
+
throttleMs;
|
|
437
|
+
timer = null;
|
|
438
|
+
lastWriteStartedAt = 0;
|
|
439
|
+
lastWrittenVersion = 0;
|
|
440
|
+
writeChain = Promise.resolve();
|
|
441
|
+
closed = false;
|
|
442
|
+
constructor(filePath, options = {}) {
|
|
443
|
+
super(options);
|
|
444
|
+
this.filePath = filePath;
|
|
445
|
+
this.throttleMs = options.throttleMs ?? DEFAULT_THROTTLE_MS;
|
|
446
|
+
}
|
|
447
|
+
afterPublish(event) {
|
|
448
|
+
if (this.closed) return;
|
|
449
|
+
const terminal = event.type === "phase:end" || event.type === "session:end";
|
|
450
|
+
this.scheduleWrite(terminal);
|
|
451
|
+
}
|
|
452
|
+
scheduleWrite(force) {
|
|
453
|
+
if (force) {
|
|
454
|
+
if (this.timer !== null) {
|
|
455
|
+
clearTimeout(this.timer);
|
|
456
|
+
this.timer = null;
|
|
457
|
+
}
|
|
458
|
+
this.enqueueWrite();
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
if (this.timer !== null) return;
|
|
462
|
+
const wait = Math.max(0, this.throttleMs - (Date.now() - this.lastWriteStartedAt));
|
|
463
|
+
if (wait === 0) {
|
|
464
|
+
this.enqueueWrite();
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
this.timer = setTimeout(() => {
|
|
468
|
+
this.timer = null;
|
|
469
|
+
this.enqueueWrite();
|
|
470
|
+
}, wait);
|
|
471
|
+
this.timer.unref();
|
|
472
|
+
}
|
|
473
|
+
enqueueWrite() {
|
|
474
|
+
this.lastWriteStartedAt = Date.now();
|
|
475
|
+
this.writeChain = this.writeChain.then(async () => {
|
|
476
|
+
const version = this.versionCounter;
|
|
477
|
+
if (version === this.lastWrittenVersion) return;
|
|
478
|
+
const payload = `${JSON.stringify(this.state, null, 2)}
|
|
479
|
+
`;
|
|
480
|
+
try {
|
|
481
|
+
await atomicWriteFile(this.filePath, payload);
|
|
482
|
+
this.lastWrittenVersion = version;
|
|
483
|
+
} catch (err) {
|
|
484
|
+
this.warnOnce(err);
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
async flush() {
|
|
489
|
+
if (this.timer !== null) {
|
|
490
|
+
clearTimeout(this.timer);
|
|
491
|
+
this.timer = null;
|
|
492
|
+
}
|
|
493
|
+
if (this.versionCounter !== this.lastWrittenVersion) {
|
|
494
|
+
this.enqueueWrite();
|
|
495
|
+
}
|
|
496
|
+
await this.writeChain;
|
|
497
|
+
}
|
|
498
|
+
async close() {
|
|
499
|
+
await this.flush();
|
|
500
|
+
this.closed = true;
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
async function atomicWriteFile(path, content) {
|
|
504
|
+
const tmpFile = `${path}.tmp`;
|
|
505
|
+
await writeFile(tmpFile, content, "utf-8");
|
|
506
|
+
await rename(tmpFile, path);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// src/core/session-publisher.ts
|
|
510
|
+
var NULL_PUBLISHER = new NullPublisher();
|
|
511
|
+
var _publisher = NULL_PUBLISHER;
|
|
512
|
+
function setSessionPublisher(publisher) {
|
|
513
|
+
_publisher = publisher ?? NULL_PUBLISHER;
|
|
514
|
+
}
|
|
515
|
+
function getSessionPublisher() {
|
|
516
|
+
return _publisher;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// src/core/state-manager.ts
|
|
520
|
+
import { copyFile, mkdtemp, readFile, rename as rename2, unlink, writeFile as writeFile2 } from "fs/promises";
|
|
521
|
+
import { tmpdir } from "os";
|
|
522
|
+
import { join } from "path";
|
|
523
|
+
import { ZodError } from "zod";
|
|
524
|
+
function isoNow() {
|
|
525
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
526
|
+
}
|
|
527
|
+
async function loadTaskPlan(path) {
|
|
528
|
+
const content = await readFile(path, "utf-8");
|
|
529
|
+
const raw = JSON.parse(content);
|
|
530
|
+
try {
|
|
531
|
+
return taskPlanSchema.parse(raw);
|
|
532
|
+
} catch (err) {
|
|
533
|
+
if (err instanceof ZodError) {
|
|
534
|
+
const issues = err.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
535
|
+
throw new Error(`Invalid tasks.json at ${path}:
|
|
536
|
+
${issues}`);
|
|
537
|
+
}
|
|
538
|
+
throw err;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
async function saveTaskPlan(path, plan) {
|
|
542
|
+
const tmpDir = await mkdtemp(join(tmpdir(), "issue-flow-task-plan-"));
|
|
543
|
+
const tmpFile = join(tmpDir, "tasks.json");
|
|
544
|
+
await writeFile2(tmpFile, `${JSON.stringify(plan, null, 2)}
|
|
545
|
+
`, "utf-8");
|
|
546
|
+
try {
|
|
547
|
+
await rename2(tmpFile, path);
|
|
548
|
+
} catch (err) {
|
|
549
|
+
if (err.code === "EXDEV") {
|
|
550
|
+
await copyFile(tmpFile, path);
|
|
551
|
+
await unlink(tmpFile);
|
|
552
|
+
} else {
|
|
553
|
+
throw err;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
function initializeState(plan) {
|
|
558
|
+
const defaultPipeline = {
|
|
559
|
+
prdCompleted: false,
|
|
560
|
+
jsonCompleted: false,
|
|
561
|
+
executionCompleted: false,
|
|
562
|
+
reviewCompleted: false,
|
|
563
|
+
prCreated: false
|
|
564
|
+
};
|
|
565
|
+
return {
|
|
566
|
+
...plan,
|
|
567
|
+
issueStatus: plan.issueStatus ?? "pending",
|
|
568
|
+
completedAt: plan.completedAt ?? null,
|
|
569
|
+
lastAttemptAt: plan.lastAttemptAt ?? null,
|
|
570
|
+
lastError: plan.lastError ?? null,
|
|
571
|
+
correctionCycle: plan.correctionCycle ?? 0,
|
|
572
|
+
maxCorrectionCycles: plan.maxCorrectionCycles ?? 3,
|
|
573
|
+
pipeline: plan.pipeline ? { ...defaultPipeline, ...plan.pipeline } : defaultPipeline
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
function allStoriesPass(plan) {
|
|
577
|
+
if (plan.userStories.length === 0) {
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
return plan.userStories.every((story) => story.passes === true);
|
|
581
|
+
}
|
|
582
|
+
function markIssueInProgress(plan, timestamp) {
|
|
583
|
+
const ts = timestamp ?? isoNow();
|
|
584
|
+
return {
|
|
585
|
+
...plan,
|
|
586
|
+
issueStatus: "in_progress",
|
|
587
|
+
completedAt: null,
|
|
588
|
+
lastAttemptAt: ts
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
function markIssueCompleted(plan) {
|
|
592
|
+
const ts = isoNow();
|
|
593
|
+
return {
|
|
594
|
+
...plan,
|
|
595
|
+
issueStatus: "completed",
|
|
596
|
+
completedAt: ts,
|
|
597
|
+
lastAttemptAt: ts,
|
|
598
|
+
lastError: null,
|
|
599
|
+
pipeline: plan.pipeline ? { ...plan.pipeline, executionCompleted: true } : plan.pipeline
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
function setLastError(plan, category, message) {
|
|
603
|
+
const ts = isoNow();
|
|
604
|
+
const error = {
|
|
605
|
+
category,
|
|
606
|
+
message,
|
|
607
|
+
at: ts
|
|
608
|
+
};
|
|
609
|
+
return {
|
|
610
|
+
...plan,
|
|
611
|
+
lastAttemptAt: ts,
|
|
612
|
+
lastError: error
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
function clearLastError(plan, attemptStartedAt) {
|
|
616
|
+
const ts = isoNow();
|
|
617
|
+
if (plan.lastError && plan.lastError.at > attemptStartedAt) {
|
|
618
|
+
return {
|
|
619
|
+
...plan,
|
|
620
|
+
lastAttemptAt: ts
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
return {
|
|
624
|
+
...plan,
|
|
625
|
+
lastAttemptAt: ts,
|
|
626
|
+
lastError: null
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
function trimErrorMessage(message) {
|
|
630
|
+
return message.split("\n").filter((line) => line.trim().length > 0).slice(0, 8).join("\n");
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// src/core/verbose.ts
|
|
634
|
+
var _verbose = false;
|
|
635
|
+
function setVerbose(enabled) {
|
|
636
|
+
_verbose = enabled;
|
|
637
|
+
}
|
|
638
|
+
function isVerbose() {
|
|
639
|
+
return _verbose;
|
|
640
|
+
}
|
|
641
|
+
var _onOutput;
|
|
642
|
+
function setOutputCallback(callback) {
|
|
643
|
+
_onOutput = callback;
|
|
644
|
+
}
|
|
645
|
+
function getOutputCallback() {
|
|
646
|
+
return _onOutput;
|
|
647
|
+
}
|
|
648
|
+
var _onStoryUpdate;
|
|
649
|
+
function setStoryUpdateCallback(callback) {
|
|
650
|
+
_onStoryUpdate = callback;
|
|
651
|
+
}
|
|
652
|
+
function getStoryUpdateCallback() {
|
|
653
|
+
return _onStoryUpdate;
|
|
654
|
+
}
|
|
655
|
+
var _globalTimeout;
|
|
656
|
+
function setGlobalTimeout(ms) {
|
|
657
|
+
_globalTimeout = ms;
|
|
658
|
+
}
|
|
659
|
+
function getGlobalTimeout() {
|
|
660
|
+
return _globalTimeout;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// src/ui/logger.ts
|
|
664
|
+
import chalk from "chalk";
|
|
665
|
+
import ora from "ora";
|
|
666
|
+
function useUnicode() {
|
|
667
|
+
if (process.env.NO_COLOR != null && process.env.NO_COLOR !== "") return false;
|
|
668
|
+
if (!process.stdout.isTTY) return false;
|
|
669
|
+
return true;
|
|
670
|
+
}
|
|
671
|
+
function useColor() {
|
|
672
|
+
if (process.env.NO_COLOR != null && process.env.NO_COLOR !== "") return false;
|
|
673
|
+
if (!process.stdout.isTTY) return false;
|
|
674
|
+
return true;
|
|
675
|
+
}
|
|
676
|
+
function getIcons() {
|
|
677
|
+
if (useUnicode()) {
|
|
678
|
+
return {
|
|
679
|
+
success: "\u2713",
|
|
680
|
+
fail: "\u2717",
|
|
681
|
+
pending: "\u23F3",
|
|
682
|
+
retry: "\u21BB",
|
|
683
|
+
warn: "\u26A0",
|
|
684
|
+
start: "\u25B6",
|
|
685
|
+
end: "\u25A0",
|
|
686
|
+
notReached: "\u25CB",
|
|
687
|
+
tool: "\u25B8",
|
|
688
|
+
connector: "\u2502"
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
return {
|
|
692
|
+
success: "[OK]",
|
|
693
|
+
fail: "[FAIL]",
|
|
694
|
+
pending: "[...]",
|
|
695
|
+
retry: "[RETRY]",
|
|
696
|
+
warn: "[WARN]",
|
|
697
|
+
start: "[START]",
|
|
698
|
+
end: "[END]",
|
|
699
|
+
notReached: "[ ]",
|
|
700
|
+
tool: ">",
|
|
701
|
+
connector: "|"
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
function getTermWidth() {
|
|
705
|
+
return process.stdout.columns ?? 80;
|
|
706
|
+
}
|
|
707
|
+
function emit(line, level = "info") {
|
|
708
|
+
getSessionPublisher().publish({ type: "log", at: isoNow(), level, message: line });
|
|
709
|
+
const cb = getOutputCallback();
|
|
710
|
+
if (cb) {
|
|
711
|
+
cb(line);
|
|
712
|
+
} else {
|
|
713
|
+
console.log(line);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
function printSuccess(message) {
|
|
717
|
+
const icons = getIcons();
|
|
718
|
+
if (useColor()) {
|
|
719
|
+
emit(chalk.green(`${icons.success} ${message}`));
|
|
720
|
+
} else {
|
|
721
|
+
emit(`${icons.success} ${message}`);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
function printError(message) {
|
|
725
|
+
const icons = getIcons();
|
|
726
|
+
if (useColor()) {
|
|
727
|
+
emit(chalk.red(`${icons.fail} ${message}`), "error");
|
|
728
|
+
} else {
|
|
729
|
+
emit(`${icons.fail} ${message}`, "error");
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
function printWarning(message) {
|
|
733
|
+
const icons = getIcons();
|
|
734
|
+
if (useColor()) {
|
|
735
|
+
emit(chalk.yellow(`${icons.warn} ${message}`), "warn");
|
|
736
|
+
} else {
|
|
737
|
+
emit(`${icons.warn} ${message}`, "warn");
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
function printRetry(message) {
|
|
741
|
+
const icons = getIcons();
|
|
742
|
+
if (useColor()) {
|
|
743
|
+
emit(chalk.yellow(`${icons.retry} ${message}`), "warn");
|
|
744
|
+
} else {
|
|
745
|
+
emit(`${icons.retry} ${message}`, "warn");
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
function printInfo(message) {
|
|
749
|
+
const icons = getIcons();
|
|
750
|
+
if (useColor()) {
|
|
751
|
+
emit(chalk.blue(`${icons.start} ${message}`));
|
|
752
|
+
} else {
|
|
753
|
+
emit(`${icons.start} ${message}`);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
function createSpinner(message) {
|
|
757
|
+
return ora({
|
|
758
|
+
text: message,
|
|
759
|
+
color: "blue",
|
|
760
|
+
spinner: "dots"
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
function formatDuration(totalSeconds) {
|
|
764
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
765
|
+
const mins = Math.floor(totalSeconds % 3600 / 60);
|
|
766
|
+
const secs = totalSeconds % 60;
|
|
767
|
+
if (hours > 0) {
|
|
768
|
+
return `${hours}h ${mins}m ${secs}s`;
|
|
769
|
+
}
|
|
770
|
+
if (mins > 0) {
|
|
771
|
+
return `${mins}m ${secs}s`;
|
|
772
|
+
}
|
|
773
|
+
return `${secs}s`;
|
|
774
|
+
}
|
|
775
|
+
var ElapsedTimer = class {
|
|
776
|
+
constructor(onTick) {
|
|
777
|
+
this.onTick = onTick;
|
|
778
|
+
this.startTime = Date.now();
|
|
779
|
+
}
|
|
780
|
+
startTime;
|
|
781
|
+
intervalId = null;
|
|
782
|
+
cleanup = () => this.stop();
|
|
783
|
+
start() {
|
|
784
|
+
this.startTime = Date.now();
|
|
785
|
+
this.intervalId = setInterval(() => {
|
|
786
|
+
const seconds = Math.floor((Date.now() - this.startTime) / 1e3);
|
|
787
|
+
this.onTick(formatDuration(seconds));
|
|
788
|
+
}, 1e3);
|
|
789
|
+
if (this.intervalId && typeof this.intervalId === "object" && "unref" in this.intervalId) {
|
|
790
|
+
this.intervalId.unref();
|
|
791
|
+
}
|
|
792
|
+
process.on("exit", this.cleanup);
|
|
793
|
+
return this;
|
|
794
|
+
}
|
|
795
|
+
getElapsedSeconds() {
|
|
796
|
+
return Math.floor((Date.now() - this.startTime) / 1e3);
|
|
797
|
+
}
|
|
798
|
+
stop() {
|
|
799
|
+
if (this.intervalId) {
|
|
800
|
+
clearInterval(this.intervalId);
|
|
801
|
+
this.intervalId = null;
|
|
802
|
+
}
|
|
803
|
+
process.removeListener("exit", this.cleanup);
|
|
804
|
+
return this.getElapsedSeconds();
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
export {
|
|
809
|
+
taskPlanSchema,
|
|
810
|
+
webConfigSchema,
|
|
811
|
+
NullPublisher,
|
|
812
|
+
FilePublisher,
|
|
813
|
+
setSessionPublisher,
|
|
814
|
+
getSessionPublisher,
|
|
815
|
+
isoNow,
|
|
816
|
+
loadTaskPlan,
|
|
817
|
+
saveTaskPlan,
|
|
818
|
+
initializeState,
|
|
819
|
+
allStoriesPass,
|
|
820
|
+
markIssueInProgress,
|
|
821
|
+
markIssueCompleted,
|
|
822
|
+
setLastError,
|
|
823
|
+
clearLastError,
|
|
824
|
+
trimErrorMessage,
|
|
825
|
+
setVerbose,
|
|
826
|
+
isVerbose,
|
|
827
|
+
setOutputCallback,
|
|
828
|
+
getOutputCallback,
|
|
829
|
+
setStoryUpdateCallback,
|
|
830
|
+
getStoryUpdateCallback,
|
|
831
|
+
setGlobalTimeout,
|
|
832
|
+
getGlobalTimeout,
|
|
833
|
+
useUnicode,
|
|
834
|
+
useColor,
|
|
835
|
+
getIcons,
|
|
836
|
+
getTermWidth,
|
|
837
|
+
printSuccess,
|
|
838
|
+
printError,
|
|
839
|
+
printWarning,
|
|
840
|
+
printRetry,
|
|
841
|
+
printInfo,
|
|
842
|
+
createSpinner,
|
|
843
|
+
formatDuration,
|
|
844
|
+
ElapsedTimer
|
|
845
|
+
};
|
|
846
|
+
//# sourceMappingURL=chunk-FNLXZXWP.js.map
|