issue-flow 0.7.5 → 0.7.7

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 (71) hide show
  1. package/dist/{analyze-TNFTUMOO.js → analyze-2RVGY5OK.js} +10 -8
  2. package/dist/{analyze-TNFTUMOO.js.map → analyze-2RVGY5OK.js.map} +1 -1
  3. package/dist/{chunk-5IRBHUYO.js → chunk-3E6KASSE.js} +11 -9
  4. package/dist/{chunk-5IRBHUYO.js.map → chunk-3E6KASSE.js.map} +1 -1
  5. package/dist/{chunk-A42R36BX.js → chunk-6IOPYFUM.js} +10 -8
  6. package/dist/{chunk-A42R36BX.js.map → chunk-6IOPYFUM.js.map} +1 -1
  7. package/dist/{chunk-JECNPBBO.js → chunk-6L2BPOE3.js} +11 -9
  8. package/dist/{chunk-JECNPBBO.js.map → chunk-6L2BPOE3.js.map} +1 -1
  9. package/dist/{chunk-DWF46N65.js → chunk-6UTVPKNF.js} +11 -9
  10. package/dist/{chunk-DWF46N65.js.map → chunk-6UTVPKNF.js.map} +1 -1
  11. package/dist/{chunk-6S2XT3JE.js → chunk-EOTULFWV.js} +10 -8
  12. package/dist/{chunk-6S2XT3JE.js.map → chunk-EOTULFWV.js.map} +1 -1
  13. package/dist/{chunk-CD7BMWXW.js → chunk-EYVXLRRD.js} +3 -3
  14. package/dist/{chunk-OE7RYTUT.js → chunk-G37JNQKB.js} +2 -2
  15. package/dist/chunk-JOK7VXRV.js +447 -0
  16. package/dist/chunk-JOK7VXRV.js.map +1 -0
  17. package/dist/{chunk-DF7SUHT5.js → chunk-KHTLN54U.js} +2 -2
  18. package/dist/{chunk-D56VWHSM.js → chunk-OPIDR2DC.js} +2 -2
  19. package/dist/{chunk-5NLLEWJD.js → chunk-RUTDK6ZF.js} +12 -2
  20. package/dist/{chunk-5NLLEWJD.js.map → chunk-RUTDK6ZF.js.map} +1 -1
  21. package/dist/chunk-SLHZPB3C.js +50 -0
  22. package/dist/chunk-SLHZPB3C.js.map +1 -0
  23. package/dist/{chunk-BMRBQ23Q.js → chunk-WGDLUPSF.js} +9 -7
  24. package/dist/{chunk-BMRBQ23Q.js.map → chunk-WGDLUPSF.js.map} +1 -1
  25. package/dist/{chunk-5Q2TMW7K.js → chunk-WO6ERABE.js} +2 -1
  26. package/dist/{chunk-QLVAEDVD.js → chunk-XPW46TI5.js} +3 -3
  27. package/dist/{chunk-I7FTA5FI.js → chunk-ZYBYMEWG.js} +2 -47
  28. package/dist/chunk-ZYBYMEWG.js.map +1 -0
  29. package/dist/cli.js +28 -13
  30. package/dist/cli.js.map +1 -1
  31. package/dist/{config-U3FC4WH6.js → config-OAZG7KIK.js} +3 -3
  32. package/dist/execute-AOEM52CD.js +14 -0
  33. package/dist/{generate-RHPOWGDX.js → generate-BEJF3SVJ.js} +10 -8
  34. package/dist/{generate-RHPOWGDX.js.map → generate-BEJF3SVJ.js.map} +1 -1
  35. package/dist/init-TBC3WI74.js +9 -0
  36. package/dist/plan-YDZYB2BO.js +16 -0
  37. package/dist/pr-SIXMJIZF.js +16 -0
  38. package/dist/pr-review-AY56ZPJR.js +16 -0
  39. package/dist/prd-MBSSBLGO.js +16 -0
  40. package/dist/review-2MDDHZ4X.js +14 -0
  41. package/dist/{run-Z6ZDXZB6.js → run-SEB6F3W4.js} +23 -196
  42. package/dist/run-SEB6F3W4.js.map +1 -0
  43. package/dist/web-3SBYL7QJ.js +188 -0
  44. package/dist/web-3SBYL7QJ.js.map +1 -0
  45. package/package.json +1 -1
  46. package/web/public/app.css +87 -0
  47. package/web/public/app.js +80 -0
  48. package/web/public/index.html +11 -0
  49. package/dist/chunk-I7FTA5FI.js.map +0 -1
  50. package/dist/execute-BNBA77WJ.js +0 -13
  51. package/dist/init-IF72GR7O.js +0 -9
  52. package/dist/plan-FITZZXZQ.js +0 -15
  53. package/dist/pr-GO244D7M.js +0 -15
  54. package/dist/pr-review-XY6VL62M.js +0 -15
  55. package/dist/prd-2Y6RMVTF.js +0 -15
  56. package/dist/review-ITKXJMX3.js +0 -13
  57. package/dist/run-Z6ZDXZB6.js.map +0 -1
  58. /package/dist/{chunk-CD7BMWXW.js.map → chunk-EYVXLRRD.js.map} +0 -0
  59. /package/dist/{chunk-OE7RYTUT.js.map → chunk-G37JNQKB.js.map} +0 -0
  60. /package/dist/{chunk-DF7SUHT5.js.map → chunk-KHTLN54U.js.map} +0 -0
  61. /package/dist/{chunk-D56VWHSM.js.map → chunk-OPIDR2DC.js.map} +0 -0
  62. /package/dist/{chunk-5Q2TMW7K.js.map → chunk-WO6ERABE.js.map} +0 -0
  63. /package/dist/{chunk-QLVAEDVD.js.map → chunk-XPW46TI5.js.map} +0 -0
  64. /package/dist/{config-U3FC4WH6.js.map → config-OAZG7KIK.js.map} +0 -0
  65. /package/dist/{execute-BNBA77WJ.js.map → execute-AOEM52CD.js.map} +0 -0
  66. /package/dist/{init-IF72GR7O.js.map → init-TBC3WI74.js.map} +0 -0
  67. /package/dist/{plan-FITZZXZQ.js.map → plan-YDZYB2BO.js.map} +0 -0
  68. /package/dist/{pr-GO244D7M.js.map → pr-SIXMJIZF.js.map} +0 -0
  69. /package/dist/{pr-review-XY6VL62M.js.map → pr-review-AY56ZPJR.js.map} +0 -0
  70. /package/dist/{prd-2Y6RMVTF.js.map → prd-MBSSBLGO.js.map} +0 -0
  71. /package/dist/{review-ITKXJMX3.js.map → review-2MDDHZ4X.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/prompt-resolver.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n/**\n * Resolve the absolute path to a directory shipped at the package root\n * (e.g. 'prompts', 'web/public'). Works from both source (src/core/) and\n * compiled (dist/) locations by walking up the directory tree until the\n * named folder is found. Returns null when it cannot be located.\n *\n * `startDir` is injectable for tests; it defaults to this module's directory.\n */\nexport function resolvePackageDir(name: string, startDir?: string): string | null {\n let dir = startDir ?? dirname(fileURLToPath(import.meta.url));\n\n for (let i = 0; i < 5; i++) {\n const candidate = join(dir, name);\n if (existsSync(candidate)) {\n return candidate;\n }\n dir = dirname(dir);\n }\n\n return null;\n}\n\nfunction getPromptsDir(): string {\n const dir = resolvePackageDir('prompts');\n if (dir === null) {\n throw new Error(\n 'Could not locate the prompts/ directory. Ensure the package is installed correctly.',\n );\n }\n return dir;\n}\n\n/**\n * Load a prompt template by name from the package's prompts/ directory.\n *\n * @param name - Prompt name without extension (e.g., 'execute', 'analyze')\n * @returns The raw template content with placeholders intact\n */\nexport async function loadPrompt(name: string): Promise<string> {\n const promptsDir = getPromptsDir();\n const filePath = join(promptsDir, `${name}.md`);\n\n try {\n return await readFile(filePath, 'utf-8');\n } catch {\n throw new Error(`Prompt template not found: ${filePath}`);\n }\n}\n\n/**\n * Replace placeholders in a prompt template with actual values.\n *\n * Placeholders use the format __KEY__ (e.g., __ISSUE_NUMBER__, __PRD_FILE__).\n */\nexport function applyPlaceholders(template: string, vars: Record<string, string>): string {\n let result = template;\n for (const [key, value] of Object.entries(vars)) {\n result = result.replaceAll(key, value);\n }\n return result;\n}\n"],"mappings":";;;AAAA,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;AACzB,SAAS,SAAS,YAAY;AAC9B,SAAS,qBAAqB;AAUvB,SAAS,kBAAkB,MAAc,UAAkC;AAChF,MAAI,MAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AAE5D,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAM,YAAY,KAAK,KAAK,IAAI;AAChC,QAAI,WAAW,SAAS,GAAG;AACzB,aAAO;AAAA,IACT;AACA,UAAM,QAAQ,GAAG;AAAA,EACnB;AAEA,SAAO;AACT;AAEA,SAAS,gBAAwB;AAC/B,QAAM,MAAM,kBAAkB,SAAS;AACvC,MAAI,QAAQ,MAAM;AAChB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAQA,eAAsB,WAAW,MAA+B;AAC9D,QAAM,aAAa,cAAc;AACjC,QAAM,WAAW,KAAK,YAAY,GAAG,IAAI,KAAK;AAE9C,MAAI;AACF,WAAO,MAAM,SAAS,UAAU,OAAO;AAAA,EACzC,QAAQ;AACN,UAAM,IAAI,MAAM,8BAA8B,QAAQ,EAAE;AAAA,EAC1D;AACF;AAOO,SAAS,kBAAkB,UAAkB,MAAsC;AACxF,MAAI,SAAS;AACb,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,GAAG;AAC/C,aAAS,OAAO,WAAW,KAAK,KAAK;AAAA,EACvC;AACA,SAAO;AACT;","names":[]}
@@ -6,21 +6,23 @@ import {
6
6
  } from "./chunk-HDV3YDTG.js";
7
7
  import {
8
8
  publishGitState
9
- } from "./chunk-QLVAEDVD.js";
9
+ } from "./chunk-XPW46TI5.js";
10
10
  import {
11
11
  EXECUTE_PHASE,
12
- applyPlaceholders,
13
12
  elapsedSecondsSince,
14
13
  getPhaseUsageTotals,
15
- loadPrompt,
16
14
  publishIterationMetrics,
17
15
  publishStoryMetrics
18
- } from "./chunk-I7FTA5FI.js";
16
+ } from "./chunk-ZYBYMEWG.js";
17
+ import {
18
+ applyPlaceholders,
19
+ loadPrompt
20
+ } from "./chunk-SLHZPB3C.js";
19
21
  import {
20
22
  createConfig,
21
23
  resolvePaths,
22
24
  validateDependencies
23
- } from "./chunk-5NLLEWJD.js";
25
+ } from "./chunk-RUTDK6ZF.js";
24
26
  import {
25
27
  allStoriesPass,
26
28
  applyStoryMetrics,
@@ -51,7 +53,7 @@ import {
51
53
  trimErrorMessage,
52
54
  useColor,
53
55
  useUnicode
54
- } from "./chunk-5Q2TMW7K.js";
56
+ } from "./chunk-WO6ERABE.js";
55
57
 
56
58
  // src/core/engine.ts
57
59
  import { existsSync } from "fs";
@@ -608,4 +610,4 @@ export {
608
610
  printRunSummary,
609
611
  runExecute
610
612
  };
