issue-flow 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/README.md +79 -274
  2. package/dist/agent-OX3KPHE4.js +30 -0
  3. package/dist/{analyze-OEET7KGE.js → analyze-WIND2SEB.js} +31 -16
  4. package/dist/analyze-WIND2SEB.js.map +1 -0
  5. package/dist/{apply-ZIA3C52S.js → apply-JNOI2LR4.js} +18 -7
  6. package/dist/{apply-ZIA3C52S.js.map → apply-JNOI2LR4.js.map} +1 -1
  7. package/dist/bench-B4VZON7E.js +910 -0
  8. package/dist/bench-B4VZON7E.js.map +1 -0
  9. package/dist/{chunk-FADTCQGM.js → chunk-2IQ6M4ZE.js} +94 -32
  10. package/dist/chunk-2IQ6M4ZE.js.map +1 -0
  11. package/dist/chunk-3BWZO4YI.js +141 -0
  12. package/dist/chunk-3BWZO4YI.js.map +1 -0
  13. package/dist/chunk-3WFZETIM.js +163 -0
  14. package/dist/chunk-3WFZETIM.js.map +1 -0
  15. package/dist/chunk-67YIU2VB.js +362 -0
  16. package/dist/chunk-67YIU2VB.js.map +1 -0
  17. package/dist/{chunk-QCPGHBSZ.js → chunk-6BENHR2I.js} +63 -21
  18. package/dist/chunk-6BENHR2I.js.map +1 -0
  19. package/dist/{chunk-BYZDPR7N.js → chunk-6QYMSNVA.js} +194 -235
  20. package/dist/chunk-6QYMSNVA.js.map +1 -0
  21. package/dist/{chunk-Y5ZDJ4PJ.js → chunk-76MMPJ53.js} +20 -10
  22. package/dist/chunk-76MMPJ53.js.map +1 -0
  23. package/dist/chunk-7Z4Y3VKM.js +26 -0
  24. package/dist/chunk-7Z4Y3VKM.js.map +1 -0
  25. package/dist/{chunk-3QHK4YQA.js → chunk-B72RQZBV.js} +346 -119
  26. package/dist/chunk-B72RQZBV.js.map +1 -0
  27. package/dist/chunk-BEZ5BA5U.js +625 -0
  28. package/dist/chunk-BEZ5BA5U.js.map +1 -0
  29. package/dist/chunk-BRCNQXYQ.js +74 -0
  30. package/dist/chunk-BRCNQXYQ.js.map +1 -0
  31. package/dist/chunk-CGKLE6WR.js +1203 -0
  32. package/dist/chunk-CGKLE6WR.js.map +1 -0
  33. package/dist/chunk-CRGIQFUQ.js +83 -0
  34. package/dist/chunk-CRGIQFUQ.js.map +1 -0
  35. package/dist/{chunk-NS6R37II.js → chunk-F6TOYSYY.js} +141 -12
  36. package/dist/chunk-F6TOYSYY.js.map +1 -0
  37. package/dist/chunk-G5EK2XRN.js +25 -0
  38. package/dist/chunk-G5EK2XRN.js.map +1 -0
  39. package/dist/chunk-HAKDLR6N.js +134 -0
  40. package/dist/chunk-HAKDLR6N.js.map +1 -0
  41. package/dist/chunk-J3SOTUVI.js +298 -0
  42. package/dist/chunk-J3SOTUVI.js.map +1 -0
  43. package/dist/chunk-JN6D3MY6.js +30 -0
  44. package/dist/chunk-JN6D3MY6.js.map +1 -0
  45. package/dist/{chunk-LAFNLLQP.js → chunk-KMRFRXIT.js} +5 -5
  46. package/dist/chunk-KT7JKOXB.js +72 -0
  47. package/dist/chunk-KT7JKOXB.js.map +1 -0
  48. package/dist/chunk-L6HSKTQE.js +77 -0
  49. package/dist/chunk-L6HSKTQE.js.map +1 -0
  50. package/dist/chunk-LBGHI4S2.js +1264 -0
  51. package/dist/chunk-LBGHI4S2.js.map +1 -0
  52. package/dist/chunk-LEW4QM74.js +180 -0
  53. package/dist/chunk-LEW4QM74.js.map +1 -0
  54. package/dist/{chunk-FJP3UQGD.js → chunk-LFEQIZLB.js} +115 -20
  55. package/dist/chunk-LFEQIZLB.js.map +1 -0
  56. package/dist/chunk-OOVG2KBY.js +77 -0
  57. package/dist/chunk-OOVG2KBY.js.map +1 -0
  58. package/dist/{chunk-56KS3TFQ.js → chunk-OR43364P.js} +115 -160
  59. package/dist/chunk-OR43364P.js.map +1 -0
  60. package/dist/chunk-P3LRUNC6.js +16 -0
  61. package/dist/chunk-P3LRUNC6.js.map +1 -0
  62. package/dist/{chunk-TSOLWK7Y.js → chunk-PBTA3QQX.js} +8 -7
  63. package/dist/{chunk-TSOLWK7Y.js.map → chunk-PBTA3QQX.js.map} +1 -1
  64. package/dist/{chunk-MJULIKQU.js → chunk-PHW2YV6N.js} +23 -47
  65. package/dist/chunk-PHW2YV6N.js.map +1 -0
  66. package/dist/{chunk-73ATPIYX.js → chunk-PZTWU277.js} +36 -71
  67. package/dist/chunk-PZTWU277.js.map +1 -0
  68. package/dist/{chunk-M5UCXTIT.js → chunk-QD4VOILZ.js} +22 -18
  69. package/dist/chunk-QD4VOILZ.js.map +1 -0
  70. package/dist/chunk-QJ5UZAH5.js +2718 -0
  71. package/dist/chunk-QJ5UZAH5.js.map +1 -0
  72. package/dist/chunk-QK7WUO7A.js +283 -0
  73. package/dist/chunk-QK7WUO7A.js.map +1 -0
  74. package/dist/{chunk-A4SLHOW7.js → chunk-QZVKRDJY.js} +27 -19
  75. package/dist/chunk-QZVKRDJY.js.map +1 -0
  76. package/dist/chunk-REO3PXXO.js +71 -0
  77. package/dist/chunk-REO3PXXO.js.map +1 -0
  78. package/dist/{chunk-EZATO3DW.js → chunk-RVO7L4FX.js} +594 -17
  79. package/dist/chunk-RVO7L4FX.js.map +1 -0
  80. package/dist/{chunk-XQUSIEKA.js → chunk-SZMHCEYV.js} +63 -14
  81. package/dist/chunk-SZMHCEYV.js.map +1 -0
  82. package/dist/chunk-TYRNTSPJ.js +243 -0
  83. package/dist/chunk-TYRNTSPJ.js.map +1 -0
  84. package/dist/chunk-UJ3L2MS7.js +238 -0
  85. package/dist/chunk-UJ3L2MS7.js.map +1 -0
  86. package/dist/chunk-V7MO5EDS.js +120 -0
  87. package/dist/chunk-V7MO5EDS.js.map +1 -0
  88. package/dist/chunk-WB2X37MO.js +415 -0
  89. package/dist/chunk-WB2X37MO.js.map +1 -0
  90. package/dist/{chunk-NTMMF5UD.js → chunk-X44FKNKP.js} +525 -252
  91. package/dist/chunk-X44FKNKP.js.map +1 -0
  92. package/dist/chunk-XEYH6ZZI.js +289 -0
  93. package/dist/chunk-XEYH6ZZI.js.map +1 -0
  94. package/dist/cli.js +267 -44
  95. package/dist/cli.js.map +1 -1
  96. package/dist/{config-PRJGWW7J.js → config-HMFMCY2M.js} +31 -5
  97. package/dist/contract-JH2WBIO6.js +8 -0
  98. package/dist/conventions-NJF2OPVQ.js +146 -0
  99. package/dist/conventions-NJF2OPVQ.js.map +1 -0
  100. package/dist/diagnostics-THH42ZMP.js +23 -0
  101. package/dist/execute-KFNNZZHI.js +35 -0
  102. package/dist/{generate-EGB4KV2B.js → generate-FW5C6NP7.js} +37 -20
  103. package/dist/{generate-EGB4KV2B.js.map → generate-FW5C6NP7.js.map} +1 -1
  104. package/dist/git-W72QQ6GT.js +33 -0
  105. package/dist/init-HTN4V67Q.js +24 -0
  106. package/dist/{operations-BYT626KQ.js → operations-BCFOBH42.js} +27 -15
  107. package/dist/{operations-BYT626KQ.js.map → operations-BCFOBH42.js.map} +1 -1
  108. package/dist/permissions-4AKNQZHK.js +18 -0
  109. package/dist/plan-SIZLGA4V.js +34 -0
  110. package/dist/{policy-6S4GFC76.js → policy-DT6Q6ZVM.js} +20 -6
  111. package/dist/policy-DT6Q6ZVM.js.map +1 -0
  112. package/dist/pr-OMWCGUOW.js +39 -0
  113. package/dist/pr-review-N24CZPKR.js +32 -0
  114. package/dist/pr-review-N24CZPKR.js.map +1 -0
  115. package/dist/prd-5OFTZAHJ.js +33 -0
  116. package/dist/prd-5OFTZAHJ.js.map +1 -0
  117. package/dist/ps-RFORTCUD.js +93 -0
  118. package/dist/ps-RFORTCUD.js.map +1 -0
  119. package/dist/recorder-KCCPMKQG.js +32 -0
  120. package/dist/recorder-KCCPMKQG.js.map +1 -0
  121. package/dist/registry-GLPXOMFG.js +15 -0
  122. package/dist/registry-GLPXOMFG.js.map +1 -0
  123. package/dist/{resume-PPCNQVVV.js → resume-M5RLKP4I.js} +55 -34
  124. package/dist/{resume-PPCNQVVV.js.map → resume-M5RLKP4I.js.map} +1 -1
  125. package/dist/review-YM4JAABY.js +35 -0
  126. package/dist/review-YM4JAABY.js.map +1 -0
  127. package/dist/routing-NNT5WKGX.js +94 -0
  128. package/dist/routing-NNT5WKGX.js.map +1 -0
  129. package/dist/run-PZUKHPQK.js +54 -0
  130. package/dist/run-PZUKHPQK.js.map +1 -0
  131. package/dist/session-publisher-T7EI3CIP.js +11 -0
  132. package/dist/session-publisher-T7EI3CIP.js.map +1 -0
  133. package/dist/{state-manager-2EDVDBAR.js → state-manager-GQH32C44.js} +4 -2
  134. package/dist/state-manager-GQH32C44.js.map +1 -0
  135. package/dist/usage-V2CHE5PY.js +177 -0
  136. package/dist/usage-V2CHE5PY.js.map +1 -0
  137. package/dist/{web-EF5WIIGG.js → web-EA3KGNFN.js} +45 -30
  138. package/dist/web-EA3KGNFN.js.map +1 -0
  139. package/package.json +6 -2
  140. package/prompts/execute.md +7 -8
  141. package/prompts/plan.md +4 -5
  142. package/prompts/pr.md +2 -2
  143. package/web/AGENTS.md +152 -13
  144. package/web/public/app.css +380 -109
  145. package/web/public/app.js +525 -25
  146. package/web/public/index.html +68 -1
  147. package/dist/analyze-OEET7KGE.js.map +0 -1
  148. package/dist/chunk-3QHK4YQA.js.map +0 -1
  149. package/dist/chunk-42PJFMAR.js +0 -605
  150. package/dist/chunk-42PJFMAR.js.map +0 -1
  151. package/dist/chunk-4JSOYE4G.js +0 -368
  152. package/dist/chunk-4JSOYE4G.js.map +0 -1
  153. package/dist/chunk-56KS3TFQ.js.map +0 -1
  154. package/dist/chunk-73ATPIYX.js.map +0 -1
  155. package/dist/chunk-A4SLHOW7.js.map +0 -1
  156. package/dist/chunk-BAUPQXPE.js +0 -173
  157. package/dist/chunk-BAUPQXPE.js.map +0 -1
  158. package/dist/chunk-BYZDPR7N.js.map +0 -1
  159. package/dist/chunk-CD5ULRVC.js +0 -615
  160. package/dist/chunk-CD5ULRVC.js.map +0 -1
  161. package/dist/chunk-DIDUVL6O.js +0 -1365
  162. package/dist/chunk-DIDUVL6O.js.map +0 -1
  163. package/dist/chunk-EZATO3DW.js.map +0 -1
  164. package/dist/chunk-FADTCQGM.js.map +0 -1
  165. package/dist/chunk-FJP3UQGD.js.map +0 -1
  166. package/dist/chunk-LWRSMTTZ.js +0 -18
  167. package/dist/chunk-LWRSMTTZ.js.map +0 -1
  168. package/dist/chunk-M5UCXTIT.js.map +0 -1
  169. package/dist/chunk-M66SJHQQ.js +0 -206
  170. package/dist/chunk-M66SJHQQ.js.map +0 -1
  171. package/dist/chunk-MJULIKQU.js.map +0 -1
  172. package/dist/chunk-NS6R37II.js.map +0 -1
  173. package/dist/chunk-NTMMF5UD.js.map +0 -1
  174. package/dist/chunk-QCPGHBSZ.js.map +0 -1
  175. package/dist/chunk-XQUSIEKA.js.map +0 -1
  176. package/dist/chunk-Y5ZDJ4PJ.js.map +0 -1
  177. package/dist/execute-XBNVL753.js +0 -18
  178. package/dist/init-AJHCXJBP.js +0 -10
  179. package/dist/plan-QOZ3RIO6.js +0 -21
  180. package/dist/policy-6S4GFC76.js.map +0 -1
  181. package/dist/pr-EBBHD2ZX.js +0 -26
  182. package/dist/pr-review-VVMW5PO4.js +0 -20
  183. package/dist/prd-JQSZF7PR.js +0 -21
  184. package/dist/review-3F6MARWH.js +0 -19
  185. package/dist/run-RXE5GDQO.js +0 -36
  186. package/dist/web-EF5WIIGG.js.map +0 -1
  187. /package/dist/{config-PRJGWW7J.js.map → agent-OX3KPHE4.js.map} +0 -0
  188. /package/dist/{chunk-LAFNLLQP.js.map → chunk-KMRFRXIT.js.map} +0 -0
  189. /package/dist/{execute-XBNVL753.js.map → config-HMFMCY2M.js.map} +0 -0
  190. /package/dist/{init-AJHCXJBP.js.map → contract-JH2WBIO6.js.map} +0 -0
  191. /package/dist/{plan-QOZ3RIO6.js.map → diagnostics-THH42ZMP.js.map} +0 -0
  192. /package/dist/{pr-EBBHD2ZX.js.map → execute-KFNNZZHI.js.map} +0 -0
  193. /package/dist/{pr-review-VVMW5PO4.js.map → git-W72QQ6GT.js.map} +0 -0
  194. /package/dist/{prd-JQSZF7PR.js.map → init-HTN4V67Q.js.map} +0 -0
  195. /package/dist/{review-3F6MARWH.js.map → permissions-4AKNQZHK.js.map} +0 -0
  196. /package/dist/{run-RXE5GDQO.js.map → plan-SIZLGA4V.js.map} +0 -0
  197. /package/dist/{state-manager-2EDVDBAR.js.map → pr-OMWCGUOW.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/bench.ts","../src/benchmark/corpus.ts","../src/benchmark/live.ts","../src/benchmark/real.ts","../src/benchmark/fixtures/materialize.ts","../src/benchmark/fixtures/templates.ts","../src/benchmark/stats.ts","../src/benchmark/tuple.ts","../src/benchmark/report.ts","../src/benchmark/synthetic.ts"],"sourcesContent":["import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { createInterface } from 'node:readline';\nimport { TASK_CLASSES, type TaskClass } from '../benchmark/corpus.js';\nimport { createLiveRepeatRunner } from '../benchmark/live.js';\nimport { type BenchCampaign, type RepeatRunner, runRealCorpus } from '../benchmark/real.js';\nimport { renderCampaignMarkdown } from '../benchmark/report.js';\nimport { runSyntheticCorpus } from '../benchmark/synthetic.js';\nimport { collectHarnessVersion } from '../benchmark/tuple.js';\nimport { printError, printInfo, printWarning } from '../ui/logger.js';\n\n/** p50 USD from the #79 before table — used only for the pre-flight estimate. */\nexport const BASELINE_COST_P50_USD: Record<TaskClass, number> = {\n trivial: 0.2,\n small: 3.17,\n medium: 6.08,\n analysis: 1.86,\n};\n\nexport class BenchConfirmationError extends Error {\n readonly exitCode = 1;\n constructor(message: string) {\n super(message);\n this.name = 'BenchConfirmationError';\n }\n}\n\nexport interface BenchOptions {\n mode?: 'synthetic' | 'real';\n task?: string[];\n arm?: string[];\n repeats?: number;\n maxCost?: number;\n maxDuration?: number;\n out?: string;\n yes?: boolean;\n repo?: string;\n json?: boolean;\n interactive?: boolean;\n stdin?: NodeJS.ReadableStream;\n stdout?: NodeJS.WritableStream;\n runPipeline?: (issue: string) => Promise<number>;\n runner?: RepeatRunner;\n harnessVersion?: string;\n}\n\nexport function estimateCampaignUsd(tasks: TaskClass[], arms: number, repeats: number): number {\n const perRepeat = tasks.reduce((sum, task) => sum + BASELINE_COST_P50_USD[task], 0);\n return perRepeat * arms * repeats;\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\nexport async function confirmRealCampaign(\n estimate: { cells: number; repeats: number; usd: number; maxCost?: number; maxDuration?: number },\n options: Pick<BenchOptions, 'yes' | 'interactive' | 'stdin' | 'stdout'> = {},\n): Promise<void> {\n printInfo(\n `Real campaign: ${estimate.cells} cells × ${estimate.repeats} repeats ≈ $${estimate.usd.toFixed(2)} (p50 of the #79 baseline).`,\n );\n if (estimate.maxCost !== undefined) {\n printInfo(`Cost ceiling: $${estimate.maxCost.toFixed(2)}.`);\n }\n if (estimate.maxDuration !== undefined) {\n printInfo(`Duration ceiling: ${estimate.maxDuration}ms.`);\n }\n if (options.yes === true) {\n printInfo('--yes: starting the campaign.');\n return;\n }\n const interactive = options.interactive ?? isInteractiveByDefault();\n if (!interactive) {\n throw new BenchConfirmationError(\n 'A real campaign spends money and the terminal is not interactive. Re-run with --yes.',\n );\n }\n const stdin = options.stdin ?? process.stdin;\n const stdout = options.stdout ?? process.stdout;\n const rl = createInterface({ input: stdin, output: stdout });\n try {\n const answer = await new Promise<string>((resolve) => {\n rl.question('Proceed with a paid campaign? [y/N] ', resolve);\n });\n if (!/^\\s*y(es)?\\s*$/i.test(answer)) {\n throw new BenchConfirmationError('Cancelled: the campaign was not confirmed.');\n }\n } finally {\n rl.close();\n }\n}\n\nfunction parseTasks(values: string[] | undefined): TaskClass[] {\n if (values === undefined || values.length === 0) return [...TASK_CLASSES];\n const tasks: TaskClass[] = [];\n for (const value of values) {\n if (!(TASK_CLASSES as readonly string[]).includes(value)) {\n throw new BenchConfirmationError(\n `Unknown task class '${value}'. Must be one of: ${TASK_CLASSES.join(', ')}.`,\n );\n }\n if (!tasks.includes(value as TaskClass)) tasks.push(value as TaskClass);\n }\n return tasks;\n}\n\nfunction printSynthetic(): void {\n const rows = runSyntheticCorpus();\n printInfo('Synthetic corpus (orchestration only; harness is a number).');\n for (const row of rows) {\n printInfo(\n `${row.task} duration=${row.taskDurationMs.toFixed(1)}ms overhead=${row.orchestrationOverheadMs.toFixed(1)}ms verdict=${row.verdict}`,\n );\n }\n}\n\nexport async function runBench(options: BenchOptions = {}): Promise<number> {\n const mode = options.mode ?? 'synthetic';\n if (mode !== 'synthetic' && mode !== 'real') {\n printError(`Unknown bench mode '${mode}'. Use synthetic or real.`);\n return 1;\n }\n if (mode === 'synthetic') {\n printSynthetic();\n return 0;\n }\n if (options.repo !== undefined) {\n printWarning(\n '--repo is an investigation escape: the row is not publishable. A disposable fixture is the only source of a comparable line.',\n );\n }\n\n let tasks: TaskClass[];\n try {\n tasks = parseTasks(options.task);\n } catch (error) {\n printError(error instanceof Error ? error.message : String(error));\n return 1;\n }\n const arms = options.arm !== undefined && options.arm.length > 0 ? options.arm : ['baseline'];\n const repeats = options.repeats ?? 5;\n const usd = estimateCampaignUsd(tasks, arms.length, repeats);\n\n try {\n await confirmRealCampaign(\n {\n cells: tasks.length * arms.length,\n repeats,\n usd,\n ...(options.maxCost === undefined ? {} : { maxCost: options.maxCost }),\n ...(options.maxDuration === undefined ? {} : { maxDuration: options.maxDuration }),\n },\n options,\n );\n } catch (error) {\n if (error instanceof BenchConfirmationError) {\n printError(error.message);\n return error.exitCode;\n }\n throw error;\n }\n\n const harnessVersion = options.harnessVersion ?? (await collectHarnessVersion('claude'));\n const runner =\n options.runner ??\n createLiveRepeatRunner({\n runPipeline:\n options.runPipeline ??\n (async (issue) => {\n const { runPipeline } = await import('./run.js');\n return runPipeline(issue, 'auto', undefined, true, false, { yes: true, only: true });\n }),\n });\n\n const campaign: BenchCampaign = await runRealCorpus({\n tasks,\n arms,\n repeats,\n tupleBase: {\n harness: 'claude',\n harnessVersion,\n model: 'default',\n modelVersion: null,\n effort: 'default',\n verification: 'none',\n strategy: 'pipeline',\n settingSourcesPinned: true,\n fallbackModelPassed: false,\n },\n ...(options.maxCost === undefined ? {} : { maxCostUsd: options.maxCost }),\n ...(options.maxDuration === undefined ? {} : { maxDurationMs: options.maxDuration }),\n runner,\n });\n\n if (campaign.stop.reason !== 'completed') {\n printWarning(\n `Campaign stopped on ${campaign.stop.reason} (spent ${campaign.stop.spent}, ceiling ${campaign.stop.ceiling}). Report is partial.`,\n );\n }\n\n const markdown = renderCampaignMarkdown(campaign, arms.join('-vs-'));\n if (options.out) {\n await mkdir(dirname(options.out), { recursive: true });\n await writeFile(options.out, markdown, 'utf-8');\n printInfo(`Wrote ${options.out}`);\n } else {\n (options.stdout ?? process.stdout).write(markdown);\n }\n if (options.json === true) {\n (options.stdout ?? process.stdout).write(`${JSON.stringify(campaign, null, 2)}\\n`);\n }\n return campaign.stop.reason === 'completed' ? 0 : 2;\n}\n","export const TASK_CLASSES = ['trivial', 'small', 'medium', 'analysis'] as const;\n\nexport type TaskClass = (typeof TASK_CLASSES)[number];\n\nexport interface CorpusTask {\n id: TaskClass;\n title: string;\n /** What a fixture repository should contain. */\n fixture: string;\n verification: 'none' | 'existing-tests' | 'tests-and-docs' | 'artifact-only';\n strategy: 'direct' | 'pipeline';\n}\n\n/**\n * Four representative tasks. The synthetic runner never opens a real repo;\n * `real` mode materializes a disposable git fixture per repetition via\n * `fixtures/materialize.ts`. `--repo` is an investigation escape and does\n * not produce a publishable row.\n */\nexport const CORPUS: readonly CorpusTask[] = [\n {\n id: 'trivial',\n title: 'Change one constant and its comment',\n fixture: '1–2 files, no test change',\n verification: 'none',\n strategy: 'direct',\n },\n {\n id: 'small',\n title: 'Fix a failing assertion in an existing test',\n fixture: 'correction against tests that already exist',\n verification: 'existing-tests',\n strategy: 'pipeline',\n },\n {\n id: 'medium',\n title: 'Add a function, tests and a README note',\n fixture: 'implementation plus tests plus documentation',\n verification: 'tests-and-docs',\n strategy: 'pipeline',\n },\n {\n id: 'analysis',\n title: 'Read the tree and write a short report',\n fixture: 'read-only artifact, no implementation',\n verification: 'artifact-only',\n strategy: 'direct',\n },\n];\n\nexport const BENCH_AXES = [\n 'task',\n 'harness',\n 'model',\n 'effort',\n 'verification',\n 'strategy',\n] as const;\n","import { setIssuesCliOverrides } from '../config.js';\nimport { loadTaskPlan } from '../core/state-manager.js';\nimport { GLOBAL_ROOT_ENV } from '../storage/paths.js';\nimport { resetStorageResolutionCache, resolveIssuePaths } from '../storage/resolve.js';\nimport { runAcceptance } from '../verify/run-issue.js';\nimport type { RepeatOutcome, RepeatRunner, RepeatRunnerInput } from './real.js';\n\n/**\n * Live repeats spend money. Unit tests must never take this path.\n * The only escape is an explicit `ISSUE_FLOW_E2E_BENCH=1`, matching the\n * `#80` Antigravity e2e gate.\n */\nexport function assertLiveBenchAllowed(env: NodeJS.ProcessEnv = process.env): void {\n if (env.VITEST && env.ISSUE_FLOW_E2E_BENCH !== '1') {\n throw new Error(\n 'issue-flow bench --mode real does not run under npm test. Set ISSUE_FLOW_E2E_BENCH=1 for a deliberate live campaign.',\n );\n }\n}\n\nexport interface LiveRepeatDeps {\n runPipeline: (issue: string) => Promise<number>;\n}\n\n/**\n * Isolate one repetition: own ISSUE_FLOW_HOME, local issues, no GitHub,\n * `--setting-sources` pinned, `--fallback-model` never passed.\n */\nexport function createLiveRepeatRunner(deps: LiveRepeatDeps): RepeatRunner {\n return async (input: RepeatRunnerInput): Promise<RepeatOutcome> => {\n assertLiveBenchAllowed();\n const previousHome = process.env[GLOBAL_ROOT_ENV];\n const previousCwd = process.cwd();\n process.env[GLOBAL_ROOT_ENV] = input.campaignHome;\n resetStorageResolutionCache();\n setIssuesCliOverrides({ preferredProvider: 'local' });\n process.chdir(input.fixture.root);\n const started = performance.now();\n try {\n await deps.runPipeline(input.fixture.issueRef);\n const paths = await resolveIssuePaths(input.fixture.issueRef, {\n projectRoot: input.fixture.root,\n });\n const plan = await loadTaskPlan(paths.tasksFile);\n const records = plan.executions ?? [];\n const acceptance = await runAcceptance({\n cwd: input.fixture.root,\n issueDir: paths.issueDir,\n declared: input.fixture.expectedVerification,\n executionId: records.at(-1)?.id ?? null,\n skipReviewer: input.arm !== 'L2',\n explicit: input.arm === 'L2',\n });\n const wall = performance.now() - started;\n const harness = records.reduce((sum, record) => sum + (record.durationMs ?? 0), 0);\n const reported = records.flatMap((record) =>\n record.cost.status === 'reported' ? [record.cost.amount] : [],\n );\n const cost =\n reported.length > 0\n ? {\n status: 'reported' as const,\n amount: reported.reduce((sum, amount) => sum + amount, 0),\n currency: 'USD' as const,\n }\n : ({ status: 'unknown' as const, reason: 'not_reported' as const } as const);\n return {\n records,\n verdict: acceptance.verdict,\n taskDurationMs: wall,\n harnessExecutionMs: harness,\n orchestrationOverheadMs: Math.max(0, wall - harness),\n attemptCount: records.reduce((max, record) => Math.max(max, record.attempt), 0) || 1,\n cost,\n };\n } finally {\n process.chdir(previousCwd);\n if (previousHome === undefined) delete process.env[GLOBAL_ROOT_ENV];\n else process.env[GLOBAL_ROOT_ENV] = previousHome;\n resetStorageResolutionCache();\n setIssuesCliOverrides({});\n }\n };\n}\n","import { mkdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { evaluateCeilings, spendFromCost } from '../routing/budget.js';\nimport { getGlobalRoot } from '../storage/paths.js';\nimport type { CostRecord, ExecutionRecord } from '../telemetry/types.js';\nimport type { VerdictStatus } from '../verify/types.js';\nimport { CORPUS, type TaskClass } from './corpus.js';\nimport { corpusTask, type FixtureHandle, materialize } from './fixtures/index.js';\nimport { p50, p95 } from './stats.js';\nimport {\n assertComparable,\n type ComparabilityTuple,\n isRowInvalid,\n type TupleField,\n tupleAxisForArm,\n} from './tuple.js';\n\nexport interface BenchRepeat {\n seed: number;\n taskDurationMs: number;\n harnessExecutionMs: number;\n orchestrationOverheadMs: number;\n timeToAcceptedResultMs: number | null;\n verdict: VerdictStatus;\n cost: CostRecord;\n attemptCount: number;\n executionIds: string[];\n invalid: boolean;\n}\n\nexport interface BenchCell {\n task: TaskClass;\n tuple: ComparabilityTuple;\n arm: string;\n repeats: BenchRepeat[];\n}\n\nexport interface PercentilePair {\n p50: number;\n p95: number;\n n: number;\n}\n\nexport interface CellSummary {\n n: number;\n taskDurationMs: PercentilePair;\n harnessExecutionMs: PercentilePair;\n orchestrationOverheadMs: PercentilePair;\n timeToAcceptedResultMs: { p50: number | null; p95: number | null; n: number };\n cost: {\n reportedP50: number | null;\n reportedP95: number | null;\n unknownRepeats: number;\n estimatedRepeats: number;\n };\n verdicts: Record<VerdictStatus, number>;\n}\n\nexport type CampaignStop =\n | { reason: 'completed' }\n | { reason: 'max_cost' | 'max_duration'; spent: number; ceiling: number; partial: true };\n\nexport interface BenchCampaign {\n id: string;\n home: string;\n cells: BenchCell[];\n stop: CampaignStop;\n}\n\nexport interface RepeatOutcome {\n records: ExecutionRecord[];\n verdict: VerdictStatus;\n taskDurationMs: number;\n harnessExecutionMs: number;\n orchestrationOverheadMs: number;\n attemptCount: number;\n cost: CostRecord;\n}\n\nexport interface RepeatRunnerInput {\n fixture: FixtureHandle;\n arm: string;\n tuple: ComparabilityTuple;\n campaignHome: string;\n}\n\nexport type RepeatRunner = (input: RepeatRunnerInput) => Promise<RepeatOutcome>;\n\nexport interface RealCorpusOptions {\n tasks?: TaskClass[];\n arms: string[];\n repeats: number;\n tupleBase: Omit<ComparabilityTuple, 'task' | 'strictMcpConfig' | 'fallbackModelPassed'> & {\n strictMcpConfig?: boolean;\n fallbackModelPassed?: boolean;\n };\n maxCostUsd?: number;\n maxDurationMs?: number;\n campaignId?: string;\n runner: RepeatRunner;\n now?: () => number;\n env?: NodeJS.ProcessEnv;\n}\n\nexport function campaignHome(campaignId: string, env?: NodeJS.ProcessEnv): string {\n return join(getGlobalRoot({ env }), 'bench', campaignId);\n}\n\nfunction combineCost(records: readonly ExecutionRecord[]): CostRecord {\n let reported = 0;\n let estimated = 0;\n let unknown = 0;\n for (const record of records) {\n if (record.cost.status === 'reported') reported += record.cost.amount;\n else if (record.cost.status === 'estimated') estimated += record.cost.amount;\n else unknown += 1;\n }\n if (reported > 0) return { status: 'reported', amount: reported, currency: 'USD' };\n if (estimated > 0) {\n return {\n status: 'estimated',\n amount: estimated,\n currency: 'USD',\n pricing: {\n tableVersion: 'bench',\n modelKey: 'aggregated',\n inputPerMillion: 0,\n outputPerMillion: 0,\n capturedAt: new Date(0).toISOString(),\n },\n };\n }\n return { status: 'unknown', reason: unknown > 0 ? 'not_reported' : 'not_reported' };\n}\n\nexport function summarizeCell(cell: BenchCell): CellSummary {\n const durations = cell.repeats.map((repeat) => repeat.taskDurationMs);\n const harness = cell.repeats.map((repeat) => repeat.harnessExecutionMs);\n const overhead = cell.repeats.map((repeat) => repeat.orchestrationOverheadMs);\n const accepted = cell.repeats\n .map((repeat) => repeat.timeToAcceptedResultMs)\n .filter((value): value is number => value !== null);\n const reported = cell.repeats\n .map((repeat) => (repeat.cost.status === 'reported' ? repeat.cost.amount : null))\n .filter((value): value is number => value !== null);\n const verdicts: Record<VerdictStatus, number> = { passed: 0, failed: 0, unverified: 0 };\n let unknownRepeats = 0;\n let estimatedRepeats = 0;\n for (const repeat of cell.repeats) {\n verdicts[repeat.verdict] += 1;\n if (repeat.cost.status === 'unknown') unknownRepeats += 1;\n if (repeat.cost.status === 'estimated') estimatedRepeats += 1;\n }\n return {\n n: cell.repeats.length,\n taskDurationMs: { p50: p50(durations), p95: p95(durations), n: durations.length },\n harnessExecutionMs: { p50: p50(harness), p95: p95(harness), n: harness.length },\n orchestrationOverheadMs: { p50: p50(overhead), p95: p95(overhead), n: overhead.length },\n timeToAcceptedResultMs: {\n p50: accepted.length === 0 ? null : p50(accepted),\n p95: accepted.length === 0 ? null : p95(accepted),\n n: accepted.length,\n },\n cost: {\n reportedP50: reported.length === 0 ? null : p50(reported),\n reportedP95: reported.length === 0 ? null : p95(reported),\n unknownRepeats,\n estimatedRepeats,\n },\n verdicts,\n };\n}\n\nfunction tupleForArm(\n task: TaskClass,\n arm: string,\n base: RealCorpusOptions['tupleBase'],\n): ComparabilityTuple {\n return {\n task,\n harness: base.harness,\n harnessVersion: base.harnessVersion,\n model: base.model,\n modelVersion: base.modelVersion,\n effort: base.effort,\n verification: base.verification,\n strategy: base.strategy,\n settingSourcesPinned: base.settingSourcesPinned,\n strictMcpConfig: arm === 'strict-mcp' ? true : (base.strictMcpConfig ?? false),\n fallbackModelPassed: base.fallbackModelPassed ?? false,\n };\n}\n\nfunction declaredIgnore(arms: readonly string[]): TupleField[] {\n const fields = new Set<TupleField>();\n for (const arm of arms) {\n const axis = tupleAxisForArm(arm);\n if (axis) fields.add(axis);\n }\n return [...fields];\n}\n\n/**\n * Run the real corpus. Arms are a parameter: new experiments do not add\n * runner code. Repeats of different arms are interleaved, not blocked.\n */\nexport async function runRealCorpus(options: RealCorpusOptions): Promise<BenchCampaign> {\n const tasks = options.tasks ?? CORPUS.map((entry) => entry.id);\n const campaignId = options.campaignId ?? `campaign-${Date.now()}`;\n const home = campaignHome(campaignId, options.env);\n await mkdir(home, { recursive: true });\n\n const cells = new Map<string, BenchCell>();\n const ignore = declaredIgnore(options.arms);\n const now = options.now ?? Date.now;\n const started = now();\n let reportedUsd = 0;\n let knownCost: CostRecord['status'] = 'unknown';\n let stop: CampaignStop = { reason: 'completed' };\n\n const schedule: Array<{ task: TaskClass; arm: string; seed: number }> = [];\n for (let seed = 0; seed < options.repeats; seed += 1) {\n for (const arm of options.arms) {\n for (const task of tasks) {\n schedule.push({ task, arm, seed });\n }\n }\n }\n\n for (const step of schedule) {\n const task = corpusTask(step.task);\n const tuple = tupleForArm(step.task, step.arm, {\n ...options.tupleBase,\n verification: task.verification,\n strategy: task.strategy,\n });\n const key = `${step.task}:${step.arm}`;\n const existing = cells.get(key);\n if (existing) {\n assertComparable(existing.tuple, tuple, { ignore });\n } else {\n for (const cell of cells.values()) {\n if (cell.task !== step.task) continue;\n assertComparable(cell.tuple, tuple, { ignore });\n }\n cells.set(key, { task: step.task, tuple, arm: step.arm, repeats: [] });\n }\n\n const fixture = await materialize(task, step.seed);\n try {\n const outcome = await options.runner({\n fixture,\n arm: step.arm,\n tuple,\n campaignHome: home,\n });\n const cost = outcome.cost.status !== undefined ? outcome.cost : combineCost(outcome.records);\n const spent = spendFromCost(cost);\n if (spent.costStatus === 'reported') {\n reportedUsd += spent.reportedUsd;\n knownCost = 'reported';\n }\n const cell = cells.get(key);\n if (!cell) throw new Error(`missing cell ${key}`);\n cell.repeats.push({\n seed: step.seed,\n taskDurationMs: outcome.taskDurationMs,\n harnessExecutionMs: outcome.harnessExecutionMs,\n orchestrationOverheadMs: outcome.orchestrationOverheadMs,\n timeToAcceptedResultMs: outcome.verdict === 'passed' ? outcome.taskDurationMs : null,\n verdict: outcome.verdict,\n cost,\n attemptCount: outcome.attemptCount,\n executionIds: outcome.records.map((record) => record.id),\n invalid: isRowInvalid(tuple),\n });\n } finally {\n await fixture.dispose();\n }\n\n const ceiling = evaluateCeilings({\n ceilings: {\n maxCostUsdPerIssue: options.maxCostUsd ?? null,\n maxDurationMsPerIssue: options.maxDurationMs ?? null,\n maxExecutionsPerIssue: null,\n onCeiling: 'block',\n },\n spent: {\n reportedUsd,\n costStatus: knownCost,\n durationMs: now() - started,\n executions: [...cells.values()].reduce((sum, cell) => sum + cell.repeats.length, 0),\n },\n });\n if (!ceiling.ok) {\n stop = {\n reason: ceiling.stopReason === 'max_attempts' ? 'max_duration' : ceiling.stopReason,\n spent: ceiling.numbers.spent,\n ceiling: ceiling.numbers.ceiling,\n partial: true,\n };\n break;\n }\n }\n\n return { id: campaignId, home, cells: [...cells.values()], stop };\n}\n","import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { ISSUES_DIR_NAME } from '../../storage/paths.js';\nimport { run } from '../../utils/shell.js';\nimport type { AcceptanceCheck } from '../../verify/types.js';\nimport type { CorpusTask } from '../corpus.js';\nimport { CORPUS } from '../corpus.js';\nimport { templateFor } from './templates.js';\n\nexport interface FixtureHandle {\n root: string;\n issueRef: string;\n expectedVerification: AcceptanceCheck[];\n seed: number;\n task: CorpusTask['id'];\n dispose(): Promise<void>;\n}\n\nconst GIT_IDENT = ['-c', 'user.name=issue-flow-bench', '-c', 'user.email=bench@issue-flow.test'];\n\nasync function git(cwd: string, args: string[]): Promise<void> {\n const result = await run('git', args, { cwd });\n if (result.exitCode !== 0) {\n throw new Error(\n `git ${args.join(' ')} failed (${result.exitCode}): ${result.stderr || result.stdout}`,\n );\n }\n}\n\nasync function writeTree(root: string, files: Record<string, string>): Promise<void> {\n for (const [relative, content] of Object.entries(files)) {\n const path = join(root, relative);\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, content, 'utf-8');\n }\n}\n\nasync function writeLocalIssue(root: string, title: string, body: string): Promise<void> {\n const issueDir = join(root, ISSUES_DIR_NAME, '1');\n await mkdir(issueDir, { recursive: true });\n await writeFile(join(issueDir, 'issue.md'), `# ${title}\\n\\n${body.trim()}\\n`, 'utf-8');\n await writeFile(\n join(issueDir, 'metadata.json'),\n `${JSON.stringify(\n {\n schemaVersion: 1,\n id: '1',\n number: 1,\n source: 'local',\n title,\n labels: ['bench'],\n state: 'open',\n createdAt: '2026-08-30T00:00:00.000Z',\n updatedAt: '2026-08-30T00:00:00.000Z',\n contentHash: 'sha256:fixture',\n },\n null,\n 2,\n )}\\n`,\n 'utf-8',\n );\n}\n\n/**\n * Materialize a disposable git repository for one corpus class and one seed.\n * A new call always starts from the same unfinished state — never from the\n * work of a previous repetition.\n */\nexport async function materialize(task: CorpusTask, seed: number): Promise<FixtureHandle> {\n const root = await mkdtemp(join(tmpdir(), `issue-flow-bench-${task.id}-${seed}-`));\n const template = templateFor(task.id, seed);\n await writeTree(root, template.files);\n await writeLocalIssue(root, template.issueTitle, template.issueBody);\n\n // An empty template skips sample hooks. Some sandboxes forbid writing\n // executable files under `.git/hooks/`.\n await git(root, ['-c', 'init.templateDir=', 'init']);\n await git(root, ['add', '.']);\n await git(root, [...GIT_IDENT, 'commit', '-m', `fixture ${task.id} seed=${seed}`]);\n\n return {\n root,\n issueRef: '1',\n expectedVerification: template.expectedVerification,\n seed,\n task: task.id,\n async dispose() {\n await rm(root, { recursive: true, force: true });\n },\n };\n}\n\nexport function corpusTask(id: CorpusTask['id']): CorpusTask {\n const task = CORPUS.find((entry) => entry.id === id);\n if (!task) throw new Error(`Unknown corpus class: ${id}`);\n return task;\n}\n","import type { AcceptanceCheck } from '../../verify/types.js';\nimport type { TaskClass } from '../corpus.js';\n\nexport interface FixtureTemplate {\n files: Record<string, string>;\n issueTitle: string;\n issueBody: string;\n expectedVerification: AcceptanceCheck[];\n}\n\nfunction withSeed(content: string, seed: number): string {\n return `${content}\\n// fixture-seed:${seed}\\n`;\n}\n\nexport function templateFor(task: TaskClass, seed: number): FixtureTemplate {\n switch (task) {\n case 'trivial':\n return {\n files: {\n 'src/answer.js': withSeed(\n [\n '/** The answer to life, the universe and everything. */',\n 'export const ANSWER = 41;',\n ].join('\\n'),\n seed,\n ),\n },\n issueTitle: 'Change one constant and its comment',\n issueBody: [\n 'Change `ANSWER` in `src/answer.js` from 41 to 42.',\n 'Update the comment so it no longer claims the wrong value.',\n 'Do not add tests.',\n ].join('\\n'),\n expectedVerification: [],\n };\n case 'small':\n return {\n files: {\n 'src/sum.js': withSeed('export function sum(a, b) {\\n return a - b;\\n}\\n', seed),\n 'src/sum.test.js': [\n \"import { sum } from './sum.js';\",\n \"import assert from 'node:assert/strict';\",\n 'assert.equal(sum(2, 2), 4);',\n '',\n ].join('\\n'),\n 'package.json': `${JSON.stringify({ name: 'bench-small', type: 'module', scripts: { test: 'node src/sum.test.js' } }, null, 2)}\\n`,\n },\n issueTitle: 'Fix a failing assertion in an existing test',\n issueBody: [\n '`src/sum.test.js` fails because `sum` subtracts instead of adding.',\n 'Fix `src/sum.js` so `node src/sum.test.js` exits 0.',\n 'Do not change the test.',\n ].join('\\n'),\n expectedVerification: [{ id: 'existing-tests', run: 'node src/sum.test.js', fatal: true }],\n };\n case 'medium':\n return {\n files: {\n 'src/greet.js': withSeed('// greet(name) is not implemented yet.\\n', seed),\n 'src/greet.test.js': [\n \"import { greet } from './greet.js';\",\n \"import assert from 'node:assert/strict';\",\n \"assert.equal(greet('Ada'), 'Hello, Ada');\",\n '',\n ].join('\\n'),\n 'README.md': ['# bench-medium', '', 'A tiny library.', ''].join('\\n'),\n 'package.json': `${JSON.stringify({ name: 'bench-medium', type: 'module', scripts: { test: 'node src/greet.test.js' } }, null, 2)}\\n`,\n },\n issueTitle: 'Add a function, tests and a README note',\n issueBody: [\n 'Implement `greet(name)` in `src/greet.js` so it returns Hello, <name>.',\n 'Keep `src/greet.test.js` as the contract.',\n 'Add an `## API` section to `README.md` documenting `greet`.',\n ].join('\\n'),\n expectedVerification: [\n { id: 'tests', run: 'node src/greet.test.js', fatal: true },\n { id: 'docs', expectFiles: ['README.md'], fatal: true },\n ],\n };\n case 'analysis':\n return {\n files: {\n 'src/alpha.js': withSeed('export const ALPHA = 1;\\n', seed),\n 'src/beta.js': withSeed(\n \"import { ALPHA } from './alpha.js';\\nexport const BETA = ALPHA + 1;\\n\",\n seed,\n ),\n 'src/gamma.js': withSeed(\n \"import { BETA } from './beta.js';\\nexport const GAMMA = BETA + 1;\\n\",\n seed,\n ),\n },\n issueTitle: 'Read the tree and write a short report',\n issueBody: [\n 'Read `src/` and write `report.md` describing the module graph.',\n 'Do not change the source files.',\n ].join('\\n'),\n expectedVerification: [{ id: 'artifact', expectFiles: ['report.md'], fatal: true }],\n };\n default: {\n const _exhaustive: never = task;\n throw new Error(`Unknown corpus class: ${_exhaustive}`);\n }\n }\n}\n","/** Shared percentiles. Used by both the synthetic CI guard and the real campaign. */\n\nfunction percentile(values: number[], p: number): number {\n const sorted = [...values].sort((a, b) => a - b);\n if (sorted.length === 0) return 0;\n const index = Math.min(sorted.length - 1, Math.ceil((p / 100) * sorted.length) - 1);\n return sorted[index] ?? 0;\n}\n\nexport function p50(values: number[]): number {\n return percentile(values, 50);\n}\n\nexport function p95(values: number[]): number {\n return percentile(values, 95);\n}\n","import { ensureHarnessVersion } from '../agents/registry.js';\nimport type { AgentProviderId } from '../agents/types.js';\nimport type { TaskClass } from './corpus.js';\n\nexport interface ComparabilityTuple {\n task: TaskClass;\n harness: string;\n harnessVersion: string;\n model: string;\n modelVersion: string | null;\n effort: string;\n verification: string;\n strategy: 'direct' | 'pipeline';\n settingSourcesPinned: boolean;\n strictMcpConfig: boolean;\n fallbackModelPassed: boolean;\n}\n\nexport type TupleField = keyof ComparabilityTuple;\n\nexport const TUPLE_FIELDS: readonly TupleField[] = [\n 'task',\n 'harness',\n 'harnessVersion',\n 'model',\n 'modelVersion',\n 'effort',\n 'verification',\n 'strategy',\n 'settingSourcesPinned',\n 'strictMcpConfig',\n 'fallbackModelPassed',\n] as const;\n\nexport class ComparabilityError extends Error {\n readonly field: TupleField;\n readonly left: unknown;\n readonly right: unknown;\n\n constructor(field: TupleField, left: unknown, right: unknown) {\n super(`ComparabilityTuple.${field} differs: ${String(left)} vs ${String(right)}`);\n this.name = 'ComparabilityError';\n this.field = field;\n this.left = left;\n this.right = right;\n }\n}\n\n/**\n * Two cells may be compared only when every field matches, except axes the\n * experiment declared (the arm). A silent mismatch is a bug.\n */\nexport function assertComparable(\n left: ComparabilityTuple,\n right: ComparabilityTuple,\n options: { ignore?: readonly TupleField[] } = {},\n): void {\n const ignored = new Set(options.ignore ?? []);\n for (const field of TUPLE_FIELDS) {\n if (ignored.has(field)) continue;\n if (left[field] !== right[field]) {\n throw new ComparabilityError(field, left[field], right[field]);\n }\n }\n}\n\n/** `--fallback-model` present means the measured model is not the one that ran. */\nexport function isRowInvalid(tuple: ComparabilityTuple): boolean {\n return tuple.fallbackModelPassed || !tuple.settingSourcesPinned;\n}\n\n/** Collect at campaign time. Never assume a version from a document. */\nexport async function collectHarnessVersion(provider: AgentProviderId): Promise<string> {\n const version = await ensureHarnessVersion(provider);\n return version ?? 'unknown';\n}\n\nexport function tupleAxisForArm(arm: string): TupleField | null {\n if (arm === 'strict-mcp') return 'strictMcpConfig';\n return null;\n}\n","import { hostname } from 'node:os';\nimport { getGlobalRoot } from '../storage/paths.js';\nimport { redactSecrets } from '../telemetry/redact.js';\nimport type { BenchCampaign, CellSummary } from './real.js';\nimport { summarizeCell } from './real.js';\nimport type { ComparabilityTuple } from './tuple.js';\n\nfunction formatMs(value: number | null): string {\n return value === null ? '—' : `${Math.round(value)}`;\n}\n\nfunction formatUsd(value: number | null): string {\n return value === null ? '—' : `$${value.toFixed(2)}`;\n}\n\nfunction tupleLine(tuple: ComparabilityTuple): string {\n return [\n tuple.task,\n tuple.harness,\n tuple.harnessVersion,\n tuple.model,\n tuple.modelVersion ?? '—',\n tuple.effort,\n tuple.verification,\n tuple.strategy,\n tuple.settingSourcesPinned ? 'setting-sources:pinned' : 'setting-sources:unpinned',\n tuple.strictMcpConfig ? 'strict-mcp' : 'mcp:default',\n tuple.fallbackModelPassed ? 'INVALID:fallback-model' : 'fallback:none',\n ].join(' × ');\n}\n\nfunction verdictCounts(summary: CellSummary): string {\n return `passed=${summary.verdicts.passed} failed=${summary.verdicts.failed} unverified=${summary.verdicts.unverified}`;\n}\n\nfunction scrubPaths(text: string): string {\n const home = getGlobalRoot();\n return text.split(home).join('<issue-flow-home>');\n}\n\nexport function renderCampaignMarkdown(campaign: BenchCampaign, subject: string): string {\n const lines: string[] = [\n `# Harness campaign — ${subject}`,\n '',\n `> **Research document.** Campaign \\`${campaign.id}\\`.`,\n `> Machine: \\`${hostname()}\\`. Node ${process.version}.`,\n campaign.stop.reason === 'completed'\n ? '> Status: complete.'\n : `> Status: **partial** — stopped on \\`${campaign.stop.reason}\\` (spent ${campaign.stop.spent}, ceiling ${campaign.stop.ceiling}). Results below are the repeats that finished; they are not a silently truncated table.`,\n '',\n '`time-to-accepted-result` is null when the verdict is not `passed`. Cost `unknown` is never summed as zero.',\n '',\n '## Cells',\n '',\n '| Task | Arm | n | duration p50/p95 (ms) | harness p50/p95 | overhead p50/p95 | accepted p50/p95 | cost p50/p95 | verdicts | invalid |',\n '|---|---|---:|---|---|---|---|---|---|---|',\n ];\n\n for (const cell of campaign.cells) {\n const summary = summarizeCell(cell);\n const invalid = cell.repeats.some((repeat) => repeat.invalid);\n lines.push(\n `| ${cell.task} | ${cell.arm} | ${summary.n} | ${formatMs(summary.taskDurationMs.p50)} / ${formatMs(summary.taskDurationMs.p95)} | ${formatMs(summary.harnessExecutionMs.p50)} / ${formatMs(summary.harnessExecutionMs.p95)} | ${formatMs(summary.orchestrationOverheadMs.p50)} / ${formatMs(summary.orchestrationOverheadMs.p95)} | ${formatMs(summary.timeToAcceptedResultMs.p50)} / ${formatMs(summary.timeToAcceptedResultMs.p95)} | ${formatUsd(summary.cost.reportedP50)} / ${formatUsd(summary.cost.reportedP95)} | ${verdictCounts(summary)} | ${invalid ? 'yes' : 'no'} |`,\n );\n }\n\n lines.push('', '## Comparability tuples', '');\n for (const cell of campaign.cells) {\n lines.push(`- **${cell.task} / ${cell.arm}**: \\`${tupleLine(cell.tuple)}\\``);\n }\n lines.push('');\n return redactSecrets(scrubPaths(lines.join('\\n')));\n}\n","import {\n createInitialSnapshot,\n reduceSessionEvent,\n type SessionEvent,\n} from '../core/session-state.js';\nimport { sessionSnapshotSchema } from '../schemas.js';\nimport { summarizePhaseTiming } from '../telemetry/timing.js';\nimport type { ExecutionRecord } from '../telemetry/types.js';\nimport { CORPUS } from './corpus.js';\nimport { p50, p95 } from './stats.js';\n\nexport { p50, p95 };\n\n/** Derived from the #79 investigation: orchestration must stay a rounding error. */\nexport const SYNTHETIC_BUDGETS = {\n /** p95 of reducing a short scripted run, in milliseconds. */\n reduceP95Ms: 50,\n /** p95 of parsing a snapshot through the lockstep schema. */\n parseP95Ms: 20,\n} as const;\n\nexport interface SyntheticResult {\n task: string;\n mode: 'synthetic';\n harness: 'mocked';\n model: 'none';\n effort: 'none';\n verification: string;\n strategy: string;\n taskDurationMs: number;\n harnessExecutionDurationMs: number;\n orchestrationOverheadMs: number;\n timeToFirstOutputMs: number | null;\n attemptCount: number;\n retryDurationMs: number;\n verdict: 'unverified';\n}\n\nfunction scriptedEvents(): SessionEvent[] {\n return [\n {\n type: 'session:start',\n at: '2026-08-30T03:00:00.000Z',\n sessionId: 'bench',\n issueNumber: 79,\n phases: ['prd', 'execute'],\n },\n { type: 'phase:start', at: '2026-08-30T03:00:01.000Z', phase: 'prd' },\n {\n type: 'phase:end',\n at: '2026-08-30T03:00:02.000Z',\n phase: 'prd',\n success: true,\n harnessExecutionMs: 800,\n orchestrationOverheadMs: 200,\n },\n { type: 'phase:start', at: '2026-08-30T03:00:02.000Z', phase: 'execute' },\n {\n type: 'phase:end',\n at: '2026-08-30T03:00:03.000Z',\n phase: 'execute',\n success: true,\n harnessExecutionMs: 900,\n orchestrationOverheadMs: 100,\n attemptCount: 1,\n },\n { type: 'session:end', at: '2026-08-30T03:00:03.000Z', status: 'completed' },\n ];\n}\n\nexport function reduceScriptedRun(): ReturnType<typeof reduceSessionEvent> {\n let snap = createInitialSnapshot();\n for (const event of scriptedEvents()) {\n snap = reduceSessionEvent(snap, event);\n }\n return snap;\n}\n\nexport function timeReduce(repeats = 20): number[] {\n const samples: number[] = [];\n for (let i = 0; i < repeats; i += 1) {\n const start = performance.now();\n reduceScriptedRun();\n samples.push(performance.now() - start);\n }\n return samples;\n}\n\nexport function timeParse(repeats = 20): number[] {\n const snap = reduceScriptedRun();\n const samples: number[] = [];\n for (let i = 0; i < repeats; i += 1) {\n const start = performance.now();\n sessionSnapshotSchema.parse(snap);\n samples.push(performance.now() - start);\n }\n return samples;\n}\n\nfunction mockedRecord(durationMs: number): ExecutionRecord {\n return {\n id: 'synthetic',\n sessionId: 'bench',\n purpose: 'execute',\n attempt: 1,\n trigger: 'initial',\n triggerReason: null,\n agent: {\n harness: 'mocked',\n provider: null,\n model: { requested: null, resolved: null, source: 'unavailable' },\n providerSessionId: null,\n },\n startedAt: '2026-08-30T03:00:00.000Z',\n finishedAt: '2026-08-30T03:00:01.000Z',\n durationMs,\n cliDurationMs: Math.max(0, durationMs - 5),\n harnessStartupMs: 5,\n ttftMs: null,\n usage: { source: 'unavailable' },\n cost: { status: 'unknown', reason: 'not_reported' },\n status: 'completed',\n failure: null,\n };\n}\n\n/** Orchestration-only measurement: the harness is a number, not a process. */\nexport function runSyntheticCorpus(): SyntheticResult[] {\n return CORPUS.map((task) => {\n const harnessMs = 1000;\n const orchestrateStart = performance.now();\n const snap = reduceScriptedRun();\n sessionSnapshotSchema.parse(snap);\n const orchestrationMs = performance.now() - orchestrateStart;\n const timing = summarizePhaseTiming(\n [mockedRecord(harnessMs)],\n 'execute',\n harnessMs + orchestrationMs,\n );\n return {\n task: task.id,\n mode: 'synthetic',\n harness: 'mocked',\n model: 'none',\n effort: 'none',\n verification: task.verification,\n strategy: task.strategy,\n taskDurationMs: harnessMs + orchestrationMs,\n harnessExecutionDurationMs: timing.harnessExecutionMs ?? harnessMs,\n orchestrationOverheadMs: timing.orchestrationOverheadMs ?? orchestrationMs,\n timeToFirstOutputMs: timing.ttftMs,\n attemptCount: timing.attemptCount ?? 1,\n retryDurationMs: timing.retryDurationMs ?? 0, // no retries in the mocked record\n verdict: 'unverified',\n };\n });\n}\n"],"mappings":"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","names":["mkdir","writeFile","dirname","mkdir","join","join","mkdir","mkdir","dirname","writeFile"]}
@@ -1,42 +1,46 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  listPullRequests
4
- } from "./chunk-TSOLWK7Y.js";
4
+ } from "./chunk-PBTA3QQX.js";
5
5
  import {
6
+ isTransientFailure,
6
7
  runPhaseWithRetry
7
- } from "./chunk-Y5ZDJ4PJ.js";
8
+ } from "./chunk-76MMPJ53.js";
8
9
  import {
9
10
  issuePlaceholders,
10
11
  resolveCommandIssue
11
- } from "./chunk-73ATPIYX.js";
12
- import {
13
- DEFAULT_HEADLESS_TIMEOUT_MS,
14
- runHeadless
15
- } from "./chunk-4JSOYE4G.js";
16
- import {
17
- isTransientFailure
18
- } from "./chunk-LWRSMTTZ.js";
12
+ } from "./chunk-REO3PXXO.js";
19
13
  import {
20
14
  publishPhaseMetrics,
21
15
  resolvePolicyPlaceholders
22
- } from "./chunk-MJULIKQU.js";
16
+ } from "./chunk-PHW2YV6N.js";
23
17
  import {
24
18
  applyPlaceholders,
25
19
  loadPrompt
26
- } from "./chunk-LAFNLLQP.js";
20
+ } from "./chunk-KMRFRXIT.js";
21
+ import {
22
+ DEFAULT_HEADLESS_TIMEOUT_MS,
23
+ runHeadless
24
+ } from "./chunk-CGKLE6WR.js";
25
+ import {
26
+ issueReferenceLines,
27
+ loadRepositoryPolicy,
28
+ pullRequestTitle,
29
+ resolveChangeType
30
+ } from "./chunk-RVO7L4FX.js";
27
31
  import {
28
32
  resolveIssuePaths
29
- } from "./chunk-DIDUVL6O.js";
33
+ } from "./chunk-QK7WUO7A.js";
30
34
  import {
31
35
  getGlobalTimeout,
32
36
  printError,
33
37
  printSuccess
34
- } from "./chunk-BYZDPR7N.js";
38
+ } from "./chunk-TYRNTSPJ.js";
35
39
  import {
36
40
  isoNow,
37
41
  loadTaskPlan,
38
42
  saveTaskPlan
39
- } from "./chunk-CD5ULRVC.js";
43
+ } from "./chunk-J3SOTUVI.js";
40
44
 
