issue-flow 0.7.6 → 0.8.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 (73) hide show
  1. package/dist/{analyze-TNFTUMOO.js → analyze-RBNKXWL5.js} +10 -8
  2. package/dist/{analyze-TNFTUMOO.js.map → analyze-RBNKXWL5.js.map} +1 -1
  3. package/dist/{chunk-JECNPBBO.js → chunk-36ECROTC.js} +11 -9
  4. package/dist/{chunk-JECNPBBO.js.map → chunk-36ECROTC.js.map} +1 -1
  5. package/dist/{chunk-6S2XT3JE.js → chunk-A4M6AKIK.js} +10 -8
  6. package/dist/{chunk-6S2XT3JE.js.map → chunk-A4M6AKIK.js.map} +1 -1
  7. package/dist/{chunk-5NLLEWJD.js → chunk-BASRRXAM.js} +12 -2
  8. package/dist/{chunk-5NLLEWJD.js.map → chunk-BASRRXAM.js.map} +1 -1
  9. package/dist/{chunk-5Q2TMW7K.js → chunk-BH7DYADV.js} +11 -1
  10. package/dist/chunk-BH7DYADV.js.map +1 -0
  11. package/dist/chunk-F52S6ZGF.js +447 -0
  12. package/dist/chunk-F52S6ZGF.js.map +1 -0
  13. package/dist/{chunk-CD7BMWXW.js → chunk-HSGTZ3CT.js} +3 -3
  14. package/dist/{chunk-QLVAEDVD.js → chunk-J3PJBZXJ.js} +3 -3
  15. package/dist/{chunk-DWF46N65.js → chunk-J4SICTAN.js} +11 -9
  16. package/dist/{chunk-DWF46N65.js.map → chunk-J4SICTAN.js.map} +1 -1
  17. package/dist/{chunk-DF7SUHT5.js → chunk-JWZKV3CL.js} +2 -2
  18. package/dist/{chunk-5IRBHUYO.js → chunk-MK43N4X3.js} +11 -9
  19. package/dist/{chunk-5IRBHUYO.js.map → chunk-MK43N4X3.js.map} +1 -1
  20. package/dist/{chunk-I7FTA5FI.js → chunk-QO2MWMAZ.js} +2 -47
  21. package/dist/chunk-QO2MWMAZ.js.map +1 -0
  22. package/dist/{chunk-D56VWHSM.js → chunk-RQW5W4XC.js} +2 -2
  23. package/dist/chunk-SLHZPB3C.js +50 -0
  24. package/dist/chunk-SLHZPB3C.js.map +1 -0
  25. package/dist/{chunk-BMRBQ23Q.js → chunk-TGP6WK65.js} +9 -7
  26. package/dist/{chunk-BMRBQ23Q.js.map → chunk-TGP6WK65.js.map} +1 -1
  27. package/dist/{chunk-OE7RYTUT.js → chunk-VPDWTZ3F.js} +2 -2
  28. package/dist/{chunk-A42R36BX.js → chunk-VSF2CR3G.js} +10 -8
  29. package/dist/{chunk-A42R36BX.js.map → chunk-VSF2CR3G.js.map} +1 -1
  30. package/dist/cli.js +28 -13
  31. package/dist/cli.js.map +1 -1
  32. package/dist/{config-U3FC4WH6.js → config-R7O5YC65.js} +3 -3
  33. package/dist/execute-LU3HWA54.js +14 -0
  34. package/dist/{generate-RHPOWGDX.js → generate-TGJQLGMV.js} +10 -8
  35. package/dist/{generate-RHPOWGDX.js.map → generate-TGJQLGMV.js.map} +1 -1
  36. package/dist/init-WP7Z2LFQ.js +9 -0
  37. package/dist/plan-2MWMCKRY.js +16 -0
  38. package/dist/pr-XVQDEQ7B.js +16 -0
  39. package/dist/pr-review-VO6ZXAFM.js +16 -0
  40. package/dist/prd-FPZAHQEO.js +16 -0
  41. package/dist/review-AHALLMXO.js +14 -0
  42. package/dist/{run-Z6ZDXZB6.js → run-2CXVR3VZ.js} +23 -196
  43. package/dist/run-2CXVR3VZ.js.map +1 -0
  44. package/dist/web-JVPK2PIF.js +188 -0
  45. package/dist/web-JVPK2PIF.js.map +1 -0
  46. package/package.json +1 -1
  47. package/web/CLAUDE.md +48 -0
  48. package/web/public/app.css +266 -0
  49. package/web/public/app.js +237 -0
  50. package/web/public/index.html +26 -0
  51. package/dist/chunk-5Q2TMW7K.js.map +0 -1
  52. package/dist/chunk-I7FTA5FI.js.map +0 -1
  53. package/dist/execute-BNBA77WJ.js +0 -13
  54. package/dist/init-IF72GR7O.js +0 -9
  55. package/dist/plan-FITZZXZQ.js +0 -15
  56. package/dist/pr-GO244D7M.js +0 -15
  57. package/dist/pr-review-XY6VL62M.js +0 -15
  58. package/dist/prd-2Y6RMVTF.js +0 -15
  59. package/dist/review-ITKXJMX3.js +0 -13
  60. package/dist/run-Z6ZDXZB6.js.map +0 -1
  61. /package/dist/{chunk-CD7BMWXW.js.map → chunk-HSGTZ3CT.js.map} +0 -0
  62. /package/dist/{chunk-QLVAEDVD.js.map → chunk-J3PJBZXJ.js.map} +0 -0
  63. /package/dist/{chunk-DF7SUHT5.js.map → chunk-JWZKV3CL.js.map} +0 -0
  64. /package/dist/{chunk-D56VWHSM.js.map → chunk-RQW5W4XC.js.map} +0 -0
  65. /package/dist/{chunk-OE7RYTUT.js.map → chunk-VPDWTZ3F.js.map} +0 -0
  66. /package/dist/{config-U3FC4WH6.js.map → config-R7O5YC65.js.map} +0 -0
  67. /package/dist/{execute-BNBA77WJ.js.map → execute-LU3HWA54.js.map} +0 -0
  68. /package/dist/{init-IF72GR7O.js.map → init-WP7Z2LFQ.js.map} +0 -0
  69. /package/dist/{plan-FITZZXZQ.js.map → plan-2MWMCKRY.js.map} +0 -0
  70. /package/dist/{pr-GO244D7M.js.map → pr-XVQDEQ7B.js.map} +0 -0
  71. /package/dist/{pr-review-XY6VL62M.js.map → pr-review-VO6ZXAFM.js.map} +0 -0
  72. /package/dist/{prd-2Y6RMVTF.js.map → prd-FPZAHQEO.js.map} +0 -0
  73. /package/dist/{review-ITKXJMX3.js.map → review-AHALLMXO.js.map} +0 -0