611
- //# sourceMappingURL=chunk-BMRBQ23Q.js.map
613
+ //# sourceMappingURL=chunk-WGDLUPSF.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/engine.ts","../src/ui/progress.ts","../src/ui/summary.ts","../src/core/executor.ts","../src/commands/execute.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { cp, mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { EngineConfig, ResolvedPaths, TaskPlan, UserStory } from '../types.js';\nimport { printError, printInfo, printRetry, printSuccess, printWarning } from '../ui/logger.js';\nimport { printIterationHeader } from '../ui/progress.js';\nimport { printStartupHeader, printSummaryBox } from '../ui/summary.js';\nimport { isTransientFailure, retryDelaySeconds, sleep } from '../utils/retry.js';\nimport { executeClaude } from './executor.js';\nimport { divideUsage } from './metrics.js';\nimport { applyPlaceholders, loadPrompt } from './prompt-resolver.js';\nimport { publishGitState } from './session-git.js';\nimport {\n EXECUTE_PHASE,\n elapsedSecondsSince,\n getPhaseUsageTotals,\n publishIterationMetrics,\n publishStoryMetrics,\n} from './session-metrics.js';\nimport { getSessionPublisher } from './session-publisher.js';\nimport {\n allStoriesPass,\n applyStoryMetrics,\n clearLastError,\n hasPendingCorrection,\n initializeState,\n isoNow,\n loadTaskPlan,\n markIssueCompleted,\n markIssueInProgress,\n saveTaskPlan,\n setLastError,\n trimErrorMessage,\n} from './state-manager.js';\nimport { getOutputCallback, getStoryUpdateCallback, isVerbose } from './verbose.js';\n\n/**\n * Emit a message through the output callback if available, otherwise console.log.\n * Used for bare console.log calls in engine functions so they route through listr2.\n */\nfunction emitLog(message: string): void {\n const cb = getOutputCallback();\n if (cb) {\n // Skip empty lines in listr2 context — they don't render meaningfully\n if (message) {\n cb(message);\n }\n } else {\n console.log(message);\n }\n}\n\n/**\n * Ids of the stories that flipped from `passes: false` to `passes: true`\n * between the two readings of the plan taken around one execute iteration.\n *\n * Stories already passing before the iteration, and stories that only exist in\n * the newer plan, are never attributed: nothing says this iteration paid for\n * them.\n */\nfunction newlyCompletedStoryIds(before: UserStory[], after: UserStory[]): string[] {\n const wasPassing = new Map(before.map((story) => [story.id, story.passes]));\n return after\n .filter((story) => story.passes && wasPassing.get(story.id) === false)\n .map((story) => story.id);\n}\n\n/**\n * Initialize the progress file if it doesn't exist.\n */\nasync function ensureProgressFile(progressFile: string): Promise<void> {\n if (!existsSync(progressFile)) {\n const content = `# Issue Flow Progress Log\\nStarted: ${new Date().toString()}\\n---\\n`;\n await writeFile(progressFile, content, 'utf-8');\n }\n}\n\n/**\n * Archive previous run artifacts if the branch has changed.\n */\nasync function archiveIfBranchChanged(plan: TaskPlan, paths: ResolvedPaths): Promise<void> {\n const { lastBranchFile, archiveDir, prdFile, progressFile } = paths;\n\n if (!existsSync(lastBranchFile)) {\n return;\n }\n\n const currentBranch = plan.branchName ?? '';\n let lastBranch = '';\n\n try {\n lastBranch = (await readFile(lastBranchFile, 'utf-8')).trim();\n } catch {\n return;\n }\n\n if (currentBranch && lastBranch && currentBranch !== lastBranch) {\n const dateStr = new Date().toISOString().split('T')[0];\n const folderName = lastBranch.replace(/^issue\\//, '').replace(/[<>:\"|?*\\\\]/g, '_');\n const archiveFolder = join(archiveDir, `${dateStr}-${folderName}`);\n\n printInfo(`Archiving previous run: ${lastBranch}`);\n await mkdir(archiveFolder, { recursive: true });\n\n if (existsSync(prdFile)) {\n await cp(prdFile, join(archiveFolder, 'tasks.json'));\n }\n if (existsSync(progressFile)) {\n await cp(progressFile, join(archiveFolder, 'progress.txt'));\n }\n\n printInfo(` Archived to: ${archiveFolder}`);\n\n // Reset progress file for new run\n await writeFile(\n progressFile,\n `# Issue Flow Progress Log\\nStarted: ${new Date().toString()}\\n---\\n`,\n 'utf-8',\n );\n }\n}\n\n/**\n * Write the current branch to the last-branch tracking file.\n */\nasync function trackBranch(plan: TaskPlan, lastBranchFile: string): Promise<void> {\n const branch = plan.branchName ?? '';\n if (branch) {\n await writeFile(lastBranchFile, `${branch}\\n`, 'utf-8');\n }\n}\n\n/**\n * Run the issue-flow engine loop.\n *\n * This replicates the full execution flow:\n * 1. Load and initialize task plan state\n * 2. Check for early exit (already complete)\n * 3. Archive previous run if branch changed\n * 4. Resolve prompt\n * 5. Main loop: iterate, execute Claude, handle results\n * 6. Print summary\n */\nexport async function runEngine(config: EngineConfig, paths: ResolvedPaths): Promise<number> {\n // Load task plan\n if (!existsSync(paths.prdFile)) {\n printError(`PRD file not found at ${paths.prdFile}`);\n if (config.issueNumber) {\n emitLog(`Have you run the resolve-issue skill for issue #${config.issueNumber} first?`);\n }\n return 1;\n }\n\n let plan = await loadTaskPlan(paths.prdFile);\n plan = initializeState(plan);\n await saveTaskPlan(paths.prdFile, plan);\n\n // Check if already completed. A pending correction (a failed review whose\n // findings haven't been addressed yet) always overrides this, even though\n // every userStories[].passes is already true — otherwise a correction\n // cycle's re-run of execute would exit here without ever looking at what\n // the review found.\n if (plan.issueStatus === 'completed' && allStoriesPass(plan) && !hasPendingCorrection(plan)) {\n emitLog(`Issue already marked complete in ${paths.prdFile}`);\n return 0;\n }\n\n // Warn if marked complete but stories still pending\n if (plan.issueStatus === 'completed' && !allStoriesPass(plan)) {\n printWarning(\n 'Issue marked completed but some stories are still pending. Resetting to in_progress.',\n );\n plan = markIssueInProgress(plan);\n plan = setLastError(\n plan,\n 'invalid_completion_state',\n 'tasks.json claimed the issue was completed before every story had passes=true.',\n );\n await saveTaskPlan(paths.prdFile, plan);\n }\n\n // Check if all stories already pass\n if (allStoriesPass(plan) && !hasPendingCorrection(plan)) {\n emitLog('All user stories already pass. Marking issue as completed.');\n plan = markIssueCompleted(plan);\n await saveTaskPlan(paths.prdFile, plan);\n return 0;\n }\n\n // Archive previous run if branch changed\n await archiveIfBranchChanged(plan, paths);\n\n // Track current branch\n await trackBranch(plan, paths.lastBranchFile);\n\n // Initialize progress file\n await ensureProgressFile(paths.progressFile);\n\n // Load prompt template\n const promptTemplate = await loadPrompt('execute');\n\n // Print startup header\n printStartupHeader(config, plan);\n\n const startTime = Date.now();\n let i = 0;\n let retryCount = 0;\n let totalRetryCount = 0;\n\n // Main loop\n while (true) {\n // Check iteration limit\n if (config.maxIterations !== undefined && i >= config.maxIterations) {\n break;\n }\n\n i++;\n getSessionPublisher().publish({ type: 'iteration:start', at: isoNow(), iteration: i });\n\n // Re-read plan to get latest state\n plan = await loadTaskPlan(paths.prdFile);\n // Baseline for story attribution: whatever was still pending before the\n // agent ran is what this iteration can claim credit for.\n const storiesBeforeIteration = plan.userStories;\n\n if (isVerbose()) {\n printIterationHeader(i, config.maxIterations, plan.userStories);\n }\n\n // Apply placeholders to prompt\n const prompt = applyPlaceholders(promptTemplate, {\n __PRD_FILE__: paths.prdFile,\n __PROGRESS_FILE__: paths.progressFile,\n });\n\n const iterationStartedAt = isoNow();\n plan = markIssueInProgress(plan, iterationStartedAt);\n await saveTaskPlan(paths.prdFile, plan);\n\n // Execute Claude\n const iterationStartedAtMs = Date.now();\n const result = await executeClaude(prompt);\n const iterationSeconds = elapsedSecondsSince(iterationStartedAtMs);\n\n if (result.exitCode !== 0) {\n // A failed iteration still burned tokens: report them on the execute\n // phase, with no attribution to any story.\n publishIterationMetrics(i, result.cost, iterationSeconds);\n\n const errorMessage = trimErrorMessage(result.output);\n\n if (isTransientFailure(result.exitCode, result.output)) {\n retryCount++;\n totalRetryCount++;\n plan = await loadTaskPlan(paths.prdFile);\n plan = setLastError(plan, 'transient_claude_failure', errorMessage);\n await saveTaskPlan(paths.prdFile, plan);\n\n if (!config.retryForever && retryCount > config.retryLimit) {\n const elapsed = Math.floor((Date.now() - startTime) / 1000);\n plan = await loadTaskPlan(paths.prdFile);\n printSummaryBox(\n 'failed',\n i,\n totalRetryCount,\n elapsed,\n plan,\n `Exceeded retry limit (${config.retryLimit}) on transient errors`,\n getPhaseUsageTotals(EXECUTE_PHASE),\n );\n return result.exitCode;\n }\n\n const delaySeconds = retryDelaySeconds(\n retryCount,\n config.backoffBaseSeconds,\n config.backoffMaxSeconds,\n );\n getSessionPublisher().publish({\n type: 'retry',\n at: isoNow(),\n attempt: retryCount,\n delaySeconds,\n reason: 'transient_claude_failure',\n });\n\n if (isVerbose()) {\n emitLog('');\n }\n printRetry(\n `Transient Claude failure on iteration ${i} (attempt ${retryCount}). Retrying in ${delaySeconds}s.`,\n );\n\n // Stay within current iteration budget\n i--;\n await sleep(delaySeconds);\n continue;\n }\n\n // Fatal failure\n plan = await loadTaskPlan(paths.prdFile);\n plan = setLastError(plan, 'fatal_claude_failure', errorMessage);\n await saveTaskPlan(paths.prdFile, plan);\n\n const elapsed = Math.floor((Date.now() - startTime) / 1000);\n printSummaryBox(\n 'failed',\n i,\n totalRetryCount,\n elapsed,\n plan,\n `Claude CLI failed with exit code ${result.exitCode}`,\n getPhaseUsageTotals(EXECUTE_PHASE),\n );\n return result.exitCode;\n }\n\n // Success — reset retry counter\n retryCount = 0;\n plan = await loadTaskPlan(paths.prdFile);\n plan = clearLastError(plan, iterationStartedAt);\n await saveTaskPlan(paths.prdFile, plan);\n\n // Notify story progress listeners (e.g., listr2 subtasks)\n const storyUpdateCb = getStoryUpdateCallback();\n if (storyUpdateCb) {\n storyUpdateCb(plan.userStories);\n }\n getSessionPublisher().publish({\n type: 'stories:update',\n at: isoNow(),\n stories: plan.userStories,\n });\n\n // Story attribution — an approximation by construction: the CLI reports a\n // single usage for the whole iteration, so the stories that flipped to\n // passing in it split tokens, cost and duration evenly. With none, nothing\n // is attributed and the whole cost stays on the execute phase. Published\n // after stories:update, which rebuilds the stories array.\n const completedStoryIds = newlyCompletedStoryIds(storiesBeforeIteration, plan.userStories);\n if (completedStoryIds.length > 0) {\n const storyShare = divideUsage(result.cost, completedStoryIds.length);\n const storySeconds = Math.round(iterationSeconds / completedStoryIds.length);\n for (const storyId of completedStoryIds) {\n publishStoryMetrics(storyId, storyShare, storySeconds);\n }\n\n // The same shares also land on tasks.json, so the numbers outlive the\n // session and are readable with web monitoring off. Persisting them is\n // observational: a write failure must never change the iteration's\n // outcome, so the plan in memory only advances when the write succeeded.\n const planWithMetrics = applyStoryMetrics(plan, completedStoryIds, storyShare, storySeconds);\n try {\n await saveTaskPlan(paths.prdFile, planWithMetrics);\n plan = planWithMetrics;\n } catch {\n // Metrics are a nice-to-have; the iteration succeeded regardless.\n }\n }\n\n getSessionPublisher().publish({ type: 'iteration:end', at: isoNow(), iteration: i });\n publishIterationMetrics(i, result.cost, iterationSeconds);\n // Low-frequency commit/PR enrichment: iteration end is one of the only\n // two sanctioned points (the other is phase boundaries in run.ts).\n await publishGitState(getSessionPublisher());\n\n // Check for completion signal\n if (result.output.includes('<promise>COMPLETE</promise>')) {\n plan = await loadTaskPlan(paths.prdFile);\n if (allStoriesPass(plan) && !hasPendingCorrection(plan)) {\n plan = markIssueCompleted(plan);\n await saveTaskPlan(paths.prdFile, plan);\n\n const elapsed = Math.floor((Date.now() - startTime) / 1000);\n printSummaryBox(\n 'success',\n i,\n totalRetryCount,\n elapsed,\n plan,\n undefined,\n getPhaseUsageTotals(EXECUTE_PHASE),\n );\n return 0;\n }\n\n plan = setLastError(\n plan,\n 'invalid_completion_signal',\n hasPendingCorrection(plan)\n ? 'Claude returned <promise>COMPLETE</promise> while lastReviewFindings was still set.'\n : 'Claude returned <promise>COMPLETE</promise> before every story had passes=true.',\n );\n await saveTaskPlan(paths.prdFile, plan);\n\n emitLog('');\n printWarning(\n hasPendingCorrection(plan)\n ? 'Claude returned a completion signal, but lastReviewFindings is still set. Ignoring completion and continuing.'\n : 'Claude returned a completion signal, but tasks.json still has pending stories. Ignoring completion and continuing.',\n );\n }\n\n if (isVerbose()) {\n printSuccess(`Iteration ${i} complete. Continuing...`);\n }\n await sleep(2);\n }\n\n // Reached max iterations\n const elapsed = Math.floor((Date.now() - startTime) / 1000);\n plan = await loadTaskPlan(paths.prdFile);\n printSummaryBox(\n 'incomplete',\n config.maxIterations ?? i,\n totalRetryCount,\n elapsed,\n plan,\n 'Reached max iterations without completing all tasks.',\n getPhaseUsageTotals(EXECUTE_PHASE),\n );\n return 1;\n}\n","import chalk from 'chalk';\nimport { getOutputCallback } from '../core/verbose.js';\nimport type { UserStory } from '../types.js';\nimport { getIcons, useColor, useUnicode } from './logger.js';\n\n/**\n * Print a visual progress bar showing passed/total stories.\n */\nexport function printProgressBar(passed: number, total: number): string {\n const barWidth = 20;\n let pct = 0;\n let filled = 0;\n\n if (total > 0) {\n pct = Math.floor((passed * 100) / total);\n filled = Math.floor((passed * barWidth) / total);\n }\n\n const empty = barWidth - filled;\n const fillChar = useUnicode() ? '\\u2588' : '#';\n const emptyChar = useUnicode() ? '\\u2591' : '-';\n\n const bar = fillChar.repeat(filled) + emptyChar.repeat(empty);\n const text = `${passed}/${total} (${pct}%)`;\n\n if (useColor()) {\n return `${chalk.green(bar)} ${text}`;\n }\n return `${bar} ${text}`;\n}\n\n/**\n * Print the iteration header showing story statuses and progress bar.\n *\n * When a global output callback is set (e.g., inside a listr2 task),\n * output is routed through it instead of console.log.\n */\nexport function printIterationHeader(\n iteration: number,\n maxIter: number | undefined,\n stories: UserStory[],\n): void {\n const icons = getIcons();\n const colored = useColor();\n const cb = getOutputCallback();\n\n const iterLabel = maxIter ? `Iteration ${iteration} of ${maxIter}` : `Iteration ${iteration}`;\n\n const total = stories.length;\n const passed = stories.filter((s) => s.passes).length;\n\n const lines: string[] = [];\n\n if (colored) {\n lines.push(chalk.blue(`\\u2501\\u2501\\u2501 ${icons.start} ${iterLabel} \\u2501\\u2501\\u2501`));\n } else {\n lines.push(`--- ${icons.start} ${iterLabel} ---`);\n }\n\n // Display each story status\n let foundFirstPending = false;\n for (const story of stories) {\n let icon: string;\n let colorFn: (s: string) => string;\n\n if (story.passes) {\n icon = icons.success;\n colorFn = colored ? chalk.green : (s: string) => s;\n } else if (!foundFirstPending) {\n // First non-passing story = current in-progress\n icon = icons.pending;\n colorFn = colored ? chalk.yellow : (s: string) => s;\n foundFirstPending = true;\n } else {\n // Not yet reached\n icon = icons.notReached;\n colorFn = colored ? chalk.gray : (s: string) => s;\n }\n\n lines.push(colorFn(` ${icon} ${story.id}: ${story.title}`));\n }\n\n lines.push(` ${printProgressBar(passed, total)}`);\n\n if (cb) {\n // Send as single output to listr2 task context\n cb(lines.join('\\n'));\n } else {\n console.log('');\n for (const line of lines) {\n console.log(line);\n }\n console.log('');\n }\n}\n","import chalk from 'chalk';\nimport { type ClaudeUsage, formatTokens } from '../core/metrics.js';\nimport type { EngineConfig, PrReviewRecommendation, TaskPlan } from '../types.js';\nimport {\n formatDuration,\n getIcons,\n getTermWidth,\n printSuccess,\n printWarning,\n useColor,\n useUnicode,\n} from './logger.js';\n\n/**\n * Truncate or pad a string to fit within a given width.\n */\nfunction fitLine(text: string, width: number): string {\n if (text.length > width) {\n return text.substring(0, width);\n }\n return text.padEnd(width);\n}\n\n/**\n * Print a box with border characters around content lines.\n * Lines equal to \"---\" render as separator rows.\n */\nexport function printBox(lines: string[]): void {\n const colored = useColor();\n const unicode = useUnicode();\n const termWidth = getTermWidth();\n\n // Find max content width\n let maxContentWidth = 0;\n for (const line of lines) {\n if (line !== '---' && line.length > maxContentWidth) {\n maxContentWidth = line.length;\n }\n }\n\n // Cap to terminal width (border + padding = 4 chars)\n const available = termWidth - 4;\n if (maxContentWidth > available) {\n maxContentWidth = available;\n }\n if (maxContentWidth < 20) {\n maxContentWidth = 20;\n }\n\n // Box drawing characters\n const tl = unicode ? '\\u256D' : '+';\n const tr = unicode ? '\\u256E' : '+';\n const bl = unicode ? '\\u2570' : '+';\n const br = unicode ? '\\u256F' : '+';\n const h = unicode ? '\\u2500' : '-';\n const v = unicode ? '\\u2502' : '|';\n const sepL = unicode ? '\\u251C' : '+';\n const sepR = unicode ? '\\u2524' : '+';\n\n const hrule = h.repeat(maxContentWidth + 2);\n\n const blue = colored ? chalk.blue : (s: string) => s;\n const _reset = (s: string) => s;\n\n // Top border\n console.log(blue(`${tl}${hrule}${tr}`));\n\n // Content lines\n for (const line of lines) {\n if (line === '---') {\n console.log(blue(`${sepL}${hrule}${sepR}`));\n } else {\n const fitted = fitLine(line, maxContentWidth);\n console.log(`${blue(v)} ${fitted} ${blue(v)}`);\n }\n }\n\n // Bottom border\n console.log(blue(`${bl}${hrule}${br}`));\n}\n\n/**\n * Print the startup header box showing engine configuration.\n */\nexport function printStartupHeader(config: EngineConfig, plan: TaskPlan): void {\n const icons = getIcons();\n\n const storiesTotal = plan.userStories.length;\n const storiesPassing = plan.userStories.filter((s) => s.passes).length;\n const branchName = plan.branchName ?? 'N/A';\n\n const issueLabel = config.issueNumber ? `Issue #${config.issueNumber}` : 'Standalone mode';\n\n const maxIterLabel =\n config.maxIterations !== undefined ? String(config.maxIterations) : 'unlimited';\n\n const retryLabel = config.retryForever\n ? 'unlimited retries'\n : `${config.retryLimit} consecutive retries`;\n\n printBox([\n `${icons.start} Issue Flow`,\n '---',\n `Issue: ${issueLabel}`,\n `Branch: ${branchName}`,\n `Stories: ${storiesPassing}/${storiesTotal} passing`,\n `Iterations: ${maxIterLabel}`,\n `Retries: ${retryLabel}`,\n ]);\n}\n\n// Re-export formatDuration from logger to maintain backwards compatibility\nexport { formatDuration } from './logger.js';\n\n/**\n * Print the final summary box.\n */\nexport function printSummaryBox(\n status: 'success' | 'incomplete' | 'failed',\n iterations: number,\n totalRetries: number,\n elapsedSeconds: number,\n plan: TaskPlan,\n extraInfo?: string,\n /**\n * Tokens and cost spent by this run, already resolved by the caller (the\n * process-owned counters in `core/session-metrics.ts`, never the session\n * snapshot — that one is empty whenever web monitoring is off). Omitted or\n * empty means \"the CLI reported nothing\", and the line is skipped entirely.\n */\n usage?: ClaudeUsage | null,\n): void {\n const icons = getIcons();\n\n const storiesTotal = plan.userStories.length;\n const storiesPassing = plan.userStories.filter((s) => s.passes).length;\n const duration = formatDuration(elapsedSeconds);\n\n let statusIcon: string;\n let statusLabel: string;\n\n switch (status) {\n case 'success':\n statusIcon = icons.success;\n statusLabel = 'Completed';\n break;\n case 'incomplete':\n statusIcon = icons.warn;\n statusLabel = 'Incomplete';\n break;\n case 'failed':\n statusIcon = icons.fail;\n statusLabel = 'Failed';\n break;\n }\n\n const boxLines = [\n `${icons.end} Issue Flow Summary`,\n '---',\n `Status: ${statusIcon} ${statusLabel}`,\n `Stories: ${storiesPassing}/${storiesTotal} passing`,\n `Iterations: ${iterations}`,\n `Duration: ${duration}`,\n ];\n\n const tokens = formatTokens(usage);\n if (tokens !== '') {\n boxLines.push(`Tokens: ${tokens}`);\n }\n\n boxLines.push(`Retries: ${totalRetries}`);\n\n if (extraInfo) {\n boxLines.push('---');\n boxLines.push(extraInfo);\n }\n\n console.log('');\n printBox(boxLines);\n}\n\n/**\n * What the optional `pr-review` phase produced, as far as the summary cares.\n * `null` in `recommendation` means the verdict could not be recovered from\n * disk — the exit code still told us whether changes were requested.\n */\nexport interface RunSummaryPrReview {\n requestedChanges: boolean;\n recommendation: PrReviewRecommendation | null;\n reportPath: string | null;\n}\n\n/**\n * Everything the final `run` summary prints, already resolved by the caller.\n */\nexport interface RunSummaryInfo {\n issueNumber: string;\n /** 'unknown' when the branch could not be detected. */\n branchName: string;\n /** Pipelines run with --no-branch open no Pull Request. */\n noBranch: boolean;\n storyCount: number;\n elapsedSeconds: number;\n /** 'unknown' when no Pull Request could be resolved. */\n prUrl: string;\n /** Only set when the pr-review phase actually ran. */\n prReview?: RunSummaryPrReview | null;\n /**\n * Tokens and cost of the whole pipeline, from the process-owned counters in\n * `core/session-metrics.ts`. Absent or empty omits the line.\n */\n usage?: ClaudeUsage | null;\n}\n\n/**\n * Detail lines of the final `run` summary (everything below the headline).\n *\n * Kept separate from the printing so the shape is testable without capturing\n * stdout. Without `prReview` the lines are exactly the ones the pipeline has\n * always printed.\n */\nexport function buildRunSummaryLines(info: RunSummaryInfo): string[] {\n const lines = [\n ` Branch: ${info.branchName}${info.noBranch ? ' (current)' : ''}`,\n ` Stories: ${info.storyCount}`,\n ` Duration: ${formatDuration(info.elapsedSeconds)}`,\n ];\n\n // Skipped altogether when the CLI reported nothing — better no line than\n // \"0 in / 0 out\" or \"~$NaN\".\n const tokens = formatTokens(info.usage);\n if (tokens !== '') {\n lines.push(` Tokens: ${tokens}`);\n }\n\n if (!info.noBranch) {\n lines.push(` PR: ${info.prUrl}`);\n }\n\n const review = info.prReview;\n if (review) {\n // The exit code is the source of truth for the verdict, so a report whose\n // recommendation could not be read still reports REQUEST_CHANGES.\n const verdict =\n review.recommendation ?? (review.requestedChanges ? 'REQUEST_CHANGES' : 'unknown');\n lines.push(` Review: ${verdict}`);\n if (review.reportPath !== null) {\n lines.push(` Report: ${review.reportPath}`);\n }\n }\n\n return lines;\n}\n\n/**\n * Print the final summary of a `run` (the flat listing, not the box used by\n * the execute engine).\n */\nexport function printRunSummary(info: RunSummaryInfo): void {\n console.log('');\n if (info.prReview?.requestedChanges) {\n printWarning(\n `Pipeline finished for issue #${info.issueNumber}, but the PR review requested changes.`,\n );\n } else {\n printSuccess(`Pipeline complete for issue #${info.issueNumber}!`);\n }\n for (const line of buildRunSummaryLines(info)) {\n console.log(line);\n }\n}\n","import { execa } from 'execa';\nimport type { ClaudeResult } from '../types.js';\nimport { type ClaudeUsage, parseUsage } from './metrics.js';\nimport { getOutputCallback } from './verbose.js';\n\n/**\n * Execute Claude CLI with a prompt piped to stdin.\n *\n * Runs: echo $PROMPT | claude --dangerously-skip-permissions --print --output-format json\n *\n * The JSON envelope is requested purely for observability: it carries the token\n * and cost metrics of the execute phase. Every flow decision keeps working off\n * the same values as before — `exitCode` and the assistant's text — so the\n * envelope is unwrapped here and never leaks to callers. Whenever stdout is not\n * parseable JSON (or the CLI failed), the behaviour falls back exactly to the\n * previous combined stdout+stderr text with no metrics.\n */\nexport async function executeClaude(prompt: string): Promise<ClaudeResult> {\n const result = await execa(\n 'claude',\n ['--dangerously-skip-permissions', '--print', '--output-format', 'json'],\n {\n input: prompt,\n reject: false,\n timeout: 0, // No timeout — let the engine handle iteration limits\n stripFinalNewline: false,\n },\n );\n\n // Combine stdout and stderr to match Bash's 2>&1 behavior\n const stdout = result.stdout?.toString() ?? '';\n const stderr = result.stderr?.toString() ?? '';\n const exitCode = result.exitCode ?? 1;\n\n let output = stdout + (stderr ? `\\n${stderr}` : '');\n let cost: ClaudeUsage | null = null;\n\n // Only unwrap on success: a failing CLI may print diagnostics on stderr that\n // trimErrorMessage()/isTransientFailure() need to see verbatim.\n if (exitCode === 0) {\n try {\n const parsed: unknown = JSON.parse(stdout);\n const text = (parsed as { result?: unknown } | null)?.result;\n // Non-string `result` would break the callers that treat output as text.\n output = typeof text === 'string' ? text : stdout;\n cost = parseUsage(parsed);\n } catch {\n // Not JSON — keep the raw combined output and report no metrics.\n }\n }\n\n // Forward output through the global callback (listr2 renderer) when active\n const onOutput = getOutputCallback();\n if (onOutput) {\n const trimmed = output.trim();\n if (trimmed) {\n onOutput(trimmed);\n }\n }\n\n return {\n exitCode,\n output,\n cost,\n };\n}\n","import { createConfig, resolvePaths, validateDependencies } from '../config.js';\nimport { runEngine } from '../core/engine.js';\nimport { allStoriesPass, loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { printError } from '../ui/logger.js';\n\nexport interface ExecuteOptions {\n issue?: string;\n maxIterations?: number;\n retryLimit?: number;\n retryForever?: boolean;\n}\n\nexport async function runExecute(\n positionalMaxIter: number | undefined,\n options: ExecuteOptions,\n): Promise<number> {\n const errors = await validateDependencies();\n if (errors.length > 0) {\n printError('The following required tools are not installed:');\n for (const err of errors) {\n console.log(err);\n }\n return 1;\n }\n\n const maxIterations = options.maxIterations ?? positionalMaxIter;\n\n const config = createConfig({\n issueNumber: options.issue,\n maxIterations,\n retryLimit: options.retryLimit,\n retryForever: options.retryForever,\n });\n\n const paths = await resolvePaths(config);\n const exitCode = await runEngine(config, paths);\n\n // Update pipeline state if all stories pass\n if (exitCode === 0 && config.issueNumber) {\n try {\n const plan = await loadTaskPlan(paths.prdFile);\n if (allStoriesPass(plan)) {\n plan.pipeline.executionCompleted = true;\n await saveTaskPlan(paths.prdFile, plan);\n }\n } catch {\n // Non-critical — engine already handled state\n }\n }\n\n return exitCode;\n}\n"],"mappings":"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","names":["chalk","chalk","elapsed"]}
1
+ {"version":3,"sources":["../src/core/engine.ts","../src/ui/progress.ts","../src/ui/summary.ts","../src/core/executor.ts","../src/commands/execute.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { cp, mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { EngineConfig, ResolvedPaths, TaskPlan, UserStory } from '../types.js';\nimport { printError, printInfo, printRetry, printSuccess, printWarning } from '../ui/logger.js';\nimport { printIterationHeader } from '../ui/progress.js';\nimport { printStartupHeader, printSummaryBox } from '../ui/summary.js';\nimport { isTransientFailure, retryDelaySeconds, sleep } from '../utils/retry.js';\nimport { executeClaude } from './executor.js';\nimport { divideUsage } from './metrics.js';\nimport { applyPlaceholders, loadPrompt } from './prompt-resolver.js';\nimport { publishGitState } from './session-git.js';\nimport {\n EXECUTE_PHASE,\n elapsedSecondsSince,\n getPhaseUsageTotals,\n publishIterationMetrics,\n publishStoryMetrics,\n} from './session-metrics.js';\nimport { getSessionPublisher } from './session-publisher.js';\nimport {\n allStoriesPass,\n applyStoryMetrics,\n clearLastError,\n hasPendingCorrection,\n initializeState,\n isoNow,\n loadTaskPlan,\n markIssueCompleted,\n markIssueInProgress,\n saveTaskPlan,\n setLastError,\n trimErrorMessage,\n} from './state-manager.js';\nimport { getOutputCallback, getStoryUpdateCallback, isVerbose } from './verbose.js';\n\n/**\n * Emit a message through the output callback if available, otherwise console.log.\n * Used for bare console.log calls in engine functions so they route through listr2.\n */\nfunction emitLog(message: string): void {\n const cb = getOutputCallback();\n if (cb) {\n // Skip empty lines in listr2 context — they don't render meaningfully\n if (message) {\n cb(message);\n }\n } else {\n console.log(message);\n }\n}\n\n/**\n * Ids of the stories that flipped from `passes: false` to `passes: true`\n * between the two readings of the plan taken around one execute iteration.\n *\n * Stories already passing before the iteration, and stories that only exist in\n * the newer plan, are never attributed: nothing says this iteration paid for\n * them.\n */\nfunction newlyCompletedStoryIds(before: UserStory[], after: UserStory[]): string[] {\n const wasPassing = new Map(before.map((story) => [story.id, story.passes]));\n return after\n .filter((story) => story.passes && wasPassing.get(story.id) === false)\n .map((story) => story.id);\n}\n\n/**\n * Initialize the progress file if it doesn't exist.\n */\nasync function ensureProgressFile(progressFile: string): Promise<void> {\n if (!existsSync(progressFile)) {\n const content = `# Issue Flow Progress Log\\nStarted: ${new Date().toString()}\\n---\\n`;\n await writeFile(progressFile, content, 'utf-8');\n }\n}\n\n/**\n * Archive previous run artifacts if the branch has changed.\n */\nasync function archiveIfBranchChanged(plan: TaskPlan, paths: ResolvedPaths): Promise<void> {\n const { lastBranchFile, archiveDir, prdFile, progressFile } = paths;\n\n if (!existsSync(lastBranchFile)) {\n return;\n }\n\n const currentBranch = plan.branchName ?? '';\n let lastBranch = '';\n\n try {\n lastBranch = (await readFile(lastBranchFile, 'utf-8')).trim();\n } catch {\n return;\n }\n\n if (currentBranch && lastBranch && currentBranch !== lastBranch) {\n const dateStr = new Date().toISOString().split('T')[0];\n const folderName = lastBranch.replace(/^issue\\//, '').replace(/[<>:\"|?*\\\\]/g, '_');\n const archiveFolder = join(archiveDir, `${dateStr}-${folderName}`);\n\n printInfo(`Archiving previous run: ${lastBranch}`);\n await mkdir(archiveFolder, { recursive: true });\n\n if (existsSync(prdFile)) {\n await cp(prdFile, join(archiveFolder, 'tasks.json'));\n }\n if (existsSync(progressFile)) {\n await cp(progressFile, join(archiveFolder, 'progress.txt'));\n }\n\n printInfo(` Archived to: ${archiveFolder}`);\n\n // Reset progress file for new run\n await writeFile(\n progressFile,\n `# Issue Flow Progress Log\\nStarted: ${new Date().toString()}\\n---\\n`,\n 'utf-8',\n );\n }\n}\n\n/**\n * Write the current branch to the last-branch tracking file.\n */\nasync function trackBranch(plan: TaskPlan, lastBranchFile: string): Promise<void> {\n const branch = plan.branchName ?? '';\n if (branch) {\n await writeFile(lastBranchFile, `${branch}\\n`, 'utf-8');\n }\n}\n\n/**\n * Run the issue-flow engine loop.\n *\n * This replicates the full execution flow:\n * 1. Load and initialize task plan state\n * 2. Check for early exit (already complete)\n * 3. Archive previous run if branch changed\n * 4. Resolve prompt\n * 5. Main loop: iterate, execute Claude, handle results\n * 6. Print summary\n */\nexport async function runEngine(config: EngineConfig, paths: ResolvedPaths): Promise<number> {\n // Load task plan\n if (!existsSync(paths.prdFile)) {\n printError(`PRD file not found at ${paths.prdFile}`);\n if (config.issueNumber) {\n emitLog(`Have you run the resolve-issue skill for issue #${config.issueNumber} first?`);\n }\n return 1;\n }\n\n let plan = await loadTaskPlan(paths.prdFile);\n plan = initializeState(plan);\n await saveTaskPlan(paths.prdFile, plan);\n\n // Check if already completed. A pending correction (a failed review whose\n // findings haven't been addressed yet) always overrides this, even though\n // every userStories[].passes is already true — otherwise a correction\n // cycle's re-run of execute would exit here without ever looking at what\n // the review found.\n if (plan.issueStatus === 'completed' && allStoriesPass(plan) && !hasPendingCorrection(plan)) {\n emitLog(`Issue already marked complete in ${paths.prdFile}`);\n return 0;\n }\n\n // Warn if marked complete but stories still pending\n if (plan.issueStatus === 'completed' && !allStoriesPass(plan)) {\n printWarning(\n 'Issue marked completed but some stories are still pending. Resetting to in_progress.',\n );\n plan = markIssueInProgress(plan);\n plan = setLastError(\n plan,\n 'invalid_completion_state',\n 'tasks.json claimed the issue was completed before every story had passes=true.',\n );\n await saveTaskPlan(paths.prdFile, plan);\n }\n\n // Check if all stories already pass\n if (allStoriesPass(plan) && !hasPendingCorrection(plan)) {\n emitLog('All user stories already pass. Marking issue as completed.');\n plan = markIssueCompleted(plan);\n await saveTaskPlan(paths.prdFile, plan);\n return 0;\n }\n\n // Archive previous run if branch changed\n await archiveIfBranchChanged(plan, paths);\n\n // Track current branch\n await trackBranch(plan, paths.lastBranchFile);\n\n // Initialize progress file\n await ensureProgressFile(paths.progressFile);\n\n // Load prompt template\n const promptTemplate = await loadPrompt('execute');\n\n // Print startup header\n printStartupHeader(config, plan);\n\n const startTime = Date.now();\n let i = 0;\n let retryCount = 0;\n let totalRetryCount = 0;\n\n // Main loop\n while (true) {\n // Check iteration limit\n if (config.maxIterations !== undefined && i >= config.maxIterations) {\n break;\n }\n\n i++;\n getSessionPublisher().publish({ type: 'iteration:start', at: isoNow(), iteration: i });\n\n // Re-read plan to get latest state\n plan = await loadTaskPlan(paths.prdFile);\n // Baseline for story attribution: whatever was still pending before the\n // agent ran is what this iteration can claim credit for.\n const storiesBeforeIteration = plan.userStories;\n\n if (isVerbose()) {\n printIterationHeader(i, config.maxIterations, plan.userStories);\n }\n\n // Apply placeholders to prompt\n const prompt = applyPlaceholders(promptTemplate, {\n __PRD_FILE__: paths.prdFile,\n __PROGRESS_FILE__: paths.progressFile,\n });\n\n const iterationStartedAt = isoNow();\n plan = markIssueInProgress(plan, iterationStartedAt);\n await saveTaskPlan(paths.prdFile, plan);\n\n // Execute Claude\n const iterationStartedAtMs = Date.now();\n const result = await executeClaude(prompt);\n const iterationSeconds = elapsedSecondsSince(iterationStartedAtMs);\n\n if (result.exitCode !== 0) {\n // A failed iteration still burned tokens: report them on the execute\n // phase, with no attribution to any story.\n publishIterationMetrics(i, result.cost, iterationSeconds);\n\n const errorMessage = trimErrorMessage(result.output);\n\n if (isTransientFailure(result.exitCode, result.output)) {\n retryCount++;\n totalRetryCount++;\n plan = await loadTaskPlan(paths.prdFile);\n plan = setLastError(plan, 'transient_claude_failure', errorMessage);\n await saveTaskPlan(paths.prdFile, plan);\n\n if (!config.retryForever && retryCount > config.retryLimit) {\n const elapsed = Math.floor((Date.now() - startTime) / 1000);\n plan = await loadTaskPlan(paths.prdFile);\n printSummaryBox(\n 'failed',\n i,\n totalRetryCount,\n elapsed,\n plan,\n `Exceeded retry limit (${config.retryLimit}) on transient errors`,\n getPhaseUsageTotals(EXECUTE_PHASE),\n );\n return result.exitCode;\n }\n\n const delaySeconds = retryDelaySeconds(\n retryCount,\n config.backoffBaseSeconds,\n config.backoffMaxSeconds,\n );\n getSessionPublisher().publish({\n type: 'retry',\n at: isoNow(),\n attempt: retryCount,\n delaySeconds,\n reason: 'transient_claude_failure',\n });\n\n if (isVerbose()) {\n emitLog('');\n }\n printRetry(\n `Transient Claude failure on iteration ${i} (attempt ${retryCount}). Retrying in ${delaySeconds}s.`,\n );\n\n // Stay within current iteration budget\n i--;\n await sleep(delaySeconds);\n continue;\n }\n\n // Fatal failure\n plan = await loadTaskPlan(paths.prdFile);\n plan = setLastError(plan, 'fatal_claude_failure', errorMessage);\n await saveTaskPlan(paths.prdFile, plan);\n\n const elapsed = Math.floor((Date.now() - startTime) / 1000);\n printSummaryBox(\n 'failed',\n i,\n totalRetryCount,\n elapsed,\n plan,\n `Claude CLI failed with exit code ${result.exitCode}`,\n getPhaseUsageTotals(EXECUTE_PHASE),\n );\n return result.exitCode;\n }\n\n // Success — reset retry counter\n retryCount = 0;\n plan = await loadTaskPlan(paths.prdFile);\n plan = clearLastError(plan, iterationStartedAt);\n await saveTaskPlan(paths.prdFile, plan);\n\n // Notify story progress listeners (e.g., listr2 subtasks)\n const storyUpdateCb = getStoryUpdateCallback();\n if (storyUpdateCb) {\n storyUpdateCb(plan.userStories);\n }\n getSessionPublisher().publish({\n type: 'stories:update',\n at: isoNow(),\n stories: plan.userStories,\n });\n\n // Story attribution — an approximation by construction: the CLI reports a\n // single usage for the whole iteration, so the stories that flipped to\n // passing in it split tokens, cost and duration evenly. With none, nothing\n // is attributed and the whole cost stays on the execute phase. Published\n // after stories:update, which rebuilds the stories array.\n const completedStoryIds = newlyCompletedStoryIds(storiesBeforeIteration, plan.userStories);\n if (completedStoryIds.length > 0) {\n const storyShare = divideUsage(result.cost, completedStoryIds.length);\n const storySeconds = Math.round(iterationSeconds / completedStoryIds.length);\n for (const storyId of completedStoryIds) {\n publishStoryMetrics(storyId, storyShare, storySeconds);\n }\n\n // The same shares also land on tasks.json, so the numbers outlive the\n // session and are readable with web monitoring off. Persisting them is\n // observational: a write failure must never change the iteration's\n // outcome, so the plan in memory only advances when the write succeeded.\n const planWithMetrics = applyStoryMetrics(plan, completedStoryIds, storyShare, storySeconds);\n try {\n await saveTaskPlan(paths.prdFile, planWithMetrics);\n plan = planWithMetrics;\n } catch {\n // Metrics are a nice-to-have; the iteration succeeded regardless.\n }\n }\n\n getSessionPublisher().publish({ type: 'iteration:end', at: isoNow(), iteration: i });\n publishIterationMetrics(i, result.cost, iterationSeconds);\n // Low-frequency commit/PR enrichment: iteration end is one of the only\n // two sanctioned points (the other is phase boundaries in run.ts).\n await publishGitState(getSessionPublisher());\n\n // Check for completion signal\n if (result.output.includes('<promise>COMPLETE</promise>')) {\n plan = await loadTaskPlan(paths.prdFile);\n if (allStoriesPass(plan) && !hasPendingCorrection(plan)) {\n plan = markIssueCompleted(plan);\n await saveTaskPlan(paths.prdFile, plan);\n\n const elapsed = Math.floor((Date.now() - startTime) / 1000);\n printSummaryBox(\n 'success',\n i,\n totalRetryCount,\n elapsed,\n plan,\n undefined,\n getPhaseUsageTotals(EXECUTE_PHASE),\n );\n return 0;\n }\n\n plan = setLastError(\n plan,\n 'invalid_completion_signal',\n hasPendingCorrection(plan)\n ? 'Claude returned <promise>COMPLETE</promise> while lastReviewFindings was still set.'\n : 'Claude returned <promise>COMPLETE</promise> before every story had passes=true.',\n );\n await saveTaskPlan(paths.prdFile, plan);\n\n emitLog('');\n printWarning(\n hasPendingCorrection(plan)\n ? 'Claude returned a completion signal, but lastReviewFindings is still set. Ignoring completion and continuing.'\n : 'Claude returned a completion signal, but tasks.json still has pending stories. Ignoring completion and continuing.',\n );\n }\n\n if (isVerbose()) {\n printSuccess(`Iteration ${i} complete. Continuing...`);\n }\n await sleep(2);\n }\n\n // Reached max iterations\n const elapsed = Math.floor((Date.now() - startTime) / 1000);\n plan = await loadTaskPlan(paths.prdFile);\n printSummaryBox(\n 'incomplete',\n config.maxIterations ?? i,\n totalRetryCount,\n elapsed,\n plan,\n 'Reached max iterations without completing all tasks.',\n getPhaseUsageTotals(EXECUTE_PHASE),\n );\n return 1;\n}\n","import chalk from 'chalk';\nimport { getOutputCallback } from '../core/verbose.js';\nimport type { UserStory } from '../types.js';\nimport { getIcons, useColor, useUnicode } from './logger.js';\n\n/**\n * Print a visual progress bar showing passed/total stories.\n */\nexport function printProgressBar(passed: number, total: number): string {\n const barWidth = 20;\n let pct = 0;\n let filled = 0;\n\n if (total > 0) {\n pct = Math.floor((passed * 100) / total);\n filled = Math.floor((passed * barWidth) / total);\n }\n\n const empty = barWidth - filled;\n const fillChar = useUnicode() ? '\\u2588' : '#';\n const emptyChar = useUnicode() ? '\\u2591' : '-';\n\n const bar = fillChar.repeat(filled) + emptyChar.repeat(empty);\n const text = `${passed}/${total} (${pct}%)`;\n\n if (useColor()) {\n return `${chalk.green(bar)} ${text}`;\n }\n return `${bar} ${text}`;\n}\n\n/**\n * Print the iteration header showing story statuses and progress bar.\n *\n * When a global output callback is set (e.g., inside a listr2 task),\n * output is routed through it instead of console.log.\n */\nexport function printIterationHeader(\n iteration: number,\n maxIter: number | undefined,\n stories: UserStory[],\n): void {\n const icons = getIcons();\n const colored = useColor();\n const cb = getOutputCallback();\n\n const iterLabel = maxIter ? `Iteration ${iteration} of ${maxIter}` : `Iteration ${iteration}`;\n\n const total = stories.length;\n const passed = stories.filter((s) => s.passes).length;\n\n const lines: string[] = [];\n\n if (colored) {\n lines.push(chalk.blue(`\\u2501\\u2501\\u2501 ${icons.start} ${iterLabel} \\u2501\\u2501\\u2501`));\n } else {\n lines.push(`--- ${icons.start} ${iterLabel} ---`);\n }\n\n // Display each story status\n let foundFirstPending = false;\n for (const story of stories) {\n let icon: string;\n let colorFn: (s: string) => string;\n\n if (story.passes) {\n icon = icons.success;\n colorFn = colored ? chalk.green : (s: string) => s;\n } else if (!foundFirstPending) {\n // First non-passing story = current in-progress\n icon = icons.pending;\n colorFn = colored ? chalk.yellow : (s: string) => s;\n foundFirstPending = true;\n } else {\n // Not yet reached\n icon = icons.notReached;\n colorFn = colored ? chalk.gray : (s: string) => s;\n }\n\n lines.push(colorFn(` ${icon} ${story.id}: ${story.title}`));\n }\n\n lines.push(` ${printProgressBar(passed, total)}`);\n\n if (cb) {\n // Send as single output to listr2 task context\n cb(lines.join('\\n'));\n } else {\n console.log('');\n for (const line of lines) {\n console.log(line);\n }\n console.log('');\n }\n}\n","import chalk from 'chalk';\nimport { type ClaudeUsage, formatTokens } from '../core/metrics.js';\nimport type { EngineConfig, PrReviewRecommendation, TaskPlan } from '../types.js';\nimport {\n formatDuration,\n getIcons,\n getTermWidth,\n printSuccess,\n printWarning,\n useColor,\n useUnicode,\n} from './logger.js';\n\n/**\n * Truncate or pad a string to fit within a given width.\n */\nfunction fitLine(text: string, width: number): string {\n if (text.length > width) {\n return text.substring(0, width);\n }\n return text.padEnd(width);\n}\n\n/**\n * Print a box with border characters around content lines.\n * Lines equal to \"---\" render as separator rows.\n */\nexport function printBox(lines: string[]): void {\n const colored = useColor();\n const unicode = useUnicode();\n const termWidth = getTermWidth();\n\n // Find max content width\n let maxContentWidth = 0;\n for (const line of lines) {\n if (line !== '---' && line.length > maxContentWidth) {\n maxContentWidth = line.length;\n }\n }\n\n // Cap to terminal width (border + padding = 4 chars)\n const available = termWidth - 4;\n if (maxContentWidth > available) {\n maxContentWidth = available;\n }\n if (maxContentWidth < 20) {\n maxContentWidth = 20;\n }\n\n // Box drawing characters\n const tl = unicode ? '\\u256D' : '+';\n const tr = unicode ? '\\u256E' : '+';\n const bl = unicode ? '\\u2570' : '+';\n const br = unicode ? '\\u256F' : '+';\n const h = unicode ? '\\u2500' : '-';\n const v = unicode ? '\\u2502' : '|';\n const sepL = unicode ? '\\u251C' : '+';\n const sepR = unicode ? '\\u2524' : '+';\n\n const hrule = h.repeat(maxContentWidth + 2);\n\n const blue = colored ? chalk.blue : (s: string) => s;\n const _reset = (s: string) => s;\n\n // Top border\n console.log(blue(`${tl}${hrule}${tr}`));\n\n // Content lines\n for (const line of lines) {\n if (line === '---') {\n console.log(blue(`${sepL}${hrule}${sepR}`));\n } else {\n const fitted = fitLine(line, maxContentWidth);\n console.log(`${blue(v)} ${fitted} ${blue(v)}`);\n }\n }\n\n // Bottom border\n console.log(blue(`${bl}${hrule}${br}`));\n}\n\n/**\n * Print the startup header box showing engine configuration.\n */\nexport function printStartupHeader(config: EngineConfig, plan: TaskPlan): void {\n const icons = getIcons();\n\n const storiesTotal = plan.userStories.length;\n const storiesPassing = plan.userStories.filter((s) => s.passes).length;\n const branchName = plan.branchName ?? 'N/A';\n\n const issueLabel = config.issueNumber ? `Issue #${config.issueNumber}` : 'Standalone mode';\n\n const maxIterLabel =\n config.maxIterations !== undefined ? String(config.maxIterations) : 'unlimited';\n\n const retryLabel = config.retryForever\n ? 'unlimited retries'\n : `${config.retryLimit} consecutive retries`;\n\n printBox([\n `${icons.start} Issue Flow`,\n '---',\n `Issue: ${issueLabel}`,\n `Branch: ${branchName}`,\n `Stories: ${storiesPassing}/${storiesTotal} passing`,\n `Iterations: ${maxIterLabel}`,\n `Retries: ${retryLabel}`,\n ]);\n}\n\n// Re-export formatDuration from logger to maintain backwards compatibility\nexport { formatDuration } from './logger.js';\n\n/**\n * Print the final summary box.\n */\nexport function printSummaryBox(\n status: 'success' | 'incomplete' | 'failed',\n iterations: number,\n totalRetries: number,\n elapsedSeconds: number,\n plan: TaskPlan,\n extraInfo?: string,\n /**\n * Tokens and cost spent by this run, already resolved by the caller (the\n * process-owned counters in `core/session-metrics.ts`, never the session\n * snapshot — that one is empty whenever web monitoring is off). Omitted or\n * empty means \"the CLI reported nothing\", and the line is skipped entirely.\n */\n usage?: ClaudeUsage | null,\n): void {\n const icons = getIcons();\n\n const storiesTotal = plan.userStories.length;\n const storiesPassing = plan.userStories.filter((s) => s.passes).length;\n const duration = formatDuration(elapsedSeconds);\n\n let statusIcon: string;\n let statusLabel: string;\n\n switch (status) {\n case 'success':\n statusIcon = icons.success;\n statusLabel = 'Completed';\n break;\n case 'incomplete':\n statusIcon = icons.warn;\n statusLabel = 'Incomplete';\n break;\n case 'failed':\n statusIcon = icons.fail;\n statusLabel = 'Failed';\n break;\n }\n\n const boxLines = [\n `${icons.end} Issue Flow Summary`,\n '---',\n `Status: ${statusIcon} ${statusLabel}`,\n `Stories: ${storiesPassing}/${storiesTotal} passing`,\n `Iterations: ${iterations}`,\n `Duration: ${duration}`,\n ];\n\n const tokens = formatTokens(usage);\n if (tokens !== '') {\n boxLines.push(`Tokens: ${tokens}`);\n }\n\n boxLines.push(`Retries: ${totalRetries}`);\n\n if (extraInfo) {\n boxLines.push('---');\n boxLines.push(extraInfo);\n }\n\n console.log('');\n printBox(boxLines);\n}\n\n/**\n * What the optional `pr-review` phase produced, as far as the summary cares.\n * `null` in `recommendation` means the verdict could not be recovered from\n * disk — the exit code still told us whether changes were requested.\n */\nexport interface RunSummaryPrReview {\n requestedChanges: boolean;\n recommendation: PrReviewRecommendation | null;\n reportPath: string | null;\n}\n\n/**\n * Everything the final `run` summary prints, already resolved by the caller.\n */\nexport interface RunSummaryInfo {\n issueNumber: string;\n /** 'unknown' when the branch could not be detected. */\n branchName: string;\n /** Pipelines run with --no-branch open no Pull Request. */\n noBranch: boolean;\n storyCount: number;\n elapsedSeconds: number;\n /** 'unknown' when no Pull Request could be resolved. */\n prUrl: string;\n /** Only set when the pr-review phase actually ran. */\n prReview?: RunSummaryPrReview | null;\n /**\n * Tokens and cost of the whole pipeline, from the process-owned counters in\n * `core/session-metrics.ts`. Absent or empty omits the line.\n */\n usage?: ClaudeUsage | null;\n}\n\n/**\n * Detail lines of the final `run` summary (everything below the headline).\n *\n * Kept separate from the printing so the shape is testable without capturing\n * stdout. Without `prReview` the lines are exactly the ones the pipeline has\n * always printed.\n */\nexport function buildRunSummaryLines(info: RunSummaryInfo): string[] {\n const lines = [\n ` Branch: ${info.branchName}${info.noBranch ? ' (current)' : ''}`,\n ` Stories: ${info.storyCount}`,\n ` Duration: ${formatDuration(info.elapsedSeconds)}`,\n ];\n\n // Skipped altogether when the CLI reported nothing — better no line than\n // \"0 in / 0 out\" or \"~$NaN\".\n const tokens = formatTokens(info.usage);\n if (tokens !== '') {\n lines.push(` Tokens: ${tokens}`);\n }\n\n if (!info.noBranch) {\n lines.push(` PR: ${info.prUrl}`);\n }\n\n const review = info.prReview;\n if (review) {\n // The exit code is the source of truth for the verdict, so a report whose\n // recommendation could not be read still reports REQUEST_CHANGES.\n const verdict =\n review.recommendation ?? (review.requestedChanges ? 'REQUEST_CHANGES' : 'unknown');\n lines.push(` Review: ${verdict}`);\n if (review.reportPath !== null) {\n lines.push(` Report: ${review.reportPath}`);\n }\n }\n\n return lines;\n}\n\n/**\n * Print the final summary of a `run` (the flat listing, not the box used by\n * the execute engine).\n */\nexport function printRunSummary(info: RunSummaryInfo): void {\n console.log('');\n if (info.prReview?.requestedChanges) {\n printWarning(\n `Pipeline finished for issue #${info.issueNumber}, but the PR review requested changes.`,\n );\n } else {\n printSuccess(`Pipeline complete for issue #${info.issueNumber}!`);\n }\n for (const line of buildRunSummaryLines(info)) {\n console.log(line);\n }\n}\n","import { execa } from 'execa';\nimport type { ClaudeResult } from '../types.js';\nimport { type ClaudeUsage, parseUsage } from './metrics.js';\nimport { getOutputCallback } from './verbose.js';\n\n/**\n * Execute Claude CLI with a prompt piped to stdin.\n *\n * Runs: echo $PROMPT | claude --dangerously-skip-permissions --print --output-format json\n *\n * The JSON envelope is requested purely for observability: it carries the token\n * and cost metrics of the execute phase. Every flow decision keeps working off\n * the same values as before — `exitCode` and the assistant's text — so the\n * envelope is unwrapped here and never leaks to callers. Whenever stdout is not\n * parseable JSON (or the CLI failed), the behaviour falls back exactly to the\n * previous combined stdout+stderr text with no metrics.\n */\nexport async function executeClaude(prompt: string): Promise<ClaudeResult> {\n const result = await execa(\n 'claude',\n ['--dangerously-skip-permissions', '--print', '--output-format', 'json'],\n {\n input: prompt,\n reject: false,\n timeout: 0, // No timeout — let the engine handle iteration limits\n stripFinalNewline: false,\n },\n );\n\n // Combine stdout and stderr to match Bash's 2>&1 behavior\n const stdout = result.stdout?.toString() ?? '';\n const stderr = result.stderr?.toString() ?? '';\n const exitCode = result.exitCode ?? 1;\n\n let output = stdout + (stderr ? `\\n${stderr}` : '');\n let cost: ClaudeUsage | null = null;\n\n // Only unwrap on success: a failing CLI may print diagnostics on stderr that\n // trimErrorMessage()/isTransientFailure() need to see verbatim.\n if (exitCode === 0) {\n try {\n const parsed: unknown = JSON.parse(stdout);\n const text = (parsed as { result?: unknown } | null)?.result;\n // Non-string `result` would break the callers that treat output as text.\n output = typeof text === 'string' ? text : stdout;\n cost = parseUsage(parsed);\n } catch {\n // Not JSON — keep the raw combined output and report no metrics.\n }\n }\n\n // Forward output through the global callback (listr2 renderer) when active\n const onOutput = getOutputCallback();\n if (onOutput) {\n const trimmed = output.trim();\n if (trimmed) {\n onOutput(trimmed);\n }\n }\n\n return {\n exitCode,\n output,\n cost,\n };\n}\n","import { createConfig, resolvePaths, validateDependencies } from '../config.js';\nimport { runEngine } from '../core/engine.js';\nimport { allStoriesPass, loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { printError } from '../ui/logger.js';\n\nexport interface ExecuteOptions {\n issue?: string;\n maxIterations?: number;\n retryLimit?: number;\n retryForever?: boolean;\n}\n\nexport async function runExecute(\n positionalMaxIter: number | undefined,\n options: ExecuteOptions,\n): Promise<number> {\n const errors = await validateDependencies();\n if (errors.length > 0) {\n printError('The following required tools are not installed:');\n for (const err of errors) {\n console.log(err);\n }\n return 1;\n }\n\n const maxIterations = options.maxIterations ?? positionalMaxIter;\n\n const config = createConfig({\n issueNumber: options.issue,\n maxIterations,\n retryLimit: options.retryLimit,\n retryForever: options.retryForever,\n });\n\n const paths = await resolvePaths(config);\n const exitCode = await runEngine(config, paths);\n\n // Update pipeline state if all stories pass\n if (exitCode === 0 && config.issueNumber) {\n try {\n const plan = await loadTaskPlan(paths.prdFile);\n if (allStoriesPass(plan)) {\n plan.pipeline.executionCompleted = true;\n await saveTaskPlan(paths.prdFile, plan);\n }\n } catch {\n // Non-critical — engine already handled state\n }\n }\n\n return exitCode;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,kBAAkB;AAC3B,SAAS,IAAI,OAAO,UAAU,iBAAiB;AAC/C,SAAS,YAAY;;;ACFrB,OAAO,WAAW;AAQX,SAAS,iBAAiB,QAAgB,OAAuB;AACtE,QAAM,WAAW;AACjB,MAAI,MAAM;AACV,MAAI,SAAS;AAEb,MAAI,QAAQ,GAAG;AACb,UAAM,KAAK,MAAO,SAAS,MAAO,KAAK;AACvC,aAAS,KAAK,MAAO,SAAS,WAAY,KAAK;AAAA,EACjD;AAEA,QAAM,QAAQ,WAAW;AACzB,QAAM,WAAW,WAAW,IAAI,WAAW;AAC3C,QAAM,YAAY,WAAW,IAAI,WAAW;AAE5C,QAAM,MAAM,SAAS,OAAO,MAAM,IAAI,UAAU,OAAO,KAAK;AAC5D,QAAM,OAAO,GAAG,MAAM,IAAI,KAAK,KAAK,GAAG;AAEvC,MAAI,SAAS,GAAG;AACd,WAAO,GAAG,MAAM,MAAM,GAAG,CAAC,IAAI,IAAI;AAAA,EACpC;AACA,SAAO,GAAG,GAAG,IAAI,IAAI;AACvB;AAQO,SAAS,qBACd,WACA,SACA,SACM;AACN,QAAM,QAAQ,SAAS;AACvB,QAAM,UAAU,SAAS;AACzB,QAAM,KAAK,kBAAkB;AAE7B,QAAM,YAAY,UAAU,aAAa,SAAS,OAAO,OAAO,KAAK,aAAa,SAAS;AAE3F,QAAM,QAAQ,QAAQ;AACtB,QAAM,SAAS,QAAQ,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE;AAE/C,QAAM,QAAkB,CAAC;AAEzB,MAAI,SAAS;AACX,UAAM,KAAK,MAAM,KAAK,sBAAsB,MAAM,KAAK,IAAI,SAAS,qBAAqB,CAAC;AAAA,EAC5F,OAAO;AACL,UAAM,KAAK,OAAO,MAAM,KAAK,IAAI,SAAS,MAAM;AAAA,EAClD;AAGA,MAAI,oBAAoB;AACxB,aAAW,SAAS,SAAS;AAC3B,QAAI;AACJ,QAAI;AAEJ,QAAI,MAAM,QAAQ;AAChB,aAAO,MAAM;AACb,gBAAU,UAAU,MAAM,QAAQ,CAAC,MAAc;AAAA,IACnD,WAAW,CAAC,mBAAmB;AAE7B,aAAO,MAAM;AACb,gBAAU,UAAU,MAAM,SAAS,CAAC,MAAc;AAClD,0BAAoB;AAAA,IACtB,OAAO;AAEL,aAAO,MAAM;AACb,gBAAU,UAAU,MAAM,OAAO,CAAC,MAAc;AAAA,IAClD;AAEA,UAAM,KAAK,QAAQ,KAAK,IAAI,IAAI,MAAM,EAAE,KAAK,MAAM,KAAK,EAAE,CAAC;AAAA,EAC7D;AAEA,QAAM,KAAK,KAAK,iBAAiB,QAAQ,KAAK,CAAC,EAAE;AAEjD,MAAI,IAAI;AAEN,OAAG,MAAM,KAAK,IAAI,CAAC;AAAA,EACrB,OAAO;AACL,YAAQ,IAAI,EAAE;AACd,eAAW,QAAQ,OAAO;AACxB,cAAQ,IAAI,IAAI;AAAA,IAClB;AACA,YAAQ,IAAI,EAAE;AAAA,EAChB;AACF;;;AC9FA,OAAOA,YAAW;AAgBlB,SAAS,QAAQ,MAAc,OAAuB;AACpD,MAAI,KAAK,SAAS,OAAO;AACvB,WAAO,KAAK,UAAU,GAAG,KAAK;AAAA,EAChC;AACA,SAAO,KAAK,OAAO,KAAK;AAC1B;AAMO,SAAS,SAAS,OAAuB;AAC9C,QAAM,UAAU,SAAS;AACzB,QAAM,UAAU,WAAW;AAC3B,QAAM,YAAY,aAAa;AAG/B,MAAI,kBAAkB;AACtB,aAAW,QAAQ,OAAO;AACxB,QAAI,SAAS,SAAS,KAAK,SAAS,iBAAiB;AACnD,wBAAkB,KAAK;AAAA,IACzB;AAAA,EACF;AAGA,QAAM,YAAY,YAAY;AAC9B,MAAI,kBAAkB,WAAW;AAC/B,sBAAkB;AAAA,EACpB;AACA,MAAI,kBAAkB,IAAI;AACxB,sBAAkB;AAAA,EACpB;AAGA,QAAM,KAAK,UAAU,WAAW;AAChC,QAAM,KAAK,UAAU,WAAW;AAChC,QAAM,KAAK,UAAU,WAAW;AAChC,QAAM,KAAK,UAAU,WAAW;AAChC,QAAM,IAAI,UAAU,WAAW;AAC/B,QAAM,IAAI,UAAU,WAAW;AAC/B,QAAM,OAAO,UAAU,WAAW;AAClC,QAAM,OAAO,UAAU,WAAW;AAElC,QAAM,QAAQ,EAAE,OAAO,kBAAkB,CAAC;AAE1C,QAAM,OAAO,UAAUC,OAAM,OAAO,CAAC,MAAc;AACnD,QAAM,SAAS,CAAC,MAAc;AAG9B,UAAQ,IAAI,KAAK,GAAG,EAAE,GAAG,KAAK,GAAG,EAAE,EAAE,CAAC;AAGtC,aAAW,QAAQ,OAAO;AACxB,QAAI,SAAS,OAAO;AAClB,cAAQ,IAAI,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,EAAE,CAAC;AAAA,IAC5C,OAAO;AACL,YAAM,SAAS,QAAQ,MAAM,eAAe;AAC5C,cAAQ,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,MAAM,IAAI,KAAK,CAAC,CAAC,EAAE;AAAA,IAC/C;AAAA,EACF;AAGA,UAAQ,IAAI,KAAK,GAAG,EAAE,GAAG,KAAK,GAAG,EAAE,EAAE,CAAC;AACxC;AAKO,SAAS,mBAAmB,QAAsB,MAAsB;AAC7E,QAAM,QAAQ,SAAS;AAEvB,QAAM,eAAe,KAAK,YAAY;AACtC,QAAM,iBAAiB,KAAK,YAAY,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE;AAChE,QAAM,aAAa,KAAK,cAAc;AAEtC,QAAM,aAAa,OAAO,cAAc,UAAU,OAAO,WAAW,KAAK;AAEzE,QAAM,eACJ,OAAO,kBAAkB,SAAY,OAAO,OAAO,aAAa,IAAI;AAEtE,QAAM,aAAa,OAAO,eACtB,sBACA,GAAG,OAAO,UAAU;AAExB,WAAS;AAAA,IACP,GAAG,MAAM,KAAK;AAAA,IACd;AAAA,IACA,gBAAgB,UAAU;AAAA,IAC1B,gBAAgB,UAAU;AAAA,IAC1B,gBAAgB,cAAc,IAAI,YAAY;AAAA,IAC9C,gBAAgB,YAAY;AAAA,IAC5B,gBAAgB,UAAU;AAAA,EAC5B,CAAC;AACH;AAQO,SAAS,gBACd,QACA,YACA,cACA,gBACA,MACA,WAOA,OACM;AACN,QAAM,QAAQ,SAAS;AAEvB,QAAM,eAAe,KAAK,YAAY;AACtC,QAAM,iBAAiB,KAAK,YAAY,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE;AAChE,QAAM,WAAW,eAAe,cAAc;AAE9C,MAAI;AACJ,MAAI;AAEJ,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,mBAAa,MAAM;AACnB,oBAAc;AACd;AAAA,IACF,KAAK;AACH,mBAAa,MAAM;AACnB,oBAAc;AACd;AAAA,IACF,KAAK;AACH,mBAAa,MAAM;AACnB,oBAAc;AACd;AAAA,EACJ;AAEA,QAAM,WAAW;AAAA,IACf,GAAG,MAAM,GAAG;AAAA,IACZ;AAAA,IACA,gBAAgB,UAAU,IAAI,WAAW;AAAA,IACzC,gBAAgB,cAAc,IAAI,YAAY;AAAA,IAC9C,gBAAgB,UAAU;AAAA,IAC1B,gBAAgB,QAAQ;AAAA,EAC1B;AAEA,QAAM,SAAS,aAAa,KAAK;AACjC,MAAI,WAAW,IAAI;AACjB,aAAS,KAAK,gBAAgB,MAAM,EAAE;AAAA,EACxC;AAEA,WAAS,KAAK,gBAAgB,YAAY,EAAE;AAE5C,MAAI,WAAW;AACb,aAAS,KAAK,KAAK;AACnB,aAAS,KAAK,SAAS;AAAA,EACzB;AAEA,UAAQ,IAAI,EAAE;AACd,WAAS,QAAQ;AACnB;AA0CO,SAAS,qBAAqB,MAAgC;AACnE,QAAM,QAAQ;AAAA,IACZ,eAAe,KAAK,UAAU,GAAG,KAAK,WAAW,eAAe,EAAE;AAAA,IAClE,eAAe,KAAK,UAAU;AAAA,IAC9B,eAAe,eAAe,KAAK,cAAc,CAAC;AAAA,EACpD;AAIA,QAAM,SAAS,aAAa,KAAK,KAAK;AACtC,MAAI,WAAW,IAAI;AACjB,UAAM,KAAK,eAAe,MAAM,EAAE;AAAA,EACpC;AAEA,MAAI,CAAC,KAAK,UAAU;AAClB,UAAM,KAAK,eAAe,KAAK,KAAK,EAAE;AAAA,EACxC;AAEA,QAAM,SAAS,KAAK;AACpB,MAAI,QAAQ;AAGV,UAAM,UACJ,OAAO,mBAAmB,OAAO,mBAAmB,oBAAoB;AAC1E,UAAM,KAAK,eAAe,OAAO,EAAE;AACnC,QAAI,OAAO,eAAe,MAAM;AAC9B,YAAM,KAAK,eAAe,OAAO,UAAU,EAAE;AAAA,IAC/C;AAAA,EACF;AAEA,SAAO;AACT;AAMO,SAAS,gBAAgB,MAA4B;AAC1D,UAAQ,IAAI,EAAE;AACd,MAAI,KAAK,UAAU,kBAAkB;AACnC;AAAA,MACE,gCAAgC,KAAK,WAAW;AAAA,IAClD;AAAA,EACF,OAAO;AACL,iBAAa,gCAAgC,KAAK,WAAW,GAAG;AAAA,EAClE;AACA,aAAW,QAAQ,qBAAqB,IAAI,GAAG;AAC7C,YAAQ,IAAI,IAAI;AAAA,EAClB;AACF;;;AC9QA,SAAS,aAAa;AAiBtB,eAAsB,cAAc,QAAuC;AACzE,QAAM,SAAS,MAAM;AAAA,IACnB;AAAA,IACA,CAAC,kCAAkC,WAAW,mBAAmB,MAAM;AAAA,IACvE;AAAA,MACE,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,SAAS;AAAA;AAAA,MACT,mBAAmB;AAAA,IACrB;AAAA,EACF;AAGA,QAAM,SAAS,OAAO,QAAQ,SAAS,KAAK;AAC5C,QAAM,SAAS,OAAO,QAAQ,SAAS,KAAK;AAC5C,QAAM,WAAW,OAAO,YAAY;AAEpC,MAAI,SAAS,UAAU,SAAS;AAAA,EAAK,MAAM,KAAK;AAChD,MAAI,OAA2B;AAI/B,MAAI,aAAa,GAAG;AAClB,QAAI;AACF,YAAM,SAAkB,KAAK,MAAM,MAAM;AACzC,YAAM,OAAQ,QAAwC;AAEtD,eAAS,OAAO,SAAS,WAAW,OAAO;AAC3C,aAAO,WAAW,MAAM;AAAA,IAC1B,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,QAAM,WAAW,kBAAkB;AACnC,MAAI,UAAU;AACZ,UAAM,UAAU,OAAO,KAAK;AAC5B,QAAI,SAAS;AACX,eAAS,OAAO;AAAA,IAClB;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;AHzBA,SAAS,QAAQ,SAAuB;AACtC,QAAM,KAAK,kBAAkB;AAC7B,MAAI,IAAI;AAEN,QAAI,SAAS;AACX,SAAG,OAAO;AAAA,IACZ;AAAA,EACF,OAAO;AACL,YAAQ,IAAI,OAAO;AAAA,EACrB;AACF;AAUA,SAAS,uBAAuB,QAAqB,OAA8B;AACjF,QAAM,aAAa,IAAI,IAAI,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,IAAI,MAAM,MAAM,CAAC,CAAC;AAC1E,SAAO,MACJ,OAAO,CAAC,UAAU,MAAM,UAAU,WAAW,IAAI,MAAM,EAAE,MAAM,KAAK,EACpE,IAAI,CAAC,UAAU,MAAM,EAAE;AAC5B;AAKA,eAAe,mBAAmB,cAAqC;AACrE,MAAI,CAAC,WAAW,YAAY,GAAG;AAC7B,UAAM,UAAU;AAAA,YAAuC,oBAAI,KAAK,GAAE,SAAS,CAAC;AAAA;AAAA;AAC5E,UAAM,UAAU,cAAc,SAAS,OAAO;AAAA,EAChD;AACF;AAKA,eAAe,uBAAuB,MAAgB,OAAqC;AACzF,QAAM,EAAE,gBAAgB,YAAY,SAAS,aAAa,IAAI;AAE9D,MAAI,CAAC,WAAW,cAAc,GAAG;AAC/B;AAAA,EACF;AAEA,QAAM,gBAAgB,KAAK,cAAc;AACzC,MAAI,aAAa;AAEjB,MAAI;AACF,kBAAc,MAAM,SAAS,gBAAgB,OAAO,GAAG,KAAK;AAAA,EAC9D,QAAQ;AACN;AAAA,EACF;AAEA,MAAI,iBAAiB,cAAc,kBAAkB,YAAY;AAC/D,UAAM,WAAU,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AACrD,UAAM,aAAa,WAAW,QAAQ,YAAY,EAAE,EAAE,QAAQ,gBAAgB,GAAG;AACjF,UAAM,gBAAgB,KAAK,YAAY,GAAG,OAAO,IAAI,UAAU,EAAE;AAEjE,cAAU,2BAA2B,UAAU,EAAE;AACjD,UAAM,MAAM,eAAe,EAAE,WAAW,KAAK,CAAC;AAE9C,QAAI,WAAW,OAAO,GAAG;AACvB,YAAM,GAAG,SAAS,KAAK,eAAe,YAAY,CAAC;AAAA,IACrD;AACA,QAAI,WAAW,YAAY,GAAG;AAC5B,YAAM,GAAG,cAAc,KAAK,eAAe,cAAc,CAAC;AAAA,IAC5D;AAEA,cAAU,mBAAmB,aAAa,EAAE;AAG5C,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,YAAuC,oBAAI,KAAK,GAAE,SAAS,CAAC;AAAA;AAAA;AAAA,MAC5D;AAAA,IACF;AAAA,EACF;AACF;AAKA,eAAe,YAAY,MAAgB,gBAAuC;AAChF,QAAM,SAAS,KAAK,cAAc;AAClC,MAAI,QAAQ;AACV,UAAM,UAAU,gBAAgB,GAAG,MAAM;AAAA,GAAM,OAAO;AAAA,EACxD;AACF;AAaA,eAAsB,UAAU,QAAsB,OAAuC;AAE3F,MAAI,CAAC,WAAW,MAAM,OAAO,GAAG;AAC9B,eAAW,yBAAyB,MAAM,OAAO,EAAE;AACnD,QAAI,OAAO,aAAa;AACtB,cAAQ,mDAAmD,OAAO,WAAW,SAAS;AAAA,IACxF;AACA,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,MAAM,aAAa,MAAM,OAAO;AAC3C,SAAO,gBAAgB,IAAI;AAC3B,QAAM,aAAa,MAAM,SAAS,IAAI;AAOtC,MAAI,KAAK,gBAAgB,eAAe,eAAe,IAAI,KAAK,CAAC,qBAAqB,IAAI,GAAG;AAC3F,YAAQ,oCAAoC,MAAM,OAAO,EAAE;AAC3D,WAAO;AAAA,EACT;AAGA,MAAI,KAAK,gBAAgB,eAAe,CAAC,eAAe,IAAI,GAAG;AAC7D;AAAA,MACE;AAAA,IACF;AACA,WAAO,oBAAoB,IAAI;AAC/B,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,UAAM,aAAa,MAAM,SAAS,IAAI;AAAA,EACxC;AAGA,MAAI,eAAe,IAAI,KAAK,CAAC,qBAAqB,IAAI,GAAG;AACvD,YAAQ,4DAA4D;AACpE,WAAO,mBAAmB,IAAI;AAC9B,UAAM,aAAa,MAAM,SAAS,IAAI;AACtC,WAAO;AAAA,EACT;AAGA,QAAM,uBAAuB,MAAM,KAAK;AAGxC,QAAM,YAAY,MAAM,MAAM,cAAc;AAG5C,QAAM,mBAAmB,MAAM,YAAY;AAG3C,QAAM,iBAAiB,MAAM,WAAW,SAAS;AAGjD,qBAAmB,QAAQ,IAAI;AAE/B,QAAM,YAAY,KAAK,IAAI;AAC3B,MAAI,IAAI;AACR,MAAI,aAAa;AACjB,MAAI,kBAAkB;AAGtB,SAAO,MAAM;AAEX,QAAI,OAAO,kBAAkB,UAAa,KAAK,OAAO,eAAe;AACnE;AAAA,IACF;AAEA;AACA,wBAAoB,EAAE,QAAQ,EAAE,MAAM,mBAAmB,IAAI,OAAO,GAAG,WAAW,EAAE,CAAC;AAGrF,WAAO,MAAM,aAAa,MAAM,OAAO;AAGvC,UAAM,yBAAyB,KAAK;AAEpC,QAAI,UAAU,GAAG;AACf,2BAAqB,GAAG,OAAO,eAAe,KAAK,WAAW;AAAA,IAChE;AAGA,UAAM,SAAS,kBAAkB,gBAAgB;AAAA,MAC/C,cAAc,MAAM;AAAA,MACpB,mBAAmB,MAAM;AAAA,IAC3B,CAAC;AAED,UAAM,qBAAqB,OAAO;AAClC,WAAO,oBAAoB,MAAM,kBAAkB;AACnD,UAAM,aAAa,MAAM,SAAS,IAAI;AAGtC,UAAM,uBAAuB,KAAK,IAAI;AACtC,UAAM,SAAS,MAAM,cAAc,MAAM;AACzC,UAAM,mBAAmB,oBAAoB,oBAAoB;AAEjE,QAAI,OAAO,aAAa,GAAG;AAGzB,8BAAwB,GAAG,OAAO,MAAM,gBAAgB;AAExD,YAAM,eAAe,iBAAiB,OAAO,MAAM;AAEnD,UAAI,mBAAmB,OAAO,UAAU,OAAO,MAAM,GAAG;AACtD;AACA;AACA,eAAO,MAAM,aAAa,MAAM,OAAO;AACvC,eAAO,aAAa,MAAM,4BAA4B,YAAY;AAClE,cAAM,aAAa,MAAM,SAAS,IAAI;AAEtC,YAAI,CAAC,OAAO,gBAAgB,aAAa,OAAO,YAAY;AAC1D,gBAAMC,WAAU,KAAK,OAAO,KAAK,IAAI,IAAI,aAAa,GAAI;AAC1D,iBAAO,MAAM,aAAa,MAAM,OAAO;AACvC;AAAA,YACE;AAAA,YACA;AAAA,YACA;AAAA,YACAA;AAAA,YACA;AAAA,YACA,yBAAyB,OAAO,UAAU;AAAA,YAC1C,oBAAoB,aAAa;AAAA,UACnC;AACA,iBAAO,OAAO;AAAA,QAChB;AAEA,cAAM,eAAe;AAAA,UACnB;AAAA,UACA,OAAO;AAAA,UACP,OAAO;AAAA,QACT;AACA,4BAAoB,EAAE,QAAQ;AAAA,UAC5B,MAAM;AAAA,UACN,IAAI,OAAO;AAAA,UACX,SAAS;AAAA,UACT;AAAA,UACA,QAAQ;AAAA,QACV,CAAC;AAED,YAAI,UAAU,GAAG;AACf,kBAAQ,EAAE;AAAA,QACZ;AACA;AAAA,UACE,yCAAyC,CAAC,aAAa,UAAU,kBAAkB,YAAY;AAAA,QACjG;AAGA;AACA,cAAM,MAAM,YAAY;AACxB;AAAA,MACF;AAGA,aAAO,MAAM,aAAa,MAAM,OAAO;AACvC,aAAO,aAAa,MAAM,wBAAwB,YAAY;AAC9D,YAAM,aAAa,MAAM,SAAS,IAAI;AAEtC,YAAMA,WAAU,KAAK,OAAO,KAAK,IAAI,IAAI,aAAa,GAAI;AAC1D;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACAA;AAAA,QACA;AAAA,QACA,oCAAoC,OAAO,QAAQ;AAAA,QACnD,oBAAoB,aAAa;AAAA,MACnC;AACA,aAAO,OAAO;AAAA,IAChB;AAGA,iBAAa;AACb,WAAO,MAAM,aAAa,MAAM,OAAO;AACvC,WAAO,eAAe,MAAM,kBAAkB;AAC9C,UAAM,aAAa,MAAM,SAAS,IAAI;AAGtC,UAAM,gBAAgB,uBAAuB;AAC7C,QAAI,eAAe;AACjB,oBAAc,KAAK,WAAW;AAAA,IAChC;AACA,wBAAoB,EAAE,QAAQ;AAAA,MAC5B,MAAM;AAAA,MACN,IAAI,OAAO;AAAA,MACX,SAAS,KAAK;AAAA,IAChB,CAAC;AAOD,UAAM,oBAAoB,uBAAuB,wBAAwB,KAAK,WAAW;AACzF,QAAI,kBAAkB,SAAS,GAAG;AAChC,YAAM,aAAa,YAAY,OAAO,MAAM,kBAAkB,MAAM;AACpE,YAAM,eAAe,KAAK,MAAM,mBAAmB,kBAAkB,MAAM;AAC3E,iBAAW,WAAW,mBAAmB;AACvC,4BAAoB,SAAS,YAAY,YAAY;AAAA,MACvD;AAMA,YAAM,kBAAkB,kBAAkB,MAAM,mBAAmB,YAAY,YAAY;AAC3F,UAAI;AACF,cAAM,aAAa,MAAM,SAAS,eAAe;AACjD,eAAO;AAAA,MACT,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,wBAAoB,EAAE,QAAQ,EAAE,MAAM,iBAAiB,IAAI,OAAO,GAAG,WAAW,EAAE,CAAC;AACnF,4BAAwB,GAAG,OAAO,MAAM,gBAAgB;AAGxD,UAAM,gBAAgB,oBAAoB,CAAC;AAG3C,QAAI,OAAO,OAAO,SAAS,6BAA6B,GAAG;AACzD,aAAO,MAAM,aAAa,MAAM,OAAO;AACvC,UAAI,eAAe,IAAI,KAAK,CAAC,qBAAqB,IAAI,GAAG;AACvD,eAAO,mBAAmB,IAAI;AAC9B,cAAM,aAAa,MAAM,SAAS,IAAI;AAEtC,cAAMA,WAAU,KAAK,OAAO,KAAK,IAAI,IAAI,aAAa,GAAI;AAC1D;AAAA,UACE;AAAA,UACA;AAAA,UACA;AAAA,UACAA;AAAA,UACA;AAAA,UACA;AAAA,UACA,oBAAoB,aAAa;AAAA,QACnC;AACA,eAAO;AAAA,MACT;AAEA,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA,qBAAqB,IAAI,IACrB,wFACA;AAAA,MACN;AACA,YAAM,aAAa,MAAM,SAAS,IAAI;AAEtC,cAAQ,EAAE;AACV;AAAA,QACE,qBAAqB,IAAI,IACrB,kHACA;AAAA,MACN;AAAA,IACF;AAEA,QAAI,UAAU,GAAG;AACf,mBAAa,aAAa,CAAC,0BAA0B;AAAA,IACvD;AACA,UAAM,MAAM,CAAC;AAAA,EACf;AAGA,QAAM,UAAU,KAAK,OAAO,KAAK,IAAI,IAAI,aAAa,GAAI;AAC1D,SAAO,MAAM,aAAa,MAAM,OAAO;AACvC;AAAA,IACE;AAAA,IACA,OAAO,iBAAiB;AAAA,IACxB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,oBAAoB,aAAa;AAAA,EACnC;AACA,SAAO;AACT;;;AI1ZA,eAAsB,WACpB,mBACA,SACiB;AACjB,QAAM,SAAS,MAAM,qBAAqB;AAC1C,MAAI,OAAO,SAAS,GAAG;AACrB,eAAW,iDAAiD;AAC5D,eAAW,OAAO,QAAQ;AACxB,cAAQ,IAAI,GAAG;AAAA,IACjB;AACA,WAAO;AAAA,EACT;AAEA,QAAM,gBAAgB,QAAQ,iBAAiB;AAE/C,QAAM,SAAS,aAAa;AAAA,IAC1B,aAAa,QAAQ;AAAA,IACrB;AAAA,IACA,YAAY,QAAQ;AAAA,IACpB,cAAc,QAAQ;AAAA,EACxB,CAAC;AAED,QAAM,QAAQ,MAAM,aAAa,MAAM;AACvC,QAAM,WAAW,MAAM,UAAU,QAAQ,KAAK;AAG9C,MAAI,aAAa,KAAK,OAAO,aAAa;AACxC,QAAI;AACF,YAAM,OAAO,MAAM,aAAa,MAAM,OAAO;AAC7C,UAAI,eAAe,IAAI,GAAG;AACxB,aAAK,SAAS,qBAAqB;AACnC,cAAM,aAAa,MAAM,SAAS,IAAI;AAAA,MACxC;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO;AACT;","names":["chalk","chalk","elapsed"]}
@@ -1166,6 +1166,7 @@ var ElapsedTimer = class {
1166
1166
  export {
1167
1167
  issueMetadataSchema,
1168
1168
  taskPlanSchema,
1169
+ sessionSnapshotSchema,
1169
1170
  webConfigSchema,
1170
1171
  issuesConfigSchema,
1171
1172
  prReviewConfigSchema,
@@ -1212,4 +1213,4 @@ export {
1212
1213
  formatDuration,
1213
1214
  ElapsedTimer
1214
1215
  };
1215
- //# sourceMappingURL=chunk-5Q2TMW7K.js.map
1216
+ //# sourceMappingURL=chunk-WO6ERABE.js.map
@@ -9,11 +9,11 @@ import {
9
9
  normalizeRemoteUrl,
10
10
  run,
11
11
  stripRemoteUrlCredentials
12
- } from "./chunk-5NLLEWJD.js";
12
+ } from "./chunk-RUTDK6ZF.js";
13
13
  import {
14
14
  NullPublisher,
15
15
  isoNow
16
- } from "./chunk-5Q2TMW7K.js";
16
+ } from "./chunk-WO6ERABE.js";
17
17
 
18
18
  // src/core/session-git.ts
19
19
  async function listPullRequests(branch) {
@@ -109,4 +109,4 @@ export {
109
109
  listPullRequests,
110
110
  publishGitState
111
111
  };
112
- //# sourceMappingURL=chunk-QLVAEDVD.js.map
112
+ //# sourceMappingURL=chunk-XPW46TI5.js.map
@@ -4,49 +4,7 @@ import {
4
4
  hasUsageData,
5
5
  isoNow,
6
6
  sumUsage
7
- } from "./chunk-5Q2TMW7K.js";
8
-
9
- // src/core/prompt-resolver.ts
10
- import { existsSync } from "fs";
11
- import { readFile } from "fs/promises";
12
- import { dirname, join } from "path";
13
- import { fileURLToPath } from "url";
14
- function resolvePackageDir(name, startDir) {
15
- let dir = startDir ?? dirname(fileURLToPath(import.meta.url));
16
- for (let i = 0; i < 5; i++) {
17
- const candidate = join(dir, name);
18
- if (existsSync(candidate)) {
19
- return candidate;
20
- }
21
- dir = dirname(dir);
22
- }
23
- return null;
24
- }
25
- function getPromptsDir() {
26
- const dir = resolvePackageDir("prompts");
27
- if (dir === null) {
28
- throw new Error(
29
- "Could not locate the prompts/ directory. Ensure the package is installed correctly."
30
- );
31
- }
32
- return dir;
33
- }
34
- async function loadPrompt(name) {
35
- const promptsDir = getPromptsDir();
36
- const filePath = join(promptsDir, `${name}.md`);
37
- try {
38
- return await readFile(filePath, "utf-8");
39
- } catch {
40
- throw new Error(`Prompt template not found: ${filePath}`);
41
- }
42
- }
43
- function applyPlaceholders(template, vars) {
44
- let result = template;
45
- for (const [key, value] of Object.entries(vars)) {
46
- result = result.replaceAll(key, value);
47
- }
48
- return result;
49
- }
7
+ } from "./chunk-WO6ERABE.js";
50
8
 
51
9
  // src/core/session-metrics.ts
52
10
  function elapsedSecondsSince(startedAtMs) {
@@ -109,9 +67,6 @@ function publishStoryMetrics(storyId, usage, durationSeconds) {
109
67
  }
110
68
 
111
69
  export {
112
- resolvePackageDir,
113
- loadPrompt,
114
- applyPlaceholders,
115
70
  elapsedSecondsSince,
116
71
  getRunUsageTotals,
117
72
  getPhaseUsageTotals,
@@ -120,4 +75,4 @@ export {
120
75
  publishIterationMetrics,
121
76
  publishStoryMetrics
122
77
  };
123
- //# sourceMappingURL=chunk-I7FTA5FI.js.map
78
+ //# sourceMappingURL=chunk-ZYBYMEWG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/session-metrics.ts"],"sourcesContent":["import { type ClaudeUsage, hasUsageData, sumUsage } from './metrics.js';\nimport { getSessionPublisher } from './session-publisher.js';\nimport { isoNow } from './state-manager.js';\n\n/**\n * Publication helper for the single-invocation phases (analyze, generate, prd,\n * plan, review, pr, pr-review).\n *\n * Each of them owns its own `runHeadless` call, so the metrics are published\n * from inside the command rather than from the `instrumentedRunners` wrapper in\n * run.ts: the wrapper only sees `() => Promise<void>` and never touches the\n * HeadlessResult. Publishing here also covers standalone invocations\n * (`issue-flow prd 42`), which never go through the pipeline wrapper at all.\n */\n\n/** Whole seconds elapsed since a `Date.now()` mark, never negative. */\nexport function elapsedSecondsSince(startedAtMs: number): number {\n return Math.max(0, Math.round((Date.now() - startedAtMs) / 1000));\n}\n\n/**\n * Process-owned totals, accumulated alongside every published event.\n *\n * The session snapshot cannot be the source of the terminal summary: with web\n * monitoring off the publisher is a `NullPublisher` and its snapshot stays\n * empty, so the numbers would silently vanish exactly in the default mode.\n * These counters live in the process and are therefore independent of any\n * publisher.\n *\n * Only phase- and iteration-scoped usage is recorded. Story metrics are a\n * rateio of an iteration already counted here — adding them would double the\n * totals, the same rule the reducer follows.\n */\nconst runTotals = {\n all: {} as ClaudeUsage,\n byPhase: new Map<string, ClaudeUsage>(),\n};\n\nfunction recordRunUsage(phase: string, usage: ClaudeUsage): void {\n runTotals.all = sumUsage(runTotals.all, usage);\n runTotals.byPhase.set(phase, sumUsage(runTotals.byPhase.get(phase), usage));\n}\n\n/** Everything this process has spent so far, across all phases. */\nexport function getRunUsageTotals(): ClaudeUsage {\n return { ...runTotals.all };\n}\n\n/** What a single phase has spent so far. Empty when the phase reported nothing. */\nexport function getPhaseUsageTotals(phase: string): ClaudeUsage {\n return { ...(runTotals.byPhase.get(phase) ?? {}) };\n}\n\n/** Test seam: the counters are module state and must be cleared between runs. */\nexport function resetRunUsageTotals(): void {\n runTotals.all = {};\n runTotals.byPhase.clear();\n}\n\n/**\n * Publish one `metrics:update` event of scope `phase` for a headless\n * invocation.\n *\n * A no-op when the CLI reported no usage at all: an event with every field\n * undefined would bump the snapshot version without adding information, and the\n * reducer would leave every counter at null anyway.\n *\n * A command with more than one invocation (the retrying phases, or `review`\n * inside a correction cycle) calls this once per invocation — the reducer's\n * summing is what turns them into the phase total.\n *\n * Never throws and never returns a value: publishing metrics must not be able\n * to change a command's exit code.\n */\nexport function publishPhaseMetrics(\n phase: string,\n usage: ClaudeUsage | null | undefined,\n startedAtMs?: number,\n): void {\n if (usage == null || !hasUsageData(usage)) return;\n\n recordRunUsage(phase, usage);\n\n getSessionPublisher().publish({\n type: 'metrics:update',\n at: isoNow(),\n scope: 'phase',\n phase,\n ...usage,\n // Informational only: a phase's durationSeconds keeps coming exclusively\n // from phase:start/phase:end, so the reducer ignores this for scope 'phase'.\n durationSeconds: startedAtMs === undefined ? undefined : elapsedSecondsSince(startedAtMs),\n });\n}\n\n/** The execute loop is the only producer of iteration- and story-scoped metrics. */\nexport const EXECUTE_PHASE = 'execute';\n\n/**\n * Publish one `metrics:update` event of scope `iteration` for a pass of the\n * execute loop.\n *\n * Lands on the `execute` phase and on the issue-wide aggregate, exactly like a\n * phase-scoped event: an iteration *is* a slice of the execute phase. Failed\n * iterations (transient or fatal) publish too — those tokens were spent.\n *\n * A no-op when the CLI reported no usage. Never throws.\n */\nexport function publishIterationMetrics(\n iteration: number,\n usage: ClaudeUsage | null | undefined,\n durationSeconds?: number,\n): void {\n if (usage == null || !hasUsageData(usage)) return;\n\n recordRunUsage(EXECUTE_PHASE, usage);\n\n getSessionPublisher().publish({\n type: 'metrics:update',\n at: isoNow(),\n scope: 'iteration',\n phase: EXECUTE_PHASE,\n iteration,\n ...usage,\n // Informational only: the reducer keeps a phase's durationSeconds coming\n // exclusively from phase:start/phase:end.\n durationSeconds,\n });\n}\n\n/**\n * Publish one `metrics:update` event of scope `story`.\n *\n * The values are the rateio of the iteration that completed the story, so the\n * reducer deliberately keeps them off the phase and the aggregate — the\n * iteration event of the same cycle already counted them there.\n *\n * A no-op when there is neither usage nor a duration to report. Never throws.\n */\nexport function publishStoryMetrics(\n storyId: string,\n usage: ClaudeUsage | null | undefined,\n durationSeconds?: number,\n): void {\n if (!hasUsageData(usage) && durationSeconds === undefined) return;\n\n getSessionPublisher().publish({\n type: 'metrics:update',\n at: isoNow(),\n scope: 'story',\n storyId,\n ...(usage ?? {}),\n durationSeconds,\n });\n}\n"],"mappings":";;;;;;;;;AAgBO,SAAS,oBAAoB,aAA6B;AAC/D,SAAO,KAAK,IAAI,GAAG,KAAK,OAAO,KAAK,IAAI,IAAI,eAAe,GAAI,CAAC;AAClE;AAeA,IAAM,YAAY;AAAA,EAChB,KAAK,CAAC;AAAA,EACN,SAAS,oBAAI,IAAyB;AACxC;AAEA,SAAS,eAAe,OAAe,OAA0B;AAC/D,YAAU,MAAM,SAAS,UAAU,KAAK,KAAK;AAC7C,YAAU,QAAQ,IAAI,OAAO,SAAS,UAAU,QAAQ,IAAI,KAAK,GAAG,KAAK,CAAC;AAC5E;AAGO,SAAS,oBAAiC;AAC/C,SAAO,EAAE,GAAG,UAAU,IAAI;AAC5B;AAGO,SAAS,oBAAoB,OAA4B;AAC9D,SAAO,EAAE,GAAI,UAAU,QAAQ,IAAI,KAAK,KAAK,CAAC,EAAG;AACnD;AAuBO,SAAS,oBACd,OACA,OACA,aACM;AACN,MAAI,SAAS,QAAQ,CAAC,aAAa,KAAK,EAAG;AAE3C,iBAAe,OAAO,KAAK;AAE3B,sBAAoB,EAAE,QAAQ;AAAA,IAC5B,MAAM;AAAA,IACN,IAAI,OAAO;AAAA,IACX,OAAO;AAAA,IACP;AAAA,IACA,GAAG;AAAA;AAAA;AAAA,IAGH,iBAAiB,gBAAgB,SAAY,SAAY,oBAAoB,WAAW;AAAA,EAC1F,CAAC;AACH;AAGO,IAAM,gBAAgB;AAYtB,SAAS,wBACd,WACA,OACA,iBACM;AACN,MAAI,SAAS,QAAQ,CAAC,aAAa,KAAK,EAAG;AAE3C,iBAAe,eAAe,KAAK;AAEnC,sBAAoB,EAAE,QAAQ;AAAA,IAC5B,MAAM;AAAA,IACN,IAAI,OAAO;AAAA,IACX,OAAO;AAAA,IACP,OAAO;AAAA,IACP;AAAA,IACA,GAAG;AAAA;AAAA;AAAA,IAGH;AAAA,EACF,CAAC;AACH;AAWO,SAAS,oBACd,SACA,OACA,iBACM;AACN,MAAI,CAAC,aAAa,KAAK,KAAK,oBAAoB,OAAW;AAE3D,sBAAoB,EAAE,QAAQ;AAAA,IAC5B,MAAM;AAAA,IACN,IAAI,OAAO;AAAA,IACX,OAAO;AAAA,IACP;AAAA,IACA,GAAI,SAAS,CAAC;AAAA,IACd;AAAA,EACF,CAAC;AACH;","names":[]}
package/dist/cli.js CHANGED
@@ -2,12 +2,12 @@
2
2
  import {
3
3
  setIssuesCliOverrides,
4
4
  setWebCliOverrides
5
- } from "./chunk-5NLLEWJD.js";
5
+ } from "./chunk-RUTDK6ZF.js";
6
6
  import {
7
7
  printError,
8
8
  setGlobalTimeout,
9
9
  setVerbose
10
- } from "./chunk-5Q2TMW7K.js";
10
+ } from "./chunk-WO6ERABE.js";
11
11
 
12
12
  // src/cli.ts
13
13
  import { createRequire } from "module";
@@ -159,8 +159,8 @@ withIssueOptions(
159
159
  program.command("init").description("Verify that all prerequisites (claude, gh, git) are met")
160
160
  )
161
161
  ).action(async () => {
162
- const { loadIssuesConfig } = await import("./config-U3FC4WH6.js");
163
- const { runInit } = await import("./init-IF72GR7O.js");
162
+ const { loadIssuesConfig } = await import("./config-OAZG7KIK.js");
163
+ const { runInit } = await import("./init-TBC3WI74.js");
164
164
  const { preferredProvider } = await loadIssuesConfig();
165
165
  const code = await runInit(preferredProvider);
166
166
  process.exit(code);
@@ -178,7 +178,7 @@ withGlobalOptions(
178
178
  }
179
179
  throw error;
180
180
  }
181
- const { runGenerate } = await import("./generate-RHPOWGDX.js");
181
+ const { runGenerate } = await import("./generate-BEJF3SVJ.js");
182
182
  const code = await runGenerate(options.prompt, target);
183
183
  process.exit(code);
184
184
  });
