issue-flow 0.5.2 → 0.6.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.
Files changed (68) hide show
  1. package/dist/{analyze-QVQ4CTCU.js → analyze-VVG7UABS.js} +15 -5
  2. package/dist/analyze-VVG7UABS.js.map +1 -0
  3. package/dist/{chunk-3ULIAC2J.js → chunk-2CSHLHYP.js} +2 -2
  4. package/dist/{chunk-PA3GI7LR.js → chunk-4HT4EVYV.js} +78 -32
  5. package/dist/chunk-4HT4EVYV.js.map +1 -0
  6. package/dist/{chunk-DLWRUTRL.js → chunk-7W5VGYXW.js} +11 -11
  7. package/dist/{chunk-JMSRJ43G.js → chunk-EHX4J5G5.js} +15 -8
  8. package/dist/chunk-EHX4J5G5.js.map +1 -0
  9. package/dist/{chunk-KMX3DUJN.js → chunk-FPGHTTVV.js} +15 -8
  10. package/dist/chunk-FPGHTTVV.js.map +1 -0
  11. package/dist/{chunk-OEJSMZMO.js → chunk-JTSAL3TI.js} +12 -5
  12. package/dist/chunk-JTSAL3TI.js.map +1 -0
  13. package/dist/{chunk-LJI45O5B.js → chunk-KWGRBNUG.js} +22 -8
  14. package/dist/chunk-KWGRBNUG.js.map +1 -0
  15. package/dist/chunk-SVOWWZQJ.js +982 -0
  16. package/dist/chunk-SVOWWZQJ.js.map +1 -0
  17. package/dist/{chunk-O5XI2CFF.js → chunk-WZDCTAWZ.js} +55 -20
  18. package/dist/chunk-WZDCTAWZ.js.map +1 -0
  19. package/dist/{chunk-VM7C7L3A.js → chunk-YM3G5BLR.js} +21 -9
  20. package/dist/chunk-YM3G5BLR.js.map +1 -0
  21. package/dist/cli.js +130 -29
  22. package/dist/cli.js.map +1 -1
  23. package/dist/config-B564P45C.js +29 -0
  24. package/dist/execute-PNOFNLZG.js +12 -0
  25. package/dist/generate-IN62ARBS.js +143 -0
  26. package/dist/generate-IN62ARBS.js.map +1 -0
  27. package/dist/init-5242XSYC.js +9 -0
  28. package/dist/plan-6KLONWPR.js +14 -0
  29. package/dist/pr-JLJLMFN3.js +14 -0
  30. package/dist/prd-TVJDYZXH.js +14 -0
  31. package/dist/review-FRV4ZMS2.js +12 -0
  32. package/dist/review-FRV4ZMS2.js.map +1 -0
  33. package/dist/{run-3GJC54DB.js → run-GPBICAD6.js} +45 -26
  34. package/dist/run-GPBICAD6.js.map +1 -0
  35. package/package.json +2 -1
  36. package/prompts/analyze.md +18 -5
  37. package/prompts/generate.md +27 -6
  38. package/prompts/plan.md +19 -2
  39. package/prompts/pr.md +23 -6
  40. package/prompts/prd.md +18 -5
  41. package/prompts/review.md +18 -6
  42. package/dist/analyze-QVQ4CTCU.js.map +0 -1
  43. package/dist/chunk-JMSRJ43G.js.map +0 -1
  44. package/dist/chunk-KLBAHTXX.js +0 -283
  45. package/dist/chunk-KLBAHTXX.js.map +0 -1
  46. package/dist/chunk-KMX3DUJN.js.map +0 -1
  47. package/dist/chunk-LJI45O5B.js.map +0 -1
  48. package/dist/chunk-O5XI2CFF.js.map +0 -1
  49. package/dist/chunk-OEJSMZMO.js.map +0 -1
  50. package/dist/chunk-PA3GI7LR.js.map +0 -1
  51. package/dist/chunk-VM7C7L3A.js.map +0 -1
  52. package/dist/execute-TEJ26L4O.js +0 -12
  53. package/dist/generate-KBQ4KW4T.js +0 -48
  54. package/dist/generate-KBQ4KW4T.js.map +0 -1
  55. package/dist/init-4VMPBRMA.js +0 -9
  56. package/dist/plan-PWYZP5AN.js +0 -13
  57. package/dist/pr-CJQWZUDR.js +0 -13
  58. package/dist/prd-4KLQY7BC.js +0 -13
  59. package/dist/review-YFCWBBKY.js +0 -11
  60. package/dist/run-3GJC54DB.js.map +0 -1
  61. /package/dist/{chunk-3ULIAC2J.js.map → chunk-2CSHLHYP.js.map} +0 -0
  62. /package/dist/{chunk-DLWRUTRL.js.map → chunk-7W5VGYXW.js.map} +0 -0
  63. /package/dist/{execute-TEJ26L4O.js.map → config-B564P45C.js.map} +0 -0
  64. /package/dist/{init-4VMPBRMA.js.map → execute-PNOFNLZG.js.map} +0 -0
  65. /package/dist/{plan-PWYZP5AN.js.map → init-5242XSYC.js.map} +0 -0
  66. /package/dist/{pr-CJQWZUDR.js.map → plan-6KLONWPR.js.map} +0 -0
  67. /package/dist/{prd-4KLQY7BC.js.map → pr-JLJLMFN3.js.map} +0 -0
  68. /package/dist/{review-YFCWBBKY.js.map → prd-TVJDYZXH.js.map} +0 -0
@@ -1,32 +1,36 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ runReview
4
+ } from "./chunk-JTSAL3TI.js";
2
5
  import {
3
6
  runInit
4
- } from "./chunk-VM7C7L3A.js";
7
+ } from "./chunk-YM3G5BLR.js";
5
8
  import {
6
9
  publishGitState,
7
10
  runExecute
8
- } from "./chunk-DLWRUTRL.js";
9
- import {
10
- loadWebConfig
11
- } from "./chunk-PA3GI7LR.js";
11
+ } from "./chunk-7W5VGYXW.js";
12
12
  import {
13
13
  runPlan
14
- } from "./chunk-KMX3DUJN.js";
14
+ } from "./chunk-FPGHTTVV.js";
15
15
  import {
16
16
  runPr
17
- } from "./chunk-LJI45O5B.js";
17
+ } from "./chunk-KWGRBNUG.js";
18
18
  import {
19
19
  runPrd
20
- } from "./chunk-JMSRJ43G.js";
21
- import "./chunk-3ULIAC2J.js";
22
- import "./chunk-HDV3YDTG.js";
20
+ } from "./chunk-EHX4J5G5.js";
21
+ import "./chunk-2CSHLHYP.js";
23
22
  import {
24
- runReview
25
- } from "./chunk-OEJSMZMO.js";
26
- import "./chunk-KLBAHTXX.js";
23
+ getProvider,
24
+ resolveCommandIssue
25
+ } from "./chunk-SVOWWZQJ.js";
26
+ import "./chunk-HDV3YDTG.js";
27
27
  import {
28
28
  resolvePackageDir
29
29
  } from "./chunk-SLHZPB3C.js";
30
+ import {
31
+ loadIssuesConfig,
32
+ loadWebConfig
33
+ } from "./chunk-4HT4EVYV.js";
30
34
  import {
31
35
  FilePublisher,
32
36
  NullPublisher,
@@ -42,7 +46,7 @@ import {
42
46
  setOutputCallback,
43
47
  setSessionPublisher,
44
48
  setStoryUpdateCallback
45
- } from "./chunk-O5XI2CFF.js";
49
+ } from "./chunk-WZDCTAWZ.js";
46
50
 
47
51
  // src/commands/run.ts
48
52
  import { randomUUID } from "crypto";
@@ -479,6 +483,10 @@ async function startWebServer(options) {
479
483
  }
480
484
 
481
485
  // src/commands/run.ts
486
+ function toIssueNumber(id) {
487
+ const parsed = Number.parseInt(id, 10);
488
+ return Number.isNaN(parsed) ? null : parsed;
489
+ }
482
490
  var RUNNABLE_PHASES = ["prd", "plan", "execute", "review", "pr"];
483
491
  var RUNNABLE_PHASES_NO_BRANCH = ["prd", "plan", "execute", "review"];
