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.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -1
  3. package/dist/{chunk-MEB3B2FI.js → analyze-IJ2SYXVM.js} +7 -10
  4. package/dist/{chunk-MEB3B2FI.js.map → analyze-IJ2SYXVM.js.map} +1 -1
  5. package/dist/{chunk-4OQ2ARLP.js → chunk-2XWXVWVD.js} +10 -2
  6. package/dist/chunk-2XWXVWVD.js.map +1 -0
  7. package/dist/{chunk-MWUIIUAS.js → chunk-AY7UZAGX.js} +8 -10
  8. package/dist/{chunk-MWUIIUAS.js.map → chunk-AY7UZAGX.js.map} +1 -1
  9. package/dist/chunk-FNLXZXWP.js +846 -0
  10. package/dist/chunk-FNLXZXWP.js.map +1 -0
  11. package/dist/{chunk-HYP64MZI.js → chunk-GYR4YHEB.js} +110 -116
  12. package/dist/chunk-GYR4YHEB.js.map +1 -0
  13. package/dist/{chunk-CYWHYPTU.js → chunk-MKQIP7YW.js} +7 -9
  14. package/dist/{chunk-CYWHYPTU.js.map → chunk-MKQIP7YW.js.map} +1 -1
  15. package/dist/{chunk-CSSXTPWE.js → chunk-Q2TQCJDN.js} +2 -2
  16. package/dist/{chunk-2JZCWJEQ.js → chunk-SI4ALORB.js} +7 -21
  17. package/dist/chunk-SI4ALORB.js.map +1 -0
  18. package/dist/{chunk-OZVHOVDT.js → chunk-SLHZPB3C.js} +15 -8
  19. package/dist/chunk-SLHZPB3C.js.map +1 -0
  20. package/dist/chunk-TYOFZ6DD.js +240 -0
  21. package/dist/chunk-TYOFZ6DD.js.map +1 -0
  22. package/dist/{chunk-4PZXIOXD.js → chunk-XJJP4TSK.js} +7 -9
  23. package/dist/{chunk-4PZXIOXD.js.map → chunk-XJJP4TSK.js.map} +1 -1
  24. package/dist/cli.js +56 -19
  25. package/dist/cli.js.map +1 -1
  26. package/dist/execute-DALVYZIH.js +11 -0
  27. package/dist/{generate-HP4CPMJG.js → generate-5OWWNRIB.js} +4 -4
  28. package/dist/init-6AWQI3IE.js +9 -0
  29. package/dist/plan-RQXK2OCP.js +11 -0
  30. package/dist/pr-UU4H7ZZR.js +11 -0
  31. package/dist/prd-WB3QH6QF.js +11 -0
  32. package/dist/review-2WHYJG7M.js +11 -0
  33. package/dist/run-UCSNYSQO.js +756 -0
  34. package/dist/run-UCSNYSQO.js.map +1 -0
  35. package/package.json +13 -3
  36. package/prompts/prd.md +5 -4
  37. package/web/public/app.css +481 -0
  38. package/web/public/app.js +522 -0
  39. package/web/public/index.html +113 -0
  40. package/dist/analyze-3SN7ZAZU.js +0 -12
  41. package/dist/chunk-2JZCWJEQ.js.map +0 -1
  42. package/dist/chunk-4OQ2ARLP.js.map +0 -1
  43. package/dist/chunk-64FU2L2T.js +0 -180
  44. package/dist/chunk-64FU2L2T.js.map +0 -1
  45. package/dist/chunk-HYP64MZI.js.map +0 -1
  46. package/dist/chunk-OZVHOVDT.js.map +0 -1
  47. package/dist/chunk-YAFHVFV5.js +0 -199
  48. package/dist/chunk-YAFHVFV5.js.map +0 -1
  49. package/dist/execute-GIA4IKS5.js +0 -11
  50. package/dist/init-SXFGOCML.js +0 -9
  51. package/dist/plan-HWZRG5GP.js +0 -12
  52. package/dist/pr-TTOFYUCV.js +0 -12
  53. package/dist/prd-ET2QN7GB.js +0 -12
  54. package/dist/review-3QDZCOMT.js +0 -12
  55. package/dist/review-3QDZCOMT.js.map +0 -1
  56. package/dist/run-PKHPULYL.js +0 -451
  57. package/dist/run-PKHPULYL.js.map +0 -1
  58. /package/dist/{chunk-CSSXTPWE.js.map → chunk-Q2TQCJDN.js.map} +0 -0
  59. /package/dist/{analyze-3SN7ZAZU.js.map → execute-DALVYZIH.js.map} +0 -0
  60. /package/dist/{generate-HP4CPMJG.js.map → generate-5OWWNRIB.js.map} +0 -0
  61. /package/dist/{execute-GIA4IKS5.js.map → init-6AWQI3IE.js.map} +0 -0
  62. /package/dist/{init-SXFGOCML.js.map → plan-RQXK2OCP.js.map} +0 -0
  63. /package/dist/{plan-HWZRG5GP.js.map → pr-UU4H7ZZR.js.map} +0 -0
  64. /package/dist/{pr-TTOFYUCV.js.map → prd-WB3QH6QF.js.map} +0 -0
  65. /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