@@ -3,25 +3,27 @@ import {
3
3
  ensureProvidersRegistered,
4
4
  getProvider,
5
5
  localIssueRef
6
- } from "./chunk-CD7BMWXW.js";
6
+ } from "./chunk-HSGTZ3CT.js";
7
7
  import {
8
8
  runHeadless
9
- } from "./chunk-DF7SUHT5.js";
9
+ } from "./chunk-JWZKV3CL.js";
10
10
  import {
11
- applyPlaceholders,
12
- loadPrompt,
13
11
  publishPhaseMetrics
14
- } from "./chunk-I7FTA5FI.js";
12
+ } from "./chunk-QO2MWMAZ.js";
13
+ import {
14
+ applyPlaceholders,
15
+ loadPrompt
16
+ } from "./chunk-SLHZPB3C.js";
15
17
  import {
16
18
  loadIssuesConfig,
17
19
  resolveProjectPaths
18
- } from "./chunk-5NLLEWJD.js";
20
+ } from "./chunk-BASRRXAM.js";
19
21
  import {
20
22
  getGlobalTimeout,
21
23
  isoNow,
22
24
  printError,
23
25
  printSuccess
24
- } from "./chunk-5Q2TMW7K.js";
26
+ } from "./chunk-BH7DYADV.js";
25
27
 
26
28
  // src/issues/draft.ts
27
29
  var DRAFT_TAG = "issue-draft";
