issue-flow 0.9.0 → 0.11.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/dist/{analyze-VSWAEVN3.js → analyze-7B5TORNK.js} +13 -8
- package/dist/analyze-7B5TORNK.js.map +1 -0
- package/dist/apply-WUJHDD7A.js +788 -0
- package/dist/apply-WUJHDD7A.js.map +1 -0
- package/dist/{chunk-NB6WKTBL.js → chunk-3BOHTH4V.js} +2 -2
- package/dist/chunk-3D4A4B46.js +2 -0
- package/dist/chunk-47VIN33V.js +983 -0
- package/dist/chunk-47VIN33V.js.map +1 -0
- package/dist/{chunk-DP43WJY4.js → chunk-6N7LZZ5U.js} +19 -13
- package/dist/chunk-6N7LZZ5U.js.map +1 -0
- package/dist/{chunk-KXAG24DD.js → chunk-AAY6KM7N.js} +6 -3
- package/dist/chunk-AAY6KM7N.js.map +1 -0
- package/dist/{chunk-VNKSMWDN.js → chunk-GANSDJV7.js} +4 -4
- package/dist/{chunk-57MH6RYJ.js → chunk-HC2MT7BX.js} +249 -181
- package/dist/chunk-HC2MT7BX.js.map +1 -0
- package/dist/chunk-HQYXVIH6.js +110 -0
- package/dist/chunk-HQYXVIH6.js.map +1 -0
- package/dist/{chunk-66ZGYXTH.js → chunk-JW7WLSZ6.js} +15 -11
- package/dist/chunk-JW7WLSZ6.js.map +1 -0
- package/dist/{chunk-M37YZYDO.js → chunk-LVKQ2AKT.js} +3 -3
- package/dist/{chunk-TLSIK656.js → chunk-MKD2D7WE.js} +12 -8
- package/dist/chunk-MKD2D7WE.js.map +1 -0
- package/dist/{chunk-WHEU55ZP.js → chunk-MSPUB635.js} +15 -11
- package/dist/chunk-MSPUB635.js.map +1 -0
- package/dist/chunk-OBLVLIUS.js +285 -0
- package/dist/chunk-OBLVLIUS.js.map +1 -0
- package/dist/{chunk-JGKPSWOA.js → chunk-PL6CNMVS.js} +2 -2
- package/dist/chunk-RKTEV6EG.js +246 -0
- package/dist/chunk-RKTEV6EG.js.map +1 -0
- package/dist/{chunk-TJMZWRS6.js → chunk-UBIQXCVE.js} +15 -11
- package/dist/chunk-UBIQXCVE.js.map +1 -0
- package/dist/{chunk-X7XBF5TS.js → chunk-VCKX262Z.js} +12 -8
- package/dist/chunk-VCKX262Z.js.map +1 -0
- package/dist/{chunk-MTJQOOMG.js → chunk-VQ5VN7DF.js} +123 -4
- package/dist/chunk-VQ5VN7DF.js.map +1 -0
- package/dist/cli.js +32 -21
- package/dist/cli.js.map +1 -1
- package/dist/{config-J5DW6HSY.js → config-4THHOXNN.js} +7 -3
- package/dist/execute-5OEKLM5X.js +15 -0
- package/dist/{generate-ERDPOTKX.js → generate-6RA2MFJB.js} +99 -12
- package/dist/generate-6RA2MFJB.js.map +1 -0
- package/dist/init-PRMOTQ7N.js +9 -0
- package/dist/plan-KENR6MJ3.js +17 -0
- package/dist/policy-XQW5NUIE.js +117 -0
- package/dist/policy-XQW5NUIE.js.map +1 -0
- package/dist/pr-KDGKG4BP.js +21 -0
- package/dist/pr-review-CAMAB7KO.js +17 -0
- package/dist/prd-V3MWDIHK.js +17 -0
- package/dist/review-7LN47SAN.js +15 -0
- package/dist/review-7LN47SAN.js.map +1 -0
- package/dist/{run-BRBFHVUO.js → run-H75MD5BQ.js} +26 -25
- package/dist/{run-BRBFHVUO.js.map → run-H75MD5BQ.js.map} +1 -1
- package/dist/{web-WQISLO66.js → web-ANQQTTRD.js} +5 -5
- package/package.json +1 -1
- package/prompts/analyze.md +24 -0
- package/prompts/execute.md +29 -1
- package/prompts/generate.md +61 -7
- package/prompts/plan.md +24 -1
- package/prompts/pr-review.md +65 -4
- package/prompts/pr.md +37 -3
- package/prompts/prd.md +17 -0
- package/prompts/review.md +40 -0
- package/dist/analyze-VSWAEVN3.js.map +0 -1
- package/dist/chunk-57MH6RYJ.js.map +0 -1
- package/dist/chunk-66ZGYXTH.js.map +0 -1
- package/dist/chunk-DP43WJY4.js.map +0 -1
- package/dist/chunk-DTNM3LTO.js +0 -101
- package/dist/chunk-DTNM3LTO.js.map +0 -1
- package/dist/chunk-KXAG24DD.js.map +0 -1
- package/dist/chunk-M72ZEPWD.js +0 -135
- package/dist/chunk-M72ZEPWD.js.map +0 -1
- package/dist/chunk-MTJQOOMG.js.map +0 -1
- package/dist/chunk-SLHZPB3C.js +0 -50
- package/dist/chunk-SLHZPB3C.js.map +0 -1
- package/dist/chunk-TJMZWRS6.js.map +0 -1
- package/dist/chunk-TLSIK656.js.map +0 -1
- package/dist/chunk-WHEU55ZP.js.map +0 -1
- package/dist/chunk-X7XBF5TS.js.map +0 -1
- package/dist/execute-Z2DKGVLR.js +0 -14
- package/dist/generate-ERDPOTKX.js.map +0 -1
- package/dist/init-UY6ZCIFT.js +0 -9
- package/dist/plan-REMAMCX4.js +0 -16
- package/dist/pr-KERP52TI.js +0 -20
- package/dist/pr-review-IU4N3TEK.js +0 -16
- package/dist/prd-SHVQW6RM.js +0 -16
- package/dist/review-L5F77QAC.js +0 -14
- /package/dist/{chunk-NB6WKTBL.js.map → chunk-3BOHTH4V.js.map} +0 -0
- /package/dist/{config-J5DW6HSY.js.map → chunk-3D4A4B46.js.map} +0 -0
- /package/dist/{chunk-VNKSMWDN.js.map → chunk-GANSDJV7.js.map} +0 -0
- /package/dist/{chunk-M37YZYDO.js.map → chunk-LVKQ2AKT.js.map} +0 -0
- /package/dist/{chunk-JGKPSWOA.js.map → chunk-PL6CNMVS.js.map} +0 -0
- /package/dist/{execute-Z2DKGVLR.js.map → config-4THHOXNN.js.map} +0 -0
- /package/dist/{init-UY6ZCIFT.js.map → execute-5OEKLM5X.js.map} +0 -0
- /package/dist/{plan-REMAMCX4.js.map → init-PRMOTQ7N.js.map} +0 -0
- /package/dist/{pr-KERP52TI.js.map → plan-KENR6MJ3.js.map} +0 -0
- /package/dist/{pr-review-IU4N3TEK.js.map → pr-KDGKG4BP.js.map} +0 -0
- /package/dist/{prd-SHVQW6RM.js.map → pr-review-CAMAB7KO.js.map} +0 -0
- /package/dist/{review-L5F77QAC.js.map → prd-V3MWDIHK.js.map} +0 -0
- /package/dist/{web-WQISLO66.js.map → web-ANQQTTRD.js.map} +0 -0
- /package/web/{CLAUDE.md → AGENTS.md} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/pr-review.ts","../src/core/pr-review/discovery.ts","../src/core/pr-review/publisher.ts","../src/core/pr-review/report.ts"],"sourcesContent":["import { join } from 'node:path';\nimport { loadPrReviewConfig } from '../config.js';\nimport { runHeadless } from '../core/headless.js';\nimport {\n PrDiscoveryError,\n type ResolvedPullRequest,\n resolvePullRequest,\n} from '../core/pr-review/discovery.js';\nimport { createPrReviewPublisher, type PrReviewPublisher } from '../core/pr-review/publisher.js';\nimport {\n buildReportMarkdown,\n type PrReviewPullRequest,\n parseFindings,\n parsePrReviewResult,\n prReviewDir,\n reportFileName,\n resolveRound,\n} from '../core/pr-review/report.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { publishPhaseMetrics } from '../core/session-metrics.js';\nimport { isoNow, loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { resolvePolicyPlaceholders } from '../policy/placeholders.js';\nimport { resolveIssuePaths } from '../storage/resolve.js';\nimport type { PrReviewRecommendation } from '../types.js';\nimport { printError, printInfo, printSuccess } from '../ui/logger.js';\nimport { run } from '../utils/shell.js';\n\n/**\n * The `pr-review` phase: review a Pull Request as a whole.\n *\n * It follows the five steps of `review.ts` — resolve the target, build the\n * prompt, run headless, parse deterministically, persist state — with two\n * differences that come from reviewing a Pull Request instead of an Issue: the\n * target has to be discovered (`core/pr-review/discovery.ts`) and the outcome\n * is an artifact on disk, written through a `PrReviewPublisher`.\n *\n * The phase is intended to be read-only: Write/Edit are not allowed, and the\n * prompt forbids commits and `gh pr review|comment|merge`. Bash remains\n * available for `gh`/`git` inspection, so the restriction is policy plus tool\n * allow-list — not a sandbox that can stop every write.\n */\n\n/** Which verdicts make the command fail. */\nexport type PrReviewFailOn = 'request-changes' | 'suggestions' | 'none';\n\nconst FAIL_ON_LEVELS: PrReviewFailOn[] = ['request-changes', 'suggestions', 'none'];\nconst DEFAULT_FAIL_ON: PrReviewFailOn = 'request-changes';\n\n/** Placeholder value for context the run does not have. */\nconst NO_ISSUE = 'none';\nconst NO_PATH = '(none)';\n\nexport interface PrReviewOptions {\n /** Issue the Pull Request belongs to; enables plan lookup and state writes. */\n issue?: string;\n /** `--round <n>`: rewrite one specific round instead of appending a new one. */\n round?: string | number;\n /** Skip the discovery confirmation (`--yes`, and every call from `run`). */\n yes?: boolean;\n failOn?: string;\n /** Injection point for tests and for future publishers (GitHub). */\n publisher?: PrReviewPublisher;\n}\n\n/**\n * Exit code for a parsed verdict. `1` is reserved for execution failures, so a\n * review that ran to completion answers only `0` or `2`.\n */\nexport function exitCodeFor(\n recommendation: PrReviewRecommendation,\n failOn: PrReviewFailOn,\n): number {\n if (failOn === 'none') {\n return 0;\n }\n if (recommendation === 'REQUEST_CHANGES') {\n return 2;\n }\n return failOn === 'suggestions' && recommendation === 'APPROVE_WITH_SUGGESTIONS' ? 2 : 0;\n}\n\nfunction parseFailOn(value: string | undefined): PrReviewFailOn | null {\n if (value === undefined || value === '') {\n return DEFAULT_FAIL_ON;\n }\n const normalized = value.trim().toLowerCase();\n return FAIL_ON_LEVELS.find((level) => level === normalized) ?? null;\n}\n\n/** Metadata `gh` knows and the discovery may not — title, URL, head revision. */\ninterface GhPullRequest {\n title?: string;\n url?: string;\n headRefName?: string;\n headRefOid?: string;\n}\n\n/**\n * Best-effort `gh pr view`. A failure here costs the report a title and the\n * index a head SHA; it never costs the review, so nothing is thrown.\n */\nasync function fetchPullRequestMetadata(number: number): Promise<GhPullRequest | null> {\n try {\n const result = await run('gh', [\n 'pr',\n 'view',\n String(number),\n '--json',\n 'title,url,headRefName,headRefOid',\n ]);\n if (result.exitCode !== 0) {\n return null;\n }\n const parsed: unknown = JSON.parse(result.stdout);\n return typeof parsed === 'object' && parsed !== null ? (parsed as GhPullRequest) : null;\n } catch {\n return null;\n }\n}\n\nfunction mergePullRequest(\n target: ResolvedPullRequest,\n meta: GhPullRequest | null,\n): PrReviewPullRequest {\n return {\n number: target.number,\n url: target.url ?? meta?.url ?? null,\n title: target.title ?? meta?.title ?? null,\n headBranch: target.headBranch ?? meta?.headRefName ?? null,\n };\n}\n\ninterface PersistInput {\n tasksPath: string;\n pullRequestNumber: number;\n round: number;\n /** null when the verdict could not be parsed. */\n recommendation: PrReviewRecommendation | null;\n at: string;\n}\n\n/**\n * Record the round on the plan. Only `prReviewCompleted` and `prReview` are\n * touched: the other phases own the rest of `pipeline`.\n *\n * Best-effort like `review.ts`: reviewing a Pull Request with no associated\n * Issue (or whose plan is gone) is a supported use of the command, not a\n * failure — the report on disk is the deliverable either way.\n */\nasync function persistState(input: PersistInput): Promise<void> {\n try {\n const plan = await loadTaskPlan(input.tasksPath);\n plan.pipeline.prReviewCompleted =\n input.recommendation === 'APPROVE' || input.recommendation === 'APPROVE_WITH_SUGGESTIONS';\n plan.prReview = {\n // Preserved, never turned on here: opting the pipeline in is `run\n // --pr-review`'s decision, not a side effect of a manual review.\n enabled: plan.prReview?.enabled ?? false,\n pullRequestNumber: input.pullRequestNumber,\n // `--round <n>` rewrites an earlier round, which must not shrink the count.\n rounds: Math.max(plan.prReview?.rounds ?? 0, input.round),\n lastRecommendation: input.recommendation ?? plan.prReview?.lastRecommendation,\n lastReviewedAt: input.at,\n };\n await saveTaskPlan(input.tasksPath, plan);\n } catch {\n // tasks.json may not exist.\n }\n}\n\nexport async function runPrReview(prArg?: string, opts: PrReviewOptions = {}): Promise<number> {\n const failOn = parseFailOn(opts.failOn);\n if (failOn === null) {\n printError(`Invalid --fail-on value '${opts.failOn}'. Expected ${FAIL_ON_LEVELS.join(', ')}.`);\n return 1;\n }\n\n const explicitRound = opts.round === undefined ? undefined : Number(opts.round);\n if (explicitRound !== undefined && (!Number.isInteger(explicitRound) || explicitRound < 1)) {\n printError(`Invalid --round value '${opts.round}'. Expected a positive integer.`);\n return 1;\n }\n\n const issueRef = opts.issue?.replace(/^#/, '').trim();\n const issue = issueRef === undefined || issueRef === '' ? undefined : issueRef;\n\n let target: ResolvedPullRequest;\n try {\n target = await resolvePullRequest(prArg, { issue, yes: opts.yes });\n } catch (err) {\n if (err instanceof PrDiscoveryError) {\n printError(err.message);\n return err.exitCode;\n }\n printError(\n `Could not determine which Pull Request to review: ${err instanceof Error ? err.message : String(err)}`,\n );\n return 1;\n }\n\n // With no Issue there is nothing to resolve: the placeholders take NO_PATH\n // and the report lands under the synthetic `pr-<N>` slug resolved below.\n const issuePaths = issue === undefined ? null : await resolveIssuePaths(issue);\n const issueDir = issuePaths?.issueDir ?? null;\n const tasksPath = issuePaths?.tasksFile ?? null;\n const prdPath = issuePaths?.prdFile ?? null;\n\n const dir = await prReviewDir({ issue, pullRequest: target.number });\n const round = await resolveRound(dir, target.number, explicitRound);\n const reportPath = join(dir, reportFileName(target.number, round));\n\n const template = await loadPrompt('pr-review');\n const prompt = applyPlaceholders(template, {\n // The repository's own conventions. Empty when it declares none, which is\n // what keeps the rendered prompt identical to the pre-policy one.\n ...(await resolvePolicyPlaceholders()),\n __PR_NUMBER__: String(target.number),\n __ISSUE_NUMBER__: issue ?? NO_ISSUE,\n __TASKS_PATH__: tasksPath ?? NO_PATH,\n __PRD_PATH__: prdPath ?? NO_PATH,\n __REPORT_PATH__: reportPath,\n __ROUND__: String(round),\n });\n\n const startedAtMs = Date.now();\n const result = await runHeadless({\n prompt,\n maxTurns: 40,\n timeout: getGlobalTimeout() ?? 900_000,\n // json (not text) so the CLI reports usage: the envelope's `result` field\n // carries the same assistant text the parsers below already consumed.\n outputFormat: 'json',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep'],\n // With an Issue, `dir` sits under `issueDir`, so the parent covers both the\n // report destination and the tasks.json/prd.md the prompt points at.\n addDirs: issueDir === null ? [dir] : [issueDir],\n statusMessage: `Reviewing Pull Request #${target.number} (round ${round})...`,\n });\n // Before the success check: the tokens were spent either way.\n publishPhaseMetrics('pr-review', result.cost, startedAtMs);\n\n if (!result.success) {\n printError(`PR review failed: ${result.error}`);\n return 1;\n }\n\n const parsed = parsePrReviewResult(result.result);\n const recommendation = parsed.ok ? parsed.result.recommendation : null;\n const blockers = parsed.ok ? parsed.result.blockers : [];\n\n const meta = await fetchPullRequestMetadata(target.number);\n const pullRequest = mergePullRequest(target, meta);\n const at = isoNow();\n const headSha = meta?.headRefOid ?? null;\n\n const report = {\n pullRequest,\n round,\n at,\n headSha,\n recommendation,\n blockers,\n findings: parseFindings(result.result),\n markdown: buildReportMarkdown({\n pullRequest,\n round,\n at,\n headSha,\n recommendation,\n body: result.result,\n parseError: parsed.ok ? null : parsed.error,\n }),\n };\n\n // Which publisher runs is configuration (`prReview.publisher`), never a code\n // path here: the command only ever holds the interface.\n const publisher =\n opts.publisher ?? createPrReviewPublisher(dir, (await loadPrReviewConfig()).publisher);\n try {\n await publisher.publish(report);\n } catch (err) {\n printError(\n `Could not publish the review report: ${err instanceof Error ? err.message : String(err)}`,\n );\n return 1;\n }\n\n if (tasksPath !== null) {\n await persistState({\n tasksPath,\n pullRequestNumber: target.number,\n round,\n recommendation,\n at,\n });\n }\n\n printInfo(`Report: ${reportPath}`);\n\n // A verdict that could not be read is an execution failure, never an\n // approval — the raw output is in the report for whoever has to look.\n if (!parsed.ok || recommendation === null) {\n printError(`PR review could not be parsed: ${parsed.ok ? 'no recommendation' : parsed.error}`);\n return 1;\n }\n\n if (recommendation === 'REQUEST_CHANGES') {\n printError(`PR review #${target.number}: REQUEST_CHANGES`);\n for (const blocker of blockers) {\n console.log(` - ${blocker}`);\n }\n return exitCodeFor(recommendation, failOn);\n }\n\n printSuccess(\n recommendation === 'APPROVE'\n ? `PR review #${target.number}: APPROVE`\n : `PR review #${target.number}: APPROVE_WITH_SUGGESTIONS — no blockers, improvements suggested`,\n );\n return exitCodeFor(recommendation, failOn);\n}\n","import { createInterface } from 'node:readline';\nimport { resolveIssuePaths } from '../../storage/resolve.js';\nimport type { PullRequestRef } from '../../types.js';\nimport { printInfo, printWarning } from '../../ui/logger.js';\nimport { getCurrentBranch } from '../../utils/git.js';\nimport { listPullRequests } from '../session-git.js';\nimport { getSessionPublisher } from '../session-publisher.js';\nimport type { SessionPullRequest } from '../session-state.js';\nimport { loadTaskPlan } from '../state-manager.js';\n\n/**\n * Which Pull Request the `pr-review` phase should look at.\n *\n * The order is deterministic and the search never falls back to \"some PR of\n * this repository\": a review pointed at an unrelated Pull Request is worse\n * than no review at all, so an exhausted search is an actionable error.\n */\n\n/** Origin of the resolved Pull Request, in precedence order. */\nexport type PullRequestSource = 'argument' | 'session' | 'plan' | 'branch';\n\nexport interface ResolvedPullRequest {\n number: number;\n /** Only known when the origin carried it; never synthesized. */\n url: string | null;\n title: string | null;\n headBranch: string | null;\n source: PullRequestSource;\n}\n\n/**\n * Failure to settle on a Pull Request. Carries the exit code the CLI should\n * return, mirroring IssueResolutionError (`issues/resolver.ts`).\n */\nexport class PrDiscoveryError extends Error {\n readonly exitCode: number;\n\n constructor(message: string, exitCode = 1) {\n super(message);\n this.name = 'PrDiscoveryError';\n this.exitCode = exitCode;\n }\n}\n\n/** Data sources, injectable for tests — the pattern of `GitStateSources`. */\nexport interface PrDiscoverySources {\n /** PRs already known to the active session publisher (`--web` runs). */\n sessionPullRequests(): SessionPullRequest[];\n /** `plan.pullRequest` persisted by the `pr` phase, or null. */\n planPullRequest(issue: string): Promise<PullRequestRef | null>;\n currentBranch(): Promise<string>;\n branchPullRequests(branch: string): Promise<SessionPullRequest[]>;\n}\n\nexport interface ResolvePullRequestOptions {\n /** Issue the pipeline is working on; enables the `plan` source. */\n issue?: string;\n /** `--yes`, or any non-interactive caller (`run`): skip the confirmation. */\n yes?: boolean;\n /**\n * Whether a prompt may be shown. Defaults to a real TTY on both ends outside\n * CI, which is what keeps the confirmation from blocking a headless run.\n */\n interactive?: boolean;\n /** Input stream for the confirmation. Defaults to process.stdin. */\n stdin?: NodeJS.ReadableStream;\n /** Output stream for the confirmation. Defaults to process.stdout. */\n stdout?: NodeJS.WritableStream;\n /** Informational sink. Defaults to printInfo. */\n info?: (message: string) => void;\n /** Warning sink. Defaults to printWarning. */\n warn?: (message: string) => void;\n sources?: Partial<PrDiscoverySources>;\n}\n\n/** How many invalid answers are tolerated before the prompt gives up. */\nconst MAX_PROMPT_ATTEMPTS = 3;\n\nconst SOURCE_LABELS: Record<PullRequestSource, string> = {\n argument: 'the command argument',\n session: 'the current session',\n plan: 'tasks.json',\n branch: 'the current branch',\n};\n\n/**\n * `plan.pullRequest` of the issue's global tasks.json. Best-effort: a missing\n * plan, an invalid one or a plan written before the `pr` phase ran is simply\n * \"no candidate\", never a failure — the next source still gets its turn.\n */\nasync function loadPlanPullRequest(issue: string): Promise<PullRequestRef | null> {\n try {\n const plan = await loadTaskPlan((await resolveIssuePaths(issue)).tasksFile);\n return plan.pullRequest ?? null;\n } catch {\n return null;\n }\n}\n\nconst defaultSources: PrDiscoverySources = {\n sessionPullRequests: () => getSessionPublisher().snapshot().pullRequests,\n planPullRequest: loadPlanPullRequest,\n currentBranch: getCurrentBranch,\n branchPullRequests: listPullRequests,\n};\n\n/**\n * The most recent of several Pull Requests: the highest number, which does not\n * depend on the order the source returned them.\n */\nexport function mostRecent(pullRequests: SessionPullRequest[]): SessionPullRequest | null {\n let latest: SessionPullRequest | null = null;\n for (const pr of pullRequests) {\n if (latest === null || pr.number > latest.number) {\n latest = pr;\n }\n }\n return latest;\n}\n\n/**\n * The PR number inside an explicit argument: `184`, `#184` or a GitHub URL.\n * Returns null for anything else, so a typo fails loudly instead of being\n * coerced into a number that addresses an unrelated Pull Request.\n */\nfunction parsePrArgument(arg: string): number | null {\n const trimmed = arg.trim();\n const fromUrl = trimmed.match(/\\/pull\\/(\\d+)/);\n if (fromUrl?.[1] !== undefined) {\n return Number(fromUrl[1]);\n }\n const plain = trimmed.match(/^#?(\\d+)$/);\n return plain?.[1] === undefined ? null : Number(plain[1]);\n}\n\nfunction isInteractiveByDefault(): boolean {\n const ci = process.env.CI;\n const inCi = ci !== undefined && ci !== '' && ci !== '0' && ci.toLowerCase() !== 'false';\n return Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY) && !inCi;\n}\n\n/** Human-readable one-liner for logs and the confirmation prompt. */\nfunction describe(pr: ResolvedPullRequest): string {\n const parts = [`#${pr.number}`];\n if (pr.title !== null) parts.push(`\"${pr.title}\"`);\n if (pr.headBranch !== null) parts.push(`(${pr.headBranch})`);\n return parts.join(' ');\n}\n\n/**\n * Ask for confirmation, defaulting to yes on an empty answer.\n *\n * Lines are queued rather than read on demand for the same reason as\n * `issues/resolver.ts`: `rl.question` drops input that arrives before the\n * prompt is written, which silently loses a piped answer. EOF answers null,\n * which the caller treats as a refusal — never as consent.\n */\nasync function confirm(\n query: string,\n stdin: NodeJS.ReadableStream,\n stdout: NodeJS.WritableStream,\n warn: (message: string) => void,\n): Promise<boolean> {\n const rl = createInterface({ input: stdin, output: stdout });\n const queued: string[] = [];\n let pending: ((line: string | null) => void) | null = null;\n let closed = false;\n\n rl.on('line', (line: string) => {\n if (pending !== null) {\n const resolve = pending;\n pending = null;\n resolve(line);\n return;\n }\n queued.push(line);\n });\n rl.on('close', () => {\n closed = true;\n if (pending !== null) {\n const resolve = pending;\n pending = null;\n resolve(null);\n }\n });\n\n const ask = (): Promise<string | null> =>\n new Promise<string | null>((resolve) => {\n const buffered = queued.shift();\n if (buffered !== undefined) {\n resolve(buffered);\n return;\n }\n if (closed) {\n resolve(null);\n return;\n }\n stdout.write(query);\n pending = resolve;\n });\n\n try {\n for (let attempt = 0; attempt < MAX_PROMPT_ATTEMPTS; attempt++) {\n const answer = await ask();\n if (answer === null) {\n return false;\n }\n const normalized = answer.trim().toLowerCase();\n if (normalized === '' || normalized === 'y' || normalized === 'yes') {\n return true;\n }\n if (normalized === 'n' || normalized === 'no') {\n return false;\n }\n warn(`Invalid answer: '${answer.trim()}'. Enter y or n.`);\n }\n return false;\n } finally {\n rl.close();\n }\n}\n\n/**\n * Settle on the Pull Request to review, in this order:\n *\n * 1. the explicit argument;\n * 2. `plan.pullRequest`, written by the `pr` phase (when an issue is known);\n * 3. the PRs the active in-memory session publisher already knows about\n * (populated during `run --web` via `publishGitState` — not by reading\n * `session.json` from disk);\n * 4. the PRs of the current branch, via `gh`;\n * 5. an actionable failure.\n *\n * The plan is preferred over the session: after `pr`, `plan.pullRequest` is the\n * authoritative record of what this pipeline opened, while the session may\n * still list older PRs for the same branch (`gh pr list --state all`).\n *\n * Sources 2–4 were discovered rather than requested, so an interactive\n * terminal is asked to confirm before a long (and paid) review runs against\n * the wrong Pull Request. Anywhere else — `--yes`, CI, a pipe or the `run`\n * command — the discovery is only logged.\n */\nexport async function resolvePullRequest(\n prArg?: string,\n opts: ResolvePullRequestOptions = {},\n): Promise<ResolvedPullRequest> {\n const info = opts.info ?? printInfo;\n const warn = opts.warn ?? printWarning;\n const sources: PrDiscoverySources = { ...defaultSources, ...opts.sources };\n\n if (prArg !== undefined && prArg.trim() !== '') {\n const number = parsePrArgument(prArg);\n if (number === null) {\n throw new PrDiscoveryError(\n `Invalid Pull Request reference '${prArg.trim()}'. ` +\n 'Use a number (e.g. 184) or a GitHub Pull Request URL.',\n );\n }\n const url = prArg.trim().includes('/pull/') ? prArg.trim() : null;\n return { number, url, title: null, headBranch: null, source: 'argument' };\n }\n\n const discovered = await discover(sources, opts.issue, warn);\n if (discovered === null) {\n throw new PrDiscoveryError(\n 'No Pull Request found for this branch, in tasks.json or in the current session. ' +\n 'Run `issue-flow pr-review <number>` with the Pull Request number.',\n );\n }\n\n const interactive = opts.yes === true ? false : (opts.interactive ?? isInteractiveByDefault());\n if (!interactive) {\n info(\n `Reviewing Pull Request ${describe(discovered)}, found in ${SOURCE_LABELS[discovered.source]}.`,\n );\n return discovered;\n }\n\n info(`Pull Request found in ${SOURCE_LABELS[discovered.source]}:`);\n info(` number: #${discovered.number}`);\n info(` title: ${discovered.title ?? '(unknown)'}`);\n info(` branch: ${discovered.headBranch ?? '(unknown)'}`);\n\n const accepted = await confirm(\n `Review Pull Request #${discovered.number}? (Y/n): `,\n opts.stdin ?? process.stdin,\n opts.stdout ?? process.stdout,\n warn,\n );\n if (!accepted) {\n throw new PrDiscoveryError(\n `Cancelled: Pull Request #${discovered.number} was not confirmed. ` +\n 'Run `issue-flow pr-review <number>` to review a specific Pull Request.',\n );\n }\n return discovered;\n}\n\n/**\n * Sources 2–4, in order, stopping at the first hit. Every source is allowed to\n * fail on its own: a broken `gh` or an unreadable plan must not mask a\n * candidate another source could still provide.\n */\nasync function discover(\n sources: PrDiscoverySources,\n issue: string | undefined,\n warn: (message: string) => void,\n): Promise<ResolvedPullRequest | null> {\n if (issue !== undefined && issue !== '') {\n try {\n const fromPlan = await sources.planPullRequest(issue);\n if (fromPlan !== null) {\n return {\n number: fromPlan.number,\n url: fromPlan.url,\n title: null,\n headBranch: fromPlan.headBranch,\n source: 'plan',\n };\n }\n } catch {\n // An unreadable plan is \"no candidate\", never a hard failure.\n }\n }\n\n let sessionPrs: SessionPullRequest[] = [];\n try {\n sessionPrs = sources.sessionPullRequests();\n } catch {\n // The monitoring snapshot is an optimization, never a requirement.\n }\n const fromSession = mostRecent(sessionPrs);\n if (fromSession !== null) {\n return {\n number: fromSession.number,\n url: fromSession.url,\n title: fromSession.title,\n headBranch: null,\n source: 'session',\n };\n }\n\n let branch = '';\n try {\n branch = await sources.currentBranch();\n } catch (err) {\n warn(\n `Could not detect the current branch: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n if (branch === '') {\n return null;\n }\n\n let branchPrs: SessionPullRequest[] = [];\n try {\n branchPrs = await sources.branchPullRequests(branch);\n } catch (err) {\n warn(\n `Could not list Pull Requests for branch '${branch}': ${err instanceof Error ? err.message : String(err)}`,\n );\n return null;\n }\n const fromBranch = mostRecent(branchPrs);\n if (fromBranch === null) {\n return null;\n }\n return {\n number: fromBranch.number,\n url: fromBranch.url,\n title: fromBranch.title,\n headBranch: branch,\n source: 'branch',\n };\n}\n","import { mkdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { PrReviewRecommendation } from '../../types.js';\nimport { writeFileAtomic } from '../../utils/fs.js';\nimport {\n type PrReviewFinding,\n type PrReviewIndex,\n type PrReviewPullRequest,\n readPrReviewIndex,\n reportFileName,\n upsertRound,\n} from './report.js';\n\n/**\n * Where the outcome of a review round goes.\n *\n * The phase itself only knows this interface, so pointing the results at\n * GitHub later (a review comment, a check run) is a new implementation and a\n * configuration change — not a change to the phase.\n */\n\n/** One finished review round, ready to be published. */\nexport interface PrReviewReport {\n pullRequest: PrReviewPullRequest;\n round: number;\n at: string;\n headSha: string | null;\n /** null when the agent output could not be parsed. */\n recommendation: PrReviewRecommendation | null;\n blockers: string[];\n findings: PrReviewFinding[];\n /** The full Markdown report, already assembled by `buildReportMarkdown()`. */\n markdown: string;\n}\n\nexport interface PrReviewPublisher {\n publish(report: PrReviewReport): Promise<void>;\n}\n\n/**\n * Writes `pr-<N>-round-<k>.md` plus `index.json` under the artifact directory.\n *\n * Rounds are additive: an existing index is loaded and merged instead of being\n * replaced, so round N+1 never erases the trail of the rounds before it. Both\n * files go through `writeFileAtomic()`, the mechanism `saveTaskPlan()` uses —\n * an interrupted write leaves the previous artifacts intact rather than a\n * truncated report.\n *\n * This publisher never writes to GitHub.\n */\nexport class LocalReportPublisher implements PrReviewPublisher {\n constructor(private readonly dir: string) {}\n\n async publish(report: PrReviewReport): Promise<void> {\n await mkdir(this.dir, { recursive: true });\n\n const fileName = reportFileName(report.pullRequest.number, report.round);\n await writeFileAtomic(join(this.dir, fileName), report.markdown);\n\n const existing = await readPrReviewIndex(this.dir);\n const index: PrReviewIndex = {\n schemaVersion: 1,\n pullRequest: report.pullRequest,\n rounds: existing?.rounds ?? [],\n };\n\n const updated = upsertRound(index, {\n round: report.round,\n at: report.at,\n recommendation: report.recommendation,\n headSha: report.headSha,\n reportPath: fileName,\n findings: report.findings,\n });\n\n await writeFileAtomic(join(this.dir, 'index.json'), `${JSON.stringify(updated, null, 2)}\\n`);\n }\n}\n\n/** Publishers selectable by configuration. v1 ships only the local one. */\nexport type PrReviewPublisherKind = 'local';\n\nconst PUBLISHERS: Record<PrReviewPublisherKind, (dir: string) => PrReviewPublisher> = {\n local: (dir) => new LocalReportPublisher(dir),\n};\n\n/**\n * The publisher the phase should use. Going through the factory is what keeps\n * the command bound to `PrReviewPublisher` alone: adding a GitHub adapter is a\n * new entry here plus a configuration key, never a change to the phase.\n */\nexport function createPrReviewPublisher(\n dir: string,\n kind: PrReviewPublisherKind = 'local',\n): PrReviewPublisher {\n return PUBLISHERS[kind](dir);\n}\n","import { readdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { resolveIssuePaths } from '../../storage/resolve.js';\nimport type { PrReviewRecommendation } from '../../types.js';\n\n/**\n * Everything the `pr-review` phase produces out of a headless run: the parsed\n * verdict, the Markdown report and the structured index.\n *\n * The Markdown is what a human reads; `index.json` is what an automation reads.\n * Keeping both means a future GitHub publisher never has to re-parse Markdown\n * to know what the review concluded.\n */\n\n/** The Pull Request a report is about. Fields the source never knew stay null. */\nexport interface PrReviewPullRequest {\n number: number;\n url: string | null;\n title: string | null;\n headBranch: string | null;\n}\n\nexport type FindingSeverity = 'blocker' | 'high' | 'medium' | 'low';\n\nexport interface PrReviewFinding {\n severity: FindingSeverity;\n file: string | null;\n line: number | null;\n title: string;\n}\n\nexport interface PrReviewResult {\n recommendation: PrReviewRecommendation;\n /** Items listed under `BLOCKERS:`; empty when the review found none. */\n blockers: string[];\n}\n\n/**\n * Parsing outcome. A failure is explicit rather than an `APPROVE` default:\n * silently approving a Pull Request because the agent's output was malformed\n * is the one failure mode this phase must never have.\n */\nexport type PrReviewParse = { ok: true; result: PrReviewResult } | { ok: false; error: string };\n\nexport interface PrReviewRoundEntry {\n round: number;\n at: string;\n /** null when the output could not be parsed — never coerced to APPROVE. */\n recommendation: PrReviewRecommendation | null;\n headSha: string | null;\n /** Relative to the directory holding index.json. */\n reportPath: string;\n findings: PrReviewFinding[];\n}\n\nexport interface PrReviewIndex {\n schemaVersion: 1;\n pullRequest: PrReviewPullRequest;\n rounds: PrReviewRoundEntry[];\n}\n\nconst RECOMMENDATIONS: Record<string, PrReviewRecommendation> = {\n APPROVE: 'APPROVE',\n APPROVE_WITH_SUGGESTIONS: 'APPROVE_WITH_SUGGESTIONS',\n REQUEST_CHANGES: 'REQUEST_CHANGES',\n};\n\n/**\n * Read the `<pr-review-result>` block. Tolerant in the spirit of\n * `parseReviewResult()` (`commands/review.ts`): without the block, a bare\n * `RECOMMENDATION:` line in the raw output still counts. Anything else — no\n * recommendation at all, or one outside the enum — is a parse failure.\n */\nexport function parsePrReviewResult(output: string): PrReviewParse {\n const block = output.match(/<pr-review-result>([\\s\\S]*?)<\\/pr-review-result>/);\n const scope = block?.[1] ?? output;\n\n const line = scope.match(/RECOMMENDATION:\\s*([^\\n\\r]+)/i);\n if (line?.[1] === undefined) {\n return {\n ok: false,\n error: block\n ? 'The <pr-review-result> block has no RECOMMENDATION line.'\n : 'No <pr-review-result> block and no RECOMMENDATION line in the output.',\n };\n }\n\n // Markdown emphasis around the verdict is stripped, but never the inner\n // underscores of APPROVE_WITH_SUGGESTIONS.\n const raw = line[1]\n .trim()\n .replace(/[*`]/g, '')\n .replace(/^_+|[_.]+$/g, '')\n .trim();\n const normalized = raw.toUpperCase().replace(/[\\s-]+/g, '_');\n const recommendation = RECOMMENDATIONS[normalized];\n if (recommendation === undefined) {\n return {\n ok: false,\n error:\n `Unknown recommendation '${raw}'. ` +\n 'Expected APPROVE, APPROVE_WITH_SUGGESTIONS or REQUEST_CHANGES.',\n };\n }\n\n return { ok: true, result: { recommendation, blockers: parseBlockers(scope) } };\n}\n\n/** Items under `BLOCKERS:`, up to the end of the block or the next label. */\nfunction parseBlockers(scope: string): string[] {\n const match = scope.match(/BLOCKERS:([\\s\\S]*)/i);\n if (match?.[1] === undefined) return [];\n\n return match[1]\n .split('\\n')\n .map((entry) => entry.trim())\n .filter((entry) => /^[-*]\\s+/.test(entry))\n .map((entry) => entry.replace(/^[-*]\\s+/, '').trim())\n .filter((entry) => entry !== '' && !/^(none|nenhum|n\\/a)\\.?$/i.test(entry));\n}\n\n/** The sections every report carries, in order. */\nexport const REPORT_SECTIONS = [\n 'Executive summary',\n 'Strengths',\n 'Issues found',\n 'Suggested improvements',\n 'Architectural observations',\n 'Risks identified',\n 'Required before merge',\n 'Final recommendation',\n] as const;\n\nexport type ReportSection = (typeof REPORT_SECTIONS)[number];\n\n/**\n * Headings that map onto a canonical section. The agent writes prose, so the\n * mapping is deliberately forgiving — an unmatched heading is not dropped, it\n * lands in \"Additional notes\".\n */\nconst SECTION_ALIASES: Record<ReportSection, string[]> = {\n 'Executive summary': ['executive summary', 'summary', 'overview', 'resumo executivo', 'resumo'],\n Strengths: ['strengths', 'positives', 'highlights', 'what went well', 'pontos positivos'],\n 'Issues found': ['issues found', 'issues', 'problems', 'findings', 'problemas encontrados'],\n 'Suggested improvements': [\n 'suggested improvements',\n 'suggestions',\n 'improvements',\n 'sugestoes de melhoria',\n 'sugestoes',\n ],\n 'Architectural observations': [\n 'architectural observations',\n 'architecture',\n 'architectural notes',\n 'observacoes arquiteturais',\n 'arquitetura',\n ],\n 'Risks identified': ['risks identified', 'risks', 'riscos identificados', 'riscos'],\n 'Required before merge': [\n 'required before merge',\n 'blockers',\n 'must fix before merge',\n 'itens obrigatorios antes do merge',\n ],\n 'Final recommendation': [\n 'final recommendation',\n 'recommendation',\n 'verdict',\n 'recomendacao final',\n 'recomendacao',\n ],\n};\n\n/** lowercase, unaccented, without list numbering or trailing punctuation. */\nfunction normalizeHeading(heading: string): string {\n return heading\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toLowerCase()\n .replace(/^\\d+[.)]\\s*/, '')\n .replace(/[*_`#]/g, '')\n .replace(/[.:;!?]+$/, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction matchSection(heading: string): ReportSection | null {\n const normalized = normalizeHeading(heading);\n for (const section of REPORT_SECTIONS) {\n if (SECTION_ALIASES[section].includes(normalized)) return section;\n }\n return null;\n}\n\ninterface SplitBody {\n sections: Map<ReportSection, string>;\n /** Preamble and unrecognized headings, preserved verbatim. */\n extra: string;\n}\n\n/**\n * Split the agent's Markdown by heading. Content that matches no canonical\n * section is kept rather than discarded: the report is the record of the\n * review, so nothing the agent wrote may be lost in the rewrite.\n */\nfunction splitBody(body: string): SplitBody {\n const sections = new Map<ReportSection, string>();\n const extra: string[] = [];\n let current: ReportSection | null = null;\n let buffer: string[] = [];\n\n const flush = () => {\n const content = buffer.join('\\n').trim();\n buffer = [];\n if (content === '') return;\n if (current === null) {\n extra.push(content);\n return;\n }\n const previous = sections.get(current);\n sections.set(current, previous === undefined ? content : `${previous}\\n\\n${content}`);\n };\n\n for (const line of body.split('\\n')) {\n const heading = line.match(/^#{1,6}\\s+(.*)$/);\n if (heading?.[1] === undefined) {\n buffer.push(line);\n continue;\n }\n flush();\n const section = matchSection(heading[1]);\n current = section;\n if (section === null) {\n buffer.push(line);\n }\n }\n flush();\n\n return { sections, extra: extra.join('\\n\\n').trim() };\n}\n\nconst FINDING_LINE = /^[-*]\\s*\\[(\\w+)\\]\\s+(.+)$/;\nconst FINDING_LOCATION = /^(\\S+?)(?::(\\d+))?\\s+[—–-]\\s+(.+)$/;\n\nconst SEVERITY_ALIASES: Record<string, FindingSeverity> = {\n blocker: 'blocker',\n blocking: 'blocker',\n critical: 'blocker',\n high: 'high',\n major: 'high',\n medium: 'medium',\n moderate: 'medium',\n low: 'low',\n minor: 'low',\n nit: 'low',\n};\n\n/**\n * Structured findings out of the report body, from list items written as\n * `- [severity] path/to/file.ts:12 — Title` under \"Issues found\" and\n * \"Required before merge\" (the format the prompt asks for). Items in any other\n * shape stay in the Markdown only: the index is a convenience for automations,\n * never the authoritative record.\n */\nexport function parseFindings(body: string): PrReviewFinding[] {\n const { sections } = splitBody(body);\n const scopes = [sections.get('Issues found'), sections.get('Required before merge')];\n const findings: PrReviewFinding[] = [];\n\n for (const scope of scopes) {\n if (scope === undefined) continue;\n for (const line of scope.split('\\n')) {\n const item = line.trim().match(FINDING_LINE);\n if (item?.[1] === undefined || item[2] === undefined) continue;\n\n const severity = SEVERITY_ALIASES[item[1].toLowerCase()];\n // Unknown markers stay in the Markdown only — inventing `medium` would\n // mislead any automation that keys off index.json severities.\n if (severity === undefined) continue;\n\n const rest = item[2].trim();\n const location = rest.match(FINDING_LOCATION);\n if (location?.[1] === undefined || location[3] === undefined) {\n findings.push({ severity, file: null, line: null, title: rest });\n continue;\n }\n findings.push({\n severity,\n file: location[1],\n line: location[2] === undefined ? null : Number(location[2]),\n title: location[3].trim(),\n });\n }\n }\n\n return dedupeFindings(findings);\n}\n\n/** A finding listed both as an issue and as a merge blocker is still one finding. */\nfunction dedupeFindings(findings: PrReviewFinding[]): PrReviewFinding[] {\n const seen = new Set<string>();\n return findings.filter((finding) => {\n const key = `${finding.file ?? ''}:${finding.line ?? ''}:${finding.title}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n\nexport interface BuildReportInput {\n pullRequest: PrReviewPullRequest;\n round: number;\n at: string;\n headSha?: string | null;\n /** null when parsing failed. */\n recommendation: PrReviewRecommendation | null;\n /** Raw headless output. */\n body: string;\n /** Set when `parsePrReviewResult()` failed, so the raw output is kept. */\n parseError?: string | null;\n}\n\n/**\n * Assemble the Markdown report: a metadata header, the canonical sections in a\n * fixed order and — when parsing failed — the raw output verbatim, so a\n * malformed run is still auditable instead of being reduced to an exit code.\n */\nexport function buildReportMarkdown(input: BuildReportInput): string {\n const pr = input.pullRequest;\n const parts: string[] = [`# Pull Request Review — #${pr.number} (round ${input.round})`, ''];\n\n const heading = pr.url === null ? `#${pr.number}` : `[#${pr.number}](${pr.url})`;\n parts.push(`- **Pull Request:** ${heading}${pr.title === null ? '' : ` — ${pr.title}`}`);\n if (pr.headBranch !== null) parts.push(`- **Head branch:** ${pr.headBranch}`);\n if (input.headSha != null) parts.push(`- **Head SHA:** ${input.headSha}`);\n parts.push(`- **Round:** ${input.round}`);\n parts.push(`- **Reviewed at:** ${input.at}`);\n parts.push(`- **Recommendation:** ${input.recommendation ?? 'UNPARSED'}`);\n parts.push('');\n\n if (input.parseError != null && input.parseError !== '') {\n parts.push(`> Verdict could not be parsed: ${input.parseError}`, '');\n }\n\n const { sections, extra } = splitBody(input.body);\n for (const section of REPORT_SECTIONS) {\n parts.push(`## ${section}`, '', sections.get(section) ?? '_Not reported._', '');\n }\n\n if (input.parseError != null && input.parseError !== '') {\n parts.push('## Raw agent output', '', '```text', input.body.trim(), '```', '');\n } else if (extra !== '') {\n parts.push('## Additional notes', '', extra, '');\n }\n\n return `${parts.join('\\n').trimEnd()}\\n`;\n}\n\n/**\n * The global `issues/<N>/pr-review/`, or `issues/pr-<N>/pr-review/` with no\n * issue.\n *\n * A Pull Request with no associated Issue still needs somewhere to keep its\n * rounds, so it gets the synthetic `pr-<N>` identifier — `getIssuePaths()`\n * accepts non-numeric identifiers, so it is a first-class issue directory as\n * far as the storage layer is concerned.\n */\nexport async function prReviewDir(opts: { issue?: string; pullRequest: number }): Promise<string> {\n const slug =\n opts.issue === undefined || opts.issue === ''\n ? `pr-${opts.pullRequest}`\n : opts.issue.replace(/^#/, '');\n return (await resolveIssuePaths(slug)).prReviewDir;\n}\n\nexport function reportFileName(pullRequest: number, round: number): string {\n return `pr-${pullRequest}-round-${round}.md`;\n}\n\nconst REPORT_FILE = /^pr-(\\d+)-round-(\\d+)\\.md$/;\n\n/**\n * Read `index.json`. Never throws: a missing or corrupt index means \"no rounds\n * recorded yet\", which the round resolution below cross-checks against the\n * report files actually on disk.\n */\nexport async function readPrReviewIndex(dir: string): Promise<PrReviewIndex | null> {\n try {\n const parsed: unknown = JSON.parse(await readFile(join(dir, 'index.json'), 'utf-8'));\n if (typeof parsed !== 'object' || parsed === null) return null;\n const index = parsed as Partial<PrReviewIndex>;\n if (!Array.isArray(index.rounds) || typeof index.pullRequest !== 'object') return null;\n return {\n schemaVersion: 1,\n pullRequest: index.pullRequest as PrReviewPullRequest,\n rounds: index.rounds,\n };\n } catch {\n return null;\n }\n}\n\n/** Rounds already written for this Pull Request, from the report file names. */\nasync function roundsOnDisk(dir: string, pullRequest: number): Promise<number[]> {\n try {\n return (await readdir(dir))\n .map((name) => name.match(REPORT_FILE))\n .filter((match): match is RegExpMatchArray => match !== null)\n .filter((match) => Number(match[1]) === pullRequest)\n .map((match) => Number(match[2]));\n } catch {\n return [];\n }\n}\n\n/**\n * Which round to write: the explicit `--round <n>`, which rewrites that round,\n * or one past the highest round seen. Both the index and the files on disk are\n * consulted so a corrupt index can never overwrite an existing report.\n */\nexport async function resolveRound(\n dir: string,\n pullRequest: number,\n explicit?: number,\n): Promise<number> {\n if (explicit !== undefined) return explicit;\n\n const index = await readPrReviewIndex(dir);\n const known = [\n ...(index?.rounds ?? []).map((entry) => entry.round),\n ...(await roundsOnDisk(dir, pullRequest)),\n ];\n return known.length === 0 ? 1 : Math.max(...known) + 1;\n}\n\n/**\n * Add or replace a round. Additive by construction: writing round N+1 keeps\n * every earlier entry, and `--round n` replaces exactly one.\n */\nexport function upsertRound(index: PrReviewIndex, entry: PrReviewRoundEntry): PrReviewIndex {\n const rounds = index.rounds.filter((existing) => existing.round !== entry.round);\n rounds.push(entry);\n rounds.sort((a, b) => a.round - b.round);\n return { ...index, rounds };\n}\n"],"mappings":"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","names":["join","join","join","join"]}
|
|
@@ -2,34 +2,35 @@
|
|
|
2
2
|
import {
|
|
3
3
|
readFileWithGrace,
|
|
4
4
|
runPhaseWithRetry
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-3BOHTH4V.js";
|
|
6
6
|
import {
|
|
7
7
|
issuePlaceholders,
|
|
8
8
|
resolveCommandIssue
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-AAY6KM7N.js";
|
|
10
|
+
import {
|
|
11
|
+
runHeadless
|
|
12
|
+
} from "./chunk-PL6CNMVS.js";
|
|
10
13
|
import {
|
|
11
14
|
isTransientFailure
|
|
12
15
|
} from "./chunk-HDV3YDTG.js";
|
|
13
16
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
publishPhaseMetrics
|
|
18
|
-
} from "./chunk-DTNM3LTO.js";
|
|
17
|
+
publishPhaseMetrics,
|
|
18
|
+
resolvePolicyPlaceholders
|
|
19
|
+
} from "./chunk-OBLVLIUS.js";
|
|
19
20
|
import {
|
|
20
21
|
applyPlaceholders,
|
|
21
22
|
loadPrompt
|
|
22
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-HQYXVIH6.js";
|
|
23
24
|
import {
|
|
24
25
|
resolveIssuePaths
|
|
25
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-VQ5VN7DF.js";
|
|
26
27
|
import {
|
|
27
28
|
getGlobalTimeout,
|
|
28
29
|
loadTaskPlan,
|
|
29
30
|
printError,
|
|
30
31
|
printSuccess,
|
|
31
32
|
saveTaskPlan
|
|
32
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-HC2MT7BX.js";
|
|
33
34
|
|
|
34
35
|
// src/commands/prd.ts
|
|
35
36
|
import { mkdir } from "fs/promises";
|
|
@@ -44,6 +45,9 @@ async function runPrd(issue, resolvedIssue) {
|
|
|
44
45
|
await mkdir(paths.issueDir, { recursive: true });
|
|
45
46
|
const template = await loadPrompt("prd");
|
|
46
47
|
const prompt = applyPlaceholders(template, {
|
|
48
|
+
// The repository's own conventions. Empty when it declares none, which is
|
|
49
|
+
// what keeps the rendered prompt identical to the pre-policy one.
|
|
50
|
+
...await resolvePolicyPlaceholders(),
|
|
47
51
|
__ISSUE_NUMBER__: issueNumber,
|
|
48
52
|
__PRD_PATH__: prdPath,
|
|
49
53
|
// Absolute: the analysis lives in the global storage, outside the working
|
|
@@ -103,4 +107,4 @@ async function runPrd(issue, resolvedIssue) {
|
|
|
103
107
|
export {
|
|
104
108
|
runPrd
|
|
105
109
|
};
|
|
106
|
-
//# sourceMappingURL=chunk-
|
|
110
|
+
//# sourceMappingURL=chunk-MSPUB635.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/prd.ts"],"sourcesContent":["import { mkdir } from 'node:fs/promises';\nimport { runHeadless } from '../core/headless.js';\nimport { readFileWithGrace, runPhaseWithRetry } from '../core/phase-runner.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { publishPhaseMetrics } from '../core/session-metrics.js';\nimport { loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { issuePlaceholders, resolveCommandIssue } from '../issues/context.js';\nimport type { ResolvedIssue } from '../issues/types.js';\nimport { resolvePolicyPlaceholders } from '../policy/placeholders.js';\nimport { resolveIssuePaths } from '../storage/resolve.js';\nimport { printError, printSuccess } from '../ui/logger.js';\nimport { isTransientFailure } from '../utils/retry.js';\n\nexport async function runPrd(issue: string, resolvedIssue?: ResolvedIssue): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const paths = await resolveIssuePaths(issueNumber);\n const prdPath = paths.prdFile;\n\n const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);\n if (!resolution.ok) {\n return resolution.code;\n }\n\n await mkdir(paths.issueDir, { recursive: true });\n\n const template = await loadPrompt('prd');\n const prompt = applyPlaceholders(template, {\n // The repository's own conventions. Empty when it declares none, which is\n // what keeps the rendered prompt identical to the pre-policy one.\n ...(await resolvePolicyPlaceholders()),\n __ISSUE_NUMBER__: issueNumber,\n __PRD_PATH__: prdPath,\n // Absolute: the analysis lives in the global storage, outside the working\n // directory the agent is started in.\n __ANALYSIS_PATH__: paths.analysisFile,\n ...issuePlaceholders(resolution.resolved),\n });\n\n const outcome = await runPhaseWithRetry({\n phase: 'prd',\n attempt: async () => {\n const startedAtMs = Date.now();\n const result = await runHeadless({\n prompt,\n maxTurns: 25,\n timeout: getGlobalTimeout() ?? 300_000,\n // json (not text) so the CLI reports usage: the envelope's `result`\n // field carries the same assistant text this phase already consumed.\n outputFormat: 'json',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep', 'Write'],\n addDirs: [paths.issueDir],\n statusMessage: `Generating PRD for issue #${issueNumber}...`,\n });\n // One event per attempt; the reducer sums them into the phase total.\n publishPhaseMetrics('prd', result.cost, startedAtMs);\n\n if (!result.success) {\n return {\n ok: false,\n transient: isTransientFailure(1, result.error ?? ''),\n error: `PRD generation failed: ${result.error}`,\n };\n }\n\n // Verify the file was created. A brief grace period absorbs a lag\n // between the Write tool completing and this process seeing it; if it\n // still isn't there, treat it as recoverable and let the phase retry.\n try {\n const content = await readFileWithGrace(prdPath);\n if (content.length < 10) {\n return { ok: false, transient: true, error: 'PRD file was created but appears empty' };\n }\n } catch {\n return { ok: false, transient: true, error: `PRD file was not created at ${prdPath}` };\n }\n\n return { ok: true };\n },\n });\n\n if (!outcome.ok) {\n printError(outcome.error ?? `PRD generation failed for issue #${issueNumber}`);\n return 1;\n }\n\n // Update pipeline state\n const tasksPath = paths.tasksFile;\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.prdCompleted = true;\n await saveTaskPlan(tasksPath, plan);\n } catch {\n // tasks.json may not exist yet\n }\n\n printSuccess(`PRD saved to ${prdPath}`);\n return 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,aAAa;AActB,eAAsB,OAAO,OAAe,eAAgD;AAC1F,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,QAAQ,MAAM,kBAAkB,WAAW;AACjD,QAAM,UAAU,MAAM;AAEtB,QAAM,aAAa,MAAM,oBAAoB,aAAa,aAAa;AACvE,MAAI,CAAC,WAAW,IAAI;AAClB,WAAO,WAAW;AAAA,EACpB;AAEA,QAAM,MAAM,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAE/C,QAAM,WAAW,MAAM,WAAW,KAAK;AACvC,QAAM,SAAS,kBAAkB,UAAU;AAAA;AAAA;AAAA,IAGzC,GAAI,MAAM,0BAA0B;AAAA,IACpC,kBAAkB;AAAA,IAClB,cAAc;AAAA;AAAA;AAAA,IAGd,mBAAmB,MAAM;AAAA,IACzB,GAAG,kBAAkB,WAAW,QAAQ;AAAA,EAC1C,CAAC;AAED,QAAM,UAAU,MAAM,kBAAkB;AAAA,IACtC,OAAO;AAAA,IACP,SAAS,YAAY;AACnB,YAAM,cAAc,KAAK,IAAI;AAC7B,YAAM,SAAS,MAAM,YAAY;AAAA,QAC/B;AAAA,QACA,UAAU;AAAA,QACV,SAAS,iBAAiB,KAAK;AAAA;AAAA;AAAA,QAG/B,cAAc;AAAA,QACd,cAAc,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,OAAO;AAAA,QACtD,SAAS,CAAC,MAAM,QAAQ;AAAA,QACxB,eAAe,6BAA6B,WAAW;AAAA,MACzD,CAAC;AAED,0BAAoB,OAAO,OAAO,MAAM,WAAW;AAEnD,UAAI,CAAC,OAAO,SAAS;AACnB,eAAO;AAAA,UACL,IAAI;AAAA,UACJ,WAAW,mBAAmB,GAAG,OAAO,SAAS,EAAE;AAAA,UACnD,OAAO,0BAA0B,OAAO,KAAK;AAAA,QAC/C;AAAA,MACF;AAKA,UAAI;AACF,cAAM,UAAU,MAAM,kBAAkB,OAAO;AAC/C,YAAI,QAAQ,SAAS,IAAI;AACvB,iBAAO,EAAE,IAAI,OAAO,WAAW,MAAM,OAAO,yCAAyC;AAAA,QACvF;AAAA,MACF,QAAQ;AACN,eAAO,EAAE,IAAI,OAAO,WAAW,MAAM,OAAO,+BAA+B,OAAO,GAAG;AAAA,MACvF;AAEA,aAAO,EAAE,IAAI,KAAK;AAAA,IACpB;AAAA,EACF,CAAC;AAED,MAAI,CAAC,QAAQ,IAAI;AACf,eAAW,QAAQ,SAAS,oCAAoC,WAAW,EAAE;AAC7E,WAAO;AAAA,EACT;AAGA,QAAM,YAAY,MAAM;AACxB,MAAI;AACF,UAAM,OAAO,MAAM,aAAa,SAAS;AACzC,SAAK,SAAS,eAAe;AAC7B,UAAM,aAAa,WAAW,IAAI;AAAA,EACpC,QAAQ;AAAA,EAER;AAEA,eAAa,gBAAgB,OAAO,EAAE;AACtC,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
loadRepositoryPolicy
|
|
4
|
+
} from "./chunk-47VIN33V.js";
|
|
5
|
+
import {
|
|
6
|
+
getBaseBranch,
|
|
7
|
+
loadPolicyConfig
|
|
8
|
+
} from "./chunk-VQ5VN7DF.js";
|
|
9
|
+
import {
|
|
10
|
+
getSessionPublisher,
|
|
11
|
+
hasUsageData,
|
|
12
|
+
isoNow,
|
|
13
|
+
sumUsage
|
|
14
|
+
} from "./chunk-HC2MT7BX.js";
|
|
15
|
+
|
|
16
|
+
// src/core/session-metrics.ts
|
|
17
|
+
function elapsedSecondsSince(startedAtMs) {
|
|
18
|
+
return Math.max(0, Math.round((Date.now() - startedAtMs) / 1e3));
|
|
19
|
+
}
|
|
20
|
+
function createAccumulator() {
|
|
21
|
+
return { all: {}, byPhase: /* @__PURE__ */ new Map() };
|
|
22
|
+
}
|
|
23
|
+
var scopes = [createAccumulator()];
|
|
24
|
+
function current() {
|
|
25
|
+
return scopes[scopes.length - 1];
|
|
26
|
+
}
|
|
27
|
+
function recordRunUsage(phase, usage) {
|
|
28
|
+
for (const scope of scopes) {
|
|
29
|
+
scope.all = sumUsage(scope.all, usage);
|
|
30
|
+
scope.byPhase.set(phase, sumUsage(scope.byPhase.get(phase), usage));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function getRunUsageTotals() {
|
|
34
|
+
return { ...current().all };
|
|
35
|
+
}
|
|
36
|
+
function getPhaseUsageTotals(phase) {
|
|
37
|
+
return { ...current().byPhase.get(phase) ?? {} };
|
|
38
|
+
}
|
|
39
|
+
function beginUsageScope() {
|
|
40
|
+
const accumulator = createAccumulator();
|
|
41
|
+
scopes.push(accumulator);
|
|
42
|
+
let open = true;
|
|
43
|
+
return {
|
|
44
|
+
totals: () => ({ ...accumulator.all }),
|
|
45
|
+
phaseTotals: (phase) => ({ ...accumulator.byPhase.get(phase) ?? {} }),
|
|
46
|
+
end: () => {
|
|
47
|
+
if (open) {
|
|
48
|
+
const index = scopes.indexOf(accumulator);
|
|
49
|
+
if (index > 0) scopes.splice(index, 1);
|
|
50
|
+
open = false;
|
|
51
|
+
}
|
|
52
|
+
return { ...accumulator.all };
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function publishPhaseMetrics(phase, usage, startedAtMs) {
|
|
57
|
+
if (usage == null || !hasUsageData(usage)) return;
|
|
58
|
+
recordRunUsage(phase, usage);
|
|
59
|
+
getSessionPublisher().publish({
|
|
60
|
+
type: "metrics:update",
|
|
61
|
+
at: isoNow(),
|
|
62
|
+
scope: "phase",
|
|
63
|
+
phase,
|
|
64
|
+
...usage,
|
|
65
|
+
// Informational only: a phase's durationSeconds keeps coming exclusively
|
|
66
|
+
// from phase:start/phase:end, so the reducer ignores this for scope 'phase'.
|
|
67
|
+
durationSeconds: startedAtMs === void 0 ? void 0 : elapsedSecondsSince(startedAtMs)
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
var EXECUTE_PHASE = "execute";
|
|
71
|
+
function publishIterationMetrics(iteration, usage, durationSeconds) {
|
|
72
|
+
if (usage == null || !hasUsageData(usage)) return;
|
|
73
|
+
recordRunUsage(EXECUTE_PHASE, usage);
|
|
74
|
+
getSessionPublisher().publish({
|
|
75
|
+
type: "metrics:update",
|
|
76
|
+
at: isoNow(),
|
|
77
|
+
scope: "iteration",
|
|
78
|
+
phase: EXECUTE_PHASE,
|
|
79
|
+
iteration,
|
|
80
|
+
...usage,
|
|
81
|
+
// Informational only: the reducer keeps a phase's durationSeconds coming
|
|
82
|
+
// exclusively from phase:start/phase:end.
|
|
83
|
+
durationSeconds
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function publishStoryMetrics(storyId, usage, durationSeconds) {
|
|
87
|
+
if (!hasUsageData(usage) && durationSeconds === void 0) return;
|
|
88
|
+
getSessionPublisher().publish({
|
|
89
|
+
type: "metrics:update",
|
|
90
|
+
at: isoNow(),
|
|
91
|
+
scope: "story",
|
|
92
|
+
storyId,
|
|
93
|
+
...usage ?? {},
|
|
94
|
+
durationSeconds
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/policy/placeholders.ts
|
|
99
|
+
var POLICY_PLACEHOLDER_KEYS = [
|
|
100
|
+
"__REPO_POLICY__",
|
|
101
|
+
"__REPO_ISSUE_TEMPLATES__",
|
|
102
|
+
"__REPO_LABELS__",
|
|
103
|
+
"__REPO_ISSUE_TYPES__",
|
|
104
|
+
"__REPO_PR_TEMPLATE__",
|
|
105
|
+
"__REPO_BASE_BRANCH__",
|
|
106
|
+
"__REPO_CONVENTIONS__",
|
|
107
|
+
"__REPO_DOCS__"
|
|
108
|
+
];
|
|
109
|
+
function emptyPolicyPlaceholders() {
|
|
110
|
+
return Object.fromEntries(POLICY_PLACEHOLDER_KEYS.map((key) => [key, ""]));
|
|
111
|
+
}
|
|
112
|
+
function estimateTokens(text) {
|
|
113
|
+
return Math.ceil(text.length / 4);
|
|
114
|
+
}
|
|
115
|
+
function renderIssueTemplates(templates) {
|
|
116
|
+
if (templates.length === 0) return "";
|
|
117
|
+
const lines = templates.map((template) => {
|
|
118
|
+
const parts = [`- **${template.name ?? template.path}** (\`${template.path}\`)`];
|
|
119
|
+
if (template.type !== null) parts.push(`type: ${template.type}`);
|
|
120
|
+
if (template.labels.length > 0) parts.push(`labels: ${template.labels.join(", ")}`);
|
|
121
|
+
if (template.title !== null) parts.push(`title prefix: \`${template.title}\``);
|
|
122
|
+
if (template.about !== null) parts.push(template.about);
|
|
123
|
+
return parts.join(" \u2014 ");
|
|
124
|
+
});
|
|
125
|
+
return lines.join("\n");
|
|
126
|
+
}
|
|
127
|
+
function renderLabels(policy) {
|
|
128
|
+
if (policy.issues.labels.length === 0) return "";
|
|
129
|
+
return policy.issues.labels.map(
|
|
130
|
+
(label) => label.description === null ? `- ${label.name}` : `- ${label.name} \u2014 ${label.description}`
|
|
131
|
+
).join("\n");
|
|
132
|
+
}
|
|
133
|
+
function renderConventions(policy) {
|
|
134
|
+
const rows = [];
|
|
135
|
+
if (policy.issues.titleConvention !== null) {
|
|
136
|
+
rows.push(`- Issue title: ${policy.issues.titleConvention}`);
|
|
137
|
+
}
|
|
138
|
+
if (policy.pullRequests.titleConvention !== null) {
|
|
139
|
+
rows.push(`- Pull Request title: ${policy.pullRequests.titleConvention}`);
|
|
140
|
+
}
|
|
141
|
+
if (policy.git.branchConvention !== null) {
|
|
142
|
+
rows.push(`- Branch: ${policy.git.branchConvention}`);
|
|
143
|
+
}
|
|
144
|
+
if (policy.git.commitConvention !== null) {
|
|
145
|
+
rows.push(`- Commit: ${policy.git.commitConvention}`);
|
|
146
|
+
}
|
|
147
|
+
return rows.join("\n");
|
|
148
|
+
}
|
|
149
|
+
function renderDocs(docs) {
|
|
150
|
+
if (docs.length === 0) return "";
|
|
151
|
+
return docs.map((doc) => {
|
|
152
|
+
const scope = doc.scope === "" ? "" : ` (applies to \`${doc.scope}/\`)`;
|
|
153
|
+
return `- \`${doc.path}\`${scope}`;
|
|
154
|
+
}).join("\n");
|
|
155
|
+
}
|
|
156
|
+
function renderPolicySummary(policy, budgetTokens) {
|
|
157
|
+
const sections = [
|
|
158
|
+
{
|
|
159
|
+
title: "Base branch",
|
|
160
|
+
body: policy.pullRequests.baseBranch ?? "",
|
|
161
|
+
essential: true,
|
|
162
|
+
pointer: ""
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
title: "Conventions",
|
|
166
|
+
body: renderConventions(policy),
|
|
167
|
+
essential: true,
|
|
168
|
+
pointer: ""
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
title: "Issue Types",
|
|
172
|
+
body: policy.issues.types.join(", "),
|
|
173
|
+
essential: true,
|
|
174
|
+
pointer: ""
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
title: "Issue Templates",
|
|
178
|
+
body: renderIssueTemplates(policy.issues.templates),
|
|
179
|
+
essential: true,
|
|
180
|
+
pointer: "Run `issue-flow policy --json` to read the templates in full."
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
title: "Labels",
|
|
184
|
+
body: renderLabels(policy),
|
|
185
|
+
essential: true,
|
|
186
|
+
pointer: "Run `gh label list` to read the labels of this repository."
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
title: "Pull Request template",
|
|
190
|
+
body: policy.pullRequests.templates[0]?.path ?? "",
|
|
191
|
+
essential: false,
|
|
192
|
+
pointer: ""
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
title: "Policy documents",
|
|
196
|
+
body: renderDocs(policy.docs),
|
|
197
|
+
essential: false,
|
|
198
|
+
pointer: ""
|
|
199
|
+
}
|
|
200
|
+
];
|
|
201
|
+
const rendered = [];
|
|
202
|
+
let used = 0;
|
|
203
|
+
for (const section of sections) {
|
|
204
|
+
if (section.body.trim() === "") continue;
|
|
205
|
+
const block = `### ${section.title}
|
|
206
|
+
|
|
207
|
+
${section.body}`;
|
|
208
|
+
const cost = estimateTokens(block);
|
|
209
|
+
if (used + cost <= budgetTokens) {
|
|
210
|
+
rendered.push(block);
|
|
211
|
+
used += cost;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (section.essential && section.pointer !== "") {
|
|
215
|
+
const fallback = `### ${section.title}
|
|
216
|
+
|
|
217
|
+
${section.pointer}`;
|
|
218
|
+
rendered.push(fallback);
|
|
219
|
+
used += estimateTokens(fallback);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return rendered.join("\n\n");
|
|
223
|
+
}
|
|
224
|
+
function isEmptyPolicy(policy) {
|
|
225
|
+
return policy.issues.templates.length === 0 && policy.issues.types.length === 0 && policy.issues.labels.length === 0 && policy.issues.titleConvention === null && policy.pullRequests.templates.length === 0 && policy.pullRequests.baseBranch === null && policy.pullRequests.titleConvention === null && policy.git.branchConvention === null && policy.git.commitConvention === null && policy.docs.length === 0;
|
|
226
|
+
}
|
|
227
|
+
function policyPlaceholders(policy, options = {}) {
|
|
228
|
+
if (policy === null || !policy.enabled || isEmptyPolicy(policy)) {
|
|
229
|
+
return emptyPolicyPlaceholders();
|
|
230
|
+
}
|
|
231
|
+
const budget = options.budgetTokens ?? DEFAULT_POLICY_CONTEXT_BUDGET;
|
|
232
|
+
return {
|
|
233
|
+
__REPO_POLICY__: renderPolicySummary(policy, budget),
|
|
234
|
+
__REPO_ISSUE_TEMPLATES__: renderIssueTemplates(policy.issues.templates),
|
|
235
|
+
__REPO_LABELS__: renderLabels(policy),
|
|
236
|
+
__REPO_ISSUE_TYPES__: policy.issues.types.join(", "),
|
|
237
|
+
// The one place a whole file is projected verbatim: a Pull Request body has
|
|
238
|
+
// to *be* the template, so a summary of it would be useless.
|
|
239
|
+
__REPO_PR_TEMPLATE__: policy.pullRequests.template ?? "",
|
|
240
|
+
__REPO_BASE_BRANCH__: policy.pullRequests.baseBranch ?? "",
|
|
241
|
+
__REPO_CONVENTIONS__: renderConventions(policy),
|
|
242
|
+
__REPO_DOCS__: renderDocs(policy.docs)
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
var DEFAULT_POLICY_CONTEXT_BUDGET = 1500;
|
|
246
|
+
var DEFAULT_BRANCH_CONVENTION = "issue/{N}-{slug}";
|
|
247
|
+
function conventionPlaceholders(policy, baseBranchFallback) {
|
|
248
|
+
return {
|
|
249
|
+
__BASE_BRANCH__: policy?.pullRequests.baseBranch ?? baseBranchFallback,
|
|
250
|
+
__BRANCH_CONVENTION__: policy?.git.branchConvention ?? DEFAULT_BRANCH_CONVENTION,
|
|
251
|
+
// The one member of this group that may legitimately render empty: it gates
|
|
252
|
+
// a conditional section rather than sitting inside a command.
|
|
253
|
+
__COMMIT_CONVENTION__: policy?.git.commitConvention ?? ""
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
async function resolvePolicyPlaceholders(options = {}) {
|
|
257
|
+
let policy = null;
|
|
258
|
+
let projection = emptyPolicyPlaceholders();
|
|
259
|
+
try {
|
|
260
|
+
policy = await loadRepositoryPolicy({ root: options.root, scope: options.scope ?? null });
|
|
261
|
+
const config = await loadPolicyConfig({ projectRoot: policy.root });
|
|
262
|
+
projection = policyPlaceholders(policy, { budgetTokens: config.contextBudget });
|
|
263
|
+
} catch {
|
|
264
|
+
policy = null;
|
|
265
|
+
}
|
|
266
|
+
let fallbackBase = "main";
|
|
267
|
+
try {
|
|
268
|
+
fallbackBase = await getBaseBranch();
|
|
269
|
+
} catch {
|
|
270
|
+
}
|
|
271
|
+
return { ...projection, ...conventionPlaceholders(policy, fallbackBase) };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export {
|
|
275
|
+
elapsedSecondsSince,
|
|
276
|
+
getRunUsageTotals,
|
|
277
|
+
getPhaseUsageTotals,
|
|
278
|
+
beginUsageScope,
|
|
279
|
+
publishPhaseMetrics,
|
|
280
|
+
EXECUTE_PHASE,
|
|
281
|
+
publishIterationMetrics,
|
|
282
|
+
publishStoryMetrics,
|
|
283
|
+
resolvePolicyPlaceholders
|
|
284
|
+
};
|
|
285
|
+
//# sourceMappingURL=chunk-OBLVLIUS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/session-metrics.ts","../src/policy/placeholders.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","import { loadPolicyConfig } from '../config.js';\nimport { getBaseBranch } from '../utils/git.js';\nimport { loadRepositoryPolicy } from './resolve.js';\nimport type { IssueTemplate, PolicyDocument, RepositoryPolicy } from './types.js';\n\n/**\n * Projection of a {@link RepositoryPolicy} onto the prompt placeholders.\n *\n * The prompts receive a **summary**, never the raw files. Dumping\n * `CONTRIBUTING.md` and three `AGENTS.md` into every prompt would cost tokens on\n * every single run and drown the actual instruction — and it would duplicate,\n * inside Issue Flow, a rule that already lives in the repository.\n *\n * The whole projection is empty when the repository declares nothing, which is\n * what lets a prompt render byte for byte as it did before this layer existed.\n */\n\n/** Every placeholder this module produces. Ordered as they appear in a prompt. */\nexport const POLICY_PLACEHOLDER_KEYS = [\n '__REPO_POLICY__',\n '__REPO_ISSUE_TEMPLATES__',\n '__REPO_LABELS__',\n '__REPO_ISSUE_TYPES__',\n '__REPO_PR_TEMPLATE__',\n '__REPO_BASE_BRANCH__',\n '__REPO_CONVENTIONS__',\n '__REPO_DOCS__',\n] as const;\n\nexport type PolicyPlaceholderKey = (typeof POLICY_PLACEHOLDER_KEYS)[number];\n\n/** Every placeholder empty — the projection of \"this repository declares nothing\". */\nexport function emptyPolicyPlaceholders(): Record<string, string> {\n return Object.fromEntries(POLICY_PLACEHOLDER_KEYS.map((key) => [key, ''])) as Record<\n string,\n string\n >;\n}\n\n/**\n * Rough token count of a rendered section.\n *\n * Four characters per token is the usual English/Portuguese approximation and is\n * deliberately crude: the budget exists to stop a policy from swallowing the\n * prompt, not to bill anyone. Erring high is the safe direction, so the estimate\n * rounds up.\n */\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n\n// ── Section renderers ───────────────────────────────────────────────────────\n\nfunction renderIssueTemplates(templates: IssueTemplate[]): string {\n if (templates.length === 0) return '';\n\n const lines = templates.map((template) => {\n const parts = [`- **${template.name ?? template.path}** (\\`${template.path}\\`)`];\n if (template.type !== null) parts.push(`type: ${template.type}`);\n if (template.labels.length > 0) parts.push(`labels: ${template.labels.join(', ')}`);\n if (template.title !== null) parts.push(`title prefix: \\`${template.title}\\``);\n if (template.about !== null) parts.push(template.about);\n return parts.join(' — ');\n });\n\n return lines.join('\\n');\n}\n\nfunction renderLabels(policy: RepositoryPolicy): string {\n if (policy.issues.labels.length === 0) return '';\n\n return policy.issues.labels\n .map((label) =>\n label.description === null ? `- ${label.name}` : `- ${label.name} — ${label.description}`,\n )\n .join('\\n');\n}\n\nfunction renderConventions(policy: RepositoryPolicy): string {\n const rows: string[] = [];\n if (policy.issues.titleConvention !== null) {\n rows.push(`- Issue title: ${policy.issues.titleConvention}`);\n }\n if (policy.pullRequests.titleConvention !== null) {\n rows.push(`- Pull Request title: ${policy.pullRequests.titleConvention}`);\n }\n if (policy.git.branchConvention !== null) {\n rows.push(`- Branch: ${policy.git.branchConvention}`);\n }\n if (policy.git.commitConvention !== null) {\n rows.push(`- Commit: ${policy.git.commitConvention}`);\n }\n return rows.join('\\n');\n}\n\n/**\n * Paths of the applicable documents — **never their content**.\n *\n * The agent has `Read` and fetches what it needs. Embedding the documents would\n * multiply the cost of every run and, worse, freeze a copy of the repository's\n * own rule inside a prompt.\n */\nfunction renderDocs(docs: PolicyDocument[]): string {\n if (docs.length === 0) return '';\n\n return docs\n .map((doc) => {\n const scope = doc.scope === '' ? '' : ` (applies to \\`${doc.scope}/\\`)`;\n return `- \\`${doc.path}\\`${scope}`;\n })\n .join('\\n');\n}\n\n// ── The budgeted summary ────────────────────────────────────────────────────\n\ninterface Section {\n /** Heading inside the summary. */\n title: string;\n body: string;\n /**\n * Kept whole even when the budget is blown. The essentials are the taxonomy\n * an agent cannot guess: it can re-read a document, but it cannot invent the\n * label a repository actually uses.\n */\n essential: boolean;\n /** Shown in place of the body once the budget is exhausted. */\n pointer: string;\n}\n\n/**\n * Compose `__REPO_POLICY__` within `budgetTokens`.\n *\n * Sections are added whole, in priority order, while they fit. A section that\n * does not fit is replaced **whole** by a pointer to where the agent can read it\n * — nothing is ever cut mid-sentence, because a summary truncated mid-rule is\n * worse than one that says where the rule lives.\n */\nexport function renderPolicySummary(policy: RepositoryPolicy, budgetTokens: number): string {\n const sections: Section[] = [\n {\n title: 'Base branch',\n body: policy.pullRequests.baseBranch ?? '',\n essential: true,\n pointer: '',\n },\n {\n title: 'Conventions',\n body: renderConventions(policy),\n essential: true,\n pointer: '',\n },\n {\n title: 'Issue Types',\n body: policy.issues.types.join(', '),\n essential: true,\n pointer: '',\n },\n {\n title: 'Issue Templates',\n body: renderIssueTemplates(policy.issues.templates),\n essential: true,\n pointer: 'Run `issue-flow policy --json` to read the templates in full.',\n },\n {\n title: 'Labels',\n body: renderLabels(policy),\n essential: true,\n pointer: 'Run `gh label list` to read the labels of this repository.',\n },\n {\n title: 'Pull Request template',\n body: policy.pullRequests.templates[0]?.path ?? '',\n essential: false,\n pointer: '',\n },\n {\n title: 'Policy documents',\n body: renderDocs(policy.docs),\n essential: false,\n pointer: '',\n },\n ];\n\n const rendered: string[] = [];\n let used = 0;\n\n for (const section of sections) {\n if (section.body.trim() === '') continue;\n\n const block = `### ${section.title}\\n\\n${section.body}`;\n const cost = estimateTokens(block);\n\n if (used + cost <= budgetTokens) {\n rendered.push(block);\n used += cost;\n continue;\n }\n\n // Over budget. An essential section keeps its slot as a pointer; a\n // non-essential one simply drops out.\n if (section.essential && section.pointer !== '') {\n const fallback = `### ${section.title}\\n\\n${section.pointer}`;\n rendered.push(fallback);\n used += estimateTokens(fallback);\n }\n }\n\n return rendered.join('\\n\\n');\n}\n\n/** True when the policy carries nothing a prompt could act on. */\nexport function isEmptyPolicy(policy: RepositoryPolicy): boolean {\n return (\n policy.issues.templates.length === 0 &&\n policy.issues.types.length === 0 &&\n policy.issues.labels.length === 0 &&\n policy.issues.titleConvention === null &&\n policy.pullRequests.templates.length === 0 &&\n policy.pullRequests.baseBranch === null &&\n policy.pullRequests.titleConvention === null &&\n policy.git.branchConvention === null &&\n policy.git.commitConvention === null &&\n policy.docs.length === 0\n );\n}\n\nexport interface PolicyPlaceholderOptions {\n /** Token budget for `__REPO_POLICY__`. Defaults to the resolved configuration. */\n budgetTokens?: number;\n}\n\n/**\n * Project a resolved policy onto the prompt placeholders.\n *\n * A `null` policy — or one that carries nothing — yields every placeholder\n * empty, which is what makes the rendered prompt identical to the pre-policy\n * one. This is the contract the whole feature rests on.\n */\nexport function policyPlaceholders(\n policy: RepositoryPolicy | null,\n options: PolicyPlaceholderOptions = {},\n): Record<string, string> {\n if (policy === null || !policy.enabled || isEmptyPolicy(policy)) {\n return emptyPolicyPlaceholders();\n }\n\n const budget = options.budgetTokens ?? DEFAULT_POLICY_CONTEXT_BUDGET;\n\n return {\n __REPO_POLICY__: renderPolicySummary(policy, budget),\n __REPO_ISSUE_TEMPLATES__: renderIssueTemplates(policy.issues.templates),\n __REPO_LABELS__: renderLabels(policy),\n __REPO_ISSUE_TYPES__: policy.issues.types.join(', '),\n // The one place a whole file is projected verbatim: a Pull Request body has\n // to *be* the template, so a summary of it would be useless.\n __REPO_PR_TEMPLATE__: policy.pullRequests.template ?? '',\n __REPO_BASE_BRANCH__: policy.pullRequests.baseBranch ?? '',\n __REPO_CONVENTIONS__: renderConventions(policy),\n __REPO_DOCS__: renderDocs(policy.docs),\n };\n}\n\n/** Default token budget for `__REPO_POLICY__`, overridable via `policy.contextBudget`. */\nexport const DEFAULT_POLICY_CONTEXT_BUDGET = 1500;\n\n// ── Conventions that always have a value ────────────────────────────────────\n\n/** Branch naming Issue Flow has always used, when the repository declares none. */\nexport const DEFAULT_BRANCH_CONVENTION = 'issue/{N}-{slug}';\n\n/**\n * Placeholders that must **never** render empty, because a prompt puts them\n * inside a command it asks the agent to run.\n *\n * `__BASE_BRANCH__` is the reason this group exists separately from the policy\n * projection. `prompts/pr.md` used to spell `main` three times — in\n * `git log main..HEAD`, in `git diff main...HEAD` and in `gh pr create --base\n * main`. In a repository whose base is `develop` that is not a missing feature\n * but an active defect: `main` often *exists* there too, so nothing fails — the\n * agent simply reviews the wrong diff and opens the Pull Request against the\n * wrong target.\n *\n * The resolution order is the repository's declared or discovered base, then\n * `getBaseBranch()`, whose own fallback is `main`. So a repository on `main`\n * renders exactly the text it rendered before.\n */\nexport function conventionPlaceholders(\n policy: RepositoryPolicy | null,\n baseBranchFallback: string,\n): Record<string, string> {\n return {\n __BASE_BRANCH__: policy?.pullRequests.baseBranch ?? baseBranchFallback,\n __BRANCH_CONVENTION__: policy?.git.branchConvention ?? DEFAULT_BRANCH_CONVENTION,\n // The one member of this group that may legitimately render empty: it gates\n // a conditional section rather than sitting inside a command.\n __COMMIT_CONVENTION__: policy?.git.commitConvention ?? '',\n };\n}\n\nexport interface ResolvePolicyPlaceholdersOptions {\n /** Repository root. Defaults to the git project root. */\n root?: string;\n /** Subdirectory the policy applies to, for monorepos. */\n scope?: string | null;\n}\n\n/**\n * Resolve the repository policy and project it, in one call.\n *\n * This is what every command uses. It **never throws and never warns**: a\n * repository that is not a git checkout, a discovery that fails, a `gh` that is\n * not installed — all of them yield the empty projection, and the command runs\n * exactly as it did before.\n *\n * The resolution itself is cached per `(root, scope)` by `loadRepositoryPolicy`,\n * so a pipeline that renders eight prompts still discovers once.\n */\nexport async function resolvePolicyPlaceholders(\n options: ResolvePolicyPlaceholdersOptions = {},\n): Promise<Record<string, string>> {\n let policy: RepositoryPolicy | null = null;\n let projection = emptyPolicyPlaceholders();\n try {\n policy = await loadRepositoryPolicy({ root: options.root, scope: options.scope ?? null });\n const config = await loadPolicyConfig({ projectRoot: policy.root });\n projection = policyPlaceholders(policy, { budgetTokens: config.contextBudget });\n } catch {\n // Discovery is an enrichment; the conventions below still have to resolve.\n policy = null;\n }\n\n let fallbackBase = 'main';\n try {\n fallbackBase = await getBaseBranch();\n } catch {\n // getBaseBranch() does not throw today, but its contract is \"never fails\",\n // and a base branch that cannot be resolved must not cost the whole prompt.\n }\n\n return { ...projection, ...conventionPlaceholders(policy, fallbackBase) };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
isoNow,
|
|
11
11
|
parseUsage,
|
|
12
12
|
useColor
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-HC2MT7BX.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-PL6CNMVS.js.map
|