issue-flow 0.8.0 → 0.10.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.
- package/README.md +16 -0
- package/dist/{analyze-RBNKXWL5.js → analyze-UZYJAWAP.js} +6 -6
- package/dist/{chunk-J4SICTAN.js → chunk-2LNQL4XH.js} +53 -9
- package/dist/chunk-2LNQL4XH.js.map +1 -0
- package/dist/{chunk-A4M6AKIK.js → chunk-2Z7OEZZT.js} +6 -6
- package/dist/{chunk-F52S6ZGF.js → chunk-652DJBCN.js} +17 -3
- package/dist/chunk-652DJBCN.js.map +1 -0
- package/dist/{chunk-TGP6WK65.js → chunk-67ZO5ROQ.js} +75 -11
- package/dist/chunk-67ZO5ROQ.js.map +1 -0
- package/dist/{chunk-VSF2CR3G.js → chunk-EWA3OGCM.js} +6 -6
- package/dist/{chunk-VPDWTZ3F.js → chunk-EZMOEBLU.js} +2 -2
- package/dist/{chunk-JWZKV3CL.js → chunk-MXTGCD5C.js} +2 -2
- package/dist/{chunk-QO2MWMAZ.js → chunk-PPLIXZI5.js} +33 -10
- package/dist/chunk-PPLIXZI5.js.map +1 -0
- package/dist/{chunk-HSGTZ3CT.js → chunk-S67Q4I2O.js} +219 -3
- package/dist/chunk-S67Q4I2O.js.map +1 -0
- package/dist/chunk-SP7DD4ID.js +265 -0
- package/dist/chunk-SP7DD4ID.js.map +1 -0
- package/dist/{chunk-36ECROTC.js → chunk-UH4KAJBJ.js} +7 -7
- package/dist/{chunk-RQW5W4XC.js → chunk-UUY4PCWW.js} +2 -2
- package/dist/{chunk-BH7DYADV.js → chunk-V5OHFSW4.js} +319 -181
- package/dist/chunk-V5OHFSW4.js.map +1 -0
- package/dist/{chunk-J3PJBZXJ.js → chunk-VOKNTBOO.js} +3 -3
- package/dist/{chunk-BASRRXAM.js → chunk-VYFKOUUR.js} +222 -6
- package/dist/chunk-VYFKOUUR.js.map +1 -0
- package/dist/cli.js +98 -29
- package/dist/cli.js.map +1 -1
- package/dist/{config-R7O5YC65.js → config-YCF3HIKR.js} +7 -3
- package/dist/execute-QYXA225W.js +14 -0
- package/dist/{generate-TGJQLGMV.js → generate-TW6QKSW5.js} +6 -6
- package/dist/init-G57UXKDG.js +9 -0
- package/dist/plan-YJLGBYBQ.js +16 -0
- package/dist/policy-NP4ZAVF6.js +1013 -0
- package/dist/policy-NP4ZAVF6.js.map +1 -0
- package/dist/pr-V5H65MRW.js +20 -0
- package/dist/pr-review-RCMXKXVP.js +16 -0
- package/dist/prd-3XR5KYJS.js +16 -0
- package/dist/review-ZZG3CSUO.js +14 -0
- package/dist/run-DIBC3YKX.js +1860 -0
- package/dist/run-DIBC3YKX.js.map +1 -0
- package/dist/{web-JVPK2PIF.js → web-O2FVOYZW.js} +4 -4
- package/package.json +1 -1
- package/prompts/execute.md +2 -2
- package/prompts/plan.md +10 -1
- package/prompts/pr.md +9 -4
- package/web/CLAUDE.md +22 -8
- package/web/public/app.css +178 -0
- package/web/public/app.js +336 -17
- package/web/public/index.html +24 -0
- package/dist/chunk-BASRRXAM.js.map +0 -1
- package/dist/chunk-BH7DYADV.js.map +0 -1
- package/dist/chunk-F52S6ZGF.js.map +0 -1
- package/dist/chunk-HSGTZ3CT.js.map +0 -1
- package/dist/chunk-J4SICTAN.js.map +0 -1
- package/dist/chunk-MK43N4X3.js +0 -123
- package/dist/chunk-MK43N4X3.js.map +0 -1
- package/dist/chunk-QO2MWMAZ.js.map +0 -1
- package/dist/chunk-TGP6WK65.js.map +0 -1
- package/dist/execute-LU3HWA54.js +0 -14
- package/dist/init-WP7Z2LFQ.js +0 -9
- package/dist/plan-2MWMCKRY.js +0 -16
- package/dist/pr-XVQDEQ7B.js +0 -16
- package/dist/pr-review-VO6ZXAFM.js +0 -16
- package/dist/prd-FPZAHQEO.js +0 -16
- package/dist/review-AHALLMXO.js +0 -14
- package/dist/run-2CXVR3VZ.js +0 -733
- package/dist/run-2CXVR3VZ.js.map +0 -1
- /package/dist/{analyze-RBNKXWL5.js.map → analyze-UZYJAWAP.js.map} +0 -0
- /package/dist/{chunk-A4M6AKIK.js.map → chunk-2Z7OEZZT.js.map} +0 -0
- /package/dist/{chunk-VSF2CR3G.js.map → chunk-EWA3OGCM.js.map} +0 -0
- /package/dist/{chunk-VPDWTZ3F.js.map → chunk-EZMOEBLU.js.map} +0 -0
- /package/dist/{chunk-JWZKV3CL.js.map → chunk-MXTGCD5C.js.map} +0 -0
- /package/dist/{chunk-36ECROTC.js.map → chunk-UH4KAJBJ.js.map} +0 -0
- /package/dist/{chunk-RQW5W4XC.js.map → chunk-UUY4PCWW.js.map} +0 -0
- /package/dist/{chunk-J3PJBZXJ.js.map → chunk-VOKNTBOO.js.map} +0 -0
- /package/dist/{config-R7O5YC65.js.map → config-YCF3HIKR.js.map} +0 -0
- /package/dist/{execute-LU3HWA54.js.map → execute-QYXA225W.js.map} +0 -0
- /package/dist/{generate-TGJQLGMV.js.map → generate-TW6QKSW5.js.map} +0 -0
- /package/dist/{init-WP7Z2LFQ.js.map → init-G57UXKDG.js.map} +0 -0
- /package/dist/{plan-2MWMCKRY.js.map → plan-YJLGBYBQ.js.map} +0 -0
- /package/dist/{pr-XVQDEQ7B.js.map → pr-V5H65MRW.js.map} +0 -0
- /package/dist/{pr-review-VO6ZXAFM.js.map → pr-review-RCMXKXVP.js.map} +0 -0
- /package/dist/{prd-FPZAHQEO.js.map → prd-3XR5KYJS.js.map} +0 -0
- /package/dist/{review-AHALLMXO.js.map → review-ZZG3CSUO.js.map} +0 -0
- /package/dist/{web-JVPK2PIF.js.map → web-O2FVOYZW.js.map} +0 -0
|
@@ -0,0 +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 selectActiveStory,\n setLastError,\n trimErrorMessage,\n} from './state-manager.js';\nimport {\n getOutputCallback,\n getStoryStageCallback,\n getStoryUpdateCallback,\n isVerbose,\n} 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 * Commit message formats handed to the execute prompt.\n *\n * Without a scope they are byte-for-byte what the prompt has always spelled\n * out, so a single-issue run produces the same history it always did. With one\n * — the only case being several issues sharing a branch — the issue becomes a\n * conventional-commit scope, which is what makes `git log` on the shared branch\n * readable per issue.\n */\nexport function commitPlaceholders(scope?: string): Record<string, string> {\n const suffix = scope === undefined || scope === '' ? '' : `(${scope})`;\n return {\n __COMMIT_MESSAGE__: `feat${suffix}: [Story ID] - [Story Title]`,\n __FIX_COMMIT_MESSAGE__: `fix${suffix}: address review findings`,\n };\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\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 // Highest-priority story with passes: false — the same rule\n // prompts/execute.md gives the agent. Computed once, here, and shared by\n // the published event and the verbose terminal header, so every surface\n // agrees on who is active instead of each deriving its own heuristic.\n const activeStoryId = selectActiveStory(plan.userStories)?.id;\n getSessionPublisher().publish({\n type: 'iteration:start',\n at: isoNow(),\n iteration: i,\n storyId: activeStoryId,\n });\n getStoryStageCallback()?.(activeStoryId);\n\n if (isVerbose()) {\n printIterationHeader(i, config.maxIterations, plan.userStories, activeStoryId);\n }\n\n // Apply placeholders to prompt\n const prompt = applyPlaceholders(promptTemplate, {\n __PRD_FILE__: paths.prdFile,\n __PROGRESS_FILE__: paths.progressFile,\n ...commitPlaceholders(config.commitScope),\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 *\n * `activeStoryId`, when provided, identifies the story the engine is\n * actually about to work on this iteration (the same id published on\n * `iteration:start` \\u2014 see `core/state-manager.ts`'s `selectActiveStory`),\n * so the header agrees with the session snapshot instead of guessing.\n * Omitted, it falls back to the previous heuristic (\"first non-passing story\n * in array order\"), which existing callers that cannot supply the real id\n * keep relying on.\n */\nexport function printIterationHeader(\n iteration: number,\n maxIter: number | undefined,\n stories: UserStory[],\n activeStoryId?: string,\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 const isActive =\n activeStoryId !== undefined\n ? story.id === activeStoryId\n : !story.passes && !foundFirstPending;\n\n if (story.passes) {\n icon = icons.success;\n colorFn = colored ? chalk.green : (s: string) => s;\n } else if (isActive) {\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/** One issue of a multi-issue queue, as the consolidated summary reports it. */\nexport interface QueueIssueSummary {\n id: string;\n title: string;\n storyCount: number;\n elapsedSeconds: number;\n /** Tokens and cost of this issue alone — never of the whole queue. */\n usage?: ClaudeUsage | null;\n}\n\n/** Everything the consolidated summary of a queue prints. */\nexport interface QueueSummaryInfo {\n /** Identifier of the queue, i.e. of its primary issue. */\n queueId: string;\n /** Branch every issue shared, `null` when the queue ran with --no-branch. */\n branchName: string | null;\n issues: QueueIssueSummary[];\n /** Issues discovered but left out of the run. */\n excluded: { id: string; title: string }[];\n elapsedSeconds: number;\n /** The single consolidated Pull Request, `null` when none was opened. */\n prUrl: string | null;\n /** Tokens and cost of the whole queue. */\n usage?: ClaudeUsage | null;\n /** Verdict of the pr-review phase, when the queue ran it. */\n prReview?: RunSummaryPrReview | null;\n}\n\n/**\n * Detail lines of a queue's final summary.\n *\n * Per-issue lines carry their own duration and cost, which is the visible half\n * of the per-issue metric scoping: what one issue spent is never folded into\n * another's line.\n */\nexport function buildQueueSummaryLines(info: QueueSummaryInfo): string[] {\n const lines: string[] = [];\n\n lines.push(` Branch: ${info.branchName ?? 'current'}`);\n lines.push(` Issues: ${info.issues.length}`);\n\n for (const issue of info.issues) {\n const tokens = formatTokens(issue.usage);\n const title = issue.title === '' ? '' : ` ${issue.title}`;\n lines.push(\n ` #${issue.id}${title} — ${issue.storyCount} stories, ${formatDuration(issue.elapsedSeconds)}` +\n (tokens === '' ? '' : `, ${tokens}`),\n );\n }\n\n lines.push(` Duration: ${formatDuration(info.elapsedSeconds)}`);\n\n const tokens = formatTokens(info.usage);\n if (tokens !== '') {\n lines.push(` Tokens: ${tokens}`);\n }\n if (info.prUrl !== null) {\n lines.push(` PR: ${info.prUrl}`);\n }\n if (info.excluded.length > 0) {\n lines.push(` Skipped: ${info.excluded.map((entry) => `#${entry.id}`).join(', ')}`);\n }\n\n const review = info.prReview;\n if (review) {\n lines.push(\n ` Review: ${review.recommendation ?? (review.requestedChanges ? 'REQUEST_CHANGES' : 'unknown')}`,\n );\n if (review.reportPath !== null) {\n lines.push(` Report: ${review.reportPath}`);\n }\n }\n\n return lines;\n}\n\n/** Print the consolidated summary of a multi-issue queue. */\nexport function printQueueSummary(info: QueueSummaryInfo): void {\n console.log('');\n if (info.prReview?.requestedChanges === true) {\n printWarning(`Queue finished for issue #${info.queueId}, but the PR review requested changes.`);\n } else {\n printSuccess(`Queue complete for issue #${info.queueId} (${info.issues.length} issues)!`);\n }\n for (const line of buildQueueSummaryLines(info)) {\n console.log(line);\n }\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 * Conventional-commit scope for the stories of this issue (`issue-71`).\n * Only the multi-issue queue sets it — see `EngineConfig.commitScope`.\n */\n commitScope?: string;\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 commitScope: options.commitScope,\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","tokens","elapsed"]}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
listPullRequests
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-VOKNTBOO.js";
|
|
5
5
|
import {
|
|
6
6
|
runHeadless
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-MXTGCD5C.js";
|
|
8
8
|
import {
|
|
9
9
|
publishPhaseMetrics
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-PPLIXZI5.js";
|
|
11
11
|
import {
|
|
12
12
|
applyPlaceholders,
|
|
13
13
|
loadPrompt
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
loadPrReviewConfig,
|
|
18
18
|
resolveIssuePaths,
|
|
19
19
|
run
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-VYFKOUUR.js";
|
|
21
21
|
import {
|
|
22
22
|
getGlobalTimeout,
|
|
23
23
|
getSessionPublisher,
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
printWarning,
|
|
30
30
|
saveTaskPlan,
|
|
31
31
|
writeFileAtomic
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-V5OHFSW4.js";
|
|
33
33
|
|
|
34
34
|
// src/commands/pr-review.ts
|
|
35
35
|
import { join as join3 } from "path";
|
|
@@ -733,4 +733,4 @@ export {
|
|
|
733
733
|
exitCodeFor,
|
|
734
734
|
runPrReview
|
|
735
735
|
};
|
|
736
|
-
//# sourceMappingURL=chunk-
|
|
736
|
+
//# sourceMappingURL=chunk-EWA3OGCM.js.map
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
printInfo,
|
|
5
5
|
printSuccess,
|
|
6
6
|
printWarning
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-V5OHFSW4.js";
|
|
8
8
|
|
|
9
9
|
// src/commands/init.ts
|
|
10
10
|
import { execa } from "execa";
|
|
@@ -132,4 +132,4 @@ async function runInit(source = "github") {
|
|
|
132
132
|
export {
|
|
133
133
|
runInit
|
|
134
134
|
};
|
|
135
|
-
//# sourceMappingURL=chunk-
|
|
135
|
+
//# sourceMappingURL=chunk-EZMOEBLU.js.map
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
isoNow,
|
|
11
11
|
parseUsage,
|
|
12
12
|
useColor
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-V5OHFSW4.js";
|
|
14
14
|
|
|
15
15
|
// src/core/headless.ts
|
|
16
16
|
import { createInterface } from "readline";
|
|
@@ -279,4 +279,4 @@ async function runHeadless(options) {
|
|
|
279
279
|
export {
|
|
280
280
|
runHeadless
|
|
281
281
|
};
|
|
282
|
-
//# sourceMappingURL=chunk-
|
|
282
|
+
//# sourceMappingURL=chunk-MXTGCD5C.js.map
|
|
@@ -4,25 +4,47 @@ import {
|
|
|
4
4
|
hasUsageData,
|
|
5
5
|
isoNow,
|
|
6
6
|
sumUsage
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-V5OHFSW4.js";
|
|
8
8
|
|
|
9
9
|
// src/core/session-metrics.ts
|
|
10
10
|
function elapsedSecondsSince(startedAtMs) {
|
|
11
11
|
return Math.max(0, Math.round((Date.now() - startedAtMs) / 1e3));
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
all: {},
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
function createAccumulator() {
|
|
14
|
+
return { all: {}, byPhase: /* @__PURE__ */ new Map() };
|
|
15
|
+
}
|
|
16
|
+
var scopes = [createAccumulator()];
|
|
17
|
+
function current() {
|
|
18
|
+
return scopes[scopes.length - 1];
|
|
19
|
+
}
|
|
17
20
|
function recordRunUsage(phase, usage) {
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
for (const scope of scopes) {
|
|
22
|
+
scope.all = sumUsage(scope.all, usage);
|
|
23
|
+
scope.byPhase.set(phase, sumUsage(scope.byPhase.get(phase), usage));
|
|
24
|
+
}
|
|
20
25
|
}
|
|
21
26
|
function getRunUsageTotals() {
|
|
22
|
-
return { ...
|
|
27
|
+
return { ...current().all };
|
|
23
28
|
}
|
|
24
29
|
function getPhaseUsageTotals(phase) {
|
|
25
|
-
return { ...
|
|
30
|
+
return { ...current().byPhase.get(phase) ?? {} };
|
|
31
|
+
}
|
|
32
|
+
function beginUsageScope() {
|
|
33
|
+
const accumulator = createAccumulator();
|
|
34
|
+
scopes.push(accumulator);
|
|
35
|
+
let open = true;
|
|
36
|
+
return {
|
|
37
|
+
totals: () => ({ ...accumulator.all }),
|
|
38
|
+
phaseTotals: (phase) => ({ ...accumulator.byPhase.get(phase) ?? {} }),
|
|
39
|
+
end: () => {
|
|
40
|
+
if (open) {
|
|
41
|
+
const index = scopes.indexOf(accumulator);
|
|
42
|
+
if (index > 0) scopes.splice(index, 1);
|
|
43
|
+
open = false;
|
|
44
|
+
}
|
|
45
|
+
return { ...accumulator.all };
|
|
46
|
+
}
|
|
47
|
+
};
|
|
26
48
|
}
|
|
27
49
|
function publishPhaseMetrics(phase, usage, startedAtMs) {
|
|
28
50
|
if (usage == null || !hasUsageData(usage)) return;
|
|
@@ -70,9 +92,10 @@ export {
|
|
|
70
92
|
elapsedSecondsSince,
|
|
71
93
|
getRunUsageTotals,
|
|
72
94
|
getPhaseUsageTotals,
|
|
95
|
+
beginUsageScope,
|
|
73
96
|
publishPhaseMetrics,
|
|
74
97
|
EXECUTE_PHASE,
|
|
75
98
|
publishIterationMetrics,
|
|
76
99
|
publishStoryMetrics
|
|
77
100
|
};
|
|
78
|
-
//# sourceMappingURL=chunk-
|
|
101
|
+
//# sourceMappingURL=chunk-PPLIXZI5.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 */\ninterface UsageAccumulator {\n all: ClaudeUsage;\n byPhase: Map<string, ClaudeUsage>;\n}\n\nfunction createAccumulator(): UsageAccumulator {\n return { all: {}, byPhase: new Map() };\n}\n\n/**\n * Stack of active accumulators, innermost last.\n *\n * The bottom entry is the process total and always exists. A multi-issue run\n * pushes one accumulator per issue (and one for the whole queue) so the terminal\n * summary of issue B never inherits what issue A spent — the leak `core/CLAUDE.md`\n * warned about when it stated that the module-level counters were safe \"only\n * under the current one-issue-per-process model\".\n *\n * Every publication feeds **all** active accumulators, so a scope is a view of\n * the same events rather than a redirection of them.\n */\nconst scopes: UsageAccumulator[] = [createAccumulator()];\n\n/** Innermost active scope: what `run` and the engine report right now. */\nfunction current(): UsageAccumulator {\n return scopes[scopes.length - 1] as UsageAccumulator;\n}\n\nfunction recordRunUsage(phase: string, usage: ClaudeUsage): void {\n for (const scope of scopes) {\n scope.all = sumUsage(scope.all, usage);\n scope.byPhase.set(phase, sumUsage(scope.byPhase.get(phase), usage));\n }\n}\n\n/** Everything the innermost active scope has spent, across all phases. */\nexport function getRunUsageTotals(): ClaudeUsage {\n return { ...current().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 { ...(current().byPhase.get(phase) ?? {}) };\n}\n\n/** A usage scope opened by {@link beginUsageScope}. */\nexport interface UsageScope {\n /** Everything published while this scope was open. */\n totals(): ClaudeUsage;\n /** What one phase published while this scope was open. */\n phaseTotals(phase: string): ClaudeUsage;\n /** Close the scope. Idempotent, and safe to call out of order. */\n end(): ClaudeUsage;\n}\n\n/**\n * Open a nested usage scope, for a run that processes several issues in one\n * process.\n *\n * The scope accumulates only what is published while it is open, which is what\n * lets a queue report a per-issue cost and a consolidated total from the same\n * stream of events. Closing it never discards anything: the outer scopes were\n * fed in parallel.\n */\nexport function beginUsageScope(): UsageScope {\n const accumulator = createAccumulator();\n scopes.push(accumulator);\n let open = true;\n\n return {\n totals: () => ({ ...accumulator.all }),\n phaseTotals: (phase: string) => ({ ...(accumulator.byPhase.get(phase) ?? {}) }),\n end: () => {\n if (open) {\n const index = scopes.indexOf(accumulator);\n // Never pop the process total, even if a caller double-ends a scope.\n if (index > 0) scopes.splice(index, 1);\n open = false;\n }\n return { ...accumulator.all };\n },\n };\n}\n\n/** Test seam: the counters are module state and must be cleared between runs. */\nexport function resetRunUsageTotals(): void {\n scopes.splice(0, scopes.length, createAccumulator());\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;AAoBA,SAAS,oBAAsC;AAC7C,SAAO,EAAE,KAAK,CAAC,GAAG,SAAS,oBAAI,IAAI,EAAE;AACvC;AAcA,IAAM,SAA6B,CAAC,kBAAkB,CAAC;AAGvD,SAAS,UAA4B;AACnC,SAAO,OAAO,OAAO,SAAS,CAAC;AACjC;AAEA,SAAS,eAAe,OAAe,OAA0B;AAC/D,aAAW,SAAS,QAAQ;AAC1B,UAAM,MAAM,SAAS,MAAM,KAAK,KAAK;AACrC,UAAM,QAAQ,IAAI,OAAO,SAAS,MAAM,QAAQ,IAAI,KAAK,GAAG,KAAK,CAAC;AAAA,EACpE;AACF;AAGO,SAAS,oBAAiC;AAC/C,SAAO,EAAE,GAAG,QAAQ,EAAE,IAAI;AAC5B;AAGO,SAAS,oBAAoB,OAA4B;AAC9D,SAAO,EAAE,GAAI,QAAQ,EAAE,QAAQ,IAAI,KAAK,KAAK,CAAC,EAAG;AACnD;AAqBO,SAAS,kBAA8B;AAC5C,QAAM,cAAc,kBAAkB;AACtC,SAAO,KAAK,WAAW;AACvB,MAAI,OAAO;AAEX,SAAO;AAAA,IACL,QAAQ,OAAO,EAAE,GAAG,YAAY,IAAI;AAAA,IACpC,aAAa,CAAC,WAAmB,EAAE,GAAI,YAAY,QAAQ,IAAI,KAAK,KAAK,CAAC,EAAG;AAAA,IAC7E,KAAK,MAAM;AACT,UAAI,MAAM;AACR,cAAM,QAAQ,OAAO,QAAQ,WAAW;AAExC,YAAI,QAAQ,EAAG,QAAO,OAAO,OAAO,CAAC;AACrC,eAAO;AAAA,MACT;AACA,aAAO,EAAE,GAAG,YAAY,IAAI;AAAA,IAC9B;AAAA,EACF;AACF;AAsBO,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":[]}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
resolveIssuePaths,
|
|
6
6
|
resolveProjectPaths,
|
|
7
7
|
run
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-VYFKOUUR.js";
|
|
9
9
|
import {
|
|
10
10
|
isoNow,
|
|
11
11
|
issueMetadataSchema,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
printInfo,
|
|
14
14
|
printWarning,
|
|
15
15
|
writeFileAtomic
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-V5OHFSW4.js";
|
|
17
17
|
|
|
18
18
|
// src/issues/registry.ts
|
|
19
19
|
var providers = /* @__PURE__ */ new Map();
|
|
@@ -47,6 +47,131 @@ function hashIssueContent(title, body) {
|
|
|
47
47
|
return `sha256:${digest}`;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
// src/issues/relations.ts
|
|
51
|
+
function emptyRelations(id) {
|
|
52
|
+
return {
|
|
53
|
+
id,
|
|
54
|
+
parent: null,
|
|
55
|
+
children: [],
|
|
56
|
+
blockedBy: [],
|
|
57
|
+
blocking: [],
|
|
58
|
+
references: [],
|
|
59
|
+
referencedBy: [],
|
|
60
|
+
heuristic: []
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function uniqueIds(ids, self) {
|
|
64
|
+
const seen = /* @__PURE__ */ new Set();
|
|
65
|
+
const out = [];
|
|
66
|
+
for (const raw of ids) {
|
|
67
|
+
const id = raw.trim();
|
|
68
|
+
if (id === "" || id === self || seen.has(id)) continue;
|
|
69
|
+
seen.add(id);
|
|
70
|
+
out.push(id);
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
function stripCode(body) {
|
|
75
|
+
return body.replace(/```[\s\S]*?```/g, " ").replace(/`[^`\n]*`/g, " ");
|
|
76
|
+
}
|
|
77
|
+
var BLOCKED_BY = /\b(?:depends[- ]on|depends upon|blocked[- ]by|requires|depende[ -]de|dependente[ -]de|bloquead[ao] por|requer)\b\s*:?\s*/gi;
|
|
78
|
+
var BLOCKING = /\b(?:blocks|blocking|bloqueia|bloqueando)\b\s*:?\s*/gi;
|
|
79
|
+
var TASK_ITEM = /^[ \t]*[-*]\s*\[[ xX]\]\s*(.*)$/gm;
|
|
80
|
+
var ANY_ID = /(?:^|[^\w#])#(\d+)\b/g;
|
|
81
|
+
function idsFrom(text) {
|
|
82
|
+
const ids = [];
|
|
83
|
+
const id = /#(\d+)/y;
|
|
84
|
+
let cursor = 0;
|
|
85
|
+
while (cursor < text.length) {
|
|
86
|
+
id.lastIndex = cursor;
|
|
87
|
+
const match = id.exec(text);
|
|
88
|
+
if (match === null) break;
|
|
89
|
+
ids.push(match[1]);
|
|
90
|
+
cursor = id.lastIndex;
|
|
91
|
+
const gloss = /^\s*\([^)]*\)/.exec(text.slice(cursor));
|
|
92
|
+
if (gloss !== null) {
|
|
93
|
+
cursor += gloss[0].length;
|
|
94
|
+
}
|
|
95
|
+
const separator = /^\s*(?:,|and\b|e\b|&)\s*/i.exec(text.slice(cursor));
|
|
96
|
+
if (separator === null) break;
|
|
97
|
+
cursor += separator[0].length;
|
|
98
|
+
}
|
|
99
|
+
return ids;
|
|
100
|
+
}
|
|
101
|
+
function collect(body, keyword) {
|
|
102
|
+
const ids = [];
|
|
103
|
+
keyword.lastIndex = 0;
|
|
104
|
+
let match = keyword.exec(body);
|
|
105
|
+
while (match !== null) {
|
|
106
|
+
ids.push(...idsFrom(body.slice(keyword.lastIndex)));
|
|
107
|
+
match = keyword.exec(body);
|
|
108
|
+
}
|
|
109
|
+
return ids;
|
|
110
|
+
}
|
|
111
|
+
function parseTextualRelations(body, self) {
|
|
112
|
+
const text = stripCode(body ?? "");
|
|
113
|
+
const blockedBy = collect(text, BLOCKED_BY);
|
|
114
|
+
const blocking = collect(text, BLOCKING);
|
|
115
|
+
const children = [];
|
|
116
|
+
TASK_ITEM.lastIndex = 0;
|
|
117
|
+
let item = TASK_ITEM.exec(text);
|
|
118
|
+
while (item !== null) {
|
|
119
|
+
const content = item[1] ?? "";
|
|
120
|
+
const first = /^\s*\[?#(\d+)\b/.exec(content);
|
|
121
|
+
if (first?.[1] !== void 0) {
|
|
122
|
+
children.push(first[1]);
|
|
123
|
+
}
|
|
124
|
+
item = TASK_ITEM.exec(text);
|
|
125
|
+
}
|
|
126
|
+
const classified = /* @__PURE__ */ new Set([...blockedBy, ...blocking, ...children]);
|
|
127
|
+
const references = [];
|
|
128
|
+
ANY_ID.lastIndex = 0;
|
|
129
|
+
let cited = ANY_ID.exec(text);
|
|
130
|
+
while (cited !== null) {
|
|
131
|
+
const id = cited[1];
|
|
132
|
+
if (!classified.has(id)) {
|
|
133
|
+
references.push(id);
|
|
134
|
+
}
|
|
135
|
+
ANY_ID.lastIndex = Math.max(ANY_ID.lastIndex - 1, cited.index + 1);
|
|
136
|
+
cited = ANY_ID.exec(text);
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
children: uniqueIds(children, self),
|
|
140
|
+
blockedBy: uniqueIds(blockedBy, self),
|
|
141
|
+
blocking: uniqueIds(blocking, self),
|
|
142
|
+
references: uniqueIds(references, self)
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function mergeRelations(structured, textual) {
|
|
146
|
+
const self = structured.id;
|
|
147
|
+
const children = uniqueIds([...structured.children, ...textual.children], self);
|
|
148
|
+
const blockedBy = uniqueIds([...structured.blockedBy, ...textual.blockedBy], self);
|
|
149
|
+
const blocking = uniqueIds([...structured.blocking, ...textual.blocking], self);
|
|
150
|
+
const references = uniqueIds([...structured.references, ...textual.references], self);
|
|
151
|
+
const fromApi = /* @__PURE__ */ new Set([
|
|
152
|
+
...structured.children,
|
|
153
|
+
...structured.blockedBy,
|
|
154
|
+
...structured.blocking,
|
|
155
|
+
...structured.parent === null ? [] : [structured.parent]
|
|
156
|
+
]);
|
|
157
|
+
const heuristic = uniqueIds(
|
|
158
|
+
[...textual.children, ...textual.blockedBy, ...textual.blocking].filter(
|
|
159
|
+
(id) => !fromApi.has(id)
|
|
160
|
+
),
|
|
161
|
+
self
|
|
162
|
+
);
|
|
163
|
+
return {
|
|
164
|
+
id: self,
|
|
165
|
+
parent: structured.parent,
|
|
166
|
+
children,
|
|
167
|
+
blockedBy,
|
|
168
|
+
blocking,
|
|
169
|
+
references,
|
|
170
|
+
referencedBy: uniqueIds(structured.referencedBy, self),
|
|
171
|
+
heuristic
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
50
175
|
// src/issues/providers/github.ts
|
|
51
176
|
var VIEW_FIELDS = "number,title,body,labels,state,url,createdAt,updatedAt";
|
|
52
177
|
var PROBE_TIMEOUT_MS = 1e4;
|
|
@@ -81,6 +206,43 @@ function toIssue(payload) {
|
|
|
81
206
|
function normalizeId(id) {
|
|
82
207
|
return id.trim().replace(/^#/, "");
|
|
83
208
|
}
|
|
209
|
+
var API_PAGE_SIZE = 100;
|
|
210
|
+
function isRecord(value) {
|
|
211
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
212
|
+
}
|
|
213
|
+
async function ghApiJson(path) {
|
|
214
|
+
try {
|
|
215
|
+
const result = await run("gh", ["api", path]);
|
|
216
|
+
if (result.exitCode !== 0) return null;
|
|
217
|
+
return JSON.parse(result.stdout);
|
|
218
|
+
} catch {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function issueNumberOf(value) {
|
|
223
|
+
if (!isRecord(value)) return null;
|
|
224
|
+
const number = value.number;
|
|
225
|
+
return typeof number === "number" && Number.isInteger(number) ? String(number) : null;
|
|
226
|
+
}
|
|
227
|
+
function issueNumbersOf(value) {
|
|
228
|
+
if (!Array.isArray(value)) return [];
|
|
229
|
+
return uniqueIds(
|
|
230
|
+
value.map((entry) => issueNumberOf(entry) ?? "").filter((entry) => entry !== "")
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
function crossReferences(timeline, self) {
|
|
234
|
+
if (!Array.isArray(timeline)) return [];
|
|
235
|
+
const ids = [];
|
|
236
|
+
for (const event of timeline) {
|
|
237
|
+
if (!isRecord(event) || event.event !== "cross-referenced") continue;
|
|
238
|
+
const source = isRecord(event.source) ? event.source : null;
|
|
239
|
+
const issue = isRecord(source?.issue) ? source.issue : null;
|
|
240
|
+
if (issue === null || issue.pull_request !== void 0) continue;
|
|
241
|
+
const number = issueNumberOf(issue);
|
|
242
|
+
if (number !== null) ids.push(number);
|
|
243
|
+
}
|
|
244
|
+
return uniqueIds(ids, self);
|
|
245
|
+
}
|
|
84
246
|
function extractIssueNumber(output) {
|
|
85
247
|
return output.match(/\/issues\/(\d+)/)?.[1] ?? null;
|
|
86
248
|
}
|
|
@@ -139,6 +301,58 @@ var GitHubIssueProvider = class {
|
|
|
139
301
|
}
|
|
140
302
|
return created;
|
|
141
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Hierarchy and dependencies of an Issue, reconciled from three GitHub
|
|
306
|
+
* mechanisms that only partially overlap:
|
|
307
|
+
*
|
|
308
|
+
* 1. **Sub-issues** (`/sub_issues` plus the `parent` field of the Issue
|
|
309
|
+
* payload) — the native hierarchy;
|
|
310
|
+
* 2. **Issue Dependencies** (`/dependencies/blocked_by` and `/blocking`);
|
|
311
|
+
* 3. the **textual heuristic** over the body, for repositories that adopted
|
|
312
|
+
* neither and write `Depends on #12` or `- [ ] #13` instead.
|
|
313
|
+
*
|
|
314
|
+
* Cross-references (`referencedBy`) come from the issue timeline, which is
|
|
315
|
+
* the only place GitHub reports "who cited me" without a search query.
|
|
316
|
+
*
|
|
317
|
+
* Every source is queried independently and is allowed to fail on its own: an
|
|
318
|
+
* organization without Issue Dependencies enabled answers 404 for two of the
|
|
319
|
+
* five calls, and that has to cost the caller those two fields, never the
|
|
320
|
+
* whole discovery. Only what could be read is reported — nothing here throws.
|
|
321
|
+
*/
|
|
322
|
+
async fetchRelations(id) {
|
|
323
|
+
const issueId = normalizeId(id);
|
|
324
|
+
if (!/^\d+$/.test(issueId)) {
|
|
325
|
+
return emptyRelations(issueId);
|
|
326
|
+
}
|
|
327
|
+
const base = `repos/{owner}/{repo}/issues/${issueId}`;
|
|
328
|
+
const [payload, subIssues, blockedBy, blocking, timeline] = await Promise.all([
|
|
329
|
+
ghApiJson(base),
|
|
330
|
+
ghApiJson(`${base}/sub_issues?per_page=${API_PAGE_SIZE}`),
|
|
331
|
+
ghApiJson(`${base}/dependencies/blocked_by?per_page=${API_PAGE_SIZE}`),
|
|
332
|
+
ghApiJson(`${base}/dependencies/blocking?per_page=${API_PAGE_SIZE}`),
|
|
333
|
+
ghApiJson(`${base}/timeline?per_page=${API_PAGE_SIZE}`)
|
|
334
|
+
]);
|
|
335
|
+
const issue = isRecord(payload) ? payload : null;
|
|
336
|
+
const body = typeof issue?.body === "string" ? issue.body : await this.bodyFallback(issueId);
|
|
337
|
+
const structured = {
|
|
338
|
+
id: issueId,
|
|
339
|
+
parent: issueNumberOf(issue?.parent),
|
|
340
|
+
children: issueNumbersOf(subIssues),
|
|
341
|
+
blockedBy: issueNumbersOf(blockedBy),
|
|
342
|
+
blocking: issueNumbersOf(blocking),
|
|
343
|
+
references: [],
|
|
344
|
+
referencedBy: crossReferences(timeline, issueId)
|
|
345
|
+
};
|
|
346
|
+
return mergeRelations(structured, parseTextualRelations(body, issueId));
|
|
347
|
+
}
|
|
348
|
+
/** Body of an Issue when the REST payload could not be read. Never throws. */
|
|
349
|
+
async bodyFallback(issueId) {
|
|
350
|
+
try {
|
|
351
|
+
return (await this.get(issueId))?.body ?? "";
|
|
352
|
+
} catch {
|
|
353
|
+
return "";
|
|
354
|
+
}
|
|
355
|
+
}
|
|
142
356
|
async close(id) {
|
|
143
357
|
const issueId = normalizeId(id);
|
|
144
358
|
const result = await run("gh", ["issue", "close", issueId]);
|
|
@@ -747,10 +961,12 @@ async function resolveCommandIssue(id, preResolved, options) {
|
|
|
747
961
|
}
|
|
748
962
|
|
|
749
963
|
export {
|
|
964
|
+
emptyRelations,
|
|
965
|
+
uniqueIds,
|
|
750
966
|
getProvider,
|
|
751
967
|
ensureProvidersRegistered,
|
|
752
968
|
localIssueRef,
|
|
753
969
|
issuePlaceholders,
|
|
754
970
|
resolveCommandIssue
|
|
755
971
|
};
|
|
756
|
-
//# sourceMappingURL=chunk-
|
|
972
|
+
//# sourceMappingURL=chunk-S67Q4I2O.js.map
|