@@ -151,4 +153,4 @@ async function runGenerate(promptText, target) {
151
153
  export {
152
154
  runGenerate
153
155
  };
154
- //# sourceMappingURL=generate-RHPOWGDX.js.map
156
+ //# sourceMappingURL=generate-TGJQLGMV.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/issues/draft.ts","../src/commands/generate.ts"],"sourcesContent":["import type { IssueDraft } from './types.js';\n\n/**\n * Structured Issue draft emitted by the `generate` prompt.\n *\n * The agent no longer creates the Issue itself: it returns the content and the\n * providers persist it, so the same draft can reach GitHub, the local files, or\n * both. Capturing the result therefore has to be exact — the old approach of\n * fishing an issue URL out of free-form prose only ever worked for GitHub.\n *\n * The block mirrors the `<issue-body>` convention already used by the phase\n * templates, which keeps a multi-line markdown body readable and free of the\n * escaping a JSON payload would demand.\n */\n\n/** Wrapper the agent must emit around the draft. */\nexport const DRAFT_TAG = 'issue-draft';\n\n/** Failure to capture a draft from the agent output. */\nexport class IssueDraftParseError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'IssueDraftParseError';\n }\n}\n\n/**\n * Last occurrence of `<tag>...</tag>` in `source`, or `null`.\n *\n * The last one wins: an agent that shows the format before filling it in (or\n * restates the draft after a correction) would otherwise have its first,\n * throwaway attempt captured. The closing tag is searched from the end for the\n * same reason a body may legitimately quote one.\n */\nfunction extractTag(source: string, tag: string): string | null {\n const open = `<${tag}>`;\n const close = `</${tag}>`;\n\n const start = source.lastIndexOf(open);\n if (start === -1) return null;\n\n const end = source.lastIndexOf(close);\n if (end === -1 || end < start) return null;\n\n return source.slice(start + open.length, end);\n}\n\nfunction parseLabels(raw: string | null): string[] {\n if (raw === null) return [];\n return raw\n .split(',')\n .map((label) => label.trim())\n .filter((label) => label.length > 0 && label.toLowerCase() !== '(none)');\n}\n\n/**\n * Read the draft the agent produced.\n *\n * @throws IssueDraftParseError when the block is absent or has no usable\n * title/body, so a malformed answer fails loudly instead of creating an empty\n * Issue.\n */\nexport function parseIssueDraft(output: string): IssueDraft {\n const normalized = output.replace(/\\r\\n?/g, '\\n');\n const block = extractTag(normalized, DRAFT_TAG);\n\n if (block === null) {\n throw new IssueDraftParseError(\n `The agent did not emit an <${DRAFT_TAG}> block, so there is no Issue to create. ` +\n `Output was: ${normalized.trim().slice(0, 300) || '(empty)'}`,\n );\n }\n\n const title = (extractTag(block, 'title') ?? '').trim();\n const body = (extractTag(block, 'body') ?? '').trim();\n\n if (title.length === 0) {\n throw new IssueDraftParseError(`The <${DRAFT_TAG}> block has no <title>.`);\n }\n if (body.length === 0) {\n throw new IssueDraftParseError(`The <${DRAFT_TAG}> block has no <body>.`);\n }\n\n return { title, body, labels: parseLabels(extractTag(block, 'labels')) };\n}\n","import { loadIssuesConfig } from '../config.js';\nimport { runHeadless } from '../core/headless.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { publishPhaseMetrics } from '../core/session-metrics.js';\nimport { isoNow } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { ensureProvidersRegistered } from '../issues/bootstrap.js';\nimport { localIssueRef } from '../issues/context.js';\nimport { IssueDraftParseError, parseIssueDraft } from '../issues/draft.js';\nimport { getProvider } from '../issues/registry.js';\nimport type { Issue, IssueDraft, IssueGenerateTarget } from '../issues/types.js';\nimport { resolveProjectPaths } from '../storage/resolve.js';\nimport { printError, printSuccess } from '../ui/logger.js';\n\n/** Human-readable pointer to a created Issue. */\nfunction issueLocation(issue: Issue): string {\n return issue.remoteRef ?? localIssueRef(issue.id);\n}\n\n/**\n * Ask the agent for the Issue content.\n *\n * The agent only drafts: creation belongs to the providers, so the same draft\n * can be persisted to GitHub, to the local files, or to both.\n */\nasync function draftIssue(promptText: string): Promise<IssueDraft> {\n // The duplicate check reads the local issues, which live in the global\n // storage: the path has to be handed over (and allowed) explicitly, since it\n // is outside the working directory the agent is started in.\n const { issuesDir } = await resolveProjectPaths();\n\n const template = await loadPrompt('generate');\n const prompt = applyPlaceholders(template, {\n __USER_PROMPT__: promptText,\n __LOCAL_ISSUES_DIR__: issuesDir,\n });\n\n const startedAtMs = Date.now();\n const result = await runHeadless({\n prompt,\n maxTurns: 15,\n timeout: getGlobalTimeout() ?? 300_000,\n // json (not text) so the CLI reports usage: the envelope's `result` field\n // carries the same assistant text parseIssueDraft() already consumed.\n outputFormat: 'json',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep'],\n addDirs: [issuesDir],\n statusMessage: 'Drafting issue...',\n });\n // Before the success check: the tokens were spent either way.\n publishPhaseMetrics('generate', result.cost, startedAtMs);\n\n if (!result.success) {\n throw new Error(`Issue creation failed: ${result.error}`);\n }\n\n return parseIssueDraft(result.result);\n}\n\n/**\n * Create the GitHub Issue and mirror it locally.\n *\n * The remote comes first on purpose: it owns the identifier, and the mirror has\n * to reuse it so both copies describe one demand. A remote failure therefore\n * leaves nothing behind, and a mirror failure is reported with the remote\n * reference that already exists.\n */\nasync function createBoth(draft: IssueDraft): Promise<Issue[]> {\n const remote = await getProvider('github').create(draft);\n\n try {\n const mirror = await getProvider('local').create({\n ...draft,\n id: remote.id,\n ...(remote.remoteRef\n ? {\n remote: {\n provider: 'github' as const,\n ref: remote.remoteRef,\n syncedAt: isoNow(),\n syncedContentHash: remote.contentHash,\n },\n }\n : {}),\n });\n return [remote, mirror];\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `GitHub issue created at ${issueLocation(remote)}, but the local mirror was not written: ` +\n `${message}. Nothing else was persisted; re-run with --local to create the mirror.`,\n );\n }\n}\n\nasync function createIssues(target: IssueGenerateTarget, draft: IssueDraft): Promise<Issue[]> {\n if (target === 'both') {\n return createBoth(draft);\n }\n return [await getProvider(target).create(draft)];\n}\n\nexport async function runGenerate(\n promptText: string,\n target?: IssueGenerateTarget,\n): Promise<number> {\n const config = await loadIssuesConfig();\n const destination = target ?? config.defaultGenerateTarget;\n\n ensureProvidersRegistered();\n\n let draft: IssueDraft;\n try {\n draft = await draftIssue(promptText);\n } catch (err) {\n if (err instanceof IssueDraftParseError) {\n printError(`Could not read the generated Issue draft: ${err.message}`);\n } else {\n printError(err instanceof Error ? err.message : String(err));\n }\n return 1;\n }\n\n let created: Issue[];\n try {\n created = await createIssues(destination, draft);\n } catch (err) {\n printError(err instanceof Error ? err.message : String(err));\n return 1;\n }\n\n for (const issue of created) {\n printSuccess(`Issue created (${issue.source}): ${issueLocation(issue)}`);\n }\n return 0;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/issues/draft.ts","../src/commands/generate.ts"],"sourcesContent":["import type { IssueDraft } from './types.js';\n\n/**\n * Structured Issue draft emitted by the `generate` prompt.\n *\n * The agent no longer creates the Issue itself: it returns the content and the\n * providers persist it, so the same draft can reach GitHub, the local files, or\n * both. Capturing the result therefore has to be exact — the old approach of\n * fishing an issue URL out of free-form prose only ever worked for GitHub.\n *\n * The block mirrors the `<issue-body>` convention already used by the phase\n * templates, which keeps a multi-line markdown body readable and free of the\n * escaping a JSON payload would demand.\n */\n\n/** Wrapper the agent must emit around the draft. */\nexport const DRAFT_TAG = 'issue-draft';\n\n/** Failure to capture a draft from the agent output. */\nexport class IssueDraftParseError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'IssueDraftParseError';\n }\n}\n\n/**\n * Last occurrence of `<tag>...</tag>` in `source`, or `null`.\n *\n * The last one wins: an agent that shows the format before filling it in (or\n * restates the draft after a correction) would otherwise have its first,\n * throwaway attempt captured. The closing tag is searched from the end for the\n * same reason a body may legitimately quote one.\n */\nfunction extractTag(source: string, tag: string): string | null {\n const open = `<${tag}>`;\n const close = `</${tag}>`;\n\n const start = source.lastIndexOf(open);\n if (start === -1) return null;\n\n const end = source.lastIndexOf(close);\n if (end === -1 || end < start) return null;\n\n return source.slice(start + open.length, end);\n}\n\nfunction parseLabels(raw: string | null): string[] {\n if (raw === null) return [];\n return raw\n .split(',')\n .map((label) => label.trim())\n .filter((label) => label.length > 0 && label.toLowerCase() !== '(none)');\n}\n\n/**\n * Read the draft the agent produced.\n *\n * @throws IssueDraftParseError when the block is absent or has no usable\n * title/body, so a malformed answer fails loudly instead of creating an empty\n * Issue.\n */\nexport function parseIssueDraft(output: string): IssueDraft {\n const normalized = output.replace(/\\r\\n?/g, '\\n');\n const block = extractTag(normalized, DRAFT_TAG);\n\n if (block === null) {\n throw new IssueDraftParseError(\n `The agent did not emit an <${DRAFT_TAG}> block, so there is no Issue to create. ` +\n `Output was: ${normalized.trim().slice(0, 300) || '(empty)'}`,\n );\n }\n\n const title = (extractTag(block, 'title') ?? '').trim();\n const body = (extractTag(block, 'body') ?? '').trim();\n\n if (title.length === 0) {\n throw new IssueDraftParseError(`The <${DRAFT_TAG}> block has no <title>.`);\n }\n if (body.length === 0) {\n throw new IssueDraftParseError(`The <${DRAFT_TAG}> block has no <body>.`);\n }\n\n return { title, body, labels: parseLabels(extractTag(block, 'labels')) };\n}\n","import { loadIssuesConfig } from '../config.js';\nimport { runHeadless } from '../core/headless.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { publishPhaseMetrics } from '../core/session-metrics.js';\nimport { isoNow } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { ensureProvidersRegistered } from '../issues/bootstrap.js';\nimport { localIssueRef } from '../issues/context.js';\nimport { IssueDraftParseError, parseIssueDraft } from '../issues/draft.js';\nimport { getProvider } from '../issues/registry.js';\nimport type { Issue, IssueDraft, IssueGenerateTarget } from '../issues/types.js';\nimport { resolveProjectPaths } from '../storage/resolve.js';\nimport { printError, printSuccess } from '../ui/logger.js';\n\n/** Human-readable pointer to a created Issue. */\nfunction issueLocation(issue: Issue): string {\n return issue.remoteRef ?? localIssueRef(issue.id);\n}\n\n/**\n * Ask the agent for the Issue content.\n *\n * The agent only drafts: creation belongs to the providers, so the same draft\n * can be persisted to GitHub, to the local files, or to both.\n */\nasync function draftIssue(promptText: string): Promise<IssueDraft> {\n // The duplicate check reads the local issues, which live in the global\n // storage: the path has to be handed over (and allowed) explicitly, since it\n // is outside the working directory the agent is started in.\n const { issuesDir } = await resolveProjectPaths();\n\n const template = await loadPrompt('generate');\n const prompt = applyPlaceholders(template, {\n __USER_PROMPT__: promptText,\n __LOCAL_ISSUES_DIR__: issuesDir,\n });\n\n const startedAtMs = Date.now();\n const result = await runHeadless({\n prompt,\n maxTurns: 15,\n timeout: getGlobalTimeout() ?? 300_000,\n // json (not text) so the CLI reports usage: the envelope's `result` field\n // carries the same assistant text parseIssueDraft() already consumed.\n outputFormat: 'json',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep'],\n addDirs: [issuesDir],\n statusMessage: 'Drafting issue...',\n });\n // Before the success check: the tokens were spent either way.\n publishPhaseMetrics('generate', result.cost, startedAtMs);\n\n if (!result.success) {\n throw new Error(`Issue creation failed: ${result.error}`);\n }\n\n return parseIssueDraft(result.result);\n}\n\n/**\n * Create the GitHub Issue and mirror it locally.\n *\n * The remote comes first on purpose: it owns the identifier, and the mirror has\n * to reuse it so both copies describe one demand. A remote failure therefore\n * leaves nothing behind, and a mirror failure is reported with the remote\n * reference that already exists.\n */\nasync function createBoth(draft: IssueDraft): Promise<Issue[]> {\n const remote = await getProvider('github').create(draft);\n\n try {\n const mirror = await getProvider('local').create({\n ...draft,\n id: remote.id,\n ...(remote.remoteRef\n ? {\n remote: {\n provider: 'github' as const,\n ref: remote.remoteRef,\n syncedAt: isoNow(),\n syncedContentHash: remote.contentHash,\n },\n }\n : {}),\n });\n return [remote, mirror];\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `GitHub issue created at ${issueLocation(remote)}, but the local mirror was not written: ` +\n `${message}. Nothing else was persisted; re-run with --local to create the mirror.`,\n );\n }\n}\n\nasync function createIssues(target: IssueGenerateTarget, draft: IssueDraft): Promise<Issue[]> {\n if (target === 'both') {\n return createBoth(draft);\n }\n return [await getProvider(target).create(draft)];\n}\n\nexport async function runGenerate(\n promptText: string,\n target?: IssueGenerateTarget,\n): Promise<number> {\n const config = await loadIssuesConfig();\n const destination = target ?? config.defaultGenerateTarget;\n\n ensureProvidersRegistered();\n\n let draft: IssueDraft;\n try {\n draft = await draftIssue(promptText);\n } catch (err) {\n if (err instanceof IssueDraftParseError) {\n printError(`Could not read the generated Issue draft: ${err.message}`);\n } else {\n printError(err instanceof Error ? err.message : String(err));\n }\n return 1;\n }\n\n let created: Issue[];\n try {\n created = await createIssues(destination, draft);\n } catch (err) {\n printError(err instanceof Error ? err.message : String(err));\n return 1;\n }\n\n for (const issue of created) {\n printSuccess(`Issue created (${issue.source}): ${issueLocation(issue)}`);\n }\n return 0;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ runInit
4
+ } from "./chunk-VPDWTZ3F.js";
5
+ import "./chunk-BH7DYADV.js";
6
+ export {
7
+ runInit
8
+ };
9
+ //# sourceMappingURL=init-WP7Z2LFQ.js.map
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ runPlan
4
+ } from "./chunk-MK43N4X3.js";
5
+ import "./chunk-RQW5W4XC.js";
6
+ import "./chunk-HSGTZ3CT.js";
7
+ import "./chunk-HDV3YDTG.js";
8
+ import "./chunk-JWZKV3CL.js";
9
+ import "./chunk-QO2MWMAZ.js";
10
+ import "./chunk-SLHZPB3C.js";
11
+ import "./chunk-BASRRXAM.js";
12
+ import "./chunk-BH7DYADV.js";
13
+ export {
14
+ runPlan
15
+ };
16
+ //# sourceMappingURL=plan-2MWMCKRY.js.map
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ runPr
4
+ } from "./chunk-J4SICTAN.js";
5
+ import "./chunk-RQW5W4XC.js";
6
+ import "./chunk-HSGTZ3CT.js";
7
+ import "./chunk-HDV3YDTG.js";
8
+ import "./chunk-JWZKV3CL.js";
9
+ import "./chunk-QO2MWMAZ.js";
10
+ import "./chunk-SLHZPB3C.js";
11
+ import "./chunk-BASRRXAM.js";
12
+ import "./chunk-BH7DYADV.js";
13
+ export {
14
+ runPr
15
+ };
16
+ //# sourceMappingURL=pr-XVQDEQ7B.js.map
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ exitCodeFor,
4
+ runPrReview
5
+ } from "./chunk-VSF2CR3G.js";
6
+ import "./chunk-J3PJBZXJ.js";
7
+ import "./chunk-JWZKV3CL.js";
8
+ import "./chunk-QO2MWMAZ.js";
9
+ import "./chunk-SLHZPB3C.js";
10
+ import "./chunk-BASRRXAM.js";
11
+ import "./chunk-BH7DYADV.js";
12
+ export {
13
+ exitCodeFor,
14
+ runPrReview
15
+ };
16
+ //# sourceMappingURL=pr-review-VO6ZXAFM.js.map
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ runPrd
4
+ } from "./chunk-36ECROTC.js";
5
+ import "./chunk-RQW5W4XC.js";
6
+ import "./chunk-HSGTZ3CT.js";
7
+ import "./chunk-HDV3YDTG.js";
8
+ import "./chunk-JWZKV3CL.js";
9
+ import "./chunk-QO2MWMAZ.js";
10
+ import "./chunk-SLHZPB3C.js";
11
+ import "./chunk-BASRRXAM.js";
12
+ import "./chunk-BH7DYADV.js";
13
+ export {
14
+ runPrd
15
+ };
16
+ //# sourceMappingURL=prd-FPZAHQEO.js.map
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ runReview
4
+ } from "./chunk-A4M6AKIK.js";
5
+ import "./chunk-HSGTZ3CT.js";
6
+ import "./chunk-JWZKV3CL.js";
7
+ import "./chunk-QO2MWMAZ.js";
8
+ import "./chunk-SLHZPB3C.js";
9
+ import "./chunk-BASRRXAM.js";
10
+ import "./chunk-BH7DYADV.js";
11
+ export {
12
+ runReview
13
+ };
14
+ //# sourceMappingURL=review-AHALLMXO.js.map
@@ -1,49 +1,52 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runPrd
4
- } from "./chunk-JECNPBBO.js";
4
+ } from "./chunk-36ECROTC.js";
5
5
  import {
6
6
  runReview
7
- } from "./chunk-6S2XT3JE.js";
7
+ } from "./chunk-A4M6AKIK.js";
8
+ import {
9
+ ensureWebMonitor
10
+ } from "./chunk-F52S6ZGF.js";
8
11
  import {
9
12
  runInit
10
- } from "./chunk-OE7RYTUT.js";
13
+ } from "./chunk-VPDWTZ3F.js";
11
14
  import {
12
15
  printRunSummary,
13
16
  runExecute
14
- } from "./chunk-BMRBQ23Q.js";
17
+ } from "./chunk-TGP6WK65.js";
15
18
  import {
16
19
  runPlan
17
- } from "./chunk-5IRBHUYO.js";
20
+ } from "./chunk-MK43N4X3.js";
18
21
  import {
19
22
  runPr
20
- } from "./chunk-DWF46N65.js";
21
- import "./chunk-D56VWHSM.js";
23
+ } from "./chunk-J4SICTAN.js";
24
+ import "./chunk-RQW5W4XC.js";
22
25
  import {
23
26
  getProvider,
24
27
  resolveCommandIssue
25
- } from "./chunk-CD7BMWXW.js";
28
+ } from "./chunk-HSGTZ3CT.js";
26
29
  import "./chunk-HDV3YDTG.js";