484
492
  async function runPipeline(issue, mode, from, noBranch) {
@@ -517,12 +525,13 @@ async function runPipeline(issue, mode, from, noBranch) {
517
525
  async function runPipelinePhases(issueNumber, issueDir, mode, publisher, from, noBranch) {
518
526
  const tasksPath = join2(issueDir, "tasks.json");
519
527
  const sessionId = randomUUID();
528
+ let publishedIssueNumber = toIssueNumber(issueNumber);
520
529
  const publishSessionStart = (phases, at, info) => {
521
530
  publisher.publish({
522
531
  type: "session:start",
523
532
  at,
524
533
  sessionId,
525
- issueNumber: Number.parseInt(issueNumber, 10) || 0,
534
+ issueNumber: publishedIssueNumber,
526
535
  issueUrl: info?.issueUrl,
527
536
  branch: info?.branch,
528
537
  phases: [...phases],
@@ -530,9 +539,10 @@ async function runPipelinePhases(issueNumber, issueDir, mode, publisher, from, n
530
539
  });
531
540
  };
532
541
  printInfo(`Starting pipeline for issue #${issueNumber} (mode: ${mode})`);
542
+ const issuesConfig = await loadIssuesConfig();
533
543
  printInfo("Running prerequisite checks...");
534
544
  const sessionStartedAt = isoNow();
535
- const initCode = await runInit();
545
+ const initCode = await runInit(issuesConfig.preferredProvider);
536
546
  if (initCode !== 0) {
537
547
  publishSessionStart(PIPELINE_PHASES, sessionStartedAt);
538
548
  publisher.publish({ type: "phase:start", at: sessionStartedAt, phase: "init" });
@@ -546,6 +556,12 @@ async function runPipelinePhases(issueNumber, issueDir, mode, publisher, from, n
546
556
  printError("Prerequisites not met. Fix the issues above and try again.");
547
557
  return 1;
548
558
  }
559
+ const resolution = await resolveCommandIssue(issueNumber, void 0, { config: issuesConfig });
560
+ if (!resolution.ok) {
561
+ return resolution.code;
562
+ }
563
+ const resolvedIssue = resolution.resolved;
564
+ publishedIssueNumber = resolvedIssue.issue.number;
549
565
  let effectiveNoBranch = noBranch ?? false;
550
566
  let planIssueUrl;
551
567
  let planBranch;
@@ -571,7 +587,7 @@ async function runPipelinePhases(issueNumber, issueDir, mode, publisher, from, n
571
587
  const activePhases = effectiveNoBranch ? PIPELINE_PHASES_NO_BRANCH : PIPELINE_PHASES;
572
588
  const phaseOrder = effectiveNoBranch ? RUNNABLE_PHASES_NO_BRANCH : RUNNABLE_PHASES;
573
589
  publishSessionStart(activePhases, sessionStartedAt, {
574
- issueUrl: planIssueUrl,
590
+ issueUrl: planIssueUrl ?? resolvedIssue.issue.remoteRef ?? void 0,
575
591
  branch: planBranch
576
592
  });
577
593
  publisher.publish({ type: "phase:start", at: sessionStartedAt, phase: "init" });
@@ -626,9 +642,9 @@ async function runPipelinePhases(issueNumber, issueDir, mode, publisher, from, n
626
642
  }
627
643
  };
628
644
  const runners = {
629
- prd: makeRunner(() => runPrd(issueNumber), "prd"),
645
+ prd: makeRunner(() => runPrd(issueNumber, resolvedIssue), "prd"),
630
646
  plan: async () => {
631
- await makeRunner(() => runPlan(issueNumber), "plan")();
647
+ await makeRunner(() => runPlan(issueNumber, resolvedIssue), "plan")();
632
648
  if (effectiveNoBranch) {
633
649
  try {
634
650
  const plan = await loadTaskPlan(tasksPath);
@@ -646,7 +662,7 @@ async function runPipelinePhases(issueNumber, issueDir, mode, publisher, from, n
646
662
  maxCycles = plan.maxCorrectionCycles;
647
663
  } catch {
648
664
  }
649
- let code = await runReview(issueNumber);
665
+ let code = await runReview(issueNumber, resolvedIssue);
650
666
  let cycle = 0;
651
667
  while (code !== 0 && cycle < maxCycles) {
652
668
  cycle++;
@@ -662,13 +678,13 @@ async function runPipelinePhases(issueNumber, issueDir, mode, publisher, from, n
662
678
  if (execCode !== 0) {
663
679
  throw new Error("Correction execution failed");
664
680
  }
665
- code = await runReview(issueNumber);
681
+ code = await runReview(issueNumber, resolvedIssue);
666
682
  }
667
683
  if (code !== 0) {
668
684
  throw new Error(`Review failed after ${maxCycles} correction cycles`);
669
685
  }
670
686
  },
671
- pr: makeRunner(() => runPr(issueNumber), "pr")
687
+ pr: makeRunner(() => runPr(issueNumber, resolvedIssue), "pr")
672
688
  };
673
689
  const instrumentedRunners = Object.fromEntries(
674
690
  Object.entries(runners).map(([phase, fn]) => [
@@ -704,14 +720,17 @@ async function runPipelinePhases(issueNumber, issueDir, mode, publisher, from, n
704
720
  printError(`Phase ${result.failedPhase} failed`);
705
721
  return 1;
706
722
  }
707
- printInfo("Closing issue...");
708
723
  try {
709
- await execa("gh", ["issue", "close", issueNumber], { reject: false });
724
+ const provider = getProvider(resolvedIssue.source);
725
+ if (provider.close !== void 0) {
726
+ printInfo("Closing issue...");
727
+ await provider.close(issueNumber);
728
+ }
710
729
  } catch {
711
730
  printWarning("Failed to close issue automatically");
712
731
  }
713
732
  let prUrl = "unknown";
714
- if (!effectiveNoBranch) {
733
+ if (!effectiveNoBranch && resolvedIssue.source === "github") {
715
734
  try {
716
735
  const proc = await execa(
717
736
  "gh",
@@ -755,4 +774,4 @@ async function runPipelinePhases(issueNumber, issueDir, mode, publisher, from, n
755
774
  export {
756
775
  runPipeline
757
776
  };
758
- //# sourceMappingURL=run-3GJC54DB.js.map
777
+ //# sourceMappingURL=run-GPBICAD6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/run.ts","../src/core/pipeline.ts","../src/ui/pipeline-renderer.ts","../src/web/server.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { join } from 'node:path';\nimport { execa } from 'execa';\nimport { loadIssuesConfig, loadWebConfig } from '../config.js';\nimport {\n PIPELINE_PHASES,\n PIPELINE_PHASES_NO_BRANCH,\n PipelineManager,\n type PipelinePhase,\n} from '../core/pipeline.js';\nimport { publishGitState } from '../core/session-git.js';\nimport { setSessionPublisher } from '../core/session-publisher.js';\nimport { FilePublisher, NullPublisher, type SessionPublisher } from '../core/session-state.js';\nimport { isoNow, loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { isVerbose } from '../core/verbose.js';\nimport { resolveCommandIssue } from '../issues/context.js';\nimport { getProvider } from '../issues/registry.js';\nimport { formatDuration, printError, printInfo, printSuccess, printWarning } from '../ui/logger.js';\nimport { runPipelineWithRenderer } from '../ui/pipeline-renderer.js';\nimport { startWebServer, type WebServerHandle } from '../web/server.js';\nimport { runExecute } from './execute.js';\nimport { runInit } from './init.js';\nimport { runPlan } from './plan.js';\nimport { runPr } from './pr.js';\nimport { runPrd } from './prd.js';\nimport { runReview } from './review.js';\n\n/**\n * Numeric form of an identifier, or `null` when the origin uses a non-numeric\n * one. Published as-is in session.json: a local id like 'auth-refactor' has no\n * number, and reporting it as 0 would claim an Issue that does not exist.\n */\nfunction toIssueNumber(id: string): number | null {\n const parsed = Number.parseInt(id, 10);\n return Number.isNaN(parsed) ? null : parsed;\n}\n\n/** Runnable phase lists (excluding 'init' which is handled separately). */\nconst RUNNABLE_PHASES: PipelinePhase[] = ['prd', 'plan', 'execute', 'review', 'pr'];\nconst RUNNABLE_PHASES_NO_BRANCH: PipelinePhase[] = ['prd', 'plan', 'execute', 'review'];\n\nexport async function runPipeline(\n issue: string,\n mode: string,\n from?: string,\n noBranch?: boolean,\n): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const issueDir = join('issues', issueNumber);\n\n const webConfig = await loadWebConfig();\n const publisher: SessionPublisher = webConfig.enabled\n ? new FilePublisher(join(issueDir, 'session.json'), {\n logLimit: webConfig.logLimit,\n includeLogs: webConfig.includeLogs,\n })\n : new NullPublisher();\n setSessionPublisher(publisher);\n\n // A null handle (port in use, ...) means the pipeline runs without a server.\n let webServer: WebServerHandle | null = null;\n if (webConfig.enabled) {\n webServer = await startWebServer({\n publisher,\n port: webConfig.port,\n host: webConfig.host,\n refreshSeconds: webConfig.refreshSeconds,\n });\n }\n\n let exitCode = 1;\n try {\n exitCode = await runPipelinePhases(issueNumber, issueDir, mode, publisher, from, noBranch);\n return exitCode;\n } finally {\n publisher.publish({\n type: 'session:end',\n at: isoNow(),\n status: exitCode === 0 ? 'completed' : 'failed',\n });\n await webServer?.close();\n await publisher.close();\n setSessionPublisher(undefined);\n }\n}\n\nasync function runPipelinePhases(\n issueNumber: string,\n issueDir: string,\n mode: string,\n publisher: SessionPublisher,\n from?: string,\n noBranch?: boolean,\n): Promise<number> {\n const tasksPath = join(issueDir, 'tasks.json');\n const sessionId = randomUUID();\n\n // Refined with the provider's own number once the Issue is resolved.\n let publishedIssueNumber = toIssueNumber(issueNumber);\n\n const publishSessionStart = (\n phases: readonly string[],\n at: string,\n info?: { issueUrl?: string; branch?: string },\n ): void => {\n publisher.publish({\n type: 'session:start',\n at,\n sessionId,\n issueNumber: publishedIssueNumber,\n issueUrl: info?.issueUrl,\n branch: info?.branch,\n phases: [...phases],\n environment: { node: process.version, platform: process.platform },\n });\n };\n\n printInfo(`Starting pipeline for issue #${issueNumber} (mode: ${mode})`);\n\n // Loaded before the checks so init knows which origin the user is heading\n // for: with a local one, a missing gh must not fail the environment.\n const issuesConfig = await loadIssuesConfig();\n\n // Phase 1: Init check\n printInfo('Running prerequisite checks...');\n const sessionStartedAt = isoNow();\n const initCode = await runInit(issuesConfig.preferredProvider);\n if (initCode !== 0) {\n publishSessionStart(PIPELINE_PHASES, sessionStartedAt);\n publisher.publish({ type: 'phase:start', at: sessionStartedAt, phase: 'init' });\n publisher.publish({\n type: 'phase:end',\n at: isoNow(),\n phase: 'init',\n success: false,\n error: 'Prerequisites not met',\n });\n printError('Prerequisites not met. Fix the issues above and try again.');\n return 1;\n }\n\n // The origin is settled once, here, and the decision travels to every phase.\n // Resolving per phase would query the providers five times and could ask the\n // user about the same divergence five times.\n const resolution = await resolveCommandIssue(issueNumber, undefined, { config: issuesConfig });\n if (!resolution.ok) {\n return resolution.code;\n }\n const resolvedIssue = resolution.resolved;\n publishedIssueNumber = resolvedIssue.issue.number;\n\n // Resolve noBranch mode: persisted value takes precedence on resume\n let effectiveNoBranch = noBranch ?? false;\n let planIssueUrl: string | undefined;\n let planBranch: string | undefined;\n try {\n const existingPlan = await loadTaskPlan(tasksPath);\n const persistedNoBranch = existingPlan.noBranch ?? false;\n planIssueUrl = existingPlan.issueUrl || undefined;\n planBranch = existingPlan.branchName || undefined;\n\n // Only warn when the user explicitly passed a flag that conflicts with the persisted value\n if (noBranch !== undefined && noBranch !== persistedNoBranch) {\n if (persistedNoBranch) {\n printWarning(\n 'This pipeline was started with --no-branch. Ignoring current flag; using persisted mode.',\n );\n } else {\n printWarning(\n 'This pipeline was started without --no-branch. Ignoring current flag; using persisted mode.',\n );\n }\n }\n\n // Persisted mode wins on resume\n effectiveNoBranch = persistedNoBranch;\n } catch {\n // No tasks.json yet — use the CLI flag as-is\n }\n\n const activePhases = effectiveNoBranch ? PIPELINE_PHASES_NO_BRANCH : PIPELINE_PHASES;\n const phaseOrder = effectiveNoBranch ? RUNNABLE_PHASES_NO_BRANCH : RUNNABLE_PHASES;\n\n // The phase list is only known after resolving --no-branch, so the init\n // phase (which already ran) is published retroactively with real timestamps.\n publishSessionStart(activePhases, sessionStartedAt, {\n issueUrl: planIssueUrl ?? resolvedIssue.issue.remoteRef ?? undefined,\n branch: planBranch,\n });\n publisher.publish({ type: 'phase:start', at: sessionStartedAt, phase: 'init' });\n publisher.publish({ type: 'phase:end', at: isoNow(), phase: 'init', success: true });\n await publishGitState(publisher);\n\n // Determine starting phase\n let startPhase: PipelinePhase = 'prd';\n if (from) {\n if (!(activePhases as readonly string[]).includes(from)) {\n const validPhases = activePhases.filter((p) => p !== 'init').join(', ');\n // Check if the phase exists in the full set but is excluded by --no-branch\n if (effectiveNoBranch && (PIPELINE_PHASES as readonly string[]).includes(from)) {\n printError(\n `The '${from}' phase is not available in --no-branch mode. Valid phases: ${validPhases}`,\n );\n } else {\n printError(`Invalid phase: ${from}. Valid phases: ${validPhases}`);\n }\n return 1;\n }\n startPhase = from as PipelinePhase;\n } else {\n // Try to auto-resume from pipeline state\n try {\n const plan = await loadTaskPlan(tasksPath);\n const mgr = new PipelineManager(plan, tasksPath, activePhases);\n const nextPhase = mgr.getNextPhase();\n if (nextPhase && nextPhase !== 'init') {\n startPhase = nextPhase;\n printInfo(`Resuming from phase: ${startPhase}`);\n }\n } catch {\n // No tasks.json yet — start from beginning\n }\n }\n\n // Validate resume prerequisites if starting from a later phase\n if (from) {\n try {\n const plan = await loadTaskPlan(tasksPath);\n const mgr = new PipelineManager(plan, tasksPath, activePhases);\n if (!mgr.canResume(startPhase)) {\n printError(`Cannot resume from ${startPhase}: prerequisite phases not complete`);\n return 1;\n }\n } catch {\n if (startPhase !== 'prd') {\n printError(`Cannot resume from ${startPhase}: no pipeline state found`);\n return 1;\n }\n }\n }\n\n const startIdx = phaseOrder.indexOf(startPhase);\n\n // Build phase runner functions that throw on failure\n const makeRunner = (fn: () => Promise<number>, phase: string) => async () => {\n const code = await fn();\n if (code !== 0) {\n throw new Error(`Phase ${phase} failed with exit code ${code}`);\n }\n };\n\n const runners: Record<string, () => Promise<void>> = {\n prd: makeRunner(() => runPrd(issueNumber, resolvedIssue), 'prd'),\n plan: async () => {\n await makeRunner(() => runPlan(issueNumber, resolvedIssue), 'plan')();\n // Persist noBranch into the newly created tasks.json\n if (effectiveNoBranch) {\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.noBranch = true;\n await saveTaskPlan(tasksPath, plan);\n } catch {\n /* non-critical: tasks.json may not exist yet if plan phase didn't create it */\n }\n }\n },\n execute: makeRunner(() => runExecute(undefined, { issue: issueNumber }), 'execute'),\n review: async () => {\n // Read maxCorrectionCycles\n let maxCycles = 3;\n try {\n const plan = await loadTaskPlan(tasksPath);\n maxCycles = plan.maxCorrectionCycles;\n } catch {\n /* use default */\n }\n\n let code = await runReview(issueNumber, resolvedIssue);\n\n // Auto-correction loop on failure\n let cycle = 0;\n while (code !== 0 && cycle < maxCycles) {\n cycle++;\n printWarning(`Review failed. Starting correction cycle ${cycle}/${maxCycles}...`);\n publisher.publish({ type: 'correction:cycle', at: isoNow(), cycle, maxCycles });\n\n // Update correction cycle in tasks.json\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.correctionCycle = cycle;\n await saveTaskPlan(tasksPath, plan);\n } catch {\n /* non-critical */\n }\n\n // Re-execute\n const execCode = await runExecute(undefined, { issue: issueNumber });\n if (execCode !== 0) {\n throw new Error('Correction execution failed');\n }\n\n // Re-review\n code = await runReview(issueNumber, resolvedIssue);\n }\n\n if (code !== 0) {\n throw new Error(`Review failed after ${maxCycles} correction cycles`);\n }\n },\n pr: makeRunner(() => runPr(issueNumber, resolvedIssue), 'pr'),\n };\n\n // Publish phase:start/phase:end around every runner without touching the\n // listr2 renderer (pipeline-renderer.ts stays publication-free). Commit/PR\n // enrichment happens only at these boundaries (and at iteration end, in\n // engine.ts) — never per HTTP request.\n const instrumentedRunners = Object.fromEntries(\n Object.entries(runners).map(([phase, fn]) => [\n phase,\n async () => {\n publisher.publish({ type: 'phase:start', at: isoNow(), phase });\n try {\n await fn();\n await publishGitState(publisher);\n publisher.publish({ type: 'phase:end', at: isoNow(), phase, success: true });\n } catch (err) {\n await publishGitState(publisher);\n publisher.publish({\n type: 'phase:end',\n at: isoNow(),\n phase,\n success: false,\n error: err instanceof Error ? err.message : String(err),\n });\n throw err;\n }\n },\n ]),\n );\n\n // Run pipeline with listr2 renderer — startup header printed above, summary below\n const result = await runPipelineWithRenderer({\n phases: phaseOrder,\n startIndex: startIdx,\n verbose: isVerbose(),\n runners: instrumentedRunners,\n tasksPath,\n });\n\n if (!result.success) {\n printError(`Phase ${result.failedPhase} failed`);\n return 1;\n }\n\n // Close the issue through whoever owns it. A provider without close() (a\n // read-only origin) has nothing to do here, so the step is simply skipped.\n try {\n const provider = getProvider(resolvedIssue.source);\n if (provider.close !== undefined) {\n printInfo('Closing issue...');\n await provider.close(issueNumber);\n }\n } catch {\n printWarning('Failed to close issue automatically');\n }\n\n // Get PR URL for summary. Only GitHub-hosted Issues have a pull request to\n // look up, and --no-branch never opens one.\n let prUrl = 'unknown';\n if (!effectiveNoBranch && resolvedIssue.source === 'github') {\n try {\n const proc = await execa(\n 'gh',\n ['pr', 'list', '--head', '', '--json', 'url', '--limit', '1'],\n { reject: false },\n );\n const parsed = JSON.parse(proc.stdout?.toString() ?? '[]');\n if (parsed[0]?.url) {\n prUrl = parsed[0].url;\n }\n } catch {\n /* non-critical */\n }\n }\n\n // Get branch and story count\n let branchName = 'unknown';\n try {\n const proc = await execa('git', ['branch', '--show-current'], { reject: false });\n branchName = proc.stdout?.toString().trim() ?? 'unknown';\n } catch {\n /* non-critical */\n }\n\n let storyCount = 0;\n try {\n const plan = await loadTaskPlan(tasksPath);\n storyCount = plan.userStories.length;\n\n // Mark as completed\n plan.issueStatus = 'completed';\n plan.completedAt = isoNow();\n plan.lastAttemptAt = isoNow();\n await saveTaskPlan(tasksPath, plan);\n } catch {\n /* non-critical */\n }\n\n const totalDuration = formatDuration(result.overallElapsedSeconds);\n\n console.log('');\n printSuccess(`Pipeline complete for issue #${issueNumber}!`);\n console.log(` Branch: ${branchName}${effectiveNoBranch ? ' (current)' : ''}`);\n console.log(` Stories: ${storyCount}`);\n console.log(` Duration: ${totalDuration}`);\n if (!effectiveNoBranch) {\n console.log(` PR: ${prUrl}`);\n }\n\n return 0;\n}\n","import type { PipelineState, TaskPlan } from '../types.js';\nimport { loadTaskPlan, saveTaskPlan } from './state-manager.js';\n\n/**\n * Ordered pipeline phases. Each phase must complete before the next can start.\n */\nexport const PIPELINE_PHASES = ['init', 'prd', 'plan', 'execute', 'review', 'pr'] as const;\n\nexport type PipelinePhase = (typeof PIPELINE_PHASES)[number];\n\n/**\n * Pipeline phases excluding the 'pr' phase, for --no-branch mode.\n */\nexport const PIPELINE_PHASES_NO_BRANCH = PIPELINE_PHASES.filter(\n (p): p is Exclude<PipelinePhase, 'pr'> => p !== 'pr',\n);\n\n/**\n * Map pipeline phases to their corresponding PipelineState field.\n * 'init' has no persisted state — it's a runtime-only check.\n */\nconst PHASE_TO_FIELD: Record<PipelinePhase, keyof PipelineState | null> = {\n init: null,\n prd: 'prdCompleted',\n plan: 'jsonCompleted',\n execute: 'executionCompleted',\n review: 'reviewCompleted',\n pr: 'prCreated',\n};\n\nexport class PipelineManager {\n private tasksJsonPath: string;\n private plan: TaskPlan;\n private activePhases: readonly PipelinePhase[];\n\n constructor(plan: TaskPlan, tasksJsonPath: string, activePhases?: readonly PipelinePhase[]) {\n this.plan = plan;\n this.tasksJsonPath = tasksJsonPath;\n this.activePhases = activePhases ?? PIPELINE_PHASES;\n }\n\n /**\n * Reload state from disk.\n */\n async reload(): Promise<void> {\n this.plan = await loadTaskPlan(this.tasksJsonPath);\n }\n\n /**\n * Check if a phase is complete.\n */\n isPhaseComplete(phase: PipelinePhase): boolean {\n const field = PHASE_TO_FIELD[phase];\n if (field === null) return true; // init is always \"complete\" after running\n return this.plan.pipeline?.[field] ?? false;\n }\n\n /**\n * Get the first incomplete phase in the pipeline.\n * Returns null if all phases are complete.\n */\n getNextPhase(): PipelinePhase | null {\n for (const phase of this.activePhases) {\n if (!this.isPhaseComplete(phase)) {\n return phase;\n }\n }\n return null;\n }\n\n /**\n * Get the current phase (alias for getNextPhase).\n */\n getCurrentPhase(): PipelinePhase | null {\n return this.getNextPhase();\n }\n\n /**\n * Check whether we can resume from a specific phase.\n * All prerequisite phases must be complete.\n */\n canResume(fromPhase: PipelinePhase): boolean {\n const idx = this.activePhases.indexOf(fromPhase);\n if (idx < 0) return false;\n\n // All active phases before fromPhase must be complete\n for (let i = 0; i < idx; i++) {\n if (!this.isPhaseComplete(this.activePhases[i])) {\n return false;\n }\n }\n return true;\n }\n\n /**\n * Mark a phase as complete and persist to disk.\n */\n async markPhaseComplete(phase: PipelinePhase): Promise<void> {\n const field = PHASE_TO_FIELD[phase];\n if (field === null) return; // init has no persisted state\n\n this.plan = {\n ...this.plan,\n pipeline: {\n ...(this.plan.pipeline ?? {\n prdCompleted: false,\n jsonCompleted: false,\n executionCompleted: false,\n reviewCompleted: false,\n prCreated: false,\n }),\n [field]: true,\n },\n };\n\n await saveTaskPlan(this.tasksJsonPath, this.plan);\n }\n}\n","import { Listr, type ListrTask, PRESET_TIMER, PRESET_TIMESTAMP } from 'listr2';\nimport type { PipelinePhase } from '../core/pipeline.js';\nimport { loadTaskPlan } from '../core/state-manager.js';\nimport { setOutputCallback, setStoryUpdateCallback } from '../core/verbose.js';\nimport type { UserStory } from '../types.js';\n\n/** Minimal interface for the listr2 task wrapper properties we use. */\ninterface TaskContext {\n title: string;\n output: string;\n newListr(tasks: ListrTask[], options?: Record<string, unknown>): Listr;\n}\n\n/**\n * Result returned after the pipeline renderer finishes.\n */\nexport interface PipelineResult {\n success: boolean;\n failedPhase?: string;\n overallElapsedSeconds: number;\n}\n\n/**\n * Options for runPipelineWithRenderer.\n */\nexport interface PipelineRendererOptions {\n /** All pipeline phases in order */\n phases: PipelinePhase[];\n /** Index of the first phase to run (phases before this are skipped) */\n startIndex: number;\n /** Whether verbose mode is enabled */\n verbose: boolean;\n /** Map of phase name to its async runner function */\n runners: Record<string, () => Promise<void>>;\n /** Path to tasks.json — enables execute-phase subtask progress when set */\n tasksPath?: string;\n}\n\n/**\n * Phase labels for display.\n */\nconst PHASE_LABELS: Record<string, string> = {\n prd: 'PRD',\n plan: 'Plan',\n execute: 'Execute',\n review: 'Review',\n pr: 'PR',\n};\n\n/**\n * Select the appropriate listr2 renderer based on environment.\n *\n * - TTY + not verbose -> 'default' (animated spinners)\n * - TTY + verbose -> 'verbose' (sequential lines)\n * - Non-TTY / CI -> 'simple' (plain timestamped output)\n *\n * NO_COLOR is handled by listr2's own color support and the useColor() utility,\n * not by switching renderers — a TTY with NO_COLOR still benefits from the\n * default renderer's layout and spinners.\n */\nfunction selectRenderer(verbose: boolean): 'default' | 'verbose' | 'simple' {\n const isTTY = !!process.stdout.isTTY;\n const isCI = !!process.env.CI;\n\n if (!isTTY || isCI) {\n return 'simple';\n }\n\n return verbose ? 'verbose' : 'default';\n}\n\n/**\n * Build the execute phase task with dynamic subtasks for each user story.\n *\n * Stories that already pass are displayed as skipped. Pending stories wait for\n * the engine to complete them via the story update callback. The parent task\n * title is updated with aggregate progress (e.g., \"Execute (3/5 stories passing)\").\n */\nfunction buildExecutePhaseTask(runner: () => Promise<void>, tasksPath: string, verbose: boolean) {\n return async (_ctx: unknown, task: TaskContext) => {\n let stories: UserStory[];\n try {\n const plan = await loadTaskPlan(tasksPath);\n stories = [...plan.userStories].sort((a, b) => a.priority - b.priority);\n } catch {\n // If we can't read the plan, fall back to running without subtasks\n setOutputCallback((line: string) => {\n task.output = line;\n });\n try {\n await runner();\n } finally {\n setOutputCallback(undefined);\n }\n return;\n }\n\n const totalStories = stories.length;\n const initialPassed = stories.filter((s) => s.passes).length;\n task.title = `Execute (${initialPassed}/${totalStories} stories passing)`;\n\n // Create promise resolvers for pending stories\n const resolvers = new Map<string, { resolve: () => void; reject: (err: Error) => void }>();\n\n // Set up story update callback — engine calls this after each iteration\n setStoryUpdateCallback((updatedStories: UserStory[]) => {\n const passed = updatedStories.filter((s) => s.passes).length;\n task.title = `Execute (${passed}/${totalStories} stories passing)`;\n\n for (const s of updatedStories) {\n if (s.passes && resolvers.has(s.id)) {\n resolvers.get(s.id)!.resolve();\n resolvers.delete(s.id);\n }\n }\n });\n\n // Build subtask definitions for each story\n const subtaskDefs = stories.map((story) => ({\n title: `${story.id}: ${story.title}`,\n skip: story.passes ? 'already passing' : false,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n task: async () => {\n await new Promise<void>((resolve, reject) => {\n resolvers.set(story.id, { resolve, reject });\n });\n },\n rendererOptions: {\n timer: PRESET_TIMER,\n },\n }));\n\n // Engine runner subtask — drives the execution loop\n const engineSubtask = {\n title: 'Running engine',\n task: async (_c: unknown, engineTask: TaskContext) => {\n setOutputCallback((line: string) => {\n engineTask.output = line;\n });\n try {\n await runner();\n } finally {\n setOutputCallback(undefined);\n setStoryUpdateCallback(undefined);\n // Resolve any remaining pending stories (engine finished successfully)\n for (const [, { resolve }] of resolvers) {\n resolve();\n }\n resolvers.clear();\n }\n },\n rendererOptions: {\n timer: PRESET_TIMER,\n outputBar: verbose ? Infinity : false,\n persistentOutput: false,\n bottomBar: verbose ? Infinity : 3,\n },\n };\n\n // Run engine + story subtasks concurrently:\n // - Engine subtask runs the actual execution loop\n // - Story subtasks resolve as the engine completes each story\n // Cleanup of setStoryUpdateCallback is handled in engineSubtask's finally block\n return task.newListr([engineSubtask, ...subtaskDefs], {\n concurrent: true,\n exitOnError: false,\n rendererOptions: {\n timer: PRESET_TIMER,\n collapseSkips: false,\n },\n });\n };\n}\n\n/**\n * Run the pipeline phases through listr2 for single-writer terminal output.\n *\n * Phases before startIndex are displayed as \"skipped\".\n * Each running phase shows an animated spinner with elapsed time.\n * Completed phases show a checkmark with final duration.\n * The execute phase shows per-story subtask progress when tasksPath is provided.\n */\nexport async function runPipelineWithRenderer(\n options: PipelineRendererOptions,\n): Promise<PipelineResult> {\n const { phases, startIndex, verbose, runners, tasksPath } = options;\n const overallStart = Date.now();\n let currentPhase: string | undefined;\n const renderer = selectRenderer(verbose);\n const isSimple = renderer === 'simple';\n\n const tasks = new Listr(\n phases.map((phase, index) => {\n const label = PHASE_LABELS[phase] ?? phase.charAt(0).toUpperCase() + phase.slice(1);\n const isExecutePhase = phase === 'execute' && tasksPath;\n\n return {\n title: label,\n skip: index < startIndex ? 'skipped' : false,\n task: isExecutePhase\n ? async (_ctx: unknown, task: TaskContext) => {\n currentPhase = phase;\n const runner = runners[phase];\n if (!runner) {\n throw new Error(`No runner defined for phase: ${phase}`);\n }\n return buildExecutePhaseTask(runner, tasksPath, verbose)(_ctx, task);\n }\n : async (_ctx: unknown, task: TaskContext) => {\n currentPhase = phase;\n const runner = runners[phase];\n if (!runner) {\n throw new Error(`No runner defined for phase: ${phase}`);\n }\n // Route all output through task.output so listr2 controls rendering\n setOutputCallback((line: string) => {\n task.output = line;\n });\n try {\n await runner();\n } finally {\n setOutputCallback(undefined);\n }\n },\n rendererOptions: {\n timer: PRESET_TIMER,\n outputBar: verbose ? Infinity : false,\n persistentOutput: false,\n },\n };\n }),\n {\n renderer,\n rendererOptions: {\n timer: PRESET_TIMER,\n collapseSkips: false,\n ...(isSimple ? { timestamp: PRESET_TIMESTAMP } : {}),\n },\n exitOnError: true,\n },\n );\n\n let failedPhase: string | undefined;\n\n try {\n await tasks.run();\n } catch {\n failedPhase = currentPhase ?? phases[startIndex];\n } finally {\n // Ensure global callbacks are always cleaned up, even on unexpected errors\n setOutputCallback(undefined);\n setStoryUpdateCallback(undefined);\n }\n\n const overallElapsedSeconds = Math.floor((Date.now() - overallStart) / 1000);\n\n return {\n success: !failedPhase,\n failedPhase,\n overallElapsedSeconds,\n };\n}\n","import { readFile } from 'node:fs/promises';\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport { createRequire } from 'node:module';\nimport type { AddressInfo } from 'node:net';\nimport { join } from 'node:path';\nimport chalk from 'chalk';\nimport { resolvePackageDir } from '../core/prompt-resolver.js';\nimport type { SessionPublisher } from '../core/session-state.js';\nimport { printInfo, printWarning } from '../ui/logger.js';\n\n/**\n * HTTP server for the web monitoring mode. Plain node:http — no new runtime\n * dependencies. Serves the publisher's in-memory snapshot (it never re-reads\n * issues/N/session.json) and the static UI assets.\n *\n * Resilience contract: nothing here may ever affect the pipeline. Listen\n * failures (EADDRINUSE included) log a warning and the execution continues\n * without a server; the server is unref()'d so it cannot keep the process\n * alive; request handling is wrapped so a handler error answers 500 instead\n * of crashing the process.\n */\n\nconst require = createRequire(import.meta.url);\n\nfunction readPackageVersion(): string {\n try {\n return (require('../../package.json') as { version: string }).version;\n } catch {\n return '0.0.0';\n }\n}\n\nexport interface WebServerOptions {\n publisher: SessionPublisher;\n port: number;\n host: string;\n /** Suggested UI polling interval, exposed via /api/health. */\n refreshSeconds?: number;\n /** Package version reported by /api/health. Default: read from package.json. */\n version?: string;\n /** Directory holding index.html/app.css/app.js. Default: auto-resolved. */\n publicDir?: string;\n /** Info logger. Default: printInfo. */\n info?: (message: string) => void;\n /** Warning logger. Default: printWarning. */\n warn?: (message: string) => void;\n}\n\nexport interface WebServerHandle {\n server: Server;\n /** Host the server is bound to. */\n host: string;\n /** Actual bound port (relevant when options.port is 0). */\n port: number;\n /** Human-facing access URL. */\n url: string;\n /** Close the server and release signal handlers. Idempotent, never rejects. */\n close(): Promise<void>;\n}\n\ninterface StaticAsset {\n body: string;\n contentType: string;\n}\n\nconst JSON_TYPE = 'application/json; charset=utf-8';\n\nconst STATIC_ROUTES: Record<string, { file: string; contentType: string }> = {\n '/': { file: 'index.html', contentType: 'text/html; charset=utf-8' },\n '/app.css': { file: 'app.css', contentType: 'text/css; charset=utf-8' },\n '/app.js': { file: 'app.js', contentType: 'text/javascript; charset=utf-8' },\n};\n\n/** Assets are read once at startup; missing files simply 404. */\nasync function loadStaticAssets(publicDir: string | null): Promise<Map<string, StaticAsset>> {\n const assets = new Map<string, StaticAsset>();\n if (publicDir === null) return assets;\n for (const [route, { file, contentType }] of Object.entries(STATIC_ROUTES)) {\n try {\n const body = await readFile(join(publicDir, file), 'utf-8');\n assets.set(route, { body, contentType });\n } catch {\n // Asset not present (e.g. UI not built yet) — route answers 404.\n }\n }\n return assets;\n}\n\n/** Headers applied to every response, including 304s and errors. */\nfunction baseHeaders(res: ServerResponse): void {\n res.setHeader('Cache-Control', 'no-store');\n res.setHeader('X-Content-Type-Options', 'nosniff');\n res.setHeader('Referrer-Policy', 'no-referrer');\n res.setHeader('X-Robots-Tag', 'noindex, nofollow');\n}\n\nfunction respond(res: ServerResponse, status: number, contentType: string, body: string): void {\n res.statusCode = status;\n res.setHeader('Content-Type', contentType);\n res.end(body);\n}\n\nfunction respondJson(res: ServerResponse, status: number, payload: unknown): void {\n respond(res, status, JSON_TYPE, JSON.stringify(payload));\n}\n\n/**\n * Start the monitoring HTTP server. Returns null when the server could not\n * listen (port in use, invalid host, ...) — the pipeline continues without\n * monitoring, it is never brought down by the server.\n */\nexport async function startWebServer(options: WebServerOptions): Promise<WebServerHandle | null> {\n const info = options.info ?? printInfo;\n const warn = options.warn ?? printWarning;\n const version = options.version ?? readPackageVersion();\n const startedAtMs = Date.now();\n\n // The UI ships at the package root as web/public/ (sibling of prompts/),\n // resolved the same way from src/ and from the published dist/ layout.\n const assets = await loadStaticAssets(\n options.publicDir ?? resolvePackageDir(join('web', 'public')),\n );\n\n // JSON serialization memoized by publisher version; the version doubles as\n // the ETag, so an unchanged poll answers 304 with an empty body.\n let cached: { version: number; body: string; etag: string } | null = null;\n const statusPayload = (): { body: string; etag: string } => {\n const v = options.publisher.version();\n if (cached === null || cached.version !== v) {\n cached = { version: v, body: JSON.stringify(options.publisher.snapshot()), etag: `\"${v}\"` };\n }\n return cached;\n };\n\n const handleRequest = (req: IncomingMessage, res: ServerResponse): void => {\n baseHeaders(res);\n\n const path = new URL(req.url ?? '/', 'http://localhost').pathname;\n\n // POST /api/control/* is reserved for future write operations (pause,\n // retry, ...); intentionally NOT registered — the v1 surface is\n // read-only (snapshot.readOnly is true and capabilities is empty).\n if (req.method !== 'GET' && req.method !== 'HEAD') {\n respondJson(res, 404, { error: 'Not found' });\n return;\n }\n\n if (path === '/api/status' || path === '/status.json') {\n const { body, etag } = statusPayload();\n res.setHeader('ETag', etag);\n if (req.headers['if-none-match'] === etag) {\n res.statusCode = 304;\n res.end();\n return;\n }\n respond(res, 200, JSON_TYPE, body);\n return;\n }\n\n if (path === '/api/sessions') {\n const snapshot = options.publisher.snapshot();\n respondJson(res, 200, [\n {\n sessionId: snapshot.sessionId,\n issueNumber: snapshot.issue.number,\n status: snapshot.status,\n startedAt: snapshot.startedAt,\n updatedAt: snapshot.updatedAt,\n statusUrl: '/api/status',\n },\n ]);\n return;\n }\n\n if (path === '/api/health') {\n respondJson(res, 200, {\n ok: true,\n uptime: Math.round((Date.now() - startedAtMs) / 1000),\n version,\n refreshSeconds: options.refreshSeconds ?? 5,\n });\n return;\n }\n\n const asset = assets.get(path);\n if (asset) {\n respond(res, 200, asset.contentType, asset.body);\n return;\n }\n\n respondJson(res, 404, { error: 'Not found' });\n };\n\n const server = createServer((req, res) => {\n try {\n handleRequest(req, res);\n } catch (err) {\n try {\n respondJson(res, 500, { error: err instanceof Error ? err.message : String(err) });\n } catch {\n // Response already destroyed — nothing to do, never crash the process.\n }\n }\n });\n\n const listening = await new Promise<boolean>((resolve) => {\n const onError = (err: NodeJS.ErrnoException): void => {\n if (err.code === 'EADDRINUSE') {\n warn(\n `Web monitor port ${options.port} is already in use. Continuing without the web server.`,\n );\n } else {\n warn(`Web monitor failed to start (${err.message}). Continuing without the web server.`);\n }\n resolve(false);\n };\n server.once('error', onError);\n server.listen(options.port, options.host, () => {\n server.removeListener('error', onError);\n resolve(true);\n });\n });\n\n if (!listening) return null;\n\n // Never keep the process alive: the pipeline ending must end the process.\n server.unref();\n // Post-listen errors must never become uncaught exceptions.\n server.on('error', (err) => {\n warn(`Web monitor server error: ${err.message}`);\n });\n\n const address = server.address() as AddressInfo;\n const port = address.port;\n const displayHost = options.host === '0.0.0.0' ? 'localhost' : options.host;\n const url = `http://${displayHost}:${port}`;\n\n if (options.host === '0.0.0.0') {\n warn('Web monitor bound to 0.0.0.0: anyone on your local network can view the session state.');\n }\n info(`Web monitor running at ${chalk.bold.cyan(url)}`);\n\n let closed = false;\n let closePromise: Promise<void> | null = null;\n\n const doClose = (): Promise<void> =>\n new Promise<void>((resolve) => {\n process.removeListener('SIGINT', onSignal);\n process.removeListener('SIGTERM', onSignal);\n server.close(() => resolve());\n // Drop idle keep-alive connections so close() never hangs.\n server.closeAllConnections();\n });\n\n const close = (): Promise<void> => {\n if (closed) return closePromise ?? Promise.resolve();\n closed = true;\n closePromise = doClose();\n return closePromise;\n };\n\n // Explicit close on SIGINT/SIGTERM, then re-raise so the default\n // termination behavior still applies.\n const onSignal = (signal: NodeJS.Signals): void => {\n void close().finally(() => {\n process.kill(process.pid, signal);\n });\n };\n process.once('SIGINT', onSignal);\n process.once('SIGTERM', onSignal);\n\n return { server, host: options.host, port, url, close };\n}\n"],"mappings":"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","names":["join","require","join"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "issue-flow",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "Unified CLI for orchestrating the full issue-flow pipeline via Claude Code Headless",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,6 +12,7 @@
12
12
  "typecheck": "tsc --noEmit",
13
13
  "test": "vitest run",
14
14
  "test:watch": "vitest",
15
+ "smoke": "bash scripts/smoke-issue-providers.sh",
15
16
  "lint": "biome check src/",
16
17
  "lint:fix": "biome check --write src/",
17
18
  "format": "biome format --write src/",
@@ -1,10 +1,23 @@
1
- You are analyzing GitHub issue #__ISSUE_NUMBER__ for this repository.
1
+ You are analyzing issue #__ISSUE_NUMBER__ for this repository.
2
+
3
+ The issue content is already resolved and given below — do NOT fetch it, and do
4
+ not assume it lives on GitHub.
5
+
6
+ - Source: __ISSUE_SOURCE__
7
+ - Reference: __ISSUE_URL__
8
+ - Title: __ISSUE_TITLE__
9
+ - Labels: __ISSUE_LABELS__
10
+
11
+ Issue body:
12
+
13
+ <issue-body>
14
+ __ISSUE_BODY__
15
+ </issue-body>
2
16
 
3
17
  Steps:
4
- 1. Fetch the issue data using: gh issue view __ISSUE_NUMBER__ --json title,body,labels,comments
5
- 2. Analyze the codebase to understand the affected areas, tech stack, and architecture
6
- 3. Identify the scope, complexity, and key files/modules involved
7
- 4. Produce a structured analysis
18
+ 1. Analyze the codebase to understand the affected areas, tech stack, and architecture
19
+ 2. Identify the scope, complexity, and key files/modules involved
20
+ 3. Produce a structured analysis
8
21
 
9
22
  Save your analysis to __ANALYSIS_PATH__ with this structure:
10
23
 
@@ -1,19 +1,40 @@
1
- You are creating a GitHub issue for this repository.
1
+ You are drafting an issue for this repository.
2
2
 
3
3
  The user provided this description:
4
4
  __USER_PROMPT__
5
5
 
6
6
  Steps:
7
7
  1. Analyze the project's tech stack, architecture, and codebase
8
- 2. Check for duplicate issues: gh issue list --state open --search "<keywords>"
9
- 3. Create a well-structured GitHub issue using gh issue create
8
+ 2. Check for duplicates before drafting:
9
+ - Local issues: read `issues/*/metadata.json` (the directory may not exist)
10
+ - Remote issues, only if `gh` is installed and authenticated:
11
+ `gh issue list --state open --search "<keywords>"`
12
+ If an existing issue already covers the request, still emit the draft, but
13
+ say so in the body under a "Possible duplicates" section.
14
+ 3. Emit the issue draft in the exact format below
10
15
 
11
16
  The issue should:
12
17
  - Have a clear, descriptive title
13
18
  - Include context about why the change is needed
14
19
  - Include acceptance criteria
15
- - Add appropriate labels (create them if they don't exist)
16
20
 
17
- Use: gh issue create --title "..." --body "..."
21
+ Do NOT create the issue. You are only drafting it: issue-flow persists the
22
+ draft to the destination the user picked (GitHub, local files, or both).
18
23
 
19
- IMPORTANT: Output the issue URL after creation so it can be parsed.
24
+ IMPORTANT: your final message must end with exactly one block in this format,
25
+ with no surrounding commentary:
26
+
27
+ <issue-draft>
28
+ <title>Concise, descriptive title</title>
29
+ <labels>label-one, label-two</labels>
30
+ <body>
31
+ Full issue body in markdown, including context and acceptance criteria.
32
+ </body>
33
+ </issue-draft>
34
+
35
+ Rules for the block:
36
+ - `<title>` and `<body>` are required; an empty one aborts the command
37
+ - `<labels>` is a comma-separated list; leave it empty when no label applies
38
+ - Only suggest labels that already exist in the repository, or that are
39
+ conventional (`bug`, `enhancement`, `documentation`)
40
+ - Write the body as plain markdown — do not wrap it in a code fence
package/prompts/plan.md CHANGED
@@ -1,5 +1,18 @@
1
1
  You are converting a PRD into a structured JSON task plan for issue #__ISSUE_NUMBER__.
2
2
 
3
+ The issue is already resolved — do NOT fetch it:
4
+
5
+ - Source: __ISSUE_SOURCE__
6
+ - Reference: __ISSUE_URL__
7
+ - Title: __ISSUE_TITLE__
8
+ - Labels: __ISSUE_LABELS__
9
+
10
+ Issue body (the original demand, for the description field):
11
+
12
+ <issue-body>
13
+ __ISSUE_BODY__
14
+ </issue-body>
15
+
3
16
  Here is the PRD:
4
17
 
5
18
  __PRD_CONTENT__
@@ -9,7 +22,7 @@ Create a tasks.json file at __TASKS_PATH__ with this exact structure:
9
22
  {
10
23
  "project": "<repo-name>",
11
24
  "issueNumber": __ISSUE_NUMBER__,
12
- "issueUrl": "<github-issue-url>",
25
+ "issueUrl": "__ISSUE_URL__",
13
26
  "branchName": "issue/__ISSUE_NUMBER__-<slug>",
14
27
  "description": "<brief description>",
15
28
  "issueStatus": "pending",
@@ -43,7 +56,11 @@ Rules:
43
56
  - Each user story from the PRD becomes one entry in userStories
44
57
  - Priority should order stories by dependency (build foundations first)
45
58
  - acceptanceCriteria must include "Typecheck passes" for code changes
46
- - Get the repo name and issue URL from: gh issue view __ISSUE_NUMBER__ --json url
59
+ - issueUrl is already resolved above: a GitHub URL when the source is `github`,
60
+ the path of the local issue file when the source is `local`. Use it verbatim
61
+ and never derive it yourself
62
+ - Get the repo name from the repository itself (package name, directory name)
63
+ - If __ISSUE_NUMBER__ is not a number, keep it as a JSON string in issueNumber
47
64
  - The branchName should use a short kebab-case slug derived from the issue title
48
65
 
49
66
  IMPORTANT: You MUST write the tasks.json to the file path above. Do not just output it.
package/prompts/pr.md CHANGED
@@ -1,17 +1,34 @@
1
1
  You are creating a pull request for issue #__ISSUE_NUMBER__ on branch __BRANCH_NAME__.
2
2
 
3
+ The issue content is already resolved and given below — do NOT fetch it:
4
+
5
+ - Source: __ISSUE_SOURCE__
6
+ - Reference: __ISSUE_URL__
7
+ - Title: __ISSUE_TITLE__
8
+ - Labels: __ISSUE_LABELS__
9
+
10
+ Issue body:
11
+
12
+ <issue-body>
13
+ __ISSUE_BODY__
14
+ </issue-body>
15
+
3
16
  Steps:
4
- 1. Fetch the issue data: gh issue view __ISSUE_NUMBER__ --json title,body
5
- 2. Read the task plan from __TASKS_PATH__ if it exists
6
- 3. Review the git log for this branch: git log main..HEAD --oneline
7
- 4. Review the diff: git diff main...HEAD --stat
8
- 5. Create a well-structured PR using gh pr create
17
+ 1. Read the task plan from __TASKS_PATH__ if it exists
18
+ 2. Review the git log for this branch: git log main..HEAD --oneline
19
+ 3. Review the diff: git diff main...HEAD --stat
20
+ 4. Create a well-structured PR using gh pr create
9
21
 
10
22
  The PR should:
11
23
  - Have a clear, concise title (under 70 characters)
12
- - Reference the issue: "Closes #__ISSUE_NUMBER__"
13
24
  - Include a summary of changes
14
25
  - Include a test plan
26
+ - Cite where the demand is described: the reference above (`__ISSUE_URL__`)
27
+
28
+ Issue reference line:
29
+ - Include this line verbatim in the PR body, on its own line: `__ISSUE_CLOSES__`
30
+ - When that line is empty, the issue has no GitHub counterpart: do NOT invent a
31
+ "Closes #" reference, and cite `__ISSUE_URL__` in the body instead
15
32
 
16
33
  Use this command format:
17
34
  gh pr create --title "..." --body "..." --base main
package/prompts/prd.md CHANGED
@@ -1,10 +1,23 @@
1
- You are generating a Product Requirements Document (PRD) for GitHub issue #__ISSUE_NUMBER__ in this repository.
1
+ You are generating a Product Requirements Document (PRD) for issue #__ISSUE_NUMBER__ in this repository.
2
+
3
+ The issue content is already resolved and given below — do NOT fetch it, and do
4
+ not assume it lives on GitHub.
5
+
6
+ - Source: __ISSUE_SOURCE__
7
+ - Reference: __ISSUE_URL__
8
+ - Title: __ISSUE_TITLE__
9
+ - Labels: __ISSUE_LABELS__
10
+
11
+ Issue body:
12
+
13
+ <issue-body>
14
+ __ISSUE_BODY__
15
+ </issue-body>
2
16
 
3
17
  Steps:
4
- 1. Fetch the issue data using: gh issue view __ISSUE_NUMBER__ --json title,body,labels,comments
5
- 2. If the file issues/__ISSUE_NUMBER__/analysis.md exists, read it for additional context
6
- 3. Analyze the codebase to understand the context
7
- 4. Generate a structured PRD
18
+ 1. If the file issues/__ISSUE_NUMBER__/analysis.md exists, read it for additional context
19
+ 2. Analyze the codebase to understand the context
20
+ 3. Generate a structured PRD
8
21
 
9
22
  Save the PRD to __PRD_PATH__ with this structure:
10
23
 
package/prompts/review.md CHANGED
@@ -1,13 +1,25 @@
1
- You are reviewing whether GitHub issue #__ISSUE_NUMBER__ has been fully resolved.
1
+ You are reviewing whether issue #__ISSUE_NUMBER__ has been fully resolved.
2
2
 
3
3
  IMPORTANT: You are running in --orchestrator mode. Do NOT close the issue directly. Only report results.
4
4
 
5
+ The issue content is already resolved and given below — do NOT fetch it:
6
+
7
+ - Source: __ISSUE_SOURCE__
8
+ - Reference: __ISSUE_URL__
9
+ - Title: __ISSUE_TITLE__
10
+ - Labels: __ISSUE_LABELS__
11
+
12
+ Issue body:
13
+
14
+ <issue-body>
15
+ __ISSUE_BODY__
16
+ </issue-body>
17
+
5
18
  Steps:
6
- 1. Fetch the issue data using: gh issue view __ISSUE_NUMBER__ --json title,body,labels
7
- 2. Read the task plan from __TASKS_PATH__ to understand what was supposed to be implemented
8
- 3. Analyze the codebase to verify all acceptance criteria are met
9
- 4. Run the project's test suite and typecheck
10
- 5. Check for regressions
19
+ 1. Read the task plan from __TASKS_PATH__ to understand what was supposed to be implemented
20
+ 2. Analyze the codebase to verify all acceptance criteria are met
21
+ 3. Run the project's test suite and typecheck
22
+ 4. Check for regressions
11
23
 
12
24
  At the end, output your result in this exact format:
13
25
 
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/analyze.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { runHeadless } from '../core/headless.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { printError, printInfo, printSuccess } from '../ui/logger.js';\n\nexport async function runAnalyze(issue: string): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const issueDir = join('issues', issueNumber);\n const analysisPath = join(issueDir, 'analysis.md');\n\n await mkdir(issueDir, { recursive: true });\n\n const template = await loadPrompt('analyze');\n const prompt = applyPlaceholders(template, {\n __ISSUE_NUMBER__: issueNumber,\n __ANALYSIS_PATH__: analysisPath,\n });\n\n const result = await runHeadless({\n prompt,\n maxTurns: 30,\n timeout: getGlobalTimeout() ?? 300_000,\n outputFormat: 'text',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep', 'Write'],\n statusMessage: `Analyzing issue #${issueNumber}...`,\n });\n\n if (!result.success) {\n printError(`Analysis failed: ${result.error}`);\n return 1;\n }\n\n // Verify the file was created\n try {\n const content = await readFile(analysisPath, 'utf-8');\n if (content.length < 10) {\n printError('Analysis file was created but appears empty');\n return 1;\n }\n } catch {\n // File wasn't created by headless — save the result as analysis\n printInfo('Headless did not create analysis file; saving output directly');\n await writeFile(analysisPath, result.result, 'utf-8');\n }\n\n // Update pipeline state\n const tasksPath = join(issueDir, 'tasks.json');\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.analyzeCompleted = true;\n await saveTaskPlan(tasksPath, plan);\n } catch {\n // tasks.json may not exist yet — that's OK for standalone analyze\n }\n\n printSuccess(`Analysis saved to ${analysisPath}`);\n return 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA,SAAS,OAAO,UAAU,iBAAiB;AAC3C,SAAS,YAAY;AAOrB,eAAsB,WAAW,OAAgC;AAC/D,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,WAAW,KAAK,UAAU,WAAW;AAC3C,QAAM,eAAe,KAAK,UAAU,aAAa;AAEjD,QAAM,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAEzC,QAAM,WAAW,MAAM,WAAW,SAAS;AAC3C,QAAM,SAAS,kBAAkB,UAAU;AAAA,IACzC,kBAAkB;AAAA,IAClB,mBAAmB;AAAA,EACrB,CAAC;AAED,QAAM,SAAS,MAAM,YAAY;AAAA,IAC/B;AAAA,IACA,UAAU;AAAA,IACV,SAAS,iBAAiB,KAAK;AAAA,IAC/B,cAAc;AAAA,IACd,cAAc,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,OAAO;AAAA,IACtD,eAAe,oBAAoB,WAAW;AAAA,EAChD,CAAC;AAED,MAAI,CAAC,OAAO,SAAS;AACnB,eAAW,oBAAoB,OAAO,KAAK,EAAE;AAC7C,WAAO;AAAA,EACT;AAGA,MAAI;AACF,UAAM,UAAU,MAAM,SAAS,cAAc,OAAO;AACpD,QAAI,QAAQ,SAAS,IAAI;AACvB,iBAAW,6CAA6C;AACxD,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAEN,cAAU,+DAA+D;AACzE,UAAM,UAAU,cAAc,OAAO,QAAQ,OAAO;AAAA,EACtD;AAGA,QAAM,YAAY,KAAK,UAAU,YAAY;AAC7C,MAAI;AACF,UAAM,OAAO,MAAM,aAAa,SAAS;AACzC,SAAK,SAAS,mBAAmB;AACjC,UAAM,aAAa,WAAW,IAAI;AAAA,EACpC,QAAQ;AAAA,EAER;AAEA,eAAa,qBAAqB,YAAY,EAAE;AAChD,SAAO;AACT;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/prd.ts"],"sourcesContent":["import { mkdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { runHeadless } from '../core/headless.js';\nimport { readFileWithGrace, runPhaseWithRetry } from '../core/phase-runner.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { printError, printSuccess } from '../ui/logger.js';\nimport { isTransientFailure } from '../utils/retry.js';\n\nexport async function runPrd(issue: string): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const issueDir = join('issues', issueNumber);\n const prdPath = join(issueDir, 'prd.md');\n\n await mkdir(issueDir, { recursive: true });\n\n const template = await loadPrompt('prd');\n const prompt = applyPlaceholders(template, {\n __ISSUE_NUMBER__: issueNumber,\n __PRD_PATH__: prdPath,\n });\n\n const outcome = await runPhaseWithRetry({\n phase: 'prd',\n attempt: async () => {\n const result = await runHeadless({\n prompt,\n maxTurns: 25,\n timeout: getGlobalTimeout() ?? 300_000,\n outputFormat: 'text',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep', 'Write'],\n statusMessage: `Generating PRD for issue #${issueNumber}...`,\n });\n\n if (!result.success) {\n return {\n ok: false,\n transient: isTransientFailure(1, result.error ?? ''),\n error: `PRD generation failed: ${result.error}`,\n };\n }\n\n // Verify the file was created. A brief grace period absorbs a lag\n // between the Write tool completing and this process seeing it; if it\n // still isn't there, treat it as recoverable and let the phase retry.\n try {\n const content = await readFileWithGrace(prdPath);\n if (content.length < 10) {\n return { ok: false, transient: true, error: 'PRD file was created but appears empty' };\n }\n } catch {\n return { ok: false, transient: true, error: `PRD file was not created at ${prdPath}` };\n }\n\n return { ok: true };\n },\n });\n\n if (!outcome.ok) {\n printError(outcome.error ?? `PRD generation failed for issue #${issueNumber}`);\n return 1;\n }\n\n // Update pipeline state\n const tasksPath = join(issueDir, 'tasks.json');\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.prdCompleted = true;\n await saveTaskPlan(tasksPath, plan);\n } catch {\n // tasks.json may not exist yet\n }\n\n printSuccess(`PRD saved to ${prdPath}`);\n return 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,aAAa;AACtB,SAAS,YAAY;AASrB,eAAsB,OAAO,OAAgC;AAC3D,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,WAAW,KAAK,UAAU,WAAW;AAC3C,QAAM,UAAU,KAAK,UAAU,QAAQ;AAEvC,QAAM,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAEzC,QAAM,WAAW,MAAM,WAAW,KAAK;AACvC,QAAM,SAAS,kBAAkB,UAAU;AAAA,IACzC,kBAAkB;AAAA,IAClB,cAAc;AAAA,EAChB,CAAC;AAED,QAAM,UAAU,MAAM,kBAAkB;AAAA,IACtC,OAAO;AAAA,IACP,SAAS,YAAY;AACnB,YAAM,SAAS,MAAM,YAAY;AAAA,QAC/B;AAAA,QACA,UAAU;AAAA,QACV,SAAS,iBAAiB,KAAK;AAAA,QAC/B,cAAc;AAAA,QACd,cAAc,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,OAAO;AAAA,QACtD,eAAe,6BAA6B,WAAW;AAAA,MACzD,CAAC;AAED,UAAI,CAAC,OAAO,SAAS;AACnB,eAAO;AAAA,UACL,IAAI;AAAA,UACJ,WAAW,mBAAmB,GAAG,OAAO,SAAS,EAAE;AAAA,UACnD,OAAO,0BAA0B,OAAO,KAAK;AAAA,QAC/C;AAAA,MACF;AAKA,UAAI;AACF,cAAM,UAAU,MAAM,kBAAkB,OAAO;AAC/C,YAAI,QAAQ,SAAS,IAAI;AACvB,iBAAO,EAAE,IAAI,OAAO,WAAW,MAAM,OAAO,yCAAyC;AAAA,QACvF;AAAA,MACF,QAAQ;AACN,eAAO,EAAE,IAAI,OAAO,WAAW,MAAM,OAAO,+BAA+B,OAAO,GAAG;AAAA,MACvF;AAEA,aAAO,EAAE,IAAI,KAAK;AAAA,IACpB;AAAA,EACF,CAAC;AAED,MAAI,CAAC,QAAQ,IAAI;AACf,eAAW,QAAQ,SAAS,oCAAoC,WAAW,EAAE;AAC7E,WAAO;AAAA,EACT;AAGA,QAAM,YAAY,KAAK,UAAU,YAAY;AAC7C,MAAI;AACF,UAAM,OAAO,MAAM,aAAa,SAAS;AACzC,SAAK,SAAS,eAAe;AAC7B,UAAM,aAAa,WAAW,IAAI;AAAA,EACpC,QAAQ;AAAA,EAER;AAEA,eAAa,gBAAgB,OAAO,EAAE;AACtC,SAAO;AACT;","names":[]}