issue-flow 0.7.7 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +16 -0
  2. package/dist/{analyze-2RVGY5OK.js → analyze-VSWAEVN3.js} +6 -6
  3. package/dist/{chunk-WO6ERABE.js → chunk-57MH6RYJ.js} +106 -7
  4. package/dist/chunk-57MH6RYJ.js.map +1 -0
  5. package/dist/{chunk-6UTVPKNF.js → chunk-66ZGYXTH.js} +53 -9
  6. package/dist/chunk-66ZGYXTH.js.map +1 -0
  7. package/dist/{chunk-WGDLUPSF.js → chunk-DP43WJY4.js} +75 -11
  8. package/dist/chunk-DP43WJY4.js.map +1 -0
  9. package/dist/{chunk-ZYBYMEWG.js → chunk-DTNM3LTO.js} +33 -10
  10. package/dist/chunk-DTNM3LTO.js.map +1 -0
  11. package/dist/{chunk-KHTLN54U.js → chunk-JGKPSWOA.js} +2 -2
  12. package/dist/{chunk-EYVXLRRD.js → chunk-KXAG24DD.js} +219 -3
  13. package/dist/chunk-KXAG24DD.js.map +1 -0
  14. package/dist/{chunk-XPW46TI5.js → chunk-M37YZYDO.js} +3 -3
  15. package/dist/{chunk-G37JNQKB.js → chunk-M72ZEPWD.js} +2 -2
  16. package/dist/{chunk-RUTDK6ZF.js → chunk-MTJQOOMG.js} +105 -4
  17. package/dist/chunk-MTJQOOMG.js.map +1 -0
  18. package/dist/{chunk-OPIDR2DC.js → chunk-NB6WKTBL.js} +2 -2
  19. package/dist/chunk-TJMZWRS6.js +265 -0
  20. package/dist/chunk-TJMZWRS6.js.map +1 -0
  21. package/dist/{chunk-6IOPYFUM.js → chunk-TLSIK656.js} +6 -6
  22. package/dist/{chunk-JOK7VXRV.js → chunk-VNKSMWDN.js} +17 -3
  23. package/dist/chunk-VNKSMWDN.js.map +1 -0
  24. package/dist/{chunk-6L2BPOE3.js → chunk-WHEU55ZP.js} +7 -7
  25. package/dist/{chunk-EOTULFWV.js → chunk-X7XBF5TS.js} +6 -6
  26. package/dist/cli.js +91 -29
  27. package/dist/cli.js.map +1 -1
  28. package/dist/{config-OAZG7KIK.js → config-J5DW6HSY.js} +3 -3
  29. package/dist/execute-Z2DKGVLR.js +14 -0
  30. package/dist/{generate-BEJF3SVJ.js → generate-ERDPOTKX.js} +6 -6
  31. package/dist/init-UY6ZCIFT.js +9 -0
  32. package/dist/plan-REMAMCX4.js +16 -0
  33. package/dist/pr-KERP52TI.js +20 -0
  34. package/dist/pr-review-IU4N3TEK.js +16 -0
  35. package/dist/prd-SHVQW6RM.js +16 -0
  36. package/dist/review-L5F77QAC.js +14 -0
  37. package/dist/run-BRBFHVUO.js +1860 -0
  38. package/dist/run-BRBFHVUO.js.map +1 -0
  39. package/dist/{web-3SBYL7QJ.js → web-WQISLO66.js} +4 -4
  40. package/package.json +1 -1
  41. package/prompts/execute.md +2 -2
  42. package/prompts/plan.md +10 -1
  43. package/prompts/pr.md +9 -4
  44. package/web/CLAUDE.md +70 -8
  45. package/web/public/app.css +444 -0
  46. package/web/public/app.js +573 -17
  47. package/web/public/index.html +50 -0
  48. package/dist/chunk-3E6KASSE.js +0 -123
  49. package/dist/chunk-3E6KASSE.js.map +0 -1
  50. package/dist/chunk-6UTVPKNF.js.map +0 -1
  51. package/dist/chunk-EYVXLRRD.js.map +0 -1
  52. package/dist/chunk-JOK7VXRV.js.map +0 -1
  53. package/dist/chunk-RUTDK6ZF.js.map +0 -1
  54. package/dist/chunk-WGDLUPSF.js.map +0 -1
  55. package/dist/chunk-WO6ERABE.js.map +0 -1
  56. package/dist/chunk-ZYBYMEWG.js.map +0 -1
  57. package/dist/execute-AOEM52CD.js +0 -14
  58. package/dist/init-TBC3WI74.js +0 -9
  59. package/dist/plan-YDZYB2BO.js +0 -16
  60. package/dist/pr-SIXMJIZF.js +0 -16
  61. package/dist/pr-review-AY56ZPJR.js +0 -16
  62. package/dist/prd-MBSSBLGO.js +0 -16
  63. package/dist/review-2MDDHZ4X.js +0 -14
  64. package/dist/run-SEB6F3W4.js +0 -733
  65. package/dist/run-SEB6F3W4.js.map +0 -1
  66. /package/dist/{analyze-2RVGY5OK.js.map → analyze-VSWAEVN3.js.map} +0 -0
  67. /package/dist/{chunk-KHTLN54U.js.map → chunk-JGKPSWOA.js.map} +0 -0
  68. /package/dist/{chunk-XPW46TI5.js.map → chunk-M37YZYDO.js.map} +0 -0
  69. /package/dist/{chunk-G37JNQKB.js.map → chunk-M72ZEPWD.js.map} +0 -0
  70. /package/dist/{chunk-OPIDR2DC.js.map → chunk-NB6WKTBL.js.map} +0 -0
  71. /package/dist/{chunk-6IOPYFUM.js.map → chunk-TLSIK656.js.map} +0 -0
  72. /package/dist/{chunk-6L2BPOE3.js.map → chunk-WHEU55ZP.js.map} +0 -0
  73. /package/dist/{chunk-EOTULFWV.js.map → chunk-X7XBF5TS.js.map} +0 -0
  74. /package/dist/{config-OAZG7KIK.js.map → config-J5DW6HSY.js.map} +0 -0
  75. /package/dist/{execute-AOEM52CD.js.map → execute-Z2DKGVLR.js.map} +0 -0
  76. /package/dist/{generate-BEJF3SVJ.js.map → generate-ERDPOTKX.js.map} +0 -0
  77. /package/dist/{init-TBC3WI74.js.map → init-UY6ZCIFT.js.map} +0 -0
  78. /package/dist/{plan-YDZYB2BO.js.map → plan-REMAMCX4.js.map} +0 -0
  79. /package/dist/{pr-SIXMJIZF.js.map → pr-KERP52TI.js.map} +0 -0
  80. /package/dist/{pr-review-AY56ZPJR.js.map → pr-review-IU4N3TEK.js.map} +0 -0
  81. /package/dist/{prd-MBSSBLGO.js.map → prd-SHVQW6RM.js.map} +0 -0
  82. /package/dist/{review-2MDDHZ4X.js.map → review-L5F77QAC.js.map} +0 -0
  83. /package/dist/{web-3SBYL7QJ.js.map → web-WQISLO66.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/run.ts","../src/core/pipeline.ts","../src/execution/plan.ts","../src/issues/graph.ts","../src/execution/order.ts","../src/execution/confirm.ts","../src/execution/discovery.ts","../src/execution/queue.ts","../src/issues/args.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 { beginUsageScope, 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 {\n markQueueIssueCompleted,\n markQueueIssueFailed,\n markQueueIssueInProgress,\n nextQueueIssue,\n saveExecutionPlan,\n setQueueBranch,\n setQueuePullRequest,\n} from '../execution/plan.js';\nimport { planQueue, type QueueDecision } from '../execution/queue.js';\nimport type { ExecutionPlan } from '../execution/types.js';\nimport { parseIssueArguments } from '../issues/args.js';\nimport { resolveCommandIssue } from '../issues/context.js';\nimport { getProvider } from '../issues/registry.js';\nimport type { Issue, IssueSource, ResolvedIssue } from '../issues/types.js';\nimport type { IssuePaths } from '../storage/paths.js';\nimport { resolveIssuePaths, resolveProjectPaths, resolveQueuePaths } from '../storage/resolve.js';\nimport type { PullRequestRef, UserStory } from '../types.js';\nimport { printError, printInfo, printSuccess, printWarning } from '../ui/logger.js';\nimport { runPipelineWithRenderer } from '../ui/pipeline-renderer.js';\nimport {\n printQueueSummary,\n printRunSummary,\n type QueueIssueSummary,\n type RunSummaryPrReview,\n} 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 { type PrQueueContext, 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 * Phases of a queue's closing pass: the work is already committed by the\n * per-issue runs, so all that is left is the single consolidated Pull Request.\n */\nconst QUEUE_PR_PHASES = ['init', 'pr'] as const satisfies readonly PipelinePhase[];\nconst QUEUE_PR_PHASES_WITH_REVIEW = [\n 'init',\n 'pr',\n 'pr-review',\n] as const satisfies readonly PipelinePhase[];\nconst RUNNABLE_QUEUE_PR_PHASES: PipelinePhase[] = ['pr'];\nconst RUNNABLE_QUEUE_PR_PHASES_WITH_REVIEW: PipelinePhase[] = ['pr', '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\n/** Options `run` accepts on top of the phase selection ones. */\nexport interface RunPipelineOptions {\n /** `--yes`: accept the discovered hierarchy without confirmation. */\n yes?: boolean;\n /** `--only`: run just the issues informed, skipping discovery. */\n only?: boolean;\n /** `--continue`: name the (automatic) User Story numbering continuity. */\n continueNumbering?: boolean;\n /** `--start-us <n>`: force the first plan of this run to start at `n`. */\n startUs?: number;\n}\n\n/**\n * Everything a queue hands to the run of one of its issues.\n *\n * Its presence is what tells `runPipelinePhases` it is a member of a queue\n * rather than a standalone pipeline: the Pull Request moves to the end of the\n * queue, the branch is shared, commits carry the issue scope, and the terminal\n * summary is the queue's, not the issue's.\n */\ninterface QueueRunContext {\n /** State of the queue, for the branch every issue of it shares. */\n plan: ExecutionPlan;\n /** True when `init` already ran in this process for the queue. */\n preChecked: boolean;\n /** Issue already resolved by the planner, if this is the primary one. */\n resolved?: ResolvedIssue;\n /**\n * Set on the final pass of a queue, which runs no implementation phase at\n * all: only `pr` (and the optional `pr-review`), for the single Pull Request\n * that consolidates every issue.\n */\n finalPr?: PrQueueContext;\n}\n\n/** What one issue's run reports back to the caller. */\ninterface IssueRunResult {\n code: number;\n /** Phase that failed, `null` on success. */\n failedPhase: string | null;\n /** `branchName` of the issue's plan once the `plan` phase produced one. */\n branchName: string | null;\n storyCount: number;\n elapsedSeconds: number;\n /** Verdict of the `pr-review` phase, when it ran. */\n review?: PrReviewOutcome | null;\n /** Set when the run stopped to hand control over to a queue. */\n queue?: { plan: ExecutionPlan; resumed: boolean; resolved: ResolvedIssue };\n}\n\n/**\n * Entry point of `issue-flow run`.\n *\n * Accepts one issue (the historical form, untouched) or several. The first\n * attempt runs as a plain single-issue pipeline; only if the planner decides\n * this invocation is really a queue does it hand control over to\n * {@link runQueue} — and that decision is taken before any session is\n * published, so nothing was written on the way.\n */\nexport async function runPipeline(\n issue: string | readonly string[],\n mode: string,\n from?: string,\n noBranch?: boolean,\n prReview?: boolean,\n options: RunPipelineOptions = {},\n): Promise<number> {\n let requested: string[];\n try {\n requested = parseIssueArguments(typeof issue === 'string' ? [issue] : issue);\n } catch (err) {\n printError(err instanceof Error ? err.message : String(err));\n return 1;\n }\n\n const first = await runIssueSession(requested[0] as string, mode, {\n from,\n noBranch,\n prReview,\n requested,\n runOptions: options,\n });\n\n if (first.queue === undefined) {\n return first.code;\n }\n\n return runQueue(\n first.queue.plan,\n { mode, from, noBranch, prReview, runOptions: options },\n first.queue.resolved,\n );\n}\n\ninterface IssueSessionInput {\n from?: string;\n noBranch?: boolean;\n prReview?: boolean;\n /** Identifiers the user asked for; only the standalone attempt needs them. */\n requested?: string[];\n runOptions?: RunPipelineOptions;\n queue?: QueueRunContext;\n}\n\n/**\n * Run one issue with its own session publisher and web monitor registration.\n *\n * This is the body `runPipeline` always had; a queue calls it once per issue,\n * which is what gives each of them its own `session.json` (and therefore its\n * own card in the monitor) inside a single process.\n */\nasync function runIssueSession(\n issueNumber: string,\n mode: string,\n input: IssueSessionInput,\n): Promise<IssueRunResult> {\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 // Read per issue rather than cached for the process: the configuration is\n // per project and cheap to read, and a cached value would leak a `--web`\n // decision from one invocation into the next inside the same process.\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 result: IssueRunResult = {\n code: 1,\n failedPhase: null,\n branchName: null,\n storyCount: 0,\n elapsedSeconds: 0,\n };\n try {\n result = await runPipelinePhases(issueNumber, paths, mode, publisher, input);\n return result;\n } finally {\n // A run that only decided to become a queue published nothing: closing the\n // session here would write a `session.json` for a pipeline that never ran.\n if (result.queue === undefined) {\n publisher.publish({\n type: 'session:end',\n at: isoNow(),\n status: result.code === 0 ? 'completed' : 'failed',\n });\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 input: IssueSessionInput,\n): Promise<IssueRunResult> {\n const { from, noBranch, prReview, queue } = input;\n // User Story numbering flags (issue #36). `--start-us` names a starting\n // point, so the queue only ever hands it to the first issue it runs —\n // applying it to each one would give them all the same ids, the very\n // collision #36 set out to remove. Every later issue continues from history,\n // which by then already includes the plans written earlier in this run.\n const continueNumbering = input.runOptions?.continueNumbering;\n const startUs = input.runOptions?.startUs;\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. Inside a queue it already ran for the whole run, so\n // the environment is not probed once per issue — the phase is still\n // published, keeping every issue's session shape identical.\n const sessionStartedAt = isoNow();\n if (queue?.preChecked !== true) {\n printInfo('Running prerequisite checks...');\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 failure(1);\n }\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, queue?.resolved, {\n config: issuesConfig,\n });\n if (!resolution.ok) {\n return failure(resolution.code);\n }\n const resolvedIssue = resolution.resolved;\n publishedIssueNumber = resolvedIssue.issue.number;\n\n // Everything above is what a queue needs to exist: prerequisites checked and\n // the primary Issue resolved, but not a single phase run and not a single\n // event published. This is where a run learns it is really a queue.\n if (queue === undefined) {\n const decision = await decideQueue({\n requested: input.requested ?? [issueNumber],\n resolved: resolvedIssue,\n noBranch: noBranch ?? false,\n prReview: prReview ?? false,\n runOptions: input.runOptions ?? {},\n });\n if (decision.kind === 'stop') {\n return failure(decision.code);\n }\n if (decision.kind === 'queue') {\n return {\n ...failure(0),\n queue: { plan: decision.plan, resumed: decision.resumed, resolved: resolvedIssue },\n };\n }\n }\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 // Inside a queue the Pull Request is opened once, after the last issue, so\n // `pr` (and with it `pr-review`) leaves the per-issue phase list. The list is\n // the same one `--no-branch` uses, but the branch is still created: what\n // changes is who opens the Pull Request, not whether there is a branch.\n const inQueue = queue !== undefined;\n // The queue's last pass implements nothing: it only opens the single Pull\n // Request that covers every issue already committed to the shared branch.\n const finalPr = queue?.finalPr;\n const activePhases = finalPr\n ? effectivePrReview\n ? QUEUE_PR_PHASES_WITH_REVIEW\n : QUEUE_PR_PHASES\n : inQueue\n ? PIPELINE_PHASES_NO_BRANCH\n : effectiveNoBranch\n ? PIPELINE_PHASES_NO_BRANCH\n : effectivePrReview\n ? PIPELINE_PHASES_WITH_PR_REVIEW\n : PIPELINE_PHASES;\n const phaseOrder = finalPr\n ? effectivePrReview\n ? RUNNABLE_QUEUE_PR_PHASES_WITH_REVIEW\n : RUNNABLE_QUEUE_PR_PHASES\n : inQueue\n ? RUNNABLE_PHASES_NO_BRANCH\n : effectiveNoBranch\n ? RUNNABLE_PHASES_NO_BRANCH\n : effectivePrReview\n ? RUNNABLE_PHASES_WITH_PR_REVIEW\n : RUNNABLE_PHASES;\n\n // Commit scope for this issue's stories: only a queue needs one, because\n // only there do several issues share a branch (and therefore a `git log`).\n const queueCommitScope = queue === undefined ? undefined : `issue-${issueNumber}`;\n // Branch this issue will work on, reported back so the queue can adopt the\n // first issue's choice for every later one.\n let producedBranch: string | null = null;\n\n // Adopted **before** the phases run, not only after `plan`: an issue whose\n // plan phase is already complete (it was run standalone before joining the\n // queue, or the queue is being resumed) never re-enters the plan runner, and\n // would otherwise send `execute` at a branch of its own.\n if (queue !== undefined && finalPr === undefined && !effectiveNoBranch) {\n producedBranch = await adoptQueueBranch(tasksPath, queue.plan.branchName);\n if (producedBranch !== null) {\n planBranch = producedBranch;\n }\n }\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 failure(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 failure(1);\n }\n } catch {\n if (startPhase !== 'prd') {\n printError(`Cannot resume from ${startPhase}: no pipeline state found`);\n return failure(1);\n }\n }\n }\n\n // A phase that is not part of this run's list (the closing pass of a queue\n // runs only `pr`) starts the renderer at the beginning rather than at -1.\n const startIdx = Math.max(phaseOrder.indexOf(startPhase), 0);\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(\n () => runPlan(issueNumber, resolvedIssue, { continueFlag: continueNumbering, startUs }),\n 'plan',\n )();\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 // A queue shares one branch: the first issue's plan decides it, every\n // later issue has it written over whatever slug the agent derived from\n // its own title. The `execute` prompt then finds the branch already\n // checked out instead of creating a second one — the creation logic\n // itself is untouched.\n if (queue !== undefined && !effectiveNoBranch) {\n producedBranch = await adoptQueueBranch(tasksPath, queue.plan.branchName);\n }\n },\n execute: makeRunner(\n () => runExecute(undefined, { issue: issueNumber, commitScope: queueCommitScope }),\n 'execute',\n ),\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, {\n issue: issueNumber,\n commitScope: queueCommitScope,\n });\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(\n // The options argument is omitted outside a queue so a standalone run\n // calls `runPr` exactly as it always did.\n () =>\n finalPr === undefined\n ? runPr(issueNumber, resolvedIssue)\n : runPr(issueNumber, resolvedIssue, { queue: finalPr }),\n 'pr',\n ),\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 {\n ...failure(1),\n failedPhase: result.failedPhase ?? null,\n branchName: producedBranch,\n elapsedSeconds: result.overallElapsedSeconds,\n };\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 // Inside a queue the issues are closed once, after the consolidated Pull\n // Request: closing one here would announce it as done while the branch that\n // carries its work has not even been proposed yet.\n if (review?.requestedChanges) {\n printInfo('Issue left open until the review blockers are addressed.');\n } else if (!inQueue) {\n await closeIssue(issueNumber, resolvedIssue.source);\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 // Inside a queue the run is not over: the summary, the Pull Request and the\n // totals belong to the queue, which prints them once at the end.\n if (inQueue) {\n if (finalPr === undefined) {\n printSuccess(`Issue #${issueNumber} completed (${storyCount} stories).`);\n }\n return {\n code: 0,\n failedPhase: null,\n branchName: producedBranch ?? (branchName === 'unknown' ? null : branchName),\n storyCount,\n elapsedSeconds: result.overallElapsedSeconds,\n review,\n };\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 {\n code: 0,\n failedPhase: null,\n branchName: branchName === 'unknown' ? null : branchName,\n storyCount,\n elapsedSeconds: result.overallElapsedSeconds,\n };\n}\n\ninterface DecideQueueInput {\n requested: string[];\n resolved: ResolvedIssue;\n noBranch: boolean;\n prReview: boolean;\n runOptions: RunPipelineOptions;\n}\n\n/**\n * Ask the planner whether this invocation is a queue, resolving the storage\n * paths it needs.\n *\n * A failure here degrades to a single-issue run when only one issue was asked\n * for — discovery is an enrichment, and losing it must never cost the user the\n * pipeline. With several issues informed there is nothing to degrade to: the\n * user asked for a queue, so the error is reported and the run stops.\n */\nasync function decideQueue(input: DecideQueueInput): Promise<QueueDecision> {\n const primary = input.requested[0] as string;\n const single = input.requested.length === 1;\n\n if (single && input.runOptions.only === true) {\n return { kind: 'single' };\n }\n\n try {\n const [project, queuePaths] = await Promise.all([\n resolveProjectPaths(),\n resolveQueuePaths(primary),\n ]);\n\n return await planQueue({\n requested: input.requested,\n source: input.resolved.source,\n known: [input.resolved.issue],\n projectId: project.projectId,\n planFile: queuePaths.planFile,\n noBranch: input.noBranch,\n prReview: input.prReview,\n confirm: { yes: input.runOptions.yes, only: input.runOptions.only },\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (single) {\n printWarning(\n `Could not inspect related issues (${message}). Running issue #${primary} alone.`,\n );\n return { kind: 'single' };\n }\n printError(`Could not plan the execution of ${input.requested.length} issues: ${message}`);\n return { kind: 'stop', code: 1 };\n }\n}\n\n/**\n * Run a queue of issues: one shared branch, one process, one issue at a time.\n *\n * Each issue goes through the very same phases a single-issue run does\n * (`prd` → `plan` → `execute` → `review`), with its own `tasks.json`, its own\n * session and its own usage scope. The queue owns only what is shared: the\n * order, the branch, the Pull Request and the consolidated totals.\n *\n * A failure stops the queue where it happened, records the issue and the phase,\n * and leaves every commit and the branch exactly as they are — the next\n * invocation resumes from the failed issue without redoing the ones before it.\n */\nasync function runQueue(\n initialPlan: ExecutionPlan,\n options: {\n mode: string;\n from?: string;\n noBranch?: boolean;\n prReview?: boolean;\n runOptions?: RunPipelineOptions;\n },\n resolvedPrimary?: ResolvedIssue,\n): Promise<number> {\n const planFile = (await resolveQueuePaths(initialPlan.id)).planFile;\n let plan = initialPlan;\n\n const queueUsage = beginUsageScope();\n const startedAtMs = Date.now();\n const summaries: QueueIssueSummary[] = [];\n let firstOfThisRun = true;\n\n try {\n while (true) {\n const entry = nextQueueIssue(plan);\n if (entry === null) break;\n\n plan = markQueueIssueInProgress(plan, entry.id);\n await saveExecutionPlan(planFile, plan);\n\n printInfo(\n `Queue ${entry.position}/${plan.issues.length}: issue #${entry.id}${entry.title === '' ? '' : ` — ${entry.title}`}`,\n );\n\n const issueUsage = beginUsageScope();\n let result: IssueRunResult;\n try {\n result = await runIssueSession(entry.id, options.mode, {\n // `--from` addresses the issue the queue is resuming, never the ones\n // that come after it — those start from their own beginning.\n from: firstOfThisRun ? options.from : undefined,\n noBranch: options.noBranch,\n // `--start-us` belongs to the first issue this invocation runs; the\n // ones after it continue from the history those plans just wrote.\n runOptions: {\n ...options.runOptions,\n startUs: firstOfThisRun ? options.runOptions?.startUs : undefined,\n },\n queue: {\n plan,\n preChecked: true,\n resolved: entry.id === initialPlan.id ? resolvedPrimary : undefined,\n },\n });\n } finally {\n firstOfThisRun = false;\n // Ending here (not only on the happy path) keeps a thrown error from\n // leaving an orphan accumulator on top of the scope stack, where it\n // would silently become \"the current scope\" for everything after it.\n issueUsage.end();\n }\n const usage = issueUsage.totals();\n\n if (result.code !== 0) {\n plan = markQueueIssueFailed(plan, entry.id, {\n phase: result.failedPhase,\n error: {\n category: 'queue_issue_failed',\n message: `Issue #${entry.id} failed${result.failedPhase === null ? '' : ` in phase ${result.failedPhase}`}`,\n at: isoNow(),\n },\n });\n await saveExecutionPlan(planFile, plan);\n\n printError(\n `Queue stopped at issue #${entry.id}${result.failedPhase === null ? '' : ` (phase ${result.failedPhase})`}. ` +\n 'The branch and every commit made so far were kept.',\n );\n printInfo(`Resume with: issue-flow run ${plan.requested.join(',')}`);\n return result.code;\n }\n\n // The first issue names the branch; every later one is made to adopt it.\n if (plan.branchName === null && result.branchName !== null) {\n plan = setQueueBranch(plan, result.branchName);\n }\n plan = markQueueIssueCompleted(plan, entry.id);\n await saveExecutionPlan(planFile, plan);\n\n summaries.push({\n id: entry.id,\n title: entry.title,\n storyCount: result.storyCount,\n elapsedSeconds: result.elapsedSeconds,\n usage,\n });\n }\n\n return await finishQueue(\n plan,\n planFile,\n summaries,\n startedAtMs,\n queueUsage,\n options,\n resolvedPrimary,\n );\n } finally {\n queueUsage.end();\n }\n}\n\n/**\n * Everything that happens once every issue of the queue is done: the single\n * consolidated Pull Request, closing the issues and reporting. Split out so the\n * loop above stays about scheduling.\n */\nasync function finishQueue(\n plan: ExecutionPlan,\n planFile: string,\n summaries: QueueIssueSummary[],\n startedAtMs: number,\n /** Read only after the consolidated Pull Request ran, so its cost counts. */\n queueUsage: { totals: () => ReturnType<typeof getRunUsageTotals> },\n options: { mode: string; noBranch?: boolean; prReview?: boolean },\n resolvedPrimary?: ResolvedIssue,\n): Promise<number> {\n let current = plan;\n let review: PrReviewOutcome | null = null;\n\n // One Pull Request for the whole queue, and only when there is a branch to\n // propose and no Pull Request has been opened for this queue yet.\n const alreadyOpened = current.pullRequest !== undefined || (await primaryPrCreated(current));\n if (!current.noBranch && options.noBranch !== true && !alreadyOpened) {\n const outcome = await runIssueSession(current.id, options.mode, {\n prReview: options.prReview ?? current.prReview,\n queue: {\n plan: current,\n preChecked: true,\n resolved: resolvedPrimary,\n finalPr: await buildPrQueueContext(current),\n },\n });\n review = outcome.review ?? null;\n\n if (outcome.code !== 0) {\n printError('The consolidated Pull Request could not be created.');\n await saveExecutionPlan(planFile, current);\n return outcome.code;\n }\n\n const pullRequest = await propagatePullRequest(current);\n if (pullRequest !== null) {\n current = setQueuePullRequest(current, pullRequest);\n }\n }\n\n await saveExecutionPlan(planFile, current);\n\n // A review asking for changes leaves every issue open, exactly as it does for\n // a single-issue run: the work is proposed, not accepted.\n if (review?.requestedChanges === true) {\n printInfo('Issues left open until the review blockers are addressed.');\n } else {\n for (const entry of current.issues) {\n await closeIssue(entry.id, entry.source);\n }\n }\n\n printQueueSummary({\n queueId: current.id,\n branchName: current.branchName,\n issues: summaries,\n excluded: current.excluded.map((entry) => ({ id: entry.id, title: entry.title })),\n elapsedSeconds: Math.round((Date.now() - startedAtMs) / 1000),\n prUrl: current.pullRequest?.url ?? null,\n // Read here, after the consolidated Pull Request (and the optional\n // pr-review) already ran: reading it before would report a queue total\n // that leaves out the phase that just spent tokens.\n usage: queueUsage.totals(),\n prReview: review,\n });\n\n return 0;\n}\n\n/**\n * Whether the `pr` phase already ran for this queue, even if it could not\n * report a URL.\n *\n * `plan.pullRequest` on the queue is the happy path; this is the safety net for\n * the case where the phase succeeded but no URL could be parsed from its output\n * — re-running it on a resume would open a second Pull Request for the same\n * branch, which is exactly what the whole feature exists to avoid.\n */\nasync function primaryPrCreated(plan: ExecutionPlan): Promise<boolean> {\n try {\n const paths = await resolveIssuePaths(plan.id);\n return (await loadTaskPlan(paths.tasksFile)).pipeline.prCreated;\n } catch {\n return false;\n }\n}\n\n/**\n * The context the consolidated Pull Request needs: what was implemented, in\n * which order, and what is knowingly left pending.\n *\n * \"Pending\" is the honest half of the report — the issues the user chose not to\n * run, and any review findings still recorded on an issue's plan. Both would\n * otherwise be invisible to whoever reviews the Pull Request.\n */\nasync function buildPrQueueContext(plan: ExecutionPlan): Promise<PrQueueContext> {\n const pending: string[] = [];\n\n for (const entry of plan.issues) {\n try {\n const paths = await resolveIssuePaths(entry.id);\n const taskPlan = await loadTaskPlan(paths.tasksFile);\n if (taskPlan.lastReviewFindings !== null && taskPlan.lastReviewFindings !== '') {\n pending.push(`Issue #${entry.id} has unresolved review findings`);\n }\n } catch {\n // No plan on disk for this issue: nothing to report about it.\n }\n }\n\n return {\n issues: plan.issues.map((entry) => ({\n id: entry.id,\n number: entry.number,\n title: entry.title,\n url: entry.url,\n source: entry.source,\n })),\n excluded: plan.excluded.map((entry) => ({\n id: entry.id,\n number: entry.number,\n title: entry.title,\n reason: entry.reason,\n })),\n pending,\n };\n}\n\n/**\n * Copy the Pull Request the `pr` phase recorded on the primary issue's plan\n * onto every other issue of the queue.\n *\n * The queue's `execution-plan.json` is the source of truth, but `pr-review`\n * discovers a Pull Request from `plan.pullRequest` in `tasks.json`\n * (`core/pr-review/discovery.ts`), so replicating it is what keeps\n * `pr-review --issue <any issue of the queue>` working without a special case.\n * A write that fails is reported and skipped: the Pull Request exists either\n * way, and the queue file already records it.\n */\nasync function propagatePullRequest(plan: ExecutionPlan): Promise<PullRequestRef | null> {\n let pullRequest: PullRequestRef | null = null;\n try {\n const primaryPaths = await resolveIssuePaths(plan.id);\n pullRequest = (await loadTaskPlan(primaryPaths.tasksFile)).pullRequest ?? null;\n } catch {\n return null;\n }\n if (pullRequest === null) return null;\n\n for (const entry of plan.issues) {\n if (entry.id === plan.id) continue;\n try {\n const paths = await resolveIssuePaths(entry.id);\n const taskPlan = await loadTaskPlan(paths.tasksFile);\n taskPlan.pullRequest = pullRequest;\n taskPlan.pipeline.prCreated = true;\n await saveTaskPlan(paths.tasksFile, taskPlan);\n } catch {\n printWarning(`Could not record the Pull Request on issue #${entry.id}'s task plan.`);\n }\n }\n\n return pullRequest;\n}\n\n/** An {@link IssueRunResult} carrying nothing but an exit code. */\nfunction failure(code: number): IssueRunResult {\n return { code, failedPhase: null, branchName: null, storyCount: 0, elapsedSeconds: 0 };\n}\n\n/**\n * Close an Issue through whoever owns it.\n *\n * A provider without `close()` (a read-only origin) has nothing to do here, so\n * the step is simply skipped, and a failure is a warning: the work is done\n * either way, and an Issue left open is a nuisance, not a broken pipeline.\n */\nasync function closeIssue(issueNumber: string, source: IssueSource): Promise<void> {\n try {\n const provider = getProvider(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/**\n * Make an issue's task plan use the queue's branch.\n *\n * With no branch decided yet, whatever the `plan` phase produced becomes the\n * queue's branch (the first issue names it, from its own title). From then on\n * the value is written over every later issue's plan, so `execute.md`'s\n * \"check it out or create from main\" finds the branch already there.\n */\nasync function adoptQueueBranch(\n tasksPath: string,\n queueBranch: string | null,\n): Promise<string | null> {\n try {\n const plan = await loadTaskPlan(tasksPath);\n if (queueBranch === null || queueBranch === '') {\n return plan.branchName === '' ? null : plan.branchName;\n }\n if (plan.branchName !== queueBranch) {\n plan.branchName = queueBranch;\n await saveTaskPlan(tasksPath, plan);\n }\n return queueBranch;\n } catch {\n // No tasks.json (the plan phase failed to write one): the queue keeps the\n // branch it already had, and the phase failure is reported on its own.\n return queueBranch;\n }\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 { mkdir, readFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { ZodError } from 'zod';\nimport { isoNow } from '../core/state-manager.js';\nimport type { DependencyGraph } from '../issues/graph.js';\nimport type { Issue } from '../issues/types.js';\nimport { executionPlanSchema } from '../storage/schemas.js';\nimport type { LastError, PullRequestRef } from '../types.js';\nimport { writeFileAtomic } from '../utils/fs.js';\nimport { priorityOf } from './order.js';\nimport type {\n ExecutionPlan,\n ExecutionPlanExcluded,\n ExecutionPlanIssue,\n QueueStatus,\n} from './types.js';\n\n/**\n * Building, reading, writing and advancing the execution plan of a queue.\n *\n * The plan is the only state a multi-issue run adds to the storage tree, and it\n * is written **only** for a queue with more than one Issue — a single-issue run\n * leaves the layout exactly as it was.\n */\n\nexport interface BuildExecutionPlanInput {\n /** Project id of the global storage tree. */\n projectId: string;\n /** Identifiers the user asked for, in order; the first one names the queue. */\n requested: readonly string[];\n graph: DependencyGraph;\n /** Ids to run, already ordered by `computeExecutionOrder`. */\n order: readonly string[];\n noBranch?: boolean;\n prReview?: boolean;\n /** Injectable clock, so tests can assert `createdAt` vs `updatedAt`. */\n now?: () => string;\n}\n\n/** Title/url/number of a node, falling back to what the id alone tells us. */\nfunction describeIssue(\n issue: Issue | null,\n id: string,\n): {\n number: number | null;\n title: string;\n url: string | null;\n source: string;\n} {\n const parsed = Number.parseInt(id, 10);\n return {\n number: issue?.number ?? (Number.isNaN(parsed) ? null : parsed),\n title: issue?.title ?? '',\n url: issue?.remoteRef ?? null,\n source: issue?.source ?? 'github',\n };\n}\n\n/**\n * Turn a graph plus an order into the persisted plan.\n *\n * Every Issue of the graph that is *not* in the order becomes an `excluded`\n * entry: the consolidated Pull Request reports them as known pending work, so a\n * hierarchy the user chose to trim never disappears silently.\n */\nexport function buildExecutionPlan(input: BuildExecutionPlanInput): ExecutionPlan {\n const now = input.now ?? isoNow;\n const at = now();\n const scheduled = new Set(input.order);\n const requested = new Set(input.requested);\n\n const issues: ExecutionPlanIssue[] = input.order.map((id, index) => {\n const node = input.graph.nodes.get(id);\n const relations = node?.relations;\n const described = describeIssue(node?.issue ?? null, id);\n\n return {\n id,\n number: described.number,\n title: described.title,\n url: described.url,\n source: described.source,\n position: index + 1,\n status: 'pending',\n origin: requested.has(id) ? 'requested' : 'discovered',\n // Only the constraints that survive inside the queue: a blocker left out\n // of it is reported in `excluded`, not enforced as a dependency.\n dependsOn: (relations?.blockedBy ?? []).filter((blocker) => scheduled.has(blocker)),\n parent: relations?.parent ?? null,\n priority: priorityOf(node?.issue?.labels ?? []),\n heuristic: (relations?.heuristic.length ?? 0) > 0,\n failedPhase: null,\n lastError: null,\n startedAt: null,\n completedAt: null,\n };\n });\n\n const excluded: ExecutionPlanExcluded[] = [];\n for (const node of input.graph.nodes.values()) {\n if (scheduled.has(node.id)) continue;\n const described = describeIssue(node.issue, node.id);\n excluded.push({\n id: node.id,\n number: described.number,\n title: described.title,\n url: described.url,\n reason: 'Discovered in the hierarchy but not selected for this run',\n });\n }\n\n return {\n schemaVersion: 1,\n id: input.requested[0] ?? (input.order[0] as string),\n project: input.projectId,\n requested: [...input.requested],\n branchName: null,\n noBranch: input.noBranch ?? false,\n prReview: input.prReview ?? false,\n status: 'pending',\n createdAt: at,\n updatedAt: at,\n truncated: input.graph.truncated,\n issues,\n excluded,\n };\n}\n\n/** Read a persisted plan. Throws with the offending fields on invalid content. */\nexport async function loadExecutionPlan(path: string): Promise<ExecutionPlan> {\n const raw: unknown = JSON.parse(await readFile(path, 'utf-8'));\n\n try {\n return executionPlanSchema.parse(raw) as ExecutionPlan;\n } catch (err) {\n if (err instanceof ZodError) {\n const issues = err.issues.map((i) => ` - ${i.path.join('.')}: ${i.message}`).join('\\n');\n throw new Error(`Invalid execution-plan.json at ${path}:\\n${issues}`);\n }\n throw err;\n }\n}\n\n/**\n * Persist a plan atomically, creating the queue directory on first write.\n *\n * `updatedAt` is stamped here so no call site can forget it — the file is the\n * only record of how far a queue got, and a stale timestamp on it is worse than\n * none.\n */\nexport async function saveExecutionPlan(\n path: string,\n plan: ExecutionPlan,\n now: () => string = isoNow,\n): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n const stamped: ExecutionPlan = { ...plan, updatedAt: now() };\n await writeFileAtomic(path, `${JSON.stringify(stamped, null, 2)}\\n`);\n}\n\n/**\n * The Issue the queue should work on next, or `null` when every one of them is\n * completed.\n *\n * The order of the lookups *is* the resume policy: an interrupted issue\n * (`in_progress`) and a failed one are retried before the queue moves on, and\n * each of them picks up from its own `tasks.json` (phase-level resume) rather\n * than from the start. A `completed` issue is never revisited — that is the\n * whole point of persisting the queue.\n */\nexport function nextQueueIssue(plan: ExecutionPlan): ExecutionPlanIssue | null {\n return (\n plan.issues.find((entry) => entry.status === 'in_progress') ??\n plan.issues.find((entry) => entry.status === 'failed') ??\n plan.issues.find((entry) => entry.status === 'pending') ??\n null\n );\n}\n\n/** Status of the queue derived from its entries. */\nexport function queueStatus(plan: ExecutionPlan): QueueStatus {\n if (plan.issues.some((entry) => entry.status === 'failed')) return 'failed';\n if (plan.issues.every((entry) => entry.status === 'completed')) return 'completed';\n if (plan.issues.some((entry) => entry.status !== 'pending')) return 'in_progress';\n return 'pending';\n}\n\n/** Whether every Issue of the queue has been resolved. */\nexport function isQueueComplete(plan: ExecutionPlan): boolean {\n return plan.issues.every((entry) => entry.status === 'completed');\n}\n\n/** Apply `patch` to one entry and recompute the queue status. Pure. */\nfunction updateIssue(\n plan: ExecutionPlan,\n id: string,\n patch: Partial<ExecutionPlanIssue>,\n): ExecutionPlan {\n const issues = plan.issues.map((entry) => (entry.id === id ? { ...entry, ...patch } : entry));\n const next: ExecutionPlan = { ...plan, issues };\n return { ...next, status: queueStatus(next) };\n}\n\nexport function markQueueIssueInProgress(\n plan: ExecutionPlan,\n id: string,\n now: () => string = isoNow,\n): ExecutionPlan {\n const current = plan.issues.find((entry) => entry.id === id);\n return updateIssue(plan, id, {\n status: 'in_progress',\n // Preserved across a resume: the queue reports when the work on this Issue\n // really began, not when it was last picked up.\n startedAt: current?.startedAt ?? now(),\n failedPhase: null,\n lastError: null,\n });\n}\n\nexport function markQueueIssueCompleted(\n plan: ExecutionPlan,\n id: string,\n now: () => string = isoNow,\n): ExecutionPlan {\n return updateIssue(plan, id, {\n status: 'completed',\n completedAt: now(),\n failedPhase: null,\n lastError: null,\n });\n}\n\nexport function markQueueIssueFailed(\n plan: ExecutionPlan,\n id: string,\n failure: { phase: string | null; error: LastError | null },\n): ExecutionPlan {\n return updateIssue(plan, id, {\n status: 'failed',\n failedPhase: failure.phase,\n lastError: failure.error,\n });\n}\n\n/** Record the branch every Issue of the queue shares. */\nexport function setQueueBranch(plan: ExecutionPlan, branchName: string): ExecutionPlan {\n return { ...plan, branchName };\n}\n\n/** Record the single Pull Request that consolidated the queue. */\nexport function setQueuePullRequest(\n plan: ExecutionPlan,\n pullRequest: PullRequestRef,\n): ExecutionPlan {\n return { ...plan, pullRequest };\n}\n","import { emptyRelations, uniqueIds } from './relations.js';\nimport type { Issue, IssueRelations } from './types.js';\n\n/**\n * Dependency graph of a set of Issues, built by walking the relations every\n * provider exposes through `fetchRelations`.\n *\n * The traversal is deliberately *structural*: it follows hierarchy\n * (parent/children) and dependencies (blockedBy/blocking) and stops there. Plain\n * mentions are recorded on each node but never expanded — a body citing \"see\n * also #12\" would otherwise drag an unrelated Issue into an execution plan the\n * user is about to confirm.\n */\n\nexport interface DependencyGraphNode {\n id: string;\n relations: IssueRelations;\n /** Distance from the closest root, in traversal hops. */\n depth: number;\n /** The Issue itself, when the caller supplied a way to read it. */\n issue: Issue | null;\n /** True when the node was requested by the user rather than discovered. */\n root: boolean;\n}\n\nexport interface DependencyGraph {\n /** Ids the traversal started from, in the order they were requested. */\n roots: string[];\n /** Every discovered node, keyed by id, in discovery order. */\n nodes: Map<string, DependencyGraphNode>;\n /**\n * Dependency cycles found among the discovered nodes, each listed once,\n * starting at its smallest member. Reported rather than thrown: discovery\n * must still answer, and it is the ordering layer that refuses to run.\n */\n cycles: string[][];\n /** True when a limit stopped the traversal before it was exhausted. */\n truncated: boolean;\n limits: { maxNodes: number; maxDepth: number };\n}\n\n/**\n * Conservative defaults: a real hierarchy is a handful of Issues, and each node\n * costs several API calls. A malformed (or simply huge) graph must not turn a\n * `run` into a rate-limit incident.\n */\nexport const DEFAULT_MAX_NODES = 25;\nexport const DEFAULT_MAX_DEPTH = 3;\n\nexport interface BuildDependencyGraphOptions {\n maxNodes?: number;\n maxDepth?: number;\n /** Issues the caller already resolved, so the traversal does not re-read them. */\n known?: Iterable<Issue>;\n /** Reader for the Issues the traversal discovers. Optional. */\n fetchIssue?: (id: string) => Promise<Issue | null>;\n /** Warning sink for a source that failed. Silent by default. */\n warn?: (message: string) => void;\n}\n\n/** Edge `from` → `to`, meaning \"`from` must be resolved before `to`\". */\nexport interface DependencyEdge {\n from: string;\n to: string;\n}\n\n/**\n * Ordering constraints of a graph: only hierarchy-free, explicit dependencies.\n *\n * `blockedBy`/`blocking` are the two spellings of the same fact, so both are\n * normalized into a single direction here. Parent/child is **not** a constraint\n * — it is a presentation and tie-breaking concern (see `computeExecutionOrder`),\n * because a parent is usually an umbrella that neither blocks nor is blocked by\n * its children.\n *\n * Edges pointing outside the graph are dropped: an Issue the traversal never\n * reached cannot be scheduled, so a constraint against it is unenforceable.\n */\nexport function dependencyEdges(graph: DependencyGraph): DependencyEdge[] {\n const edges: DependencyEdge[] = [];\n const seen = new Set<string>();\n\n const add = (from: string, to: string): void => {\n if (from === to || !graph.nodes.has(from) || !graph.nodes.has(to)) return;\n const key = `${from}->${to}`;\n if (seen.has(key)) return;\n seen.add(key);\n edges.push({ from, to });\n };\n\n for (const node of graph.nodes.values()) {\n for (const blocker of node.relations.blockedBy) {\n add(blocker, node.id);\n }\n for (const blocked of node.relations.blocking) {\n add(node.id, blocked);\n }\n }\n\n return edges;\n}\n\n/** Every cycle of the dependency edges, each reported once. */\nexport function findCycles(graph: DependencyGraph): string[][] {\n const adjacency = new Map<string, string[]>();\n for (const { from, to } of dependencyEdges(graph)) {\n const list = adjacency.get(from);\n if (list === undefined) {\n adjacency.set(from, [to]);\n } else {\n list.push(to);\n }\n }\n\n const cycles: string[][] = [];\n const reported = new Set<string>();\n const state = new Map<string, 'visiting' | 'done'>();\n const stack: string[] = [];\n\n const record = (cycle: string[]): void => {\n // Rotate to the smallest member so the same cycle found from two entry\n // points is reported once.\n let pivot = 0;\n for (let i = 1; i < cycle.length; i++) {\n if (compareIds(cycle[i] as string, cycle[pivot] as string) < 0) pivot = i;\n }\n const normalized = [...cycle.slice(pivot), ...cycle.slice(0, pivot)];\n const key = normalized.join('->');\n if (reported.has(key)) return;\n reported.add(key);\n cycles.push(normalized);\n };\n\n const visit = (id: string): void => {\n state.set(id, 'visiting');\n stack.push(id);\n\n for (const next of adjacency.get(id) ?? []) {\n const status = state.get(next);\n if (status === 'visiting') {\n record(stack.slice(stack.indexOf(next)));\n continue;\n }\n if (status === undefined) {\n visit(next);\n }\n }\n\n stack.pop();\n state.set(id, 'done');\n };\n\n for (const id of graph.nodes.keys()) {\n if (!state.has(id)) visit(id);\n }\n\n return cycles;\n}\n\n/** Numeric when both ids are numbers, lexicographic otherwise. */\nexport function compareIds(a: string, b: string): number {\n const na = Number(a);\n const nb = Number(b);\n if (Number.isInteger(na) && Number.isInteger(nb)) {\n return na - nb;\n }\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\n/** Ids a node links to structurally, in traversal order. */\nfunction neighbours(relations: IssueRelations): string[] {\n return uniqueIds(\n [\n ...(relations.parent === null ? [] : [relations.parent]),\n ...relations.children,\n ...relations.blockedBy,\n ...relations.blocking,\n ],\n relations.id,\n );\n}\n\n/**\n * Walk the relations of `roots` breadth-first and return the whole structure.\n *\n * Failures never abort the walk: a node whose relations cannot be read enters\n * the graph with empty relations (and a warning), exactly like an Issue that\n * genuinely relates to nothing. That keeps a partially unavailable API from\n * costing the user the Issues that *were* discovered.\n *\n * Breadth-first is what makes `depth` meaningful under a limit: when the walk is\n * truncated, what is missing is the far edge of the graph, not a random subset.\n */\nexport async function buildDependencyGraph(\n roots: readonly string[],\n fetchRelations: (id: string) => Promise<IssueRelations | null>,\n options: BuildDependencyGraphOptions = {},\n): Promise<DependencyGraph> {\n const maxNodes = options.maxNodes ?? DEFAULT_MAX_NODES;\n const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;\n const warn = options.warn ?? ((): void => {});\n\n const known = new Map<string, Issue>();\n for (const issue of options.known ?? []) {\n known.set(issue.id, issue);\n }\n\n const rootIds = uniqueIds(roots);\n const nodes = new Map<string, DependencyGraphNode>();\n const queued = new Set<string>(rootIds);\n let truncated = false;\n\n let frontier: { id: string; depth: number }[] = rootIds.map((id) => ({ id, depth: 0 }));\n\n while (frontier.length > 0) {\n const next: { id: string; depth: number }[] = [];\n\n for (const { id, depth } of frontier) {\n if (nodes.size >= maxNodes) {\n truncated = true;\n break;\n }\n\n let relations: IssueRelations | null = null;\n try {\n relations = await fetchRelations(id);\n } catch (err) {\n warn(\n `Could not read the relations of issue '${id}': ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n let issue = known.get(id) ?? null;\n if (issue === null && options.fetchIssue !== undefined) {\n try {\n issue = await options.fetchIssue(id);\n } catch (err) {\n warn(`Could not read issue '${id}': ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n const resolved = relations ?? emptyRelations(id);\n nodes.set(id, { id, relations: resolved, depth, issue, root: rootIds.includes(id) });\n\n if (depth >= maxDepth) {\n // Only a node that actually has unexplored neighbours truncates the\n // graph; a leaf at max depth explored everything there was.\n if (neighbours(resolved).some((neighbour) => !queued.has(neighbour))) {\n truncated = true;\n }\n continue;\n }\n\n for (const neighbour of neighbours(resolved)) {\n if (queued.has(neighbour)) continue;\n queued.add(neighbour);\n next.push({ id: neighbour, depth: depth + 1 });\n }\n }\n\n if (nodes.size >= maxNodes && next.length > 0) {\n truncated = true;\n break;\n }\n frontier = next;\n }\n\n const graph: DependencyGraph = {\n roots: rootIds,\n nodes,\n cycles: [],\n truncated,\n limits: { maxNodes, maxDepth },\n };\n\n linkReferencedBy(graph);\n graph.cycles = findCycles(graph);\n\n return graph;\n}\n\n/**\n * Complete `referencedBy` with the inverse of `references` among the discovered\n * nodes.\n *\n * The origin only reports the citations it can see (GitHub's timeline covers\n * cross-references but not, for instance, a mention added by the textual\n * heuristic); inverting what the graph already holds costs no API call and\n * keeps the two directions consistent inside it.\n */\nfunction linkReferencedBy(graph: DependencyGraph): void {\n for (const node of graph.nodes.values()) {\n for (const cited of node.relations.references) {\n const target = graph.nodes.get(cited);\n if (target === undefined || target.id === node.id) continue;\n target.relations.referencedBy = uniqueIds(\n [...target.relations.referencedBy, node.id],\n target.id,\n );\n }\n }\n}\n","import { compareIds, type DependencyGraph, dependencyEdges, findCycles } from '../issues/graph.js';\nimport type { IssuePriority } from './types.js';\n\n/**\n * Turning a dependency graph into the order the queue runs in.\n *\n * The criteria are applied in the exact precedence the initiative specifies:\n *\n * 1. **dependencies and blocks** — hard constraints; an Issue never starts\n * before something it depends on has finished. This is the topological part\n * and the only one that can make the computation fail (a cycle);\n * 2. **hierarchy** — with two Issues both ready to run, the ancestor goes\n * first, so an umbrella Issue frames the work of its sub-issues;\n * 3. **priority** — the GitHub labels `high`, `medium` and `low`;\n * 4. **logical order** — the identifier, numerically when it is a number.\n *\n * Everything below the first item is a *tie-break among Issues that are already\n * free to run*, never a constraint: a `high` Issue that depends on a `low` one\n * still runs second, which is the whole point of the feature.\n */\n\nconst PRIORITY_LABELS: Record<string, IssuePriority> = {\n high: 'high',\n medium: 'medium',\n low: 'low',\n};\n\n/** Ranking of a priority; an Issue with no label sorts after every labelled one. */\nconst PRIORITY_RANK: Record<IssuePriority, number> = { high: 0, medium: 1, low: 2 };\nconst NO_PRIORITY_RANK = 3;\n\n/**\n * Priority carried by a label set, or `null` when there is none.\n *\n * Both the bare labels used by this repository (`high`) and the qualified\n * spellings other projects use (`priority: high`, `priority/high`, `P-high`)\n * are accepted; anything else is simply not a priority. The absence of a label\n * is never an error — most Issues have none.\n */\nexport function priorityOf(labels: readonly string[]): IssuePriority | null {\n for (const label of labels) {\n const normalized = label.trim().toLowerCase();\n const direct = PRIORITY_LABELS[normalized];\n if (direct !== undefined) return direct;\n\n const qualified = /^(?:priority|prio|p)\\s*[:/-]\\s*(high|medium|low)$/.exec(normalized);\n const matched = qualified?.[1];\n if (matched !== undefined) return PRIORITY_LABELS[matched] as IssuePriority;\n }\n return null;\n}\n\nexport interface ExecutionOrderOptions {\n /**\n * Subset of the graph to schedule. Defaults to every node — the \"run the\n * whole hierarchy\" answer; the \"only the ones I asked for\" answer passes the\n * requested ids.\n */\n include?: readonly string[];\n}\n\nexport type ExecutionOrderResult =\n | { ok: true; order: string[] }\n | { ok: false; cycles: string[][] };\n\n/** Number of ancestors of `id` inside the selection: 0 for a top-level Issue. */\nfunction hierarchyDepth(\n id: string,\n parentOf: Map<string, string | null>,\n selected: Set<string>,\n): number {\n let depth = 0;\n let current = parentOf.get(id) ?? null;\n const seen = new Set<string>([id]);\n\n while (current !== null && selected.has(current) && !seen.has(current)) {\n depth++;\n seen.add(current);\n current = parentOf.get(current) ?? null;\n }\n\n return depth;\n}\n\n/**\n * Order the selected Issues of a graph.\n *\n * Kahn's algorithm with a deterministic tie-break, so the same graph always\n * produces the same order — a plan the user confirmed must not reshuffle itself\n * on the next run.\n *\n * A cycle is reported, never worked around: an arbitrary order would silently\n * implement an Issue before its dependency, which is exactly the failure this\n * whole feature exists to prevent. Constraints pointing outside the selection\n * are dropped — an Issue that is not going to run cannot block anything.\n */\nexport function computeExecutionOrder(\n graph: DependencyGraph,\n options: ExecutionOrderOptions = {},\n): ExecutionOrderResult {\n const selected = new Set(\n (options.include ?? [...graph.nodes.keys()]).filter((id) => graph.nodes.has(id)),\n );\n\n const parentOf = new Map<string, string | null>();\n const priority = new Map<string, IssuePriority | null>();\n for (const id of selected) {\n const node = graph.nodes.get(id);\n parentOf.set(id, node?.relations.parent ?? null);\n priority.set(id, priorityOf(node?.issue?.labels ?? []));\n }\n\n const incoming = new Map<string, Set<string>>();\n const outgoing = new Map<string, string[]>();\n for (const id of selected) {\n incoming.set(id, new Set());\n outgoing.set(id, []);\n }\n for (const { from, to } of dependencyEdges(graph)) {\n if (!selected.has(from) || !selected.has(to) || from === to) continue;\n incoming.get(to)?.add(from);\n outgoing.get(from)?.push(to);\n }\n\n const depth = new Map<string, number>();\n for (const id of selected) {\n depth.set(id, hierarchyDepth(id, parentOf, selected));\n }\n\n const rank = (id: string): number => {\n const value = priority.get(id) ?? null;\n return value === null ? NO_PRIORITY_RANK : PRIORITY_RANK[value];\n };\n\n const better = (a: string, b: string): number => {\n const byHierarchy = (depth.get(a) ?? 0) - (depth.get(b) ?? 0);\n if (byHierarchy !== 0) return byHierarchy;\n const byPriority = rank(a) - rank(b);\n if (byPriority !== 0) return byPriority;\n return compareIds(a, b);\n };\n\n const ready = [...selected].filter((id) => (incoming.get(id)?.size ?? 0) === 0);\n const order: string[] = [];\n\n while (ready.length > 0) {\n ready.sort(better);\n const next = ready.shift() as string;\n order.push(next);\n\n for (const dependent of outgoing.get(next) ?? []) {\n const pending = incoming.get(dependent);\n if (pending === undefined) continue;\n pending.delete(next);\n if (pending.size === 0) {\n ready.push(dependent);\n }\n }\n }\n\n if (order.length !== selected.size) {\n // Prefer the cycles the graph already recorded; recompute only if the\n // selection is what closed the loop.\n const cycles = graph.cycles.length > 0 ? graph.cycles : findCycles(graph);\n const relevant = cycles.filter((cycle) => cycle.every((id) => selected.has(id)));\n return { ok: false, cycles: relevant.length > 0 ? relevant : cycles };\n }\n\n return { ok: true, order };\n}\n\n/** One-line description of the cycles, for an error message. */\nexport function describeCycles(cycles: readonly string[][]): string {\n return cycles.map((cycle) => [...cycle, cycle[0]].map((id) => `#${id}`).join(' → ')).join('; ');\n}\n","import { createInterface } from 'node:readline';\nimport { printInfo, printWarning } from '../ui/logger.js';\nimport type { ExecutionPlan, ExecutionPlanIssue } from './types.js';\n\n/**\n * The one place a multi-issue run stops and asks.\n *\n * Discovery may well turn `issue-flow run 50` into four Issues; implementing\n * them without saying so would be the worst possible outcome of this feature.\n * So the pipeline halts **before** any phase runs, prints what it found and\n * lets the user pick the scope.\n */\n\n/** What the user decided about the discovered hierarchy. */\nexport type QueueChoice = 'requested' | 'all' | 'cancel';\n\n/** How many invalid answers are tolerated before the prompt gives up. */\nconst MAX_PROMPT_ATTEMPTS = 3;\n\n/** A confirmation that cannot be asked for and was not answered by a flag. */\nexport class QueueConfirmationError extends Error {\n readonly exitCode: number;\n\n constructor(message: string, exitCode = 1) {\n super(message);\n this.name = 'QueueConfirmationError';\n this.exitCode = exitCode;\n }\n}\n\nexport interface ConfirmQueueOptions {\n /** `--yes`: accept the suggested plan (the whole hierarchy) without asking. */\n yes?: boolean;\n /** `--only`: run just the requested Issues, without asking. */\n only?: boolean;\n /**\n * Whether a prompt may be shown. Defaults to a real TTY on both ends outside\n * CI, mirroring `pr-review`'s discovery confirmation.\n */\n interactive?: boolean;\n /**\n * Whether the user asked for a single Issue. Only the hierarchy around it is\n * up for confirmation, so a non-interactive run falls back to that Issue\n * alone instead of failing: running exactly what was asked for can never\n * implement something nobody approved.\n */\n singleRequest?: boolean;\n stdin?: NodeJS.ReadableStream;\n stdout?: NodeJS.WritableStream;\n info?: (message: string) => void;\n warn?: (message: string) => void;\n}\n\nfunction isInteractiveByDefault(): boolean {\n const ci = process.env.CI;\n const inCi = ci !== undefined && ci !== '' && ci !== '0' && ci.toLowerCase() !== 'false';\n return Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY) && !inCi;\n}\n\n/** `#50` for a numeric identifier, `auth-refactor` for anything else. */\nexport function issueLabel(entry: { id: string; number: number | null }): string {\n return entry.number === null ? entry.id : `#${entry.number}`;\n}\n\n/** Why this Issue is in the queue, in a few words. */\nfunction reasonFor(entry: ExecutionPlanIssue): string {\n const parts: string[] = [];\n if (entry.origin === 'requested') {\n parts.push('requested');\n }\n if (entry.dependsOn.length > 0) {\n parts.push(`after ${entry.dependsOn.map((id) => `#${id}`).join(', ')}`);\n }\n if (entry.parent !== null) {\n parts.push(`sub-issue of #${entry.parent}`);\n }\n if (entry.priority !== null) {\n parts.push(entry.priority);\n }\n return parts.join(', ');\n}\n\n/**\n * The summary printed before the prompt.\n *\n * Returned as lines rather than printed so the shape is testable without\n * capturing stdout — the same split `buildRunSummaryLines` uses.\n */\nexport function buildQueueSummaryLines(plan: ExecutionPlan): string[] {\n const primary = plan.issues.find((entry) => entry.id === plan.id) ?? plan.issues[0];\n const lines: string[] = [];\n\n if (primary !== undefined) {\n lines.push(` Main issue: ${issueLabel(primary)}${primary.title ? ` ${primary.title}` : ''}`);\n }\n lines.push(` Total issues: ${plan.issues.length}`);\n lines.push(' Suggested order:');\n\n for (const entry of plan.issues) {\n const reason = reasonFor(entry);\n const flag = entry.heuristic ? ' ~' : '';\n const title = entry.title === '' ? '' : ` ${entry.title}`;\n lines.push(\n ` ${entry.position}. ${issueLabel(entry)}${title}${reason === '' ? '' : ` (${reason})`}${flag}`,\n );\n }\n\n if (plan.issues.some((entry) => entry.heuristic)) {\n lines.push(' ~ relation found only in the issue text, which may be a false positive');\n }\n if (plan.truncated) {\n lines.push(' Note: discovery hit its limit — the hierarchy may be larger than shown');\n }\n\n return lines;\n}\n\n/**\n * Ask, in an interactive terminal, which scope to run.\n *\n * Outside a TTY the answer must come from a flag: `--yes` for the whole\n * hierarchy, `--only` for just what was asked for. Neither is a *default* —\n * silently picking one would either implement Issues nobody approved or ignore\n * a dependency the user was never told about. The command fails instead, which\n * is what keeps an unattended CI run honest.\n */\nexport async function confirmQueue(\n plan: ExecutionPlan,\n requestedOnlyCount: number,\n options: ConfirmQueueOptions = {},\n): Promise<QueueChoice> {\n const info = options.info ?? printInfo;\n const warn = options.warn ?? printWarning;\n\n info(`Issue ${plan.requested.map((id) => `#${id}`).join(', ')} is part of a larger structure:`);\n for (const line of buildQueueSummaryLines(plan)) {\n (options.stdout ?? process.stdout).write(`${line}\\n`);\n }\n\n if (options.only === true) {\n info(`--only: running just the ${requestedOnlyCount} issue(s) you asked for.`);\n return 'requested';\n }\n if (options.yes === true) {\n info(`--yes: running the whole hierarchy (${plan.issues.length} issues).`);\n return 'all';\n }\n\n const interactive = options.interactive ?? isInteractiveByDefault();\n if (!interactive && options.singleRequest === true) {\n warn(\n 'A larger structure was found, but the terminal is not interactive: running just ' +\n `issue ${plan.requested.map((id) => `#${id}`).join(', ')}. ` +\n 'Re-run with --yes to execute the whole hierarchy.',\n );\n return 'requested';\n }\n if (!interactive) {\n throw new QueueConfirmationError(\n 'This run involves more than one issue and the terminal is not interactive. ' +\n 'Re-run with --yes to execute the whole hierarchy, or --only to execute just the ' +\n 'issues you informed.',\n );\n }\n\n const query =\n `Which scope should run? [1] Only the issues informed (${requestedOnlyCount}) ` +\n `[2] The whole hierarchy (${plan.issues.length}) [3] Cancel: `;\n\n return await ask(query, options.stdin ?? process.stdin, options.stdout ?? process.stdout, warn);\n}\n\n/**\n * Numbered prompt, sharing the queued-lines discipline of `issues/resolver.ts`:\n * `rl.question` drops input that arrives before the prompt is written, which\n * silently loses a piped answer. EOF answers `cancel` — never consent.\n */\nasync function ask(\n query: string,\n stdin: NodeJS.ReadableStream,\n stdout: NodeJS.WritableStream,\n warn: (message: string) => void,\n): Promise<QueueChoice> {\n const rl = createInterface({ input: stdin, output: stdout });\n const queued: string[] = [];\n let pending: ((line: string | null) => void) | null = null;\n let closed = false;\n\n rl.on('line', (line: string) => {\n if (pending !== null) {\n const resolve = pending;\n pending = null;\n resolve(line);\n return;\n }\n queued.push(line);\n });\n rl.on('close', () => {\n closed = true;\n if (pending !== null) {\n const resolve = pending;\n pending = null;\n resolve(null);\n }\n });\n\n const read = (): Promise<string | null> =>\n new Promise<string | null>((resolve) => {\n const buffered = queued.shift();\n if (buffered !== undefined) {\n resolve(buffered);\n return;\n }\n if (closed) {\n resolve(null);\n return;\n }\n stdout.write(query);\n pending = resolve;\n });\n\n try {\n for (let attempt = 0; attempt < MAX_PROMPT_ATTEMPTS; attempt++) {\n const answer = await read();\n if (answer === null) {\n return 'cancel';\n }\n const choice = answer.trim();\n if (choice === '1') return 'requested';\n // An empty answer accepts the suggestion, which is the whole point of\n // having computed an order: the same convention as `pr-review`'s (Y/n).\n if (choice === '' || choice === '2') return 'all';\n if (choice === '3') return 'cancel';\n warn(`Invalid choice: '${choice}'. Enter 1, 2 or 3.`);\n }\n return 'cancel';\n } finally {\n rl.close();\n }\n}\n","import {\n type BuildDependencyGraphOptions,\n buildDependencyGraph,\n type DependencyGraph,\n} from '../issues/graph.js';\nimport { getProvider } from '../issues/registry.js';\nimport type { Issue, IssueRelations, IssueSource } from '../issues/types.js';\n\n/**\n * Bridge between the Issue providers and the dependency graph.\n *\n * Discovery is a *best-effort enrichment* of a run, never a prerequisite: an\n * origin with no `fetchRelations`, an unregistered provider or a failing API all\n * degrade to \"this issue relates to nothing\", which is exactly the single-issue\n * pipeline that existed before. That is what makes the feature additive.\n */\n\nexport interface DiscoverGraphOptions extends BuildDependencyGraphOptions {\n /** Origin to ask about relations. Defaults to the first known Issue's source. */\n source?: IssueSource;\n}\n\n/** Whether an origin can answer questions about related Issues at all. */\nexport function supportsRelations(source: IssueSource): boolean {\n try {\n return typeof getProvider(source).fetchRelations === 'function';\n } catch {\n return false;\n }\n}\n\n/**\n * Walk the hierarchy of `roots` through the provider of `source`.\n *\n * Discovered Issues are read from the same origin: a queue is resolved against\n * one provider, so an id found in a GitHub hierarchy is a GitHub Issue. The\n * Issues the caller already resolved are passed through `known`, so the roots\n * never cost a second round-trip.\n */\nexport async function discoverIssueGraph(\n roots: readonly string[],\n options: DiscoverGraphOptions = {},\n): Promise<DependencyGraph> {\n const source = options.source ?? [...(options.known ?? [])][0]?.source ?? 'github';\n\n let fetchRelations: ((id: string) => Promise<IssueRelations | null>) | null = null;\n let fetchIssue: ((id: string) => Promise<Issue | null>) | null = null;\n try {\n const provider = getProvider(source);\n const relations = provider.fetchRelations?.bind(provider);\n if (relations !== undefined) {\n fetchRelations = (id) => relations(id);\n fetchIssue = (id) => provider.get(id);\n }\n } catch {\n // Unknown origin: nothing to discover, and the run continues single-issue.\n }\n\n if (fetchRelations === null) {\n return buildDependencyGraph(roots, async () => null, { ...options, fetchIssue: undefined });\n }\n\n return buildDependencyGraph(roots, fetchRelations, {\n ...options,\n fetchIssue: options.fetchIssue ?? fetchIssue ?? undefined,\n });\n}\n","import type { Issue, IssueSource } from '../issues/types.js';\nimport { printInfo, printWarning } from '../ui/logger.js';\nimport { type ConfirmQueueOptions, confirmQueue, QueueConfirmationError } from './confirm.js';\nimport { discoverIssueGraph, supportsRelations } from './discovery.js';\nimport { computeExecutionOrder, describeCycles } from './order.js';\nimport {\n buildExecutionPlan,\n isQueueComplete,\n loadExecutionPlan,\n saveExecutionPlan,\n} from './plan.js';\nimport type { ExecutionPlan } from './types.js';\n\n/**\n * Deciding, before any phase runs, whether this invocation is a single-issue\n * pipeline or a queue — and, when it is a queue, what exactly it will run.\n *\n * Everything here happens *before* the session starts, so a run that turns out\n * to be a plain single-issue pipeline is indistinguishable from what the CLI\n * did before this feature existed: no prompt, no artifact, no extra output.\n */\n\nexport type QueueDecision =\n /** Nothing to coordinate: run the single issue exactly as before. */\n | { kind: 'single' }\n | { kind: 'queue'; plan: ExecutionPlan; resumed: boolean }\n /** The run must stop; `code` is what the CLI returns. */\n | { kind: 'stop'; code: number };\n\nexport interface PlanQueueInput {\n /** Identifiers the user asked for, in order. */\n requested: string[];\n /** Origin the run resolved, used to ask the right provider about relations. */\n source: IssueSource;\n /** Issues already resolved by the caller, so the roots are not re-read. */\n known: Issue[];\n projectId: string;\n /** Where the queue's `execution-plan.json` lives. */\n planFile: string;\n noBranch: boolean;\n prReview: boolean;\n /** `--yes` / `--only`, forwarded to the confirmation. */\n confirm?: ConfirmQueueOptions;\n info?: (message: string) => void;\n warn?: (message: string) => void;\n}\n\n/** A persisted queue that matches this invocation, or `null`. */\nasync function loadResumableQueue(\n planFile: string,\n requested: readonly string[],\n): Promise<ExecutionPlan | null> {\n let plan: ExecutionPlan;\n try {\n plan = await loadExecutionPlan(planFile);\n } catch {\n // Absent (the common case) or unreadable: plan the queue from scratch. A\n // corrupted file must not strand the user — the plan is derived state.\n return null;\n }\n\n // The stored queue answers a different question than the one being asked.\n const sameRequest =\n plan.requested.length === requested.length &&\n plan.requested.every((id, index) => id === requested[index]);\n\n return sameRequest ? plan : null;\n}\n\n/**\n * Work out what this invocation runs.\n *\n * The order of the checks is what keeps the feature additive:\n *\n * 1. an unfinished queue for the very same request is resumed, never\n * re-confirmed — the user already answered that question;\n * 2. `--only` with a single issue skips discovery altogether, so an automation\n * that knows what it wants pays nothing for the feature;\n * 3. discovery runs; if it finds nothing beyond the requested issue, the run\n * stays single-issue and no artifact is created;\n * 4. only a genuinely larger structure reaches the confirmation.\n */\nexport async function planQueue(input: PlanQueueInput): Promise<QueueDecision> {\n const info = input.info ?? printInfo;\n const warn = input.warn ?? printWarning;\n const single = input.requested.length === 1;\n\n const resumable = await loadResumableQueue(input.planFile, input.requested);\n // A queue that already finished has nothing left to run, and re-planning it\n // would overwrite its `execution-plan.json` — losing the Pull Request and\n // the record of what ran. Report it and stop successfully instead.\n if (resumable !== null && isQueueComplete(resumable)) {\n const pr = resumable.pullRequest?.url;\n info(\n `The execution queue of issue #${resumable.id} is already complete ` +\n `(${resumable.issues.length} issues)${pr === undefined ? '' : ` — ${pr}`}. Nothing to run.`,\n );\n return { kind: 'stop', code: 0 };\n }\n if (resumable !== null) {\n const done = resumable.issues.filter((entry) => entry.status === 'completed').length;\n info(\n `Resuming the execution queue of issue #${resumable.id}: ` +\n `${done}/${resumable.issues.length} issues already completed.`,\n );\n return { kind: 'queue', plan: resumable, resumed: true };\n }\n\n // `--only` on a single issue is the explicit \"do not look around\" answer.\n if (single && input.confirm?.only === true) {\n return { kind: 'single' };\n }\n\n if (single && !supportsRelations(input.source)) {\n // An origin with no notion of related Issues (the local provider) can only\n // ever produce a single-issue run.\n return { kind: 'single' };\n }\n\n const graph = await discoverIssueGraph(input.requested, {\n source: input.source,\n known: input.known,\n warn,\n });\n\n if (single && graph.nodes.size <= 1) {\n return { kind: 'single' };\n }\n\n const suggested = computeExecutionOrder(graph);\n if (!suggested.ok) {\n // A cycle anywhere in the discovered hierarchy must not take down a run\n // the user asked for on a single issue: they never opted into the\n // hierarchy, so degrade to the pipeline that has always worked and say\n // why. Only an explicitly multi-issue request is refused outright.\n if (single) {\n warn(\n `Dependency cycle between issues: ${describeCycles(suggested.cycles)}. ` +\n 'Running just the issue you informed; fix the dependencies on GitHub to run the hierarchy.',\n );\n return { kind: 'single' };\n }\n warn(\n `Dependency cycle between issues: ${describeCycles(suggested.cycles)}. ` +\n 'Fix the dependencies on GitHub, or re-run with --only to execute just the issues you informed.',\n );\n return { kind: 'stop', code: 1 };\n }\n\n const requestedOrder = computeExecutionOrder(graph, { include: input.requested });\n const requestedCount = requestedOrder.ok ? requestedOrder.order.length : input.requested.length;\n\n const suggestedPlan = buildExecutionPlan({\n projectId: input.projectId,\n requested: input.requested,\n graph,\n order: suggested.order,\n noBranch: input.noBranch,\n prReview: input.prReview,\n });\n\n let choice: 'all' | 'requested' | 'cancel';\n try {\n choice = await confirmQueue(suggestedPlan, requestedCount, {\n ...(input.confirm ?? {}),\n singleRequest: single,\n });\n } catch (err) {\n if (err instanceof QueueConfirmationError) {\n warn(err.message);\n return { kind: 'stop', code: err.exitCode };\n }\n throw err;\n }\n\n if (choice === 'cancel') {\n info('Cancelled: nothing was executed.');\n return { kind: 'stop', code: 1 };\n }\n\n let plan = suggestedPlan;\n if (choice === 'requested') {\n if (!requestedOrder.ok) {\n warn(\n `Dependency cycle between the issues you informed: ${describeCycles(requestedOrder.cycles)}.`,\n );\n return { kind: 'stop', code: 1 };\n }\n // A single issue after trimming is not a queue at all: fall back to the\n // untouched single-issue pipeline instead of creating an artifact for it.\n // But that fallback only makes sense when one issue was requested — with\n // several, trimming to one means the others never made it into the graph\n // (unreadable issue, node limit), and silently running a subset of what\n // the user asked for is worse than stopping.\n if (requestedOrder.order.length <= 1) {\n if (input.requested.length > 1) {\n const missing = input.requested.filter((id) => !requestedOrder.order.includes(id));\n warn(\n `Only issue #${requestedOrder.order[0] ?? '—'} of the ones you informed could be read; ` +\n `${missing.map((id) => `#${id}`).join(', ')} did not make it into the dependency graph. ` +\n 'Nothing was executed — check the identifiers and try again.',\n );\n return { kind: 'stop', code: 1 };\n }\n return { kind: 'single' };\n }\n plan = buildExecutionPlan({\n projectId: input.projectId,\n requested: input.requested,\n graph,\n order: requestedOrder.order,\n noBranch: input.noBranch,\n prReview: input.prReview,\n });\n }\n\n await saveExecutionPlan(input.planFile, plan);\n return { kind: 'queue', plan, resumed: false };\n}\n","/**\n * Parsing of the issue identifiers a command receives on the command line.\n *\n * `run` accepts both spellings of the same list — `42,43,50` and `42 43 50` —\n * because both are what a user naturally types, and commander gives them to us\n * as different shapes (one argument with commas, or several arguments). Normal-\n * izing here keeps every downstream layer working on a plain `string[]`.\n */\n\n/** Malformed issue arguments, reported as a CLI error. */\nexport class IssueArgumentError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'IssueArgumentError';\n }\n}\n\n/**\n * Rejected before an identifier ever becomes a path segment or an API call.\n * Mirrors `normalizeIssueNumber` in `storage/paths.ts`: the storage layer would\n * refuse these anyway, and failing here names the offending argument instead.\n */\nconst INVALID_ID = /[/\\\\\\s]/;\n\n/**\n * Normalize the positional arguments of `run` into a list of issue identifiers.\n *\n * - splits on commas, so `42,43` and `42 43` produce the same list;\n * - strips a leading `#` and surrounding whitespace;\n * - preserves the order the user typed, which is what a tie-break falls back to;\n * - drops exact duplicates silently (`42 42` is a typo with an obvious intent),\n * but rejects an empty or malformed identifier, which is not.\n *\n * @throws IssueArgumentError when no identifier survives or one is malformed.\n */\nexport function parseIssueArguments(values: readonly string[]): string[] {\n const ids: string[] = [];\n const seen = new Set<string>();\n\n for (const value of values) {\n for (const raw of value.split(',')) {\n const id = raw.trim().replace(/^#/, '').trim();\n if (id === '') {\n // An empty slot is always a typo: `42,,43` or a stray comma.\n if (raw.trim() !== '' || value.includes(',')) {\n throw new IssueArgumentError(\n `Invalid issue list '${value}': it has an empty entry. Use '42,43' or '42 43'.`,\n );\n }\n continue;\n }\n if (INVALID_ID.test(id) || id === '.' || id === '..') {\n throw new IssueArgumentError(`Invalid issue identifier '${raw.trim()}'.`);\n }\n if (seen.has(id)) continue;\n seen.add(id);\n ids.push(id);\n }\n }\n\n if (ids.length === 0) {\n throw new IssueArgumentError('No issue was informed. Pass at least one issue number.');\n }\n\n return ids;\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 {\n setOutputCallback,\n setStoryStageCallback,\n setStoryUpdateCallback,\n} 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 * Plain subtask title for a story, optionally suffixed to show it is the one\n * `execute` is currently working on. Exported standalone for testing without\n * spinning up a `listr2` renderer.\n */\nexport function storySubtaskTitle(\n story: Pick<UserStory, 'id' | 'title'>,\n executing: boolean,\n): string {\n const base = `${story.id}: ${story.title}`;\n return executing ? `${base} → Executando...` : base;\n}\n\n/** Minimal view of a listr2 task the tracker needs: a writable title. */\ninterface TitledTask {\n title: string;\n}\n\nexport interface ActiveStoryTracker {\n /** The story `execute` is now working on, or `undefined` for none. */\n setActive(storyId: string | undefined): void;\n /** Nothing is executing any more — drop the suffix from whoever holds it. */\n clear(): void;\n}\n\n/**\n * Keeps the \"executing\" suffix on exactly one story subtask at a time.\n *\n * `clear()` matters as much as `setActive()`: the engine leaves its loop the\n * moment the last story passes, so no further `iteration:start` ever arrives\n * to move the suffix off the story that just finished. Without an explicit\n * clear at the end of the phase, the terminal would keep showing a completed\n * story as executing while the web panel already shows it awaiting review.\n */\nexport function createActiveStoryTracker(\n stories: ReadonlyArray<Pick<UserStory, 'id' | 'title'>>,\n storyTasks: ReadonlyMap<string, TitledTask>,\n): ActiveStoryTracker {\n let activeStoryId: string | undefined;\n\n const retitle = (storyId: string, executing: boolean): void => {\n const task = storyTasks.get(storyId);\n if (!task) return;\n task.title = storySubtaskTitle(\n stories.find((s) => s.id === storyId) ?? { id: storyId, title: '' },\n executing,\n );\n };\n\n return {\n setActive(storyId: string | undefined): void {\n if (activeStoryId !== undefined && activeStoryId !== storyId) {\n retitle(activeStoryId, false);\n }\n activeStoryId = storyId;\n if (storyId !== undefined) retitle(storyId, true);\n },\n clear(): void {\n if (activeStoryId === undefined) return;\n retitle(activeStoryId, false);\n activeStoryId = undefined;\n },\n };\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 // Own listr2 task handle per story subtask, so the story-stage callback\n // below can update a title after the subtask has already started — the\n // resolvers map alone has no reference back into the renderer.\n const storyTasks = new Map<string, TaskContext>();\n const tracker = createActiveStoryTracker(stories, storyTasks);\n\n // Engine calls this alongside every iteration:start, with the same\n // storyId the published event and the session snapshot carry — never a\n // second, independently computed \"who is active\" heuristic. Only ever\n // meaningful for a story still pending (an already-passing one has no\n // subtask left to update: it already resolved and disappeared from view).\n setStoryStageCallback((storyId: string | undefined) => {\n tracker.setActive(storyId);\n });\n\n // Build subtask definitions for each story\n const subtaskDefs = stories.map((story) => ({\n title: storySubtaskTitle(story, false),\n skip: story.passes ? 'already passing' : false,\n task: async (_c: unknown, subTask: TaskContext) => {\n storyTasks.set(story.id, subTask);\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 setStoryStageCallback(undefined);\n // The engine is done — nothing is executing any more, whether it\n // finished the plan or failed mid-story.\n tracker.clear();\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 setStoryStageCallback(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":["mkdir","failure","mkdir"]}
@@ -4,20 +4,20 @@ import {
4
4
  getWebLockFile,
5
5
  readWebLock,
6
6
  removeWebLock
7
- } from "./chunk-JOK7VXRV.js";
7
+ } from "./chunk-VNKSMWDN.js";
8
8
  import "./chunk-SLHZPB3C.js";
9
9
  import {
10
10
  ISSUES_DIR_NAME,
11
11
  PROJECTS_DIR_NAME,
12
12
  getGlobalRoot,
13
13
  loadWebConfig
14
- } from "./chunk-RUTDK6ZF.js";
14
+ } from "./chunk-MTJQOOMG.js";
15
15
  import {
16
16
  printError,
17
17
  printInfo,
18
18
  printWarning,
19
19
  sessionSnapshotSchema
20
- } from "./chunk-WO6ERABE.js";
20
+ } from "./chunk-57MH6RYJ.js";
21
21
 
22
22
  // src/web/session-directory.ts
23
23
  import { readdir, readFile, stat as statFile } from "fs/promises";
@@ -185,4 +185,4 @@ export {
185
185
  runWebServe,
186
186
  runWebStop
187
187
  };
188
- //# sourceMappingURL=web-3SBYL7QJ.js.map
188
+ //# sourceMappingURL=web-WQISLO66.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "issue-flow",
3
- "version": "0.7.7",
3
+ "version": "0.9.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,7 +12,7 @@ You are an autonomous coding agent working on a software project.
12
12
  6. Implement that single user story
13
13
  7. Run quality checks (e.g., typecheck, lint, test - use whatever your project requires)
14
14
  8. Update CLAUDE.md files if you discover reusable patterns (see below)
15
- 9. If checks pass, commit ALL changes with message: `feat: [Story ID] - [Story Title]`
15
+ 9. If checks pass, commit ALL changes with message: `__COMMIT_MESSAGE__`
16
16
  10. Update the PRD to set `passes: true` for the completed story
17
17
  11. Append your progress to `__PROGRESS_FILE__`
18
18
 
@@ -23,7 +23,7 @@ A prior automated review may have already run against this same code and found i
23
23
  - Treat it as **the** priority for this iteration, ahead of any story whose `passes` is still `false`.
24
24
  - The findings describe concrete defects in code that was already implemented and marked `passes: true` — they are not a new story. Do **not** re-implement stories from scratch; make the smallest correct change that addresses every finding.
25
25
  - Run the project's quality checks (typecheck, lint, test) to confirm the fix, exactly as for a normal story.
26
- - Commit the fix with message `fix: address review findings` (or a more specific message naming what was fixed).
26
+ - Commit the fix with message `__FIX_COMMIT_MESSAGE__` (or a more specific message naming what was fixed, keeping the same prefix).
27
27
  - Once every finding has been addressed, set `lastReviewFindings` back to `null` in the PRD before finishing this iteration. Leaving it non-null means the orchestrator will treat the issue as still uncorrected and loop again.
28
28
  - If a finding turns out to be a false positive or already fixed, still clear `lastReviewFindings` to `null`, but say so explicitly in the progress log entry so a human can double-check.
29
29
  - If you cannot fully resolve the findings in this iteration, leave `lastReviewFindings` as-is (do not clear it) and record what you did and what remains in the progress log — the orchestrator will invoke another iteration.
package/prompts/plan.md CHANGED
@@ -17,6 +17,13 @@ Here is the PRD:
17
17
 
18
18
  __PRD_CONTENT__
19
19
 
20
+ The next available User Story number for this project is __NEXT_US_NUMBER__. This
21
+ was already resolved by the CLI (from the project's numbering history, or from an
22
+ explicit --continue / --start-us override) — do NOT restart numbering at US-001
23
+ unless __NEXT_US_NUMBER__ is itself US-001. Number the first story in this plan
24
+ __NEXT_US_NUMBER__ and every subsequent story sequentially from there (e.g. if
25
+ __NEXT_US_NUMBER__ is US-016, use US-016, US-017, US-018, ...).
26
+
20
27
  Create a tasks.json file at __TASKS_PATH__ with this exact structure:
21
28
 
22
29
  {
@@ -42,7 +49,7 @@ Create a tasks.json file at __TASKS_PATH__ with this exact structure:
42
49
  },
43
50
  "userStories": [
44
51
  {
45
- "id": "US-001",
52
+ "id": "__NEXT_US_NUMBER__",
46
53
  "title": "...",
47
54
  "description": "As a ..., I want ... so that ...",
48
55
  "acceptanceCriteria": ["..."],
@@ -56,6 +63,8 @@ Create a tasks.json file at __TASKS_PATH__ with this exact structure:
56
63
  Rules:
57
64
  - Each user story from the PRD becomes one entry in userStories
58
65
  - Priority should order stories by dependency (build foundations first)
66
+ - Story ids MUST start at __NEXT_US_NUMBER__ and increase sequentially — never
67
+ restart at US-001 when __NEXT_US_NUMBER__ says otherwise
59
68
  - acceptanceCriteria must include "Typecheck passes" for code changes
60
69
  - issueUrl is already resolved above: a GitHub URL when the source is `github`,
61
70
  the path of the local issue file when the source is `local`. Use it verbatim
package/prompts/pr.md CHANGED
@@ -25,10 +25,15 @@ The PR should:
25
25
  - Include a test plan
26
26
  - Cite where the demand is described: the reference above (`__ISSUE_URL__`)
27
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
28
+ Issue reference line(s):
29
+ - Include the lines below verbatim in the PR body, each entry on its own line,
30
+ with no surrounding code fence or quoting GitHub only auto-closes an issue
31
+ when the `Closes #N` line is plain body text:
32
+ __ISSUE_CLOSES__
33
+ - When nothing follows the line above, the issue has no GitHub counterpart: do
34
+ NOT invent a "Closes #" reference, and cite `__ISSUE_URL__` in the body instead
35
+
36
+ __MULTI_ISSUE_CONTEXT__
32
37
 
33
38
  Use this command format:
34
39
  gh pr create --title "..." --body "..." --base main
package/web/CLAUDE.md CHANGED
@@ -8,18 +8,80 @@ vitest. Toda verificação de mudança aqui é manual, no navegador.
8
8
 
9
9
  ## Contrato de dados
10
10
 
11
- O painel consome `GET api/status` (o `SessionSnapshot` serializado) e
12
- `GET api/health`. Ele precisa renderizar **session.json de execuções
13
- antigas**, então todo campo pode chegar como `undefined` (não existia na
14
- versão que gravou o arquivo) além do `null` (existe, não informado). Os dois
15
- significam "não informado" e nunca podem virar `0`, `NaN` ou `undefined` na
16
- tela daí o helper `metric()`, que normaliza qualquer coisa que não seja
17
- número finito para `null`. Prefira `x !== null && x !== undefined` a `!x`:
18
- zero é um valor legítimo.
11
+ O painel consome `GET api/sessions` (lista enriquecida para o dashboard),
12
+ `GET api/status` (o `SessionSnapshot` serializado, opcionalmente com
13
+ `?session=<id>`) e `GET api/health`. Ele precisa renderizar **session.json de
14
+ execuções antigas**, então todo campo pode chegar como `undefined` (não
15
+ existia na versão que gravou o arquivo) além do `null` (existe, não
16
+ informado). Os dois significam "não informado" e nunca podem virar `0`,
17
+ `NaN` ou `undefined` na tela daí o helper `metric()`, que normaliza
18
+ qualquer coisa que não seja número finito para `null`. Prefira
19
+ `x !== null && x !== undefined` a `!x`: zero é um valor legítimo.
20
+
21
+ Com **uma** sessão ativa o painel abre direto no detalhe (comportamento
22
+ histórico). Com **duas ou mais**, `renderDashboard()` lista um card por
23
+ execução; o clique define `state.selectedSessionId` e o poll passa a usar o
24
+ `statusUrl` daquela sessão. `selectedSessionId === null` é o modo automático.
25
+ Trocas de sessão (e o modo dashboard) zeram `snapshot`/`etag` via
26
+ `detailSessionId` para não pintar dados da execução anterior. Clique durante
27
+ um poll em andamento marca `pollAgain` em vez de ser descartado.
28
+
29
+ Os cards do dashboard são `<button>` como os do Kanban: só *phrasing content*
30
+ (`<span>`), nunca `<div>`/`<p>` dentro do botão. `issueDescription` em
31
+ `/api/sessions` já vem truncada no servidor (preview); o client ainda aplica
32
+ `truncateText` na renderização.
19
33
 
20
34
  Texto dinâmico sempre via `textContent`/`el()`; nunca `innerHTML` com dados
21
35
  do snapshot.
22
36
 
37
+ ## Abas, Kanban e drawer
38
+
39
+ O painel tem duas abas ("Execução" e "Kanban") e um drawer de detalhes por
40
+ story. Três regras seguram esse conjunto:
41
+
42
+ - **Acesso a story sempre por `getStoryById()` / `getStories()`.** Elas são a
43
+ camada de leitura: normalizam num lugar só o que pode faltar num
44
+ `session.json` antigo (`status` → `'backlog'`, `dependencies`/
45
+ `acceptanceCriteria` → `[]`, `description` → `''`) e são o ponto onde uma
46
+ futura camada de escrita entra. Nenhum consumidor varre `snapshot.stories`
47
+ por conta própria, ou a normalização se espalha.
48
+ - **Estado de UI vive em `state`** (`activeTab`, `selectedStoryId`), junto de
49
+ `logFilter`, nunca em variável solta ou em referência a nó do DOM. O drawer
50
+ guarda o **id** da story, não o card: `render()` recria o Kanban a cada poll,
51
+ então uma referência guardada na abertura apontaria para um nó fora do
52
+ documento. Pelo mesmo motivo o foco volta ao card via
53
+ `[data-story-id="…"]` no momento de fechar.
54
+ - **`renderKanban()` e `renderDrawer()` são chamadas incondicionalmente** dentro
55
+ de `render()`, não ao trocar de aba. Uma aba inativa não pode ficar defasada,
56
+ e é o `renderDrawer()` de cada poll que mantém o drawer aberto em dia (e o
57
+ fecha quando a story some do plano).
58
+
59
+ Cada card do Kanban é um `<button>` — Enter/Espaço e foco saem de graça. Como
60
+ `<button>` só aceita *phrasing content*, todo o conteúdo interno é `<span>` com
61
+ `display: block`/`flex` no CSS; `<p>` ou `<div>` ali dentro é HTML inválido.
62
+
63
+ Detalhes cosméticos que não são cosméticos: `.tab-panel[hidden]` e
64
+ `.drawer[hidden]` precisam de `display: none` explícito, senão o `display:
65
+ grid`/`flex` da regra base vence o atributo `hidden`. E o overlay/drawer ficam
66
+ em `z-index` 20/21 para cobrir o `.banner` de desconexão, que é `sticky` com
67
+ `z-index: 10`.
68
+
69
+ ## Escrita: o que ainda não existe
70
+
71
+ A interface é somente leitura por contrato (`snapshot.readOnly === true`,
72
+ `capabilities: []`), e o servidor não registra nenhuma rota de escrita — o
73
+ comentário em `src/web/server.ts` reserva `POST /api/control/*` para isso. O
74
+ Kanban e o drawer foram desenhados para que a escrita caiba depois sem
75
+ redesenhar a interação: o drawer já é o lugar onde uma story é inspecionada por
76
+ id, e `getStoryById()` já é o único ponto por onde ela é lida.
77
+
78
+ Quando essa etapa chegar, o contrato esperado é: as rotas de escrita passam a
79
+ ser anunciadas em `capabilities` (o client decide o que renderizar a partir
80
+ disso, nunca da versão do servidor), `readOnly` passa a `false`, e cada
81
+ mutação responde com o snapshot atualizado para que a UI não precise adivinhar
82
+ o efeito nem esperar o próximo poll. Enquanto `capabilities` estiver vazio,
83
+ nenhum controle de escrita deve aparecer na tela.
84
+
23
85
  ## Métricas (tokens e custo)
24
86
 
25
87
  `formatUsage()` espelha `formatTokens()` de `src/core/metrics.ts` — mesma