27
30
  import {
28
31
  mostRecent,
29
32
  prReviewDir,
30
33
  readPrReviewIndex,
31
34
  runPrReview
32
- } from "./chunk-A42R36BX.js";
35
+ } from "./chunk-VSF2CR3G.js";
33
36
  import {
34
37
  listPullRequests,
35
38
  publishGitState
36
- } from "./chunk-QLVAEDVD.js";
37
- import "./chunk-DF7SUHT5.js";
39
+ } from "./chunk-J3PJBZXJ.js";
40
+ import "./chunk-JWZKV3CL.js";
38
41
  import {
39
- getRunUsageTotals,
40
- resolvePackageDir
41
- } from "./chunk-I7FTA5FI.js";
42
+ getRunUsageTotals
43
+ } from "./chunk-QO2MWMAZ.js";
44
+ import "./chunk-SLHZPB3C.js";
42
45
  import {
43
46
  loadIssuesConfig,
44
47
  loadWebConfig,
45
48
  resolveIssuePaths
46
- } from "./chunk-5NLLEWJD.js";
49
+ } from "./chunk-BASRRXAM.js";
47
50
  import {
48
51
  FilePublisher,
49
52
  NullPublisher,
@@ -57,12 +60,12 @@ import {
57
60
  setOutputCallback,
58
61
  setSessionPublisher,
59
62
  setStoryUpdateCallback
60
- } from "./chunk-5Q2TMW7K.js";
63
+ } from "./chunk-BH7DYADV.js";
61
64
 