41
45
  // src/commands/pr.ts
42
46
  import { execa } from "execa";
@@ -48,25 +52,48 @@ function parsePrNumber(url) {
48
52
  const match = url.match(/\/pull\/(\d+)/);
49
53
  return match?.[1] === void 0 ? null : Number(match[1]);
50
54
  }
51
- function issueClosesLine(issue, fallbackId) {
55
+ function issueClosesLine(issue, fallbackId, complete) {
52
56
  if (issue.remoteRef === null) {
53
57
  return "";
54
58
  }
55
- return `Closes #${issue.number ?? fallbackId}`;
59
+ const number = issue.number ?? (/^\d+$/.test(fallbackId) ? Number(fallbackId) : null);
60
+ if (number === null) {
61
+ return "";
62
+ }
63
+ return issueReferenceLines({ references: [{ number, complete }] });
56
64
  }
57
65
  function issueRef(entry) {
58
66
  return entry.number === null ? entry.id : `#${entry.number}`;
59
67
  }
60
68
  function issueClosesLines(issues) {
61
- return issues.filter((entry) => entry.source === "github" && entry.number !== null).map((entry) => `Closes #${entry.number}`).join("\n");
69
+ return issueReferenceLines({
70
+ references: issues.filter((entry) => entry.source === "github" && entry.number !== null).map((entry) => ({
71
+ number: entry.number,
72
+ complete: entry.complete !== false
73
+ }))
74
+ });
62
75
  }
63
76
  function multiIssueContext(queue) {
64
77
  if (queue === void 0 || queue.issues.length <= 1) {
65
78
  return "";
66
79
  }
67
- const order = queue.issues.map(
68
- (entry, index) => `${index + 1}. ${issueRef(entry)}${entry.title ? ` \u2014 ${entry.title}` : ""}`
69
- ).join("\n");
80
+ const byId = new Map(queue.issues.map((entry) => [entry.id, entry]));
81
+ const depthOf = (entry) => {
82
+ let depth = 0;
83
+ let parent = entry.parent ?? null;
84
+ const seen = /* @__PURE__ */ new Set();
85
+ while (parent !== null && !seen.has(parent)) {
86
+ seen.add(parent);
87
+ depth += 1;
88
+ parent = byId.get(parent)?.parent ?? null;
89
+ }
90
+ return depth;
91
+ };
92
+ const order = queue.issues.map((entry, index) => {
93
+ const indent = " ".repeat(depthOf(entry));
94
+ const role = entry.role === "container" ? " (container)" : "";
95
+ return `${index + 1}. ${indent}${issueRef(entry)}${entry.title ? ` \u2014 ${entry.title}` : ""}${role}`;
96
+ }).join("\n");
70
97
  const pending = [
71
98
  ...queue.excluded.map(
72
99
  (entry) => `- ${issueRef(entry)}${entry.title ? ` \u2014 ${entry.title}` : ""}: ${entry.reason}`
@@ -145,6 +172,24 @@ async function runPr(issue, resolvedIssue, options = {}) {
145
172
  if (adopted) return 0;
146
173
  const queue = options.queue;
147
174
  const consolidating = queue !== void 0 && queue.issues.length > 1;
175
+ let planComplete = false;
176
+ try {
177
+ const existing = await loadTaskPlan(tasksPath);
178
+ planComplete = existing.userStories.every((story) => story.passes) && (existing.lastReviewFindings === null || existing.lastReviewFindings === "");
179
+ } catch {
180
+ planComplete = false;
181
+ }
182
+ const policy = await loadRepositoryPolicy();
183
+ const change = resolveChangeType({
184
+ labels: resolution.resolved.issue.labels,
185
+ title: resolution.resolved.issue.title,
186
+ titleConvention: policy.issues.titleConvention,
187
+ typeMap: policy.git.typeMap
188
+ });
189
+ const prTitle = pullRequestTitle({
190
+ type: change.type,
191
+ subject: resolution.resolved.issue.title.replace(/^\s*\[[^\]]+\]\s*/, "")
192
+ });
148
193
  const template = await loadPrompt("pr");
149
194
  const prompt = applyPlaceholders(template, {
150
195
  // The repository's own conventions. Empty when it declares none, which is
@@ -153,7 +198,8 @@ async function runPr(issue, resolvedIssue, options = {}) {
153
198
  __ISSUE_NUMBER__: issueNumber,
154
199
  __BRANCH_NAME__: branchName,
155
200
  __TASKS_PATH__: tasksPath,
156
- __ISSUE_CLOSES__: consolidating ? issueClosesLines(queue.issues) : issueClosesLine(resolution.resolved.issue, issueNumber),
201
+ __PR_TITLE_CONVENTION__: prTitle,
202
+ __ISSUE_REFERENCE__: consolidating ? issueClosesLines(queue.issues) : issueClosesLine(resolution.resolved.issue, issueNumber, planComplete),
157
203
  __MULTI_ISSUE_CONTEXT__: multiIssueContext(queue),
158
204
  ...issuePlaceholders(resolution.resolved)
159
205
  });
@@ -166,18 +212,24 @@ async function runPr(issue, resolvedIssue, options = {}) {
166
212
  prompt,
167
213
  maxTurns: 15,
168
214
  timeout: getGlobalTimeout() ?? DEFAULT_HEADLESS_TIMEOUT_MS,
215
+ timeoutHistory: {
216
+ phase: "pr",
217
+ journalFiles: [paths.rotatedEventsFile, paths.eventsFile]
218
+ },
169
219
  // json (not text) so the CLI reports usage: the envelope's `result`
170
220
  // field carries the same assistant text parsePrUrl() already consumed.
171
221
  outputFormat: "json",
172
222
  allowedTools: ["Bash", "Read", "Glob", "Grep"],
173
223
  addDirs: [paths.issueDir],
174
- statusMessage: `Creating PR for issue #${issueNumber}...`
224
+ statusMessage: `Creating PR for issue #${issueNumber}...`,
225
+ phase: "pr",
226
+ permission: "workspace"
175
227
  });
176
- publishPhaseMetrics("pr", result.cost, startedAtMs);
228
+ publishPhaseMetrics("pr", result.cost, startedAtMs, result.agent?.provider);
177
229
  if (!result.success) {
178
230
  return {
179
231
  ok: false,
180
- transient: isTransientFailure(1, result.error ?? ""),
232
+ transient: result.retryExhausted !== true && isTransientFailure(1, result.error ?? ""),
181
233
  error: `PR creation failed: ${result.error}`
182
234
  };
183
235
  }
@@ -190,14 +242,24 @@ async function runPr(issue, resolvedIssue, options = {}) {
190
242
  return 1;
191
243
  }
192
244
  const prUrl = parsePrUrl(headlessOutput);
245
+ const parsedPrNumber = prUrl === null ? null : parsePrNumber(prUrl);
246
+ let pullRequest = prUrl === null || parsedPrNumber === null ? null : { number: parsedPrNumber, url: prUrl };
247
+ if (pullRequest === null) {
248
+ try {
249
+ const [discovered] = await listPullRequests(branchName, { state: "open" });
250
+ if (discovered !== void 0) {
251
+ pullRequest = { number: discovered.number, url: discovered.url };
252
+ }
253
+ } catch {
254
+ }
255
+ }
193
256
  try {
194
257
  const plan = await loadTaskPlan(tasksPath);
195
258
  plan.pipeline.prCreated = true;
196
- const prNumber = prUrl === null ? null : parsePrNumber(prUrl);
197
- if (prUrl !== null && prNumber !== null) {
259
+ if (pullRequest !== null) {
198
260
  plan.pullRequest = {
199
- number: prNumber,
200
- url: prUrl,
261
+ number: pullRequest.number,
262
+ url: pullRequest.url,
201
263
  headBranch: branchName,
202
264
  createdAt: isoNow()
203
265
  };
@@ -205,8 +267,8 @@ async function runPr(issue, resolvedIssue, options = {}) {
205
267
  await saveTaskPlan(tasksPath, plan);
206
268
  } catch {
207
269
  }
208
- if (prUrl) {
209
- printSuccess(`PR created: ${prUrl}`);
270
+ if (pullRequest !== null) {
271
+ printSuccess(`PR created: ${pullRequest.url}`);
210
272
  } else {
211
273
  printSuccess("PR creation completed");
212
274
  }
@@ -218,4 +280,4 @@ export {
218
280
  multiIssueContext,
219
281
  runPr
220
282
  };
221
- //# sourceMappingURL=chunk-FADTCQGM.js.map
283
+ //# sourceMappingURL=chunk-2IQ6M4ZE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/pr.ts"],"sourcesContent":["import { execa } from 'execa';\nimport {\n issueReferenceLines,\n pullRequestTitle,\n resolveChangeType,\n} from '../conventions/git/index.js';\nimport { DEFAULT_HEADLESS_TIMEOUT_MS, runHeadless } from '../core/headless.js';\nimport { runPhaseWithRetry } from '../core/phase-runner.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { listPullRequests } from '../core/session-git.js';\nimport { publishPhaseMetrics } from '../core/session-metrics.js';\nimport { isoNow, loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { issuePlaceholders, resolveCommandIssue } from '../issues/context.js';\nimport type { Issue, IssueSource, ResolvedIssue } from '../issues/types.js';\nimport { loadRepositoryPolicy } from '../policy/index.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\n/**\n * Extract a PR URL from headless output.\n */\nfunction parsePrUrl(output: string): string | null {\n const match = output.match(/(https:\\/\\/github\\.com\\/[^\\s]+\\/pull\\/\\d+)/);\n return match?.[1] ?? null;\n}\n\n/**\n * The numeric id inside a PR URL, so later phases (`pr-review`) address the\n * Pull Request without querying GitHub again.\n */\nfunction parsePrNumber(url: string): number | null {\n const match = url.match(/\\/pull\\/(\\d+)/);\n return match?.[1] === undefined ? null : Number(match[1]);\n}\n\n/**\n * The `Closes #N` line for the PR body, empty when the Issue has no remote\n * counterpart: GitHub only understands the reference for Issues it hosts, and\n * an invented `#N` would silently point at an unrelated Issue.\n */\nfunction issueClosesLine(issue: Issue, fallbackId: string, complete: boolean): string {\n if (issue.remoteRef === null) {\n return '';\n }\n const number = issue.number ?? (/^\\d+$/.test(fallbackId) ? Number(fallbackId) : null);\n if (number === null) {\n return '';\n }\n return issueReferenceLines({ references: [{ number, complete }] });\n}\n\n/** One Issue of a queue, as the consolidated Pull Request has to describe it. */\nexport interface PrQueueIssue {\n id: string;\n number: number | null;\n title: string;\n /** `null` when the Issue was never read — see {@link issueClosesLines}. */\n url: string | null;\n /** Origin the Issue came from; `github` is what GitHub can close. */\n source: IssueSource;\n parent?: string | null;\n role?: 'executable' | 'container';\n /** When false, the body uses `Refs` instead of `Closes`. */\n complete?: boolean;\n}\n\n/**\n * What a multi-issue queue hands to the `pr` phase.\n *\n * Absent for every standalone run, and that absence is what guarantees the\n * single-issue Pull Request is byte-for-byte the one this command has always\n * produced: the extra placeholders resolve to empty strings.\n */\nexport interface PrQueueContext {\n /** Issues of the queue, in the order they were executed. */\n issues: PrQueueIssue[];\n /** Issues discovered but not executed, with the reason. */\n excluded: { id: string; number: number | null; title: string; reason: string }[];\n /** Anything else worth reporting as pending (unresolved review findings). */\n pending: string[];\n}\n\nexport interface RunPrOptions {\n /** Set only when the Pull Request consolidates a queue. */\n queue?: PrQueueContext;\n}\n\n/** `#51` when the Issue has a number, its raw identifier otherwise. */\nfunction issueRef(entry: { id: string; number: number | null }): string {\n return entry.number === null ? entry.id : `#${entry.number}`;\n}\n\n/**\n * Every `Closes #N` line of a consolidated Pull Request, one per line.\n *\n * Issues that GitHub does not host are skipped for the same reason a\n * single-issue Pull Request skips them — it cannot close what it does not\n * host — and a queue that mixes both origins still gets a valid body.\n *\n * The test is the origin and the number, not the URL: an Issue whose read\n * failed during discovery reaches the queue with `url: null`, and it is still\n * executed and still closed by the queue. Filtering on the URL would leave it\n * out of the very body that is supposed to reference it.\n */\nexport function issueClosesLines(issues: readonly PrQueueIssue[]): string {\n return issueReferenceLines({\n references: issues\n .filter((entry) => entry.source === 'github' && entry.number !== null)\n .map((entry) => ({\n number: entry.number as number,\n complete: entry.complete !== false,\n })),\n });\n}\n\n/**\n * The extra instructions the prompt receives when the Pull Request covers a\n * whole queue.\n *\n * Returns `''` for a standalone run, which is what keeps `pr.md` rendering\n * exactly as before for the single-issue path — no empty \"Issues implemented\"\n * section, no redundant ordering of a list of one.\n */\nexport function multiIssueContext(queue: PrQueueContext | undefined): string {\n if (queue === undefined || queue.issues.length <= 1) {\n return '';\n }\n\n const byId = new Map(queue.issues.map((entry) => [entry.id, entry]));\n const depthOf = (entry: PrQueueIssue): number => {\n let depth = 0;\n let parent = entry.parent ?? null;\n const seen = new Set<string>();\n while (parent !== null && !seen.has(parent)) {\n seen.add(parent);\n depth += 1;\n parent = byId.get(parent)?.parent ?? null;\n }\n return depth;\n };\n const order = queue.issues\n .map((entry, index) => {\n const indent = ' '.repeat(depthOf(entry));\n const role = entry.role === 'container' ? ' (container)' : '';\n return `${index + 1}. ${indent}${issueRef(entry)}${entry.title ? ` — ${entry.title}` : ''}${role}`;\n })\n .join('\\n');\n\n const pending = [\n ...queue.excluded.map(\n (entry) => `- ${issueRef(entry)}${entry.title ? ` — ${entry.title}` : ''}: ${entry.reason}`,\n ),\n ...queue.pending.map((note) => `- ${note}`),\n ].join('\\n');\n\n return [\n '',\n 'This Pull Request consolidates several issues implemented on this same branch,',\n 'in this execution order:',\n '',\n order,\n '',\n 'The PR body MUST additionally contain:',\n '- an \"Issues implemented\" section listing every issue above, in that order,',\n ' with one line per issue describing what it delivered;',\n '- a \"Pending\" section with the items below, verbatim, plus anything you find',\n ' unfinished while reviewing the diff. Write \"None\" when the list below is',\n ' empty and you find nothing else;',\n '',\n pending === '' ? '(no known pending items)' : pending,\n '',\n 'The commits of this branch are scoped per issue (`feat(issue-N): …`), which is',\n 'how you tell which change belongs to which issue in `git log`.',\n ].join('\\n');\n}\n\n/**\n * Adopt the Pull Request that is already open for this branch, if there is one.\n *\n * \"Already open\" is the only safe reading: a *closed* Pull Request for the same\n * branch is a decision someone made, and reopening or reusing it would undo it.\n * A merged one means the work is done and a new branch is the answer.\n *\n * Returns `true` when the phase has nothing left to do. Never throws — a `gh`\n * that cannot answer leaves the phase to run exactly as it did before.\n */\nasync function adoptExistingPullRequest(\n branchName: string,\n tasksPath: string,\n issueNumber: string,\n): Promise<boolean> {\n let open: Awaited<ReturnType<typeof listPullRequests>>;\n try {\n open = await listPullRequests(branchName, { state: 'open' });\n } catch {\n return false;\n }\n\n const existing = open[0];\n if (existing === undefined) return false;\n\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.prCreated = true;\n plan.pullRequest = {\n number: existing.number,\n url: existing.url,\n headBranch: branchName,\n // The Pull Request predates this attempt; what is recorded is when this\n // run adopted it, which is the only timestamp this process can vouch for.\n createdAt: isoNow(),\n };\n await saveTaskPlan(tasksPath, plan);\n } catch {\n // No plan yet: the adoption still stands, there is just nowhere to note it.\n }\n\n printSuccess(\n `Pull Request already open for ${branchName}: ${existing.url}. Adopting it instead of opening a second one for issue #${issueNumber}.`,\n );\n return true;\n}\n\nexport async function runPr(\n issue: string,\n resolvedIssue?: ResolvedIssue,\n options: RunPrOptions = {},\n): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const paths = await resolveIssuePaths(issueNumber);\n const tasksPath = paths.tasksFile;\n\n const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);\n if (!resolution.ok) {\n return resolution.code;\n }\n\n // Get current branch\n let branchName: string;\n try {\n const proc = await execa('git', ['branch', '--show-current'], { reject: false });\n branchName = proc.stdout?.toString().trim() ?? '';\n if (!branchName) {\n printError('Could not determine current branch');\n return 1;\n }\n } catch {\n printError('Failed to get current branch');\n return 1;\n }\n\n // Idempotence, before anything is invoked.\n //\n // `runPhaseWithRetry` retries this phase up to three times, and the timeout\n // that used to end a run is now correctly classified as transient — so an\n // attempt that created the Pull Request and *then* timed out would have the\n // next attempt create a second one. Asking GitHub what already exists is the\n // cheap half of the fix; the expensive half would be undoing a duplicate.\n const adopted = await adoptExistingPullRequest(branchName, tasksPath, issueNumber);\n if (adopted) return 0;\n\n const queue = options.queue;\n const consolidating = queue !== undefined && queue.issues.length > 1;\n\n let planComplete = false;\n try {\n const existing = await loadTaskPlan(tasksPath);\n planComplete =\n existing.userStories.every((story) => story.passes) &&\n (existing.lastReviewFindings === null || existing.lastReviewFindings === '');\n } catch {\n planComplete = false;\n }\n\n const policy = await loadRepositoryPolicy();\n const change = resolveChangeType({\n labels: resolution.resolved.issue.labels,\n title: resolution.resolved.issue.title,\n titleConvention: policy.issues.titleConvention,\n typeMap: policy.git.typeMap,\n });\n const prTitle = pullRequestTitle({\n type: change.type,\n subject: resolution.resolved.issue.title.replace(/^\\s*\\[[^\\]]+\\]\\s*/, ''),\n });\n\n const template = await loadPrompt('pr');\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 __BRANCH_NAME__: branchName,\n __TASKS_PATH__: tasksPath,\n __PR_TITLE_CONVENTION__: prTitle,\n __ISSUE_REFERENCE__: consolidating\n ? issueClosesLines(queue.issues)\n : issueClosesLine(resolution.resolved.issue, issueNumber, planComplete),\n __MULTI_ISSUE_CONTEXT__: multiIssueContext(queue),\n ...issuePlaceholders(resolution.resolved),\n });\n\n let headlessOutput = '';\n\n const outcome = await runPhaseWithRetry({\n phase: 'pr',\n attempt: async () => {\n const startedAtMs = Date.now();\n const result = await runHeadless({\n prompt,\n maxTurns: 15,\n timeout: getGlobalTimeout() ?? DEFAULT_HEADLESS_TIMEOUT_MS,\n timeoutHistory: {\n phase: 'pr',\n journalFiles: [paths.rotatedEventsFile, paths.eventsFile],\n },\n // json (not text) so the CLI reports usage: the envelope's `result`\n // field carries the same assistant text parsePrUrl() already consumed.\n outputFormat: 'json',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep'],\n addDirs: [paths.issueDir],\n statusMessage: `Creating PR for issue #${issueNumber}...`,\n phase: 'pr',\n permission: 'workspace',\n });\n // One event per attempt; the reducer sums them into the phase total.\n publishPhaseMetrics('pr', result.cost, startedAtMs, result.agent?.provider);\n\n if (!result.success) {\n return {\n ok: false,\n transient: result.retryExhausted !== true && isTransientFailure(1, result.error ?? ''),\n error: `PR creation failed: ${result.error}`,\n };\n }\n\n headlessOutput = result.result;\n return { ok: true };\n },\n });\n\n if (!outcome.ok) {\n printError(outcome.error ?? `PR creation failed for issue #${issueNumber}`);\n return 1;\n }\n\n const prUrl = parsePrUrl(headlessOutput);\n const parsedPrNumber = prUrl === null ? null : parsePrNumber(prUrl);\n let pullRequest =\n prUrl === null || parsedPrNumber === null ? null : { number: parsedPrNumber, url: prUrl };\n\n // The agent may create the Pull Request successfully but omit (or mangle)\n // its URL in the final answer. Ask GitHub again after the side effect so the\n // durable plan never loses a trustworthy PR that now exists for this branch.\n if (pullRequest === null) {\n try {\n const [discovered] = await listPullRequests(branchName, { state: 'open' });\n if (discovered !== undefined) {\n pullRequest = { number: discovered.number, url: discovered.url };\n }\n } catch {\n // With no trustworthy answer, keep the historical successful-but-unlinked state.\n }\n }\n\n // Update pipeline state\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.prCreated = true;\n // Without an output URL or a post-create GitHub match the PR is still\n // assumed created (the phase succeeded), but no number is invented.\n if (pullRequest !== null) {\n plan.pullRequest = {\n number: pullRequest.number,\n url: pullRequest.url,\n headBranch: branchName,\n createdAt: isoNow(),\n };\n }\n await saveTaskPlan(tasksPath, plan);\n } catch {\n // tasks.json may not exist\n }\n\n if (pullRequest !== null) {\n printSuccess(`PR created: ${pullRequest.url}`);\n } else {\n printSuccess('PR creation completed');\n }\n return 0;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ MemoryPublisher
4
+ } from "./chunk-6QYMSNVA.js";
5
+
6
+ // src/core/journal.ts
7
+ import { appendFile, mkdir, rename, stat, unlink } from "fs/promises";
8
+ import { dirname } from "path";
9
+ var DEFAULT_JOURNAL_MAX_BYTES = 10 * 1024 * 1024;
10
+ var JournalPublisher = class extends MemoryPublisher {
11
+ filePath;
12
+ rotatedPath;
13
+ maxFileBytes;
14
+ seq = 0;
15
+ writeChain = Promise.resolve();
16
+ closed = false;
17
+ /** Bytes in the current file. `null` until the first write measures it. */
18
+ bytes = null;
19
+ constructor(filePath, rotatedPath, options = {}) {
20
+ super(options);
21
+ this.filePath = filePath;
22
+ this.rotatedPath = rotatedPath;
23
+ this.maxFileBytes = options.maxFileBytes ?? DEFAULT_JOURNAL_MAX_BYTES;
24
+ }
25
+ afterPublish(event) {
26
+ if (this.closed) return;
27
+ this.seq++;
28
+ const line = `${JSON.stringify({ seq: this.seq, event })}
29
+ `;
30
+ this.enqueue(line);
31
+ }
32
+ enqueue(line) {
33
+ this.writeChain = this.writeChain.then(async () => {
34
+ try {
35
+ await this.appendLine(line);
36
+ } catch (err) {
37
+ this.warnOnce(err);
38
+ }
39
+ });
40
+ }
41
+ async appendLine(line) {
42
+ const size = Buffer.byteLength(line);
43
+ if (this.bytes === null) {
44
+ await mkdir(dirname(this.filePath), { recursive: true });
45
+ this.bytes = await currentSize(this.filePath);
46
+ }
47
+ if (this.maxFileBytes > 0 && this.bytes > 0 && this.bytes + size > this.maxFileBytes) {
48
+ await this.rotate();
49
+ }
50
+ await appendFile(this.filePath, line, "utf-8");
51
+ this.bytes += size;
52
+ }
53
+ /**
54
+ * Keep exactly one previous generation.
55
+ *
56
+ * Deliberately not a numbered cascade: two files bound the disk cost at
57
+ * `2 * maxFileBytes`, and a run long enough to fill 10 MB twice has already
58
+ * put everything a human would look for in the current file. A caller that
59
+ * needs full retention sets `maxFileBytes: 0` and rotates on its own terms.
60
+ */
61
+ async rotate() {
62
+ try {
63
+ await unlink(this.rotatedPath);
64
+ } catch {
65
+ }
66
+ await rename(this.filePath, this.rotatedPath);
67
+ this.bytes = 0;
68
+ }
69
+ async flush() {
70
+ await this.writeChain;
71
+ }
72
+ async close() {
73
+ await this.flush();
74
+ this.closed = true;
75
+ }
76
+ };
77
+ async function currentSize(path) {
78
+ try {
79
+ return (await stat(path)).size;
80
+ } catch {
81
+ return 0;
82
+ }
83
+ }
84
+ function parseJournal(content) {
85
+ const entries = [];
86
+ for (const line of content.split("\n")) {
87
+ const trimmed = line.trim();
88
+ if (trimmed === "") continue;
89
+ let parsed;
90
+ try {
91
+ parsed = JSON.parse(trimmed);
92
+ } catch {
93
+ continue;
94
+ }
95
+ if (!isEntry(parsed)) continue;
96
+ entries.push(parsed);
97
+ }
98
+ return entries;
99
+ }
100
+ function isEntry(value) {
101
+ if (typeof value !== "object" || value === null) return false;
102
+ const candidate = value;
103
+ if (typeof candidate.seq !== "number") return false;
104
+ const event = candidate.event;
105
+ return typeof event === "object" && event !== null && typeof event.type === "string";
106
+ }
107
+ var MultiPublisher = class {
108
+ members;
109
+ primary;
110
+ constructor(members) {
111
+ if (members.length === 0) {
112
+ throw new Error("MultiPublisher needs at least one member");
113
+ }
114
+ this.members = members;
115
+ this.primary = members[0];
116
+ }
117
+ publish(event) {
118
+ for (const member of this.members) {
119
+ member.publish(event);
120
+ }
121
+ }
122
+ snapshot() {
123
+ return this.primary.snapshot();
124
+ }
125
+ version() {
126
+ return this.primary.version();
127
+ }
128
+ async flush() {
129
+ await Promise.all(this.members.map((member) => member.flush()));
130
+ }
131
+ async close() {
132
+ await Promise.all(this.members.map((member) => member.close()));
133
+ }
134
+ };
135
+
136
+ export {
137
+ JournalPublisher,
138
+ parseJournal,
139
+ MultiPublisher
140
+ };
141
+ //# sourceMappingURL=chunk-3BWZO4YI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/journal.ts"],"sourcesContent":["import { appendFile, mkdir, rename, stat, unlink } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport {\n createInitialSnapshot,\n MemoryPublisher,\n type MemoryPublisherOptions,\n reduceSessionEvent,\n type SessionEvent,\n type SessionPublisher,\n type SessionReducerOptions,\n type SessionSnapshot,\n} from './session-state.js';\n\n/**\n * Append-only journal of `SessionEvent`s.\n *\n * `session.json` is a *projection*: the reducer folds every event into one\n * snapshot and the events themselves are discarded. That is the right shape for\n * a dashboard and the wrong one for an audit — after a six-hour run, \"what\n * happened at 3am\" has no answer, because the retries, the failures and their\n * order were consumed to produce a single final state.\n *\n * This publisher writes the events instead of the state: one JSON line each, in\n * order, with a monotonic `seq`. It sits **beside** `FilePublisher`, never in\n * its place. The snapshot stays the projection; the journal is the history, and\n * replaying it through `reduceSessionEvent` reproduces the snapshot — which is\n * the property `replayJournal()` and its test exist to keep true.\n *\n * The invariant of `session-state.ts` holds here unchanged: `publish()` is\n * synchronous, never throws, and every I/O failure is swallowed after a single\n * warning. Monitoring never affects the pipeline.\n */\n\n/** One line of `events.jsonl`. */\nexport interface JournalEntry {\n /** 1-based, monotonic within a file *and* across a rotation. */\n seq: number;\n event: SessionEvent;\n}\n\n/** Default ceiling before the journal rotates. */\nexport const DEFAULT_JOURNAL_MAX_BYTES = 10 * 1024 * 1024;\n\nexport interface JournalPublisherOptions extends MemoryPublisherOptions {\n /**\n * Rotate once the file would grow past this. Default 10 MB. `0` disables\n * rotation, which is what a caller that manages its own retention wants.\n */\n maxFileBytes?: number;\n}\n\n/**\n * Publisher that appends every event to `events.jsonl`.\n *\n * Writes are serialized on a promise chain — the same discipline as\n * `FilePublisher` — so lines never interleave and the order on disk is the\n * order of `publish()`. Nothing is throttled: dropping or coalescing events is\n * exactly what the snapshot already does, and the point of the journal is that\n * it does not.\n */\nexport class JournalPublisher extends MemoryPublisher {\n private readonly filePath: string;\n private readonly rotatedPath: string;\n private readonly maxFileBytes: number;\n private seq = 0;\n private writeChain: Promise<void> = Promise.resolve();\n private closed = false;\n /** Bytes in the current file. `null` until the first write measures it. */\n private bytes: number | null = null;\n\n constructor(filePath: string, rotatedPath: string, options: JournalPublisherOptions = {}) {\n super(options);\n this.filePath = filePath;\n this.rotatedPath = rotatedPath;\n this.maxFileBytes = options.maxFileBytes ?? DEFAULT_JOURNAL_MAX_BYTES;\n }\n\n protected override afterPublish(event: SessionEvent): void {\n if (this.closed) return;\n this.seq++;\n const line = `${JSON.stringify({ seq: this.seq, event } satisfies JournalEntry)}\\n`;\n this.enqueue(line);\n }\n\n private enqueue(line: string): void {\n this.writeChain = this.writeChain.then(async () => {\n try {\n await this.appendLine(line);\n } catch (err) {\n this.warnOnce(err);\n }\n });\n }\n\n private async appendLine(line: string): Promise<void> {\n const size = Buffer.byteLength(line);\n\n if (this.bytes === null) {\n await mkdir(dirname(this.filePath), { recursive: true });\n this.bytes = await currentSize(this.filePath);\n }\n\n // Rotate *before* the write that would cross the ceiling, so a line is\n // never split across two files: an entry that cannot be parsed whole is\n // worse than a file slightly under its limit.\n if (this.maxFileBytes > 0 && this.bytes > 0 && this.bytes + size > this.maxFileBytes) {\n await this.rotate();\n }\n\n await appendFile(this.filePath, line, 'utf-8');\n this.bytes += size;\n }\n\n /**\n * Keep exactly one previous generation.\n *\n * Deliberately not a numbered cascade: two files bound the disk cost at\n * `2 * maxFileBytes`, and a run long enough to fill 10 MB twice has already\n * put everything a human would look for in the current file. A caller that\n * needs full retention sets `maxFileBytes: 0` and rotates on its own terms.\n */\n private async rotate(): Promise<void> {\n try {\n await unlink(this.rotatedPath);\n } catch {\n // No previous generation. Nothing to remove.\n }\n await rename(this.filePath, this.rotatedPath);\n this.bytes = 0;\n }\n\n override async flush(): Promise<void> {\n await this.writeChain;\n }\n\n override async close(): Promise<void> {\n await this.flush();\n this.closed = true;\n }\n}\n\n/** Size of `path` in bytes, or `0` when it does not exist yet. */\nasync function currentSize(path: string): Promise<number> {\n try {\n return (await stat(path)).size;\n } catch {\n return 0;\n }\n}\n\n/**\n * Parse the lines of a journal, skipping what cannot be read.\n *\n * A journal is appended to by a live process and read by another, so the last\n * line may well be a partial write. Tolerating it — and any line a newer\n * release wrote in a shape this one does not know — is what makes the file\n * usable at all; the alternative is a reader that throws on a healthy run.\n */\nexport function parseJournal(content: string): JournalEntry[] {\n const entries: JournalEntry[] = [];\n\n for (const line of content.split('\\n')) {\n const trimmed = line.trim();\n if (trimmed === '') continue;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch {\n continue;\n }\n\n if (!isEntry(parsed)) continue;\n entries.push(parsed);\n }\n\n return entries;\n}\n\nfunction isEntry(value: unknown): value is JournalEntry {\n if (typeof value !== 'object' || value === null) return false;\n const candidate = value as { seq?: unknown; event?: unknown };\n if (typeof candidate.seq !== 'number') return false;\n const event = candidate.event;\n return (\n typeof event === 'object' && event !== null && typeof (event as SessionEvent).type === 'string'\n );\n}\n\n/**\n * Rebuild the snapshot from a journal.\n *\n * This is the reproducibility contract of the file: the same reducer that\n * produced `session.json` live, run over the events in the order they were\n * written, must land on the same state. A journal that cannot do that is a log,\n * not a journal.\n */\nexport function replayJournal(\n content: string,\n options: SessionReducerOptions = {},\n): SessionSnapshot {\n let snapshot = createInitialSnapshot();\n for (const entry of parseJournal(content)) {\n snapshot = reduceSessionEvent(snapshot, entry.event, options);\n }\n return snapshot;\n}\n\n/**\n * Fan one event stream out to several publishers.\n *\n * The journal sits **beside** the snapshot writer, so something has to hold\n * both. It is the composite rather than the journal that does it, because the\n * relationship is symmetric: neither surface owns the other, and dropping\n * either one has to leave the remaining one untouched.\n *\n * `snapshot()` and `version()` answer from the first member, which is the\n * primary surface. A member that fails has already swallowed its own error —\n * that is the contract of every publisher here — so the loop needs no guard of\n * its own beyond `publish()` never throwing.\n */\nexport class MultiPublisher implements SessionPublisher {\n private readonly members: readonly SessionPublisher[];\n private readonly primary: SessionPublisher;\n\n constructor(members: readonly SessionPublisher[]) {\n if (members.length === 0) {\n throw new Error('MultiPublisher needs at least one member');\n }\n this.members = members;\n this.primary = members[0] as SessionPublisher;\n }\n\n publish(event: SessionEvent): void {\n for (const member of this.members) {\n member.publish(event);\n }\n }\n\n snapshot(): SessionSnapshot {\n return this.primary.snapshot();\n }\n\n version(): number {\n return this.primary.version();\n }\n\n async flush(): Promise<void> {\n await Promise.all(this.members.map((member) => member.flush()));\n }\n\n async close(): Promise<void> {\n await Promise.all(this.members.map((member) => member.close()));\n }\n}\n"],"mappings":"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","names":[]}