@@ -202,7 +202,7 @@ withWebOptions(
202
202
  }
203
203
  throw error;
204
204
  }
205
- const { runPipeline } = await import("./run-Z6ZDXZB6.js");
205
+ const { runPipeline } = await import("./run-SEB6F3W4.js");
206
206
  const code = await runPipeline(
207
207
  issue,
208
208
  options.mode,
@@ -218,7 +218,7 @@ withIssueOptions(
218
218
  program.command("analyze").description("Analyze an issue via Claude Code Headless").argument("<issue>", "Issue number")
219
219
  )
220
220
  ).action(async (issue) => {
221
- const { runAnalyze } = await import("./analyze-TNFTUMOO.js");
221
+ const { runAnalyze } = await import("./analyze-2RVGY5OK.js");
222
222
  const code = await runAnalyze(issue);
223
223
  process.exit(code);
224
224
  });
@@ -227,7 +227,7 @@ withIssueOptions(
227
227
  program.command("prd").description("Generate a PRD from an analyzed issue via Claude Code Headless").argument("<issue>", "Issue number")
228
228
  )
229
229
  ).action(async (issue) => {
230
- const { runPrd } = await import("./prd-2Y6RMVTF.js");
230
+ const { runPrd } = await import("./prd-MBSSBLGO.js");
231
231
  const code = await runPrd(issue);
232
232
  process.exit(code);
233
233
  });