62
65
  // src/commands/run.ts
63
66
  import { randomUUID } from "crypto";
64
67
  import { mkdir } from "fs/promises";
65
- import { join as join2 } from "path";
68
+ import { join } from "path";
66
69
  import { execa } from "execa";
67
70
 
68
71
  // src/core/pipeline.ts
@@ -326,180 +329,6 @@ async function runPipelineWithRenderer(options) {
326
329
  };
327
330
  }
328
331
 
329
- // src/web/server.ts
330
- import { readFile } from "fs/promises";
331
- import { createServer } from "http";
332
- import { createRequire } from "module";
333
- import { join } from "path";
334
- import chalk from "chalk";
335
- var require2 = createRequire(import.meta.url);
336
- function readPackageVersion() {
337
- try {
338
- return require2("../../package.json").version;
339
- } catch {
340
- return "0.0.0";
341
- }
342
- }
343
- var JSON_TYPE = "application/json; charset=utf-8";
344
- var STATIC_ROUTES = {
345
- "/": { file: "index.html", contentType: "text/html; charset=utf-8" },
346
- "/app.css": { file: "app.css", contentType: "text/css; charset=utf-8" },
347
- "/app.js": { file: "app.js", contentType: "text/javascript; charset=utf-8" }
348
- };
349
- async function loadStaticAssets(publicDir) {
350
- const assets = /* @__PURE__ */ new Map();
351
- if (publicDir === null) return assets;
352
- for (const [route, { file, contentType }] of Object.entries(STATIC_ROUTES)) {
353
- try {
354
- const body = await readFile(join(publicDir, file), "utf-8");
355
- assets.set(route, { body, contentType });
356
- } catch {
357
- }
358
- }
359
- return assets;
360
- }
361
- function baseHeaders(res) {
362
- res.setHeader("Cache-Control", "no-store");
363
- res.setHeader("X-Content-Type-Options", "nosniff");
364
- res.setHeader("Referrer-Policy", "no-referrer");
365
- res.setHeader("X-Robots-Tag", "noindex, nofollow");
366
- }
367
- function respond(res, status, contentType, body) {
368
- res.statusCode = status;
369
- res.setHeader("Content-Type", contentType);
370
- res.end(body);
371
- }
372
- function respondJson(res, status, payload) {
373
- respond(res, status, JSON_TYPE, JSON.stringify(payload));
374
- }
375
- async function startWebServer(options) {
376
- const info = options.info ?? printInfo;
377
- const warn = options.warn ?? printWarning;
378
- const version = options.version ?? readPackageVersion();
379
- const startedAtMs = Date.now();
380
- const assets = await loadStaticAssets(
381
- options.publicDir ?? resolvePackageDir(join("web", "public"))
382
- );
383
- let cached = null;
384
- const statusPayload = () => {
385
- const v = options.publisher.version();
386
- if (cached === null || cached.version !== v) {
387
- cached = { version: v, body: JSON.stringify(options.publisher.snapshot()), etag: `"${v}"` };
388
- }
389
- return cached;
390
- };
391
- const handleRequest = (req, res) => {
392
- baseHeaders(res);
393
- const path = new URL(req.url ?? "/", "http://localhost").pathname;
394
- if (req.method !== "GET" && req.method !== "HEAD") {
395
- respondJson(res, 404, { error: "Not found" });
396
- return;
397
- }
398
- if (path === "/api/status" || path === "/status.json") {
399
- const { body, etag } = statusPayload();
400
- res.setHeader("ETag", etag);
401
- if (req.headers["if-none-match"] === etag) {
402
- res.statusCode = 304;
403
- res.end();
404
- return;
405
- }
406
- respond(res, 200, JSON_TYPE, body);
407
- return;
408
- }
409
- if (path === "/api/sessions") {
410
- const snapshot = options.publisher.snapshot();
411
- respondJson(res, 200, [
412
- {
413
- sessionId: snapshot.sessionId,
414
- issueNumber: snapshot.issue.number,
415
- status: snapshot.status,
416
- startedAt: snapshot.startedAt,
417
- updatedAt: snapshot.updatedAt,
418
- statusUrl: "/api/status"
419
- }
420
- ]);
421
- return;
422
- }
423
- if (path === "/api/health") {
424
- respondJson(res, 200, {
425
- ok: true,
426
- uptime: Math.round((Date.now() - startedAtMs) / 1e3),
427
- version,
428
- refreshSeconds: options.refreshSeconds ?? 5
429
- });
430
- return;
431
- }
432
- const asset = assets.get(path);
433
- if (asset) {
434
- respond(res, 200, asset.contentType, asset.body);
435
- return;
436
- }
437
- respondJson(res, 404, { error: "Not found" });
438
- };
439
- const server = createServer((req, res) => {
440
- try {
441
- handleRequest(req, res);
442
- } catch (err) {
443
- try {
444
- respondJson(res, 500, { error: err instanceof Error ? err.message : String(err) });
445
- } catch {
446
- }
447
- }
448
- });
449
- const listening = await new Promise((resolve) => {
450
- const onError = (err) => {
451
- if (err.code === "EADDRINUSE") {
452
- warn(
453
- `Web monitor port ${options.port} is already in use. Continuing without the web server.`
454
- );
455
- } else {
456
- warn(`Web monitor failed to start (${err.message}). Continuing without the web server.`);
457
- }
458
- resolve(false);
459
- };
460
- server.once("error", onError);
461
- server.listen(options.port, options.host, () => {
462
- server.removeListener("error", onError);
463
- resolve(true);
464
- });
465
- });
466
- if (!listening) return null;
467
- server.unref();
468
- server.on("error", (err) => {
469
- warn(`Web monitor server error: ${err.message}`);
470
- });
471
- const address = server.address();
472
- const port = address.port;
473
- const displayHost = options.host === "0.0.0.0" ? "localhost" : options.host;
474
- const url = `http://${displayHost}:${port}`;
475
- if (options.host === "0.0.0.0") {
476
- warn("Web monitor bound to 0.0.0.0: anyone on your local network can view the session state.");
477
- }
478
- info(`Web monitor running at ${chalk.bold.cyan(url)}`);
479
- let closed = false;
480
- let closePromise = null;
481
- const doClose = () => new Promise((resolve) => {
482
- process.removeListener("SIGINT", onSignal);
483
- process.removeListener("SIGTERM", onSignal);
484
- server.close(() => resolve());
485
- server.closeAllConnections();
486
- });
487
- const close = () => {
488
- if (closed) return closePromise ?? Promise.resolve();
489
- closed = true;
490
- closePromise = doClose();
491
- return closePromise;
492
- };
493
- const onSignal = (signal) => {
494
- void close().finally(() => {
495
- process.kill(process.pid, signal);
496
- });
497
- };
498
- process.once("SIGINT", onSignal);
499
- process.once("SIGTERM", onSignal);
500
- return { server, host: options.host, port, url, close };
501
- }
502
-
503
332
  // src/commands/run.ts
504
333
  function toIssueNumber(id) {
505
334
  const parsed = Number.parseInt(id, 10);
@@ -541,7 +370,7 @@ async function readPrReviewOutcome(issue, tasksPath, requestedChanges) {
541
370
  null
542
371
  );
543
372
  if (last) {
544
- outcome.reportPath = join2(dir, last.reportPath);
373
+ outcome.reportPath = join(dir, last.reportPath);
545
374
  outcome.recommendation ??= last.recommendation;
546
375
  }
547
376
  }
@@ -562,9 +391,8 @@ async function runPipeline(issue, mode, from, noBranch, prReview) {
562
391
  });
563
392
  }
564
393
  setSessionPublisher(publisher);
565
- let webServer = null;
566
394
  if (webConfig.enabled) {
567
- webServer = await startWebServer({
395
+ await ensureWebMonitor({
568
396
  publisher,
569
397
  port: webConfig.port,
570
398
  host: webConfig.host,
@@ -589,7 +417,6 @@ async function runPipeline(issue, mode, from, noBranch, prReview) {
589
417
  at: isoNow(),
590
418
  status: exitCode === 0 ? "completed" : "failed"
591
419
  });
592
- await webServer?.close();
593
420
  await publisher.close();
594
421
  setSessionPublisher(void 0);
595
422
  }
@@ -903,4 +730,4 @@ export {
903
730
  publishStorySeed,
904
731
  runPipeline
905
732
  };
906
- //# sourceMappingURL=run-Z6ZDXZB6.js.map
733
+ //# sourceMappingURL=run-2CXVR3VZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/run.ts","../src/core/pipeline.ts","../src/ui/pipeline-renderer.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { mkdir } from 'node:fs/promises';\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 PIPELINE_PHASES_WITH_PR_REVIEW,\n PipelineManager,\n type PipelinePhase,\n} from '../core/pipeline.js';\nimport { mostRecent } from '../core/pr-review/discovery.js';\nimport {\n type PrReviewRoundEntry,\n prReviewDir,\n readPrReviewIndex,\n} from '../core/pr-review/report.js';\nimport { listPullRequests, publishGitState } from '../core/session-git.js';\nimport { getRunUsageTotals } from '../core/session-metrics.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 type { Issue } from '../issues/types.js';\nimport type { IssuePaths } from '../storage/paths.js';\nimport { resolveIssuePaths } from '../storage/resolve.js';\nimport type { UserStory } from '../types.js';\nimport { printError, printInfo, printWarning } from '../ui/logger.js';\nimport { runPipelineWithRenderer } from '../ui/pipeline-renderer.js';\nimport { printRunSummary, type RunSummaryPrReview } from '../ui/summary.js';\nimport { ensureWebMonitor } from '../web/lock.js';\nimport { runExecute } from './execute.js';\nimport { runInit } from './init.js';\nimport { runPlan } from './plan.js';\nimport { runPr } from './pr.js';\nimport { runPrReview } from './pr-review.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/**\n * Seed the snapshot with the stories a `tasks.json` already holds on disk, so\n * the monitor shows the plan instead of \"no user story yet\" until the first\n * execute iteration republishes them.\n *\n * Must be called **after** `session:start`, which resets the snapshot through\n * `createInitialSnapshot()`. An empty plan publishes nothing: the event would\n * bump the publisher's version without adding any content.\n *\n * Returns whether anything was published.\n */\nexport function publishStorySeed(\n publisher: SessionPublisher,\n stories: readonly UserStory[],\n at: string,\n): boolean {\n if (stories.length === 0) return false;\n publisher.publish({ type: 'stories:update', at, stories: [...stories] });\n return true;\n}\n\n/**\n * Publish the Issue's structural data (title, description, labels, state) so\n * the panel shows what is being implemented without a detour through GitHub.\n *\n * Same window as the story seed: right after `session:start`, which resets the\n * snapshot. The data comes from the `ResolvedIssue` the run already holds — no\n * extra provider call — and the description goes out whole, untruncated.\n */\nexport function publishIssueDetails(publisher: SessionPublisher, issue: Issue, at: string): void {\n publisher.publish({\n type: 'issue:update',\n at,\n number: issue.number,\n // Left undefined (rather than null) when the origin has no remote, so the\n // reducer keeps whatever URL session:start already published.\n url: issue.remoteRef ?? undefined,\n title: issue.title,\n description: issue.body,\n labels: issue.labels,\n state: issue.state,\n });\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'];\nconst RUNNABLE_PHASES_WITH_PR_REVIEW: PipelinePhase[] = [...RUNNABLE_PHASES, 'pr-review'];\n\n/**\n * What the `pr-review` phase left behind, for the steps that run after it: the\n * automatic issue close, the highlighted warning and the final summary.\n *\n * Same shape the summary consumes: `requestedChanges` drives the close\n * suppression and is true on exit code 2 even when the plan is gone.\n */\ntype PrReviewOutcome = RunSummaryPrReview;\n\n/**\n * Recover the verdict and the report path the `pr-review` phase produced.\n *\n * Best-effort by design: the exit code already tells whether changes were\n * requested, so a missing plan or index costs the summary a detail, never the\n * decision to keep the issue open.\n */\nasync function readPrReviewOutcome(\n issue: string,\n tasksPath: string,\n requestedChanges: boolean,\n): Promise<PrReviewOutcome> {\n const outcome: PrReviewOutcome = { requestedChanges, recommendation: null, reportPath: null };\n try {\n const plan = await loadTaskPlan(tasksPath);\n outcome.recommendation = plan.prReview?.lastRecommendation ?? null;\n\n const pullRequest = plan.prReview?.pullRequestNumber;\n if (pullRequest !== undefined) {\n const dir = await prReviewDir({ issue, pullRequest });\n const index = await readPrReviewIndex(dir);\n const last = index?.rounds.reduce<PrReviewRoundEntry | null>(\n (latest, entry) => (latest === null || entry.round > latest.round ? entry : latest),\n null,\n );\n if (last) {\n outcome.reportPath = join(dir, last.reportPath);\n outcome.recommendation ??= last.recommendation;\n }\n }\n } catch {\n /* non-critical */\n }\n return outcome;\n}\n\nexport async function runPipeline(\n issue: string,\n mode: string,\n from?: string,\n noBranch?: boolean,\n prReview?: boolean,\n): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n // Resolved once, at the top: every phase that runs below shares the process\n // cache, so the git call and the legacy migration happen a single time for\n // the whole run instead of once per phase.\n const paths = await resolveIssuePaths(issueNumber);\n\n const webConfig = await loadWebConfig();\n let publisher: SessionPublisher = new NullPublisher();\n if (webConfig.enabled) {\n // resolveIssuePaths never creates directories, and a run may well be the\n // first thing to touch this issue's global folder — so the writer creates\n // it. Only under --web: with monitoring off the pipeline still creates\n // nothing at all (US-009).\n await mkdir(paths.issueDir, { recursive: true });\n publisher = new FilePublisher(paths.sessionFile, {\n logLimit: webConfig.logLimit,\n includeLogs: webConfig.includeLogs,\n });\n }\n setSessionPublisher(publisher);\n\n // A null handle (port in use, ...) means the pipeline runs without a server.\n // ensureWebMonitor reuses an already-running, healthy instance instead of\n // binding a second one (US-001), or spawns it detached when none exists\n // (US-002) — either way the returned handle never owns a local server that\n // this process would need to close.\n if (webConfig.enabled) {\n await ensureWebMonitor({\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(\n issueNumber,\n paths,\n mode,\n publisher,\n from,\n noBranch,\n prReview,\n );\n return exitCode;\n } finally {\n publisher.publish({\n type: 'session:end',\n at: isoNow(),\n status: exitCode === 0 ? 'completed' : 'failed',\n });\n // The web monitor is no longer this process's to close (US-002): it is a\n // detached, single machine-wide instance meant to outlive the pipeline\n // and serve other invocations. Only this run's own publication ends here.\n await publisher.close();\n setSessionPublisher(undefined);\n }\n}\n\nasync function runPipelinePhases(\n issueNumber: string,\n paths: IssuePaths,\n mode: string,\n publisher: SessionPublisher,\n from?: string,\n noBranch?: boolean,\n prReview?: boolean,\n): Promise<number> {\n const tasksPath = paths.tasksFile;\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 // Resolve pr-review mode: flag > persisted value > default (off). Unlike\n // --no-branch, the flag wins: the phase adds a step at the end instead of\n // changing what the earlier phases did, so opting in on resume is safe.\n let effectivePrReview = prReview ?? false;\n let planIssueUrl: string | undefined;\n let planBranch: string | undefined;\n // Kept from the same read as planIssueUrl/planBranch — seeding the snapshot\n // must not cost a second trip to disk.\n let planStories: UserStory[] = [];\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 planStories = existingPlan.userStories;\n effectivePrReview = prReview ?? existingPlan.prReview?.enabled ?? false;\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 // The CLI rejects --pr-review with --no-branch, but the persisted no-branch\n // mode can only be known here. Without a pr phase there is no Pull Request\n // to review, so the opt-in is dropped instead of failing a resumed run.\n if (effectiveNoBranch && effectivePrReview) {\n printWarning(\n 'This pipeline runs with --no-branch and opens no PR. Skipping the pr-review phase.',\n );\n effectivePrReview = false;\n }\n\n // Persist the opt-in as soon as we know it, not only after the `plan` phase.\n // A mid-pipeline `--pr-review` (e.g. `--from pr --pr-review`) never re-enters\n // the plan runner, and without `enabled: true` a later resume without the flag\n // would drop the phase even when `prReviewCompleted` is still false.\n if (effectivePrReview) {\n try {\n const plan = await loadTaskPlan(tasksPath);\n if (plan.prReview?.enabled !== true) {\n plan.prReview = { ...plan.prReview, enabled: true, rounds: plan.prReview?.rounds ?? 0 };\n await saveTaskPlan(tasksPath, plan);\n }\n } catch {\n // No tasks.json yet — the plan runner persists it after creating the file.\n }\n }\n\n const activePhases = effectiveNoBranch\n ? PIPELINE_PHASES_NO_BRANCH\n : effectivePrReview\n ? PIPELINE_PHASES_WITH_PR_REVIEW\n : PIPELINE_PHASES;\n const phaseOrder = effectiveNoBranch\n ? RUNNABLE_PHASES_NO_BRANCH\n : effectivePrReview\n ? RUNNABLE_PHASES_WITH_PR_REVIEW\n : 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 // Right after session:start (which resets the snapshot) and before any phase\n // event, so the first /api/status poll already answers with the Issue and\n // the plan.\n publishIssueDetails(publisher, resolvedIssue.issue, sessionStartedAt);\n publishStorySeed(publisher, planStories, sessionStartedAt);\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 the phase-selection modes into the newly created tasks.json\n if (effectiveNoBranch || effectivePrReview) {\n try {\n const plan = await loadTaskPlan(tasksPath);\n if (effectiveNoBranch) {\n plan.noBranch = true;\n }\n if (effectivePrReview) {\n plan.prReview = { ...plan.prReview, enabled: true, rounds: plan.prReview?.rounds ?? 0 };\n }\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 // Filled by the pr-review runner; read after the pipeline finishes. Held in a\n // box because a `let` written only inside a closure keeps its initial `null`\n // narrowing at the read site.\n const reviewBox: { outcome: PrReviewOutcome | null } = { outcome: null };\n if (effectivePrReview) {\n runners['pr-review'] = async () => {\n // `yes` because the run is autonomous: the phase must never stop to ask\n // which Pull Request it is reviewing.\n const code = await runPrReview(undefined, { issue: issueNumber, yes: true });\n if (code === 1) {\n throw new Error('Phase pr-review failed with exit code 1');\n }\n // Exit code 2 is a verdict, not a failure: the review ran, the report is\n // on disk and the pipeline keeps going.\n reviewBox.outcome = await readPrReviewOutcome(issueNumber, tasksPath, code === 2);\n };\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 // A PR review asking for changes is not a pipeline failure, but the work is\n // not done either: the warning is highlighted and the issue stays open.\n const review = reviewBox.outcome;\n if (review?.requestedChanges) {\n console.log('');\n printWarning('PR review requested changes — the Pull Request is not ready to merge.');\n if (review.reportPath !== null) {\n console.log(` Report: ${review.reportPath}`);\n }\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 // REQUEST_CHANGES also leaves the local plan unfinished: marking\n // `issueStatus: completed` while `prReviewCompleted` is false would lie to\n // every tool that keys off the local status.\n if (review?.requestedChanges) {\n printInfo('Issue left open until the review blockers are addressed.');\n } else {\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\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 let planPrUrl: string | null = null;\n try {\n const plan = await loadTaskPlan(tasksPath);\n storyCount = plan.userStories.length;\n // The `pr` phase records the Pull Request it opened; trusting it spares a\n // round-trip to the GitHub CLI below.\n planPrUrl = plan.pullRequest?.url ?? null;\n\n plan.lastAttemptAt = isoNow();\n if (!review?.requestedChanges) {\n plan.issueStatus = 'completed';\n plan.completedAt = isoNow();\n }\n await saveTaskPlan(tasksPath, plan);\n } catch {\n /* non-critical */\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. Falling back to the GitHub CLI\n // uses the branch actually checked out: a query without a head branch would\n // return an unrelated Pull Request.\n let prUrl = 'unknown';\n if (!effectiveNoBranch && resolvedIssue.source === 'github') {\n if (planPrUrl !== null) {\n prUrl = planPrUrl;\n } else if (branchName !== 'unknown' && branchName !== '') {\n try {\n const latest = mostRecent(await listPullRequests(branchName));\n if (latest !== null) {\n prUrl = latest.url;\n }\n } catch {\n /* non-critical */\n }\n }\n }\n\n printRunSummary({\n issueNumber,\n branchName,\n noBranch: effectiveNoBranch,\n storyCount,\n elapsedSeconds: result.overallElapsedSeconds,\n prUrl,\n prReview: review,\n // Process-owned counters: the session snapshot is empty whenever web\n // monitoring is off, so it cannot be the source of these totals.\n usage: getRunUsageTotals(),\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 *\n * `pr-review` is deliberately NOT here: it is opt-in via `--pr-review`. Adding\n * it to the default set would make every existing `tasks.json` (which has no\n * `prReviewCompleted`) look unfinished, so `getNextPhase()` would resume into a\n * phase the user never asked for.\n */\nexport const PIPELINE_PHASES = ['init', 'prd', 'plan', 'execute', 'review', 'pr'] as const;\n\n/**\n * The default set plus the opt-in `pr-review` phase, used when the user passes\n * `--pr-review` (or the flag was persisted in `plan.prReview.enabled`).\n */\nexport const PIPELINE_PHASES_WITH_PR_REVIEW = [...PIPELINE_PHASES, 'pr-review'] as const;\n\nexport type PipelinePhase = (typeof PIPELINE_PHASES_WITH_PR_REVIEW)[number];\n\n/**\n * Pipeline phases excluding the 'pr' phase, for --no-branch mode.\n * `pr-review` is excluded too: without a branch there is no PR to review.\n */\nexport const PIPELINE_PHASES_NO_BRANCH = PIPELINE_PHASES.filter(\n (p): p is Exclude<PipelinePhase, 'pr' | 'pr-review'> => 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 'pr-review': 'prReviewCompleted',\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 *\n * Typed as `Record<string, string>` on purpose (phases arrive as plain strings\n * here), which also means adding a phase to `PipelinePhase` does *not* break\n * the build when its label is missing — it silently falls back to the\n * capitalized phase name. Hence `phaseLabel()` below being covered by a test.\n */\nconst PHASE_LABELS: Record<string, string> = {\n prd: 'PRD',\n plan: 'Plan',\n execute: 'Execute',\n review: 'Review',\n pr: 'PR',\n 'pr-review': 'PR Review',\n};\n\n/**\n * Display label for a pipeline phase, falling back to the capitalized phase\n * name for anything not in the table.\n */\nexport function phaseLabel(phase: string): string {\n return PHASE_LABELS[phase] ?? phase.charAt(0).toUpperCase() + phase.slice(1);\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 //\n // exitOnError must stay true: the per-story subtasks never reject (they\n // only ever get resolve()d, either by the story-update callback or by\n // engineSubtask's finally block once the engine is done), so the only way\n // this nested Listr can fail is engineSubtask itself throwing. With\n // exitOnError: false that failure was swallowed here — the outer \"execute\"\n // phase task then resolved successfully even though the engine failed,\n // and the pipeline moved on to `review` with 0 stories passing instead of\n // stopping. exitOnError: true lets the failure propagate out of\n // task.newListr(...) so the outer phase list (which does have\n // exitOnError: true) aborts before ever starting `review`.\n return task.newListr([engineSubtask, ...subtaskDefs], {\n concurrent: true,\n exitOnError: true,\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 = phaseLabel(phase);\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"],"mappings":"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","names":[]}