@@ -236,7 +236,7 @@ withIssueOptions(
236
236
  program.command("plan").description("Convert a PRD to a tasks.json task plan via Claude Code Headless").argument("<issue>", "Issue number")
237
237
  )
238
238
  ).action(async (issue) => {
239
- const { runPlan } = await import("./plan-FITZZXZQ.js");
239
+ const { runPlan } = await import("./plan-YDZYB2BO.js");
240
240
  const code = await runPlan(issue);
241
241
  process.exit(code);
242
242
  });
@@ -255,7 +255,7 @@ withWebOptions(
255
255
  ).action(
256
256
  async (positionalMaxIter, options) => {
257
257
  try {
258
- const { runExecute } = await import("./execute-BNBA77WJ.js");
258
+ const { runExecute } = await import("./execute-AOEM52CD.js");
259
259
  const code = await runExecute(positionalMaxIter, options);
260
260
  process.exit(code);
261
261
  } catch (error) {
@@ -269,7 +269,7 @@ withIssueOptions(
269
269
  program.command("review").description("Validate an issue resolution via Claude Code Headless").argument("<issue>", "Issue number")
270
270
  )
271
271
  ).action(async (issue) => {
272
- const { runReview } = await import("./review-ITKXJMX3.js");
272
+ const { runReview } = await import("./review-2MDDHZ4X.js");
273
273
  const code = await runReview(issue);
274
274
  process.exit(code);
275
275
  });
@@ -278,10 +278,25 @@ withIssueOptions(
278
278
  program.command("pr").description("Create a pull request via Claude Code Headless").argument("<issue>", "Issue number")
279
279
  )
280
280
  ).action(async (issue) => {
281
- const { runPr } = await import("./pr-GO244D7M.js");
281
+ const { runPr } = await import("./pr-SIXMJIZF.js");
282
282
  const code = await runPr(issue);
283
283
  process.exit(code);
284
284
  });
285
+ var webCommand = program.command("web").description("Manage the web monitoring server");
286
+ webCommand.command("serve").description(
287
+ "Run the web monitor server in the foreground (internal \u2014 spawned detached by --web)"
288
+ ).option("--port <n>", "Web server port (default: 3737)", parseInteger).option("--host <h>", "Web server host (default: 0.0.0.0)").option("--refresh <s>", "Suggested UI polling interval in seconds", parseInteger).action(async (options) => {
289
+ const { runWebServe } = await import("./web-3SBYL7QJ.js");
290
+ const code = await runWebServe(options);
291
+ if (code !== 0) {
292
+ process.exit(code);
293
+ }
294
+ });
295
+ webCommand.command("stop").description("Stop the running web monitor server").action(async () => {
296
+ const { runWebStop } = await import("./web-3SBYL7QJ.js");
297
+ const code = await runWebStop();
298
+ process.exit(code);
299
+ });
285
300
  withGlobalOptions(
286
301
  program.command("pr-review").description("Review a Pull Request as a whole via Claude Code Headless").argument("[pr]", "Pull Request number (discovered from the session when omitted)").option("--issue <n>", "Issue the Pull Request belongs to").option("--round <n>", "Rewrite a specific review round instead of appending a new one").option("--yes", "Skip the confirmation of the discovered Pull Request").option(
287
302
  "--fail-on <level>",
@@ -289,7 +304,7 @@ withGlobalOptions(
289
304
  )
290
305
  ).action(
291
306
  async (pr, options) => {
292
- const { runPrReview } = await import("./pr-review-XY6VL62M.js");
307
+ const { runPrReview } = await import("./pr-review-AY56ZPJR.js");
293
308
  const code = await runPrReview(pr, options);
294
309
  process.exit(code);
295
310
  }