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
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/journal.ts","../src/core/pipeline.ts","../src/execution/plan.ts","../src/issues/graph.ts","../src/execution/order.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","import type { PipelineState, TaskPlan } from '../types.js';\nimport { loadTaskPlan, saveTaskPlan } from './state-manager.js';\n\n/**\n * Ordered pipeline phases. Each phase must complete before the next can start.\n *\n * `pr-review` is deliberately NOT here: it is opt-in via `--pr-review`. Adding\n * it to the default set would make every existing `tasks.json` (which has no\n * `prReviewCompleted`) look unfinished, so `getNextPhase()` would resume into a\n * phase the user never asked for.\n */\nexport const PIPELINE_PHASES = ['init', 'prd', 'plan', 'execute', 'review', 'pr'] as const;\n\n/**\n * The default set plus the opt-in `pr-review` phase, used when the user passes\n * `--pr-review` (or the flag was persisted in `plan.prReview.enabled`).\n */\nexport const PIPELINE_PHASES_WITH_PR_REVIEW = [...PIPELINE_PHASES, 'pr-review'] as const;\n\nexport type PipelinePhase = (typeof PIPELINE_PHASES_WITH_PR_REVIEW)[number];\n\n/**\n * Pipeline phases excluding the 'pr' phase, for --no-branch mode.\n * `pr-review` is excluded too: without a branch there is no PR to review.\n */\nexport const PIPELINE_PHASES_NO_BRANCH = PIPELINE_PHASES.filter(\n (p): p is Exclude<PipelinePhase, 'pr' | 'pr-review'> => p !== 'pr',\n);\n\n/**\n * Map pipeline phases to their corresponding PipelineState field.\n * 'init' has no persisted state — it's a runtime-only check.\n */\nconst PHASE_TO_FIELD: Record<PipelinePhase, keyof PipelineState | null> = {\n init: null,\n prd: 'prdCompleted',\n plan: 'jsonCompleted',\n execute: 'executionCompleted',\n review: 'reviewCompleted',\n pr: 'prCreated',\n 'pr-review': 'prReviewCompleted',\n};\n\nexport class PipelineManager {\n private tasksJsonPath: string;\n private plan: TaskPlan;\n private activePhases: readonly PipelinePhase[];\n\n constructor(plan: TaskPlan, tasksJsonPath: string, activePhases?: readonly PipelinePhase[]) {\n this.plan = plan;\n this.tasksJsonPath = tasksJsonPath;\n this.activePhases = activePhases ?? PIPELINE_PHASES;\n }\n\n /**\n * Reload state from disk.\n */\n async reload(): Promise<void> {\n this.plan = await loadTaskPlan(this.tasksJsonPath);\n }\n\n /**\n * Check if a phase is complete.\n */\n isPhaseComplete(phase: PipelinePhase): boolean {\n const field = PHASE_TO_FIELD[phase];\n if (field === null) return true; // init is always \"complete\" after running\n return this.plan.pipeline?.[field] ?? false;\n }\n\n /**\n * Get the first incomplete phase in the pipeline.\n * Returns null if all phases are complete.\n */\n getNextPhase(): PipelinePhase | null {\n for (const phase of this.activePhases) {\n if (!this.isPhaseComplete(phase)) {\n return phase;\n }\n }\n return null;\n }\n\n /**\n * Get the current phase (alias for getNextPhase).\n */\n getCurrentPhase(): PipelinePhase | null {\n return this.getNextPhase();\n }\n\n /**\n * Check whether we can resume from a specific phase.\n * All prerequisite phases must be complete.\n */\n canResume(fromPhase: PipelinePhase): boolean {\n const idx = this.activePhases.indexOf(fromPhase);\n if (idx < 0) return false;\n\n // All active phases before fromPhase must be complete\n for (let i = 0; i < idx; i++) {\n if (!this.isPhaseComplete(this.activePhases[i])) {\n return false;\n }\n }\n return true;\n }\n\n /**\n * Mark a phase as complete and persist to disk.\n */\n async markPhaseComplete(phase: PipelinePhase): Promise<void> {\n const field = PHASE_TO_FIELD[phase];\n if (field === null) return; // init has no persisted state\n\n this.plan = {\n ...this.plan,\n pipeline: {\n ...(this.plan.pipeline ?? {\n prdCompleted: false,\n jsonCompleted: false,\n executionCompleted: false,\n reviewCompleted: false,\n prCreated: false,\n }),\n [field]: true,\n },\n };\n\n await saveTaskPlan(this.tasksJsonPath, this.plan);\n }\n}\n","import { mkdir, readFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { ZodError } from 'zod';\nimport { isoNow } from '../core/state-manager.js';\nimport type { DependencyGraph } from '../issues/graph.js';\nimport type { Issue } from '../issues/types.js';\nimport { executionPlanSchema } from '../storage/schemas.js';\nimport type { LastError, PullRequestRef } from '../types.js';\nimport { writeFileAtomic } from '../utils/fs.js';\nimport { priorityOf } from './order.js';\nimport type {\n ExecutionPlan,\n ExecutionPlanExcluded,\n ExecutionPlanIssue,\n QueueIssueStatus,\n QueueStatus,\n} from './types.js';\n\n/**\n * Building, reading, writing and advancing the execution plan of a queue.\n *\n * The plan is the only state a multi-issue run adds to the storage tree, and it\n * is written **only** for a queue with more than one Issue — a single-issue run\n * leaves the layout exactly as it was.\n */\n\nexport interface BuildExecutionPlanInput {\n /** Project id of the global storage tree. */\n projectId: string;\n /** Identifiers the user asked for, in order; the first one names the queue. */\n requested: readonly string[];\n graph: DependencyGraph;\n /** Ids to run, already ordered by `computeExecutionOrder`. */\n order: readonly string[];\n noBranch?: boolean;\n prReview?: boolean;\n /** Injectable clock, so tests can assert `createdAt` vs `updatedAt`. */\n now?: () => string;\n}\n\n/** Title/url/number of a node, falling back to what the id alone tells us. */\nfunction describeIssue(\n issue: Issue | null,\n id: string,\n): {\n number: number | null;\n title: string;\n url: string | null;\n source: string;\n} {\n const parsed = Number.parseInt(id, 10);\n return {\n number: issue?.number ?? (Number.isNaN(parsed) ? null : parsed),\n title: issue?.title ?? '',\n url: issue?.remoteRef ?? null,\n source: issue?.source ?? 'github',\n };\n}\n\n/**\n * Turn a graph plus an order into the persisted plan.\n *\n * Every Issue of the graph that is *not* in the order becomes an `excluded`\n * entry: the consolidated Pull Request reports them as known pending work, so a\n * hierarchy the user chose to trim never disappears silently.\n */\nexport function buildExecutionPlan(input: BuildExecutionPlanInput): ExecutionPlan {\n const now = input.now ?? isoNow;\n const at = now();\n const scheduled = new Set(input.order);\n const requested = new Set(input.requested);\n\n const issues: ExecutionPlanIssue[] = input.order.map((id, index) => {\n const node = input.graph.nodes.get(id);\n const relations = node?.relations;\n const described = describeIssue(node?.issue ?? null, id);\n\n return {\n id,\n number: described.number,\n title: described.title,\n url: described.url,\n source: described.source,\n position: index + 1,\n status: 'pending',\n origin: requested.has(id) ? 'requested' : 'discovered',\n // Only the constraints that survive inside the queue: a blocker left out\n // of it is reported in `excluded`, not enforced as a dependency.\n dependsOn: (relations?.blockedBy ?? []).filter((blocker) => scheduled.has(blocker)),\n parent: relations?.parent ?? null,\n priority: priorityOf(node?.issue?.labels ?? []),\n heuristic: (relations?.heuristic.length ?? 0) > 0,\n failedPhase: null,\n lastError: null,\n attempts: 0,\n blockedReason: null,\n startedAt: null,\n completedAt: null,\n };\n });\n\n const excluded: ExecutionPlanExcluded[] = [];\n for (const node of input.graph.nodes.values()) {\n if (scheduled.has(node.id)) continue;\n const described = describeIssue(node.issue, node.id);\n excluded.push({\n id: node.id,\n number: described.number,\n title: described.title,\n url: described.url,\n reason: 'Discovered in the hierarchy but not selected for this run',\n });\n }\n\n return {\n schemaVersion: 1,\n id: input.requested[0] ?? (input.order[0] as string),\n project: input.projectId,\n requested: [...input.requested],\n branchName: null,\n noBranch: input.noBranch ?? false,\n prReview: input.prReview ?? false,\n status: 'pending',\n createdAt: at,\n updatedAt: at,\n truncated: input.graph.truncated,\n issues,\n excluded,\n };\n}\n\n/** Read a persisted plan. Throws with the offending fields on invalid content. */\nexport async function loadExecutionPlan(path: string): Promise<ExecutionPlan> {\n const raw: unknown = JSON.parse(await readFile(path, 'utf-8'));\n\n try {\n return executionPlanSchema.parse(raw) as ExecutionPlan;\n } catch (err) {\n if (err instanceof ZodError) {\n const issues = err.issues.map((i) => ` - ${i.path.join('.')}: ${i.message}`).join('\\n');\n throw new Error(`Invalid execution-plan.json at ${path}:\\n${issues}`);\n }\n throw err;\n }\n}\n\n/**\n * Persist a plan atomically, creating the queue directory on first write.\n *\n * `updatedAt` is stamped here so no call site can forget it — the file is the\n * only record of how far a queue got, and a stale timestamp on it is worse than\n * none.\n */\nexport async function saveExecutionPlan(\n path: string,\n plan: ExecutionPlan,\n now: () => string = isoNow,\n): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n const stamped: ExecutionPlan = { ...plan, updatedAt: now() };\n await writeFileAtomic(path, `${JSON.stringify(stamped, null, 2)}\\n`);\n}\n\n/**\n * The Issue the queue should work on next, or `null` when every one of them is\n * completed.\n *\n * The order of the lookups *is* the resume policy: an interrupted issue\n * (`in_progress`) and a failed one are retried before the queue moves on, and\n * each of them picks up from its own `tasks.json` (phase-level resume) rather\n * than from the start. A `completed` issue is never revisited — that is the\n * whole point of persisting the queue.\n */\nexport interface NextQueueIssueOptions {\n /**\n * Ids this invocation is done with, however they ended. A queue that skips a\n * failing Issue and comes back to it needs a way to say \"not this one again\",\n * or \"come back later\" becomes \"come back forever\".\n */\n exclude?: ReadonlySet<string>;\n}\n\n/**\n * Whether every dependency this Issue declares *inside the queue* is finished.\n *\n * The execution order already places dependencies first, so this only bites\n * once an entry stops being `completed` — skipped, blocked or failed. Handing\n * out an Issue whose blocker was skipped is how a queue produces work that\n * cannot compile.\n */\nfunction dependenciesSatisfied(plan: ExecutionPlan, entry: ExecutionPlanIssue): boolean {\n return entry.dependsOn.every((id) => {\n const dependency = plan.issues.find((candidate) => candidate.id === id);\n return dependency === undefined || dependency.status === 'completed';\n });\n}\n\n/**\n * The Issue to work on next.\n *\n * The order of the four lookups is the resumption policy:\n * an interrupted Issue first (it was mid-flight), then one that failed on a\n * previous invocation, then the untouched ones, and only at the end the ones\n * this queue deliberately set aside — which is what \"go on with the\n * independent work and come back to it\" means in one line.\n *\n * `blocked` is absent on purpose: it needs a human, and no ordering of the\n * queue changes that.\n */\nexport function nextQueueIssue(\n plan: ExecutionPlan,\n options: NextQueueIssueOptions = {},\n): ExecutionPlanIssue | null {\n const exclude = options.exclude ?? new Set<string>();\n const eligible = (status: QueueIssueStatus) => (entry: ExecutionPlanIssue) =>\n entry.status === status && !exclude.has(entry.id) && dependenciesSatisfied(plan, entry);\n\n return (\n plan.issues.find(eligible('in_progress')) ??\n plan.issues.find(eligible('failed')) ??\n plan.issues.find(eligible('pending')) ??\n plan.issues.find(eligible('skipped')) ??\n null\n );\n}\n\n/**\n * Status of the queue derived from its entries.\n *\n * `skipped` is *not* terminal — the queue means to come back to it — so a queue\n * holding one is still `in_progress`. `blocked` is: nothing the queue can do\n * moves it, so a queue that ends with one ends `failed`, which is what a person\n * needs to see.\n */\nexport function queueStatus(plan: ExecutionPlan): QueueStatus {\n if (plan.issues.some((entry) => entry.status === 'failed' || entry.status === 'blocked')) {\n return 'failed';\n }\n if (plan.issues.every((entry) => entry.status === 'completed')) return 'completed';\n if (plan.issues.some((entry) => entry.status !== 'pending')) return 'in_progress';\n return 'pending';\n}\n\n/** Whether every Issue of the queue has been resolved. */\nexport function isQueueComplete(plan: ExecutionPlan): boolean {\n return plan.issues.every((entry) => entry.status === 'completed');\n}\n\n/** Apply `patch` to one entry and recompute the queue status. Pure. */\nfunction updateIssue(\n plan: ExecutionPlan,\n id: string,\n patch: Partial<ExecutionPlanIssue>,\n): ExecutionPlan {\n const issues = plan.issues.map((entry) => (entry.id === id ? { ...entry, ...patch } : entry));\n const next: ExecutionPlan = { ...plan, issues };\n return { ...next, status: queueStatus(next) };\n}\n\nexport function markQueueIssueInProgress(\n plan: ExecutionPlan,\n id: string,\n now: () => string = isoNow,\n): ExecutionPlan {\n const current = plan.issues.find((entry) => entry.id === id);\n return updateIssue(plan, id, {\n status: 'in_progress',\n // Preserved across a resume: the queue reports when the work on this Issue\n // really began, not when it was last picked up.\n startedAt: current?.startedAt ?? now(),\n failedPhase: null,\n lastError: null,\n });\n}\n\nexport function markQueueIssueCompleted(\n plan: ExecutionPlan,\n id: string,\n now: () => string = isoNow,\n): ExecutionPlan {\n return updateIssue(plan, id, {\n status: 'completed',\n completedAt: now(),\n failedPhase: null,\n lastError: null,\n });\n}\n\nexport function markQueueIssueFailed(\n plan: ExecutionPlan,\n id: string,\n failure: { phase: string | null; error: LastError | null },\n): ExecutionPlan {\n const current = plan.issues.find((entry) => entry.id === id);\n return updateIssue(plan, id, {\n status: 'failed',\n failedPhase: failure.phase,\n lastError: failure.error,\n attempts: (current?.attempts ?? 0) + 1,\n });\n}\n\n/**\n * Set an Issue aside and come back to it later.\n *\n * The difference from `failed` is the whole point of `--on-issue-failure skip`:\n * a failure that stopped this attempt is not a verdict on the eleven other\n * Issues in the queue, and `skipped` is the status that says \"not now\" rather\n * than \"not at all\". `attempts` is incremented here because the counter is what\n * keeps \"come back later\" from becoming \"come back forever\".\n */\nexport function markQueueIssueSkipped(\n plan: ExecutionPlan,\n id: string,\n failure: { phase: string | null; error: LastError | null },\n): ExecutionPlan {\n const current = plan.issues.find((entry) => entry.id === id);\n return updateIssue(plan, id, {\n status: 'skipped',\n failedPhase: failure.phase,\n lastError: failure.error,\n attempts: (current?.attempts ?? 0) + 1,\n });\n}\n\n/**\n * Stop working on an Issue until a person looks at it.\n *\n * `blocked` is never handed back out by `nextQueueIssue`: waiting cannot fix\n * a missing credential or a repository mid-rebase, and a queue that could\n * unblock itself would spin on the same cause forever.\n */\nexport function markQueueIssueBlocked(\n plan: ExecutionPlan,\n id: string,\n reason: string,\n failure: { phase: string | null; error: LastError | null },\n): ExecutionPlan {\n const current = plan.issues.find((entry) => entry.id === id);\n return updateIssue(plan, id, {\n status: 'blocked',\n blockedReason: reason,\n failedPhase: failure.phase,\n lastError: failure.error,\n attempts: (current?.attempts ?? 0) + 1,\n });\n}\n\n/** Record the branch every Issue of the queue shares. */\nexport function setQueueBranch(plan: ExecutionPlan, branchName: string): ExecutionPlan {\n return { ...plan, branchName };\n}\n\n/** Record the single Pull Request that consolidated the queue. */\nexport function setQueuePullRequest(\n plan: ExecutionPlan,\n pullRequest: PullRequestRef,\n): ExecutionPlan {\n return { ...plan, pullRequest };\n}\n","import { emptyRelations, uniqueIds } from './relations.js';\nimport type { Issue, IssueRelations } from './types.js';\n\n/**\n * Dependency graph of a set of Issues, built by walking the relations every\n * provider exposes through `fetchRelations`.\n *\n * The traversal is deliberately *structural*: it follows hierarchy\n * (parent/children) and dependencies (blockedBy/blocking) and stops there. Plain\n * mentions are recorded on each node but never expanded — a body citing \"see\n * also #12\" would otherwise drag an unrelated Issue into an execution plan the\n * user is about to confirm.\n */\n\nexport interface DependencyGraphNode {\n id: string;\n relations: IssueRelations;\n /** Distance from the closest root, in traversal hops. */\n depth: number;\n /** The Issue itself, when the caller supplied a way to read it. */\n issue: Issue | null;\n /** True when the node was requested by the user rather than discovered. */\n root: boolean;\n}\n\nexport interface DependencyGraph {\n /** Ids the traversal started from, in the order they were requested. */\n roots: string[];\n /** Every discovered node, keyed by id, in discovery order. */\n nodes: Map<string, DependencyGraphNode>;\n /**\n * Dependency cycles found among the discovered nodes, each listed once,\n * starting at its smallest member. Reported rather than thrown: discovery\n * must still answer, and it is the ordering layer that refuses to run.\n */\n cycles: string[][];\n /** True when a limit stopped the traversal before it was exhausted. */\n truncated: boolean;\n limits: { maxNodes: number; maxDepth: number };\n}\n\n/**\n * Conservative defaults: a real hierarchy is a handful of Issues, and each node\n * costs several API calls. A malformed (or simply huge) graph must not turn a\n * `run` into a rate-limit incident.\n */\nexport const DEFAULT_MAX_NODES = 25;\nexport const DEFAULT_MAX_DEPTH = 3;\n\nexport interface BuildDependencyGraphOptions {\n maxNodes?: number;\n maxDepth?: number;\n /** Issues the caller already resolved, so the traversal does not re-read them. */\n known?: Iterable<Issue>;\n /** Reader for the Issues the traversal discovers. Optional. */\n fetchIssue?: (id: string) => Promise<Issue | null>;\n /** Warning sink for a source that failed. Silent by default. */\n warn?: (message: string) => void;\n}\n\n/** Edge `from` → `to`, meaning \"`from` must be resolved before `to`\". */\nexport interface DependencyEdge {\n from: string;\n to: string;\n}\n\n/**\n * Ordering constraints of a graph: only hierarchy-free, explicit dependencies.\n *\n * `blockedBy`/`blocking` are the two spellings of the same fact, so both are\n * normalized into a single direction here. Parent/child is **not** a constraint\n * — it is a presentation and tie-breaking concern (see `computeExecutionOrder`),\n * because a parent is usually an umbrella that neither blocks nor is blocked by\n * its children.\n *\n * Edges pointing outside the graph are dropped: an Issue the traversal never\n * reached cannot be scheduled, so a constraint against it is unenforceable.\n */\nexport function dependencyEdges(graph: DependencyGraph): DependencyEdge[] {\n const edges: DependencyEdge[] = [];\n const seen = new Set<string>();\n\n const add = (from: string, to: string): void => {\n if (from === to || !graph.nodes.has(from) || !graph.nodes.has(to)) return;\n const key = `${from}->${to}`;\n if (seen.has(key)) return;\n seen.add(key);\n edges.push({ from, to });\n };\n\n for (const node of graph.nodes.values()) {\n for (const blocker of node.relations.blockedBy) {\n add(blocker, node.id);\n }\n for (const blocked of node.relations.blocking) {\n add(node.id, blocked);\n }\n }\n\n return edges;\n}\n\n/** Every cycle of the dependency edges, each reported once. */\nexport function findCycles(graph: DependencyGraph): string[][] {\n const adjacency = new Map<string, string[]>();\n for (const { from, to } of dependencyEdges(graph)) {\n const list = adjacency.get(from);\n if (list === undefined) {\n adjacency.set(from, [to]);\n } else {\n list.push(to);\n }\n }\n\n const cycles: string[][] = [];\n const reported = new Set<string>();\n const state = new Map<string, 'visiting' | 'done'>();\n const stack: string[] = [];\n\n const record = (cycle: string[]): void => {\n // Rotate to the smallest member so the same cycle found from two entry\n // points is reported once.\n let pivot = 0;\n for (let i = 1; i < cycle.length; i++) {\n if (compareIds(cycle[i] as string, cycle[pivot] as string) < 0) pivot = i;\n }\n const normalized = [...cycle.slice(pivot), ...cycle.slice(0, pivot)];\n const key = normalized.join('->');\n if (reported.has(key)) return;\n reported.add(key);\n cycles.push(normalized);\n };\n\n const visit = (id: string): void => {\n state.set(id, 'visiting');\n stack.push(id);\n\n for (const next of adjacency.get(id) ?? []) {\n const status = state.get(next);\n if (status === 'visiting') {\n record(stack.slice(stack.indexOf(next)));\n continue;\n }\n if (status === undefined) {\n visit(next);\n }\n }\n\n stack.pop();\n state.set(id, 'done');\n };\n\n for (const id of graph.nodes.keys()) {\n if (!state.has(id)) visit(id);\n }\n\n return cycles;\n}\n\n/** Numeric when both ids are numbers, lexicographic otherwise. */\nexport function compareIds(a: string, b: string): number {\n const na = Number(a);\n const nb = Number(b);\n if (Number.isInteger(na) && Number.isInteger(nb)) {\n return na - nb;\n }\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\n/** Ids a node links to structurally, in traversal order. */\nfunction neighbours(relations: IssueRelations): string[] {\n return uniqueIds(\n [\n ...(relations.parent === null ? [] : [relations.parent]),\n ...relations.children,\n ...relations.blockedBy,\n ...relations.blocking,\n ],\n relations.id,\n );\n}\n\n/**\n * Walk the relations of `roots` breadth-first and return the whole structure.\n *\n * Failures never abort the walk: a node whose relations cannot be read enters\n * the graph with empty relations (and a warning), exactly like an Issue that\n * genuinely relates to nothing. That keeps a partially unavailable API from\n * costing the user the Issues that *were* discovered.\n *\n * Breadth-first is what makes `depth` meaningful under a limit: when the walk is\n * truncated, what is missing is the far edge of the graph, not a random subset.\n */\nexport async function buildDependencyGraph(\n roots: readonly string[],\n fetchRelations: (id: string) => Promise<IssueRelations | null>,\n options: BuildDependencyGraphOptions = {},\n): Promise<DependencyGraph> {\n const maxNodes = options.maxNodes ?? DEFAULT_MAX_NODES;\n const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;\n const warn = options.warn ?? ((): void => {});\n\n const known = new Map<string, Issue>();\n for (const issue of options.known ?? []) {\n known.set(issue.id, issue);\n }\n\n const rootIds = uniqueIds(roots);\n const nodes = new Map<string, DependencyGraphNode>();\n const queued = new Set<string>(rootIds);\n let truncated = false;\n\n let frontier: { id: string; depth: number }[] = rootIds.map((id) => ({ id, depth: 0 }));\n\n while (frontier.length > 0) {\n const next: { id: string; depth: number }[] = [];\n\n for (const { id, depth } of frontier) {\n if (nodes.size >= maxNodes) {\n truncated = true;\n break;\n }\n\n let relations: IssueRelations | null = null;\n try {\n relations = await fetchRelations(id);\n } catch (err) {\n warn(\n `Could not read the relations of issue '${id}': ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n let issue = known.get(id) ?? null;\n if (issue === null && options.fetchIssue !== undefined) {\n try {\n issue = await options.fetchIssue(id);\n } catch (err) {\n warn(`Could not read issue '${id}': ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n const resolved = relations ?? emptyRelations(id);\n nodes.set(id, { id, relations: resolved, depth, issue, root: rootIds.includes(id) });\n\n if (depth >= maxDepth) {\n // Only a node that actually has unexplored neighbours truncates the\n // graph; a leaf at max depth explored everything there was.\n if (neighbours(resolved).some((neighbour) => !queued.has(neighbour))) {\n truncated = true;\n }\n continue;\n }\n\n for (const neighbour of neighbours(resolved)) {\n if (queued.has(neighbour)) continue;\n queued.add(neighbour);\n next.push({ id: neighbour, depth: depth + 1 });\n }\n }\n\n if (nodes.size >= maxNodes && next.length > 0) {\n truncated = true;\n break;\n }\n frontier = next;\n }\n\n const graph: DependencyGraph = {\n roots: rootIds,\n nodes,\n cycles: [],\n truncated,\n limits: { maxNodes, maxDepth },\n };\n\n linkReferencedBy(graph);\n graph.cycles = findCycles(graph);\n\n return graph;\n}\n\n/**\n * Complete `referencedBy` with the inverse of `references` among the discovered\n * nodes.\n *\n * The origin only reports the citations it can see (GitHub's timeline covers\n * cross-references but not, for instance, a mention added by the textual\n * heuristic); inverting what the graph already holds costs no API call and\n * keeps the two directions consistent inside it.\n */\nfunction linkReferencedBy(graph: DependencyGraph): void {\n for (const node of graph.nodes.values()) {\n for (const cited of node.relations.references) {\n const target = graph.nodes.get(cited);\n if (target === undefined || target.id === node.id) continue;\n target.relations.referencedBy = uniqueIds(\n [...target.relations.referencedBy, node.id],\n target.id,\n );\n }\n }\n}\n","import { compareIds, type DependencyGraph, dependencyEdges, findCycles } from '../issues/graph.js';\nimport type { IssuePriority } from './types.js';\n\n/**\n * Turning a dependency graph into the order the queue runs in.\n *\n * The criteria are applied in the exact precedence the initiative specifies:\n *\n * 1. **dependencies and blocks** — hard constraints; an Issue never starts\n * before something it depends on has finished. This is the topological part\n * and the only one that can make the computation fail (a cycle);\n * 2. **hierarchy** — with two Issues both ready to run, the ancestor goes\n * first, so an umbrella Issue frames the work of its sub-issues;\n * 3. **priority** — the GitHub labels `high`, `medium` and `low`;\n * 4. **logical order** — the identifier, numerically when it is a number.\n *\n * Everything below the first item is a *tie-break among Issues that are already\n * free to run*, never a constraint: a `high` Issue that depends on a `low` one\n * still runs second, which is the whole point of the feature.\n */\n\nconst PRIORITY_LABELS: Record<string, IssuePriority> = {\n high: 'high',\n medium: 'medium',\n low: 'low',\n};\n\n/** Ranking of a priority; an Issue with no label sorts after every labelled one. */\nconst PRIORITY_RANK: Record<IssuePriority, number> = { high: 0, medium: 1, low: 2 };\nconst NO_PRIORITY_RANK = 3;\n\n/**\n * Priority carried by a label set, or `null` when there is none.\n *\n * Both the bare labels used by this repository (`high`) and the qualified\n * spellings other projects use (`priority: high`, `priority/high`, `P-high`)\n * are accepted; anything else is simply not a priority. The absence of a label\n * is never an error — most Issues have none.\n */\nexport function priorityOf(labels: readonly string[]): IssuePriority | null {\n for (const label of labels) {\n const normalized = label.trim().toLowerCase();\n const direct = PRIORITY_LABELS[normalized];\n if (direct !== undefined) return direct;\n\n const qualified = /^(?:priority|prio|p)\\s*[:/-]\\s*(high|medium|low)$/.exec(normalized);\n const matched = qualified?.[1];\n if (matched !== undefined) return PRIORITY_LABELS[matched] as IssuePriority;\n }\n return null;\n}\n\nexport interface ExecutionOrderOptions {\n /**\n * Subset of the graph to schedule. Defaults to every node — the \"run the\n * whole hierarchy\" answer; the \"only the ones I asked for\" answer passes the\n * requested ids.\n */\n include?: readonly string[];\n}\n\nexport type ExecutionOrderResult =\n | { ok: true; order: string[] }\n | { ok: false; cycles: string[][] };\n\n/** Number of ancestors of `id` inside the selection: 0 for a top-level Issue. */\nfunction hierarchyDepth(\n id: string,\n parentOf: Map<string, string | null>,\n selected: Set<string>,\n): number {\n let depth = 0;\n let current = parentOf.get(id) ?? null;\n const seen = new Set<string>([id]);\n\n while (current !== null && selected.has(current) && !seen.has(current)) {\n depth++;\n seen.add(current);\n current = parentOf.get(current) ?? null;\n }\n\n return depth;\n}\n\n/**\n * Order the selected Issues of a graph.\n *\n * Kahn's algorithm with a deterministic tie-break, so the same graph always\n * produces the same order — a plan the user confirmed must not reshuffle itself\n * on the next run.\n *\n * A cycle is reported, never worked around: an arbitrary order would silently\n * implement an Issue before its dependency, which is exactly the failure this\n * whole feature exists to prevent. Constraints pointing outside the selection\n * are dropped — an Issue that is not going to run cannot block anything.\n */\nexport function computeExecutionOrder(\n graph: DependencyGraph,\n options: ExecutionOrderOptions = {},\n): ExecutionOrderResult {\n const selected = new Set(\n (options.include ?? [...graph.nodes.keys()]).filter((id) => graph.nodes.has(id)),\n );\n\n const parentOf = new Map<string, string | null>();\n const priority = new Map<string, IssuePriority | null>();\n for (const id of selected) {\n const node = graph.nodes.get(id);\n parentOf.set(id, node?.relations.parent ?? null);\n priority.set(id, priorityOf(node?.issue?.labels ?? []));\n }\n\n const incoming = new Map<string, Set<string>>();\n const outgoing = new Map<string, string[]>();\n for (const id of selected) {\n incoming.set(id, new Set());\n outgoing.set(id, []);\n }\n for (const { from, to } of dependencyEdges(graph)) {\n if (!selected.has(from) || !selected.has(to) || from === to) continue;\n incoming.get(to)?.add(from);\n outgoing.get(from)?.push(to);\n }\n\n const depth = new Map<string, number>();\n for (const id of selected) {\n depth.set(id, hierarchyDepth(id, parentOf, selected));\n }\n\n const rank = (id: string): number => {\n const value = priority.get(id) ?? null;\n return value === null ? NO_PRIORITY_RANK : PRIORITY_RANK[value];\n };\n\n const better = (a: string, b: string): number => {\n const byHierarchy = (depth.get(a) ?? 0) - (depth.get(b) ?? 0);\n if (byHierarchy !== 0) return byHierarchy;\n const byPriority = rank(a) - rank(b);\n if (byPriority !== 0) return byPriority;\n return compareIds(a, b);\n };\n\n const ready = [...selected].filter((id) => (incoming.get(id)?.size ?? 0) === 0);\n const order: string[] = [];\n\n while (ready.length > 0) {\n ready.sort(better);\n const next = ready.shift() as string;\n order.push(next);\n\n for (const dependent of outgoing.get(next) ?? []) {\n const pending = incoming.get(dependent);\n if (pending === undefined) continue;\n pending.delete(next);\n if (pending.size === 0) {\n ready.push(dependent);\n }\n }\n }\n\n if (order.length !== selected.size) {\n // Prefer the cycles the graph already recorded; recompute only if the\n // selection is what closed the loop.\n const cycles = graph.cycles.length > 0 ? graph.cycles : findCycles(graph);\n const relevant = cycles.filter((cycle) => cycle.every((id) => selected.has(id)));\n return { ok: false, cycles: relevant.length > 0 ? relevant : cycles };\n }\n\n return { ok: true, order };\n}\n\n/** One-line description of the cycles, for an error message. */\nexport function describeCycles(cycles: readonly string[][]): string {\n return cycles.map((cycle) => [...cycle, cycle[0]].map((id) => `#${id}`).join(' → ')).join('; ');\n}\n"],"mappings":"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","names":["mkdir","dirname","mkdir","dirname"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/issues/registry.ts","../src/issues/hash.ts","../src/issues/providers/github.ts","../src/issues/providers/local.ts","../src/issues/bootstrap.ts","../src/issues/resolver.ts","../src/issues/context.ts"],"sourcesContent":["import type { IssueProvider } from './provider.js';\nimport type { IssueSource } from './types.js';\n\n/**\n * Registry of Issue providers.\n *\n * Adding an origin is creating a provider file and registering one line here\n * (or from the caller's side), so nothing in src/commands/ needs to know the\n * list of sources.\n */\nconst providers = new Map<IssueSource, IssueProvider>();\n\n/**\n * Register a provider under its own `name`.\n *\n * Registering the same source twice replaces the previous entry, which is what\n * lets tests substitute a real provider with a fake one.\n */\nexport function registerProvider(provider: IssueProvider): void {\n providers.set(provider.name, provider);\n}\n\n/** Sources currently registered, in registration order. */\nexport function getRegisteredSources(): IssueSource[] {\n return [...providers.keys()];\n}\n\n/**\n * Look up a provider by origin.\n *\n * @throws when the origin is not registered, listing what is available so the\n * caller knows which sources they can pick instead.\n */\nexport function getProvider(source: IssueSource): IssueProvider {\n const provider = providers.get(source);\n if (provider === undefined) {\n const available = getRegisteredSources();\n const hint =\n available.length > 0\n ? `Available sources: ${available.join(', ')}.`\n : 'No Issue providers are registered.';\n throw new Error(`Unknown Issue source: '${source}'. ${hint}`);\n }\n return provider;\n}\n\n/** Drop every registration. Intended for test isolation. */\nexport function clearProviders(): void {\n providers.clear();\n}\n","import { createHash } from 'node:crypto';\n\n/**\n * Content hashing shared by every Issue provider.\n *\n * The hash is what lets the resolver tell \"the local and the remote Issue are\n * the same demand\" from \"they diverged\", so it has to be stable across\n * platforms: Windows checkouts store CRLF, editors add or drop trailing\n * newlines, and neither should count as a content change.\n */\n\n/** Normalizes line endings (CRLF/CR to LF) and trims the edges. */\nfunction normalizeText(value: string): string {\n return value.replace(/\\r\\n?/g, '\\n').trim();\n}\n\n/**\n * Returns `sha256:<hex>` for the given title and body.\n *\n * The payload is a canonical JSON object rather than a concatenation, so text\n * moved from the title into the body always changes the hash.\n */\nexport function hashIssueContent(title: string, body: string): string {\n const payload = JSON.stringify({\n title: normalizeText(title),\n body: normalizeText(body),\n });\n const digest = createHash('sha256').update(payload, 'utf8').digest('hex');\n return `sha256:${digest}`;\n}\n","import { getActiveResilienceConfig } from '../../config.js';\nimport type { ClassifiedFailure } from '../../resilience/errors.js';\nimport { ClassifiedError, requiresHumanAction } from '../../resilience/errors.js';\nimport { type PolicyConfig, resolvePolicy } from '../../resilience/policy.js';\nimport type { RetryPolicyFor } from '../../resilience/retry.js';\nimport { type ExecResult, run } from '../../utils/shell.js';\nimport { hashIssueContent } from '../hash.js';\nimport type { IssueProvider, ProviderAvailability } from '../provider.js';\nimport { emptyRelations, mergeRelations, parseTextualRelations, uniqueIds } from '../relations.js';\nimport type { Issue, IssueDraft, IssueRelations, IssueState } from '../types.js';\n\n/** Fields requested from `gh issue view`, in the order the PRD specifies. */\nconst VIEW_FIELDS = 'number,title,body,labels,state,url,createdAt,updatedAt';\n\n/** Timeout for the availability probes, matching the `init` prerequisite checks. */\nconst PROBE_TIMEOUT_MS = 10_000;\n\n/**\n * Every `gh` invocation carries the resilience policy of the failure it hits:\n * the `network` budget for a DNS blip, the `rate_limit` budget (and the\n * server's `Retry-After`) for a rate limit, and — because `resolvePolicy()`\n * clamps them — **no** attempt at all for an authentication or configuration\n * failure. This is the one place `gh` failures stop being fatal on sight.\n */\nfunction ghPolicy(): RetryPolicyFor {\n return (failure: ClassifiedFailure) =>\n resolvePolicy(failure.kind, getActiveResilienceConfig() as PolicyConfig);\n}\n\n/**\n * The budget of the availability probes, capped well below the full one.\n *\n * A probe answers a question about liveness; the answer must not take minutes.\n * The real read (`get`, `create`, `close`) keeps the full policy, so a blip\n * during the work is still absorbed at its documented budget — what is capped\n * here is only how long an *unreachable* GitHub delays a `local` Issue that\n * would have resolved instantly.\n */\nconst PROBE_MAX_ATTEMPTS = 3;\nconst PROBE_MAX_DELAY_MS = 5_000;\n\nfunction ghProbePolicy(): RetryPolicyFor {\n const policyFor = ghPolicy();\n return (failure) => {\n const policy = policyFor(failure);\n return {\n ...policy,\n maxAttempts: Math.min(policy.maxAttempts, PROBE_MAX_ATTEMPTS),\n maxDelayMs: Math.min(policy.maxDelayMs, PROBE_MAX_DELAY_MS),\n retryForever: false,\n };\n };\n}\n\n/** `gh <args>` under the full resilience policy. */\nfunction gh(args: string[], options: { timeout?: number } = {}): Promise<ExecResult> {\n return run('gh', args, {\n retry: ghPolicy(),\n ...(options.timeout === undefined ? {} : { timeout: options.timeout }),\n });\n}\n\n/**\n * gh reports a missing Issue on stderr with one of these phrasings. The match\n * must stay issue-specific: a bare /not found/ would also swallow\n * `gh: command not found`, turning a broken environment into \"issue absent\".\n * Anything else on a non-zero exit is a real failure (network, auth, gh not\n * installed) and must surface as a thrown error.\n */\nconst NOT_FOUND_PATTERN =\n /could not resolve to an issue|no issues? found|issue not found|could not find any issue/i;\n\ninterface GhLabel {\n name?: unknown;\n}\n\ninterface GhIssuePayload {\n number?: unknown;\n title?: unknown;\n body?: unknown;\n labels?: unknown;\n state?: unknown;\n url?: unknown;\n createdAt?: unknown;\n updatedAt?: unknown;\n}\n\nfunction normalizeState(state: unknown): IssueState {\n return String(state ?? '').toUpperCase() === 'CLOSED' ? 'closed' : 'open';\n}\n\nfunction normalizeLabels(labels: unknown): string[] {\n if (!Array.isArray(labels)) return [];\n return labels\n .map((label: GhLabel | string) => (typeof label === 'string' ? label : label?.name))\n .filter((name): name is string => typeof name === 'string' && name.length > 0);\n}\n\nfunction toIssue(payload: GhIssuePayload): Issue {\n const title = typeof payload.title === 'string' ? payload.title : '';\n const body = typeof payload.body === 'string' ? payload.body : '';\n const number = typeof payload.number === 'number' ? payload.number : null;\n const url = typeof payload.url === 'string' && payload.url.length > 0 ? payload.url : null;\n\n return {\n id: number === null ? '' : String(number),\n number,\n title,\n body,\n labels: normalizeLabels(payload.labels),\n state: normalizeState(payload.state),\n source: 'github',\n remoteRef: url,\n createdAt: typeof payload.createdAt === 'string' ? payload.createdAt : '',\n updatedAt: typeof payload.updatedAt === 'string' ? payload.updatedAt : '',\n contentHash: hashIssueContent(title, body),\n raw: payload,\n };\n}\n\n/** Strip a leading `#` so both `23` and `#23` are accepted. */\nfunction normalizeId(id: string): string {\n return id.trim().replace(/^#/, '');\n}\n\n/** Page size for the relation endpoints — one page is enough in practice. */\nconst API_PAGE_SIZE = 100;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * `gh api <path>`, parsed as JSON.\n *\n * Answers `null` for every failure — a disabled feature (404), a plan without\n * Issue Dependencies (403), a network hiccup or unparseable output. That is the\n * whole graceful-degradation contract of `fetchRelations`: a source that cannot\n * answer contributes nothing instead of failing the discovery.\n */\nasync function ghApiJson(path: string): Promise<unknown> {\n try {\n const result = await gh(['api', path]);\n if (result.exitCode !== 0) return null;\n return JSON.parse(result.stdout) as unknown;\n } catch {\n return null;\n }\n}\n\n/** `number` of an Issue-shaped payload, as a string id. */\nfunction issueNumberOf(value: unknown): string | null {\n if (!isRecord(value)) return null;\n const number = value.number;\n return typeof number === 'number' && Number.isInteger(number) ? String(number) : null;\n}\n\n/** Ids of an array of Issue-shaped payloads, skipping anything unusable. */\nfunction issueNumbersOf(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return uniqueIds(\n value.map((entry) => issueNumberOf(entry) ?? '').filter((entry) => entry !== ''),\n );\n}\n\n/**\n * Issues that cited this one, from the timeline's `cross-referenced` events.\n *\n * Pull Requests are filtered out: they reference the Issue as *work on it*, not\n * as another demand to schedule.\n */\nfunction crossReferences(timeline: unknown, self: string): string[] {\n if (!Array.isArray(timeline)) return [];\n\n const ids: string[] = [];\n for (const event of timeline) {\n if (!isRecord(event) || event.event !== 'cross-referenced') continue;\n const source = isRecord(event.source) ? event.source : null;\n const issue = isRecord(source?.issue) ? source.issue : null;\n if (issue === null || issue.pull_request !== undefined) continue;\n const number = issueNumberOf(issue);\n if (number !== null) ids.push(number);\n }\n\n return uniqueIds(ids, self);\n}\n\nfunction extractIssueNumber(output: string): string | null {\n return output.match(/\\/issues\\/(\\d+)/)?.[1] ?? null;\n}\n\n/**\n * A failed `gh` result turned into an error that still carries its verdict.\n *\n * `run()` already classified it — every `gh` call here goes through the retry\n * chokepoint — so the kind, and with it the \"retry or escalate\" decision, must\n * not be flattened back into a message by the throw.\n */\nfunction ghError(message: string, result: ExecResult): Error {\n const failure = result.failure;\n if (failure === undefined) return new Error(message);\n return new ClassifiedError(message, failure, ghAction(failure));\n}\n\n/** The action a human has to take, for the kinds that need one. */\nfunction ghAction(failure: ClassifiedFailure): string | undefined {\n if (!requiresHumanAction(failure.kind)) return undefined;\n if (failure.kind === 'authentication') {\n return 'Run `gh auth login` to authenticate the GitHub CLI';\n }\n return undefined;\n}\n\n/**\n * A failed probe as an availability answer, keeping the classification.\n *\n * `action` is only attached when the failure actually needs a human: telling\n * someone to run `gh auth login` because their Wi-Fi dropped is worse than\n * saying nothing, and the caller uses the presence of an action to decide that\n * waiting will not help.\n */\nfunction unavailable(result: ExecResult, action: string): ProviderAvailability {\n const failure = result.failure;\n const needsHuman = failure === undefined || requiresHumanAction(failure.kind);\n return {\n available: false,\n ...(failure === undefined ? {} : { failure }),\n ...(needsHuman ? { action } : {}),\n };\n}\n\n/**\n * Issue provider backed by the GitHub CLI.\n *\n * Every call goes through `run` (execa, no shell) so tests can mock the shell\n * layer instead of the network, and so the Issue content is identical across\n * every pipeline phase.\n */\nexport class GitHubIssueProvider implements IssueProvider {\n readonly name = 'github' as const;\n\n /** True when gh is installed and authenticated. Never throws. */\n async isAvailable(): Promise<boolean> {\n return (await this.checkAvailability()).available;\n }\n\n /**\n * Whether gh is installed and authenticated, with the reason when it is not.\n *\n * Both probes retry on the capped probe budget, so a DNS blip during a long\n * run no longer reports GitHub as unavailable and takes the whole run down\n * with it — while an authentication failure is clamped to zero attempts by\n * `resolvePolicy()` and comes back immediately, with the action to take.\n */\n async checkAvailability(): Promise<ProviderAvailability> {\n try {\n const retry = ghProbePolicy();\n\n const version = await run('gh', ['--version'], { retry, timeout: PROBE_TIMEOUT_MS });\n if (version.exitCode !== 0) {\n return unavailable(version, 'Install the GitHub CLI: https://cli.github.com');\n }\n\n const auth = await run('gh', ['auth', 'status'], { retry, timeout: PROBE_TIMEOUT_MS });\n if (auth.exitCode !== 0) {\n return unavailable(auth, 'Run `gh auth login` to authenticate the GitHub CLI');\n }\n\n return { available: true };\n } catch {\n return { available: false };\n }\n }\n\n async get(id: string): Promise<Issue | null> {\n const issueId = normalizeId(id);\n const result = await gh(['issue', 'view', issueId, '--json', VIEW_FIELDS]);\n\n if (result.exitCode !== 0) {\n const detail = (result.stderr || result.stdout).trim();\n if (NOT_FOUND_PATTERN.test(detail)) return null;\n throw ghError(\n `Failed to fetch GitHub issue #${issueId}: ${detail || 'gh issue view failed'}`,\n result,\n );\n }\n\n let payload: GhIssuePayload;\n try {\n payload = JSON.parse(result.stdout);\n } catch {\n throw new Error(\n `Failed to parse gh output for issue #${issueId}: ${result.stdout.slice(0, 200)}`,\n );\n }\n\n return toIssue(payload);\n }\n\n async create(draft: IssueDraft): Promise<Issue> {\n const args = ['issue', 'create', '--title', draft.title, '--body', draft.body];\n for (const label of draft.labels) {\n args.push('--label', label);\n }\n // Only when the draft actually chose one. An organization without Issue\n // Types rejects the flag outright, so it must never be sent speculatively.\n if (draft.type !== undefined && draft.type !== '') {\n args.push('--type', draft.type);\n }\n\n const result = await gh(args);\n if (result.exitCode !== 0) {\n const detail = (result.stderr || result.stdout).trim();\n throw ghError(`Failed to create GitHub issue: ${detail || 'gh issue create failed'}`, result);\n }\n\n const number = extractIssueNumber(result.stdout);\n if (!number) {\n throw new Error(\n `GitHub issue creation did not report an issue URL: ${result.stdout.trim() || '(no output)'}`,\n );\n }\n\n const created = await this.get(number);\n if (!created) {\n throw new Error(`GitHub issue #${number} was created but could not be read back`);\n }\n return created;\n }\n\n /**\n * Hierarchy and dependencies of an Issue, reconciled from three GitHub\n * mechanisms that only partially overlap:\n *\n * 1. **Sub-issues** (`/sub_issues` plus the `parent` field of the Issue\n * payload) — the native hierarchy;\n * 2. **Issue Dependencies** (`/dependencies/blocked_by` and `/blocking`);\n * 3. the **textual heuristic** over the body, for repositories that adopted\n * neither and write `Depends on #12` or `- [ ] #13` instead.\n *\n * Cross-references (`referencedBy`) come from the issue timeline, which is\n * the only place GitHub reports \"who cited me\" without a search query.\n *\n * Every source is queried independently and is allowed to fail on its own: an\n * organization without Issue Dependencies enabled answers 404 for two of the\n * five calls, and that has to cost the caller those two fields, never the\n * whole discovery. Only what could be read is reported — nothing here throws.\n */\n async fetchRelations(id: string): Promise<IssueRelations> {\n const issueId = normalizeId(id);\n if (!/^\\d+$/.test(issueId)) {\n // A non-numeric identifier cannot address a GitHub REST endpoint; the\n // Issue simply has no relations we can discover.\n return emptyRelations(issueId);\n }\n\n const base = `repos/{owner}/{repo}/issues/${issueId}`;\n const [payload, subIssues, blockedBy, blocking, timeline] = await Promise.all([\n ghApiJson(base),\n ghApiJson(`${base}/sub_issues?per_page=${API_PAGE_SIZE}`),\n ghApiJson(`${base}/dependencies/blocked_by?per_page=${API_PAGE_SIZE}`),\n ghApiJson(`${base}/dependencies/blocking?per_page=${API_PAGE_SIZE}`),\n ghApiJson(`${base}/timeline?per_page=${API_PAGE_SIZE}`),\n ]);\n\n const issue = isRecord(payload) ? payload : null;\n // Without the REST payload the body still comes from `gh issue view`, so a\n // repository where only the REST API is blocked keeps the textual fallback.\n const body = typeof issue?.body === 'string' ? issue.body : await this.bodyFallback(issueId);\n\n const structured = {\n id: issueId,\n parent: issueNumberOf(issue?.parent),\n children: issueNumbersOf(subIssues),\n blockedBy: issueNumbersOf(blockedBy),\n blocking: issueNumbersOf(blocking),\n references: [],\n referencedBy: crossReferences(timeline, issueId),\n };\n\n return mergeRelations(structured, parseTextualRelations(body, issueId));\n }\n\n /** Body of an Issue when the REST payload could not be read. Never throws. */\n private async bodyFallback(issueId: string): Promise<string> {\n try {\n return (await this.get(issueId))?.body ?? '';\n } catch {\n return '';\n }\n }\n\n async close(id: string): Promise<void> {\n const issueId = normalizeId(id);\n const result = await gh(['issue', 'close', issueId]);\n\n if (result.exitCode !== 0) {\n const detail = (result.stderr || result.stdout).trim();\n throw ghError(\n `Failed to close GitHub issue #${issueId}: ${detail || 'gh issue close failed'}`,\n result,\n );\n }\n }\n}\n\n/** Shared instance; providers are stateless, so a singleton is enough. */\nexport const githubIssueProvider = new GitHubIssueProvider();\n","import { constants } from 'node:fs';\nimport { access, mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { ZodError } from 'zod';\nimport { isoNow } from '../../core/state-manager.js';\nimport { issueMetadataSchema } from '../../schemas.js';\nimport type { IssuePaths } from '../../storage/paths.js';\nimport { resolveIssuePaths, resolveProjectPaths } from '../../storage/resolve.js';\nimport { writeFileAtomic } from '../../utils/fs.js';\nimport { getProjectRoot } from '../../utils/git.js';\nimport { run } from '../../utils/shell.js';\nimport { hashIssueContent } from '../hash.js';\nimport type { IssueProvider } from '../provider.js';\nimport type { Issue, IssueDraft, IssueMetadata } from '../types.js';\n\n/** Timeout for the optional remote probes done while allocating an identifier. */\nconst REMOTE_PROBE_TIMEOUT_MS = 10_000;\n\nfunction isNotFound(err: unknown): boolean {\n const code = (err as NodeJS.ErrnoException).code;\n return code === 'ENOENT' || code === 'ENOTDIR';\n}\n\n/**\n * True when `dir` already is a writable directory, or does not exist yet but\n * would be created successfully by `mkdir(dir, { recursive: true })` — i.e.\n * its nearest existing ancestor is a writable directory.\n *\n * Never mutates the filesystem: `isAvailable()` only needs to answer \"could\n * this work\", which must not leave a `~/.issue-flow/projects/<id>/` directory\n * behind for a project that ends up never using local issues (every other\n * source is queried on every resolution, so this runs far more often than the\n * provider is actually chosen).\n */\nasync function isWritableDirectory(dir: string): Promise<boolean> {\n let current = dir;\n for (;;) {\n let stats: Awaited<ReturnType<typeof stat>>;\n try {\n stats = await stat(current);\n } catch (err: unknown) {\n if (!isNotFound(err)) return false;\n const parent = dirname(current);\n if (parent === current) return false; // reached the filesystem root\n current = parent;\n continue;\n }\n if (!stats.isDirectory()) return false;\n try {\n await access(current, constants.W_OK);\n return true;\n } catch {\n return false;\n }\n }\n}\n\n/**\n * Identifiers become path segments, so anything that could escape the issues\n * directory is rejected before it reaches `join`.\n */\nfunction normalizeId(id: string): string {\n const normalized = id.trim().replace(/^#/, '');\n if (normalized.length === 0) {\n throw new Error('Local issue identifier cannot be empty');\n }\n if (/[/\\\\]/.test(normalized) || normalized === '.' || normalized === '..') {\n throw new Error(`Invalid local issue identifier: '${id}'`);\n }\n return normalized;\n}\n\nfunction toNumber(id: string): number | null {\n return /^\\d+$/.test(id) ? Number.parseInt(id, 10) : null;\n}\n\n/**\n * Title lives in the leading H1, body is everything after it.\n *\n * Only the first non-empty line is considered: an H1 further down belongs to\n * the body (issue descriptions routinely use headings), so promoting it would\n * silently retitle the Issue.\n */\nexport function parseIssueMarkdown(content: string): { title: string; body: string } {\n const lines = content.replace(/\\r\\n?/g, '\\n').split('\\n');\n const headingIndex = lines.findIndex((line) => line.trim().length > 0);\n const heading = headingIndex === -1 ? undefined : lines[headingIndex].match(/^#[ \\t]+(.*)$/);\n\n if (!heading) {\n return { title: '', body: lines.join('\\n').trim() };\n }\n\n return {\n title: heading[1].trim(),\n body: lines\n .slice(headingIndex + 1)\n .join('\\n')\n .trim(),\n };\n}\n\nfunction renderIssueMarkdown(title: string, body: string): string {\n return `# ${title}\\n\\n${body.trim()}\\n`;\n}\n\n/**\n * Issue provider backed by plain files in the project's global storage\n * directory (`~/.issue-flow/projects/<project-id>/issues/<id>/`).\n *\n * Enables the whole pipeline without any network access: no gh, no remote, no\n * authentication. `issue.md` is the source of truth for the content and\n * `metadata.json` for everything else; the content hash is always recomputed\n * from the file so a hand-edited issue.md is never reported as unchanged.\n *\n * Every path goes through `resolveIssuePaths()`, exactly like the pipeline\n * commands, so an issue that still only exists under the legacy\n * `<projectRoot>/issues/` tree is migrated on first read instead of coming back\n * as \"not found\".\n */\nexport class LocalFileIssueProvider implements IssueProvider {\n readonly name = 'local' as const;\n\n private readonly configuredRoot?: string;\n private cachedRoot?: string;\n\n /**\n * @param projectRoot Repository the issues belong to. It is no longer the\n * literal parent of an `issues/` directory: it is the root the project id —\n * and therefore the global storage directory — is derived from.\n */\n constructor(projectRoot?: string) {\n this.configuredRoot = projectRoot;\n }\n\n /**\n * True whenever the project's global storage directory already is — or\n * could be — a writable directory. Never mutates the filesystem, never\n * throws.\n *\n * A brand new project has no directory of its own until something writes, so\n * \"does it already exist\" would report the provider as unavailable exactly\n * when it is about to create the very first issue — `isWritableDirectory`\n * walks up to the nearest existing ancestor instead of requiring the leaf to\n * be there already.\n */\n async isAvailable(): Promise<boolean> {\n try {\n const { projectDir } = await resolveProjectPaths({ projectRoot: await this.root() });\n return await isWritableDirectory(projectDir);\n } catch {\n return false;\n }\n }\n\n async get(id: string): Promise<Issue | null> {\n const issueId = normalizeId(id);\n const markdownPath = await this.issueFile(issueId);\n\n let raw: string;\n try {\n raw = await readFile(markdownPath, 'utf-8');\n } catch (err: unknown) {\n if (isNotFound(err)) return null;\n throw err;\n }\n\n const { title: headingTitle, body } = parseIssueMarkdown(raw);\n const metadata = await this.readMetadata(issueId);\n const title = headingTitle || metadata?.title || '';\n\n if (metadata) {\n return {\n id: metadata.id,\n number: metadata.number,\n title,\n body,\n labels: metadata.labels,\n state: metadata.state,\n source: 'local',\n remoteRef: metadata.remote?.ref ?? null,\n createdAt: metadata.createdAt,\n updatedAt: metadata.updatedAt,\n contentHash: hashIssueContent(title, body),\n raw: metadata,\n };\n }\n\n // metadata.json is optional: an issue.md dropped in by hand still works,\n // with timestamps taken from the file itself.\n const stats = await stat(markdownPath);\n return {\n id: issueId,\n number: toNumber(issueId),\n title,\n body,\n labels: [],\n state: 'open',\n source: 'local',\n remoteRef: null,\n createdAt: stats.birthtime.toISOString(),\n updatedAt: stats.mtime.toISOString(),\n contentHash: hashIssueContent(title, body),\n };\n }\n\n async create(draft: IssueDraft): Promise<Issue> {\n // An explicit id is how a mirror keeps the identifier of the Issue it\n // mirrors: allocating a fresh one would make `issue-flow run <n>` see two\n // unrelated Issues instead of one demand in two places.\n const id = draft.id === undefined ? String(await this.allocateNumber()) : normalizeId(draft.id);\n const paths = await this.paths(id);\n\n await mkdir(paths.issueDir, { recursive: true });\n\n // Exclusive create (the 'wx' flag, O_EXCL under the hood) instead of a\n // check-then-write: two concurrent `create()` calls racing the same id\n // would both pass a plain existence check before either writes, and the\n // second would then silently overwrite the first through the atomic\n // temp+rename writer. The OS now rejects the loser instead.\n try {\n await writeFile(paths.issueFile, renderIssueMarkdown(draft.title, draft.body), {\n encoding: 'utf-8',\n flag: 'wx',\n });\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === 'EEXIST') {\n throw new Error(\n `Local issue '${id}' already exists at ${paths.issueFile}. ` +\n 'Remove it or pick another identifier before creating a new Issue.',\n );\n }\n throw err;\n }\n\n const timestamp = isoNow();\n const metadata: IssueMetadata = {\n schemaVersion: 1,\n id,\n number: toNumber(id),\n source: 'local',\n title: draft.title,\n labels: draft.labels,\n state: 'open',\n createdAt: timestamp,\n updatedAt: timestamp,\n contentHash: hashIssueContent(draft.title, draft.body),\n ...(draft.remote ? { remote: draft.remote } : {}),\n };\n\n await this.writeMetadata(id, metadata);\n\n return {\n id,\n number: metadata.number,\n title: draft.title,\n body: draft.body.trim(),\n labels: draft.labels,\n state: 'open',\n source: 'local',\n remoteRef: draft.remote?.ref ?? null,\n createdAt: timestamp,\n updatedAt: timestamp,\n contentHash: metadata.contentHash,\n raw: metadata,\n };\n }\n\n async close(id: string): Promise<void> {\n const issue = await this.get(id);\n if (!issue) {\n const issueDir = (await this.paths(normalizeId(id))).issueDir;\n throw new Error(`Local issue '${normalizeId(id)}' not found at ${issueDir}`);\n }\n\n const issueId = normalizeId(id);\n const existing = await this.readMetadata(issueId);\n\n await this.writeMetadata(issueId, {\n schemaVersion: 1,\n id: issue.id,\n number: issue.number,\n source: existing?.source ?? 'local',\n title: issue.title,\n labels: issue.labels,\n state: 'closed',\n createdAt: issue.createdAt,\n updatedAt: isoNow(),\n contentHash: issue.contentHash,\n ...(existing?.remote ? { remote: existing.remote } : {}),\n });\n }\n\n /**\n * Next free identifier: above every local number and, when GitHub answers,\n * above every remote one too. Sharing a numbering space with the remote is\n * what makes a naive `localMax + 1` collide the moment someone opens an Issue\n * on GitHub.\n */\n async allocateNumber(): Promise<number> {\n const [local, remote] = await Promise.all([this.highestLocalNumber(), highestRemoteNumber()]);\n return Math.max(local, remote) + 1;\n }\n\n private async root(): Promise<string> {\n if (this.configuredRoot !== undefined) return this.configuredRoot;\n this.cachedRoot ??= await getProjectRoot();\n return this.cachedRoot;\n }\n\n /**\n * Every artifact of one issue, from the same resolver the pipeline commands\n * use — which is also what triggers the migration of a legacy-only issue.\n */\n private async paths(id: string): Promise<IssuePaths> {\n return resolveIssuePaths(id, { projectRoot: await this.root() });\n }\n\n private async issueFile(id: string): Promise<string> {\n return (await this.paths(id)).issueFile;\n }\n\n private async metadataFile(id: string): Promise<string> {\n return (await this.paths(id)).metadataFile;\n }\n\n /** Parsed metadata.json, or `null` when the file is absent. */\n private async readMetadata(id: string): Promise<IssueMetadata | null> {\n const path = await this.metadataFile(id);\n\n let raw: string;\n try {\n raw = await readFile(path, 'utf-8');\n } catch (err: unknown) {\n if (isNotFound(err)) return null;\n throw err;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err: unknown) {\n throw new Error(`Invalid JSON in ${path}: ${(err as Error).message}`);\n }\n\n try {\n return issueMetadataSchema.parse(parsed);\n } catch (err: unknown) {\n if (err instanceof ZodError) {\n const details = err.issues\n .map((issue) => ` - ${issue.path.join('.') || '(root)'}: ${issue.message}`)\n .join('\\n');\n throw new Error(`Invalid issue metadata at ${path}:\\n${details}`);\n }\n throw err;\n }\n }\n\n private async writeMetadata(id: string, metadata: IssueMetadata): Promise<void> {\n await writeFileAtomic(await this.metadataFile(id), `${JSON.stringify(metadata, null, 2)}\\n`);\n }\n\n /**\n * Highest number across every `<issuesDir>/<id>/metadata.json` and numeric\n * directory name.\n *\n * Resolving `issuesDir` also migrates the legacy tree when it is the only one\n * that exists, which is what keeps an already-taken number from being handed\n * out a second time after adopting the global storage.\n */\n private async highestLocalNumber(): Promise<number> {\n const { issuesDir: dir } = await resolveProjectPaths({ projectRoot: await this.root() });\n\n let entries: string[];\n try {\n entries = (await readdir(dir, { withFileTypes: true }))\n .filter((entry) => entry.isDirectory())\n .map((entry) => entry.name);\n } catch (err: unknown) {\n if (isNotFound(err)) return 0;\n throw err;\n }\n\n const numbers = await Promise.all(\n entries.map(async (name) => {\n const fromName = toNumber(name) ?? 0;\n // A directory whose metadata is unreadable still occupies its number.\n const fromMetadata = await this.readMetadata(name)\n .then((metadata) => metadata?.number ?? 0)\n .catch(() => 0);\n return Math.max(fromName, fromMetadata);\n }),\n );\n\n return numbers.reduce((max, value) => Math.max(max, value), 0);\n }\n}\n\n/** How many of the most-recently-created issues/PRs to inspect per probe. */\nconst REMOTE_PROBE_SAMPLE_SIZE = 20;\n\n/**\n * Highest number already taken on GitHub, or 0 when the remote is unreachable.\n *\n * Issues and pull requests share one counter, so both are probed: allocating\n * above the newest Issue alone would still collide with an open PR. Every\n * failure degrades to 0 — being offline must not block local creation.\n *\n * `gh ... list` defaults to sorting by creation date descending, which lines\n * up with number order in the overwhelming majority of repos — but that sort\n * is an implementation detail, not a contract. Rather than trust that the\n * single most-recent entry is also the highest-numbered one, a small sample\n * is fetched and the max is taken across it, which also tolerates the rare\n * case of a transferred issue whose creation date and number disagree.\n */\nasync function highestRemoteNumber(): Promise<number> {\n const probes = [\n ['issue', 'list'],\n ['pr', 'list'],\n ];\n\n const results = await Promise.all(\n probes.map(async (args) => {\n try {\n const result = await run(\n 'gh',\n [\n ...args,\n '--state',\n 'all',\n '--limit',\n String(REMOTE_PROBE_SAMPLE_SIZE),\n '--json',\n 'number',\n ],\n { timeout: REMOTE_PROBE_TIMEOUT_MS },\n );\n if (result.exitCode !== 0) return 0;\n\n const parsed = JSON.parse(result.stdout);\n if (!Array.isArray(parsed)) return 0;\n return parsed.reduce((max: number, entry: unknown) => {\n const n = (entry as { number?: unknown } | null)?.number;\n return typeof n === 'number' ? Math.max(max, n) : max;\n }, 0);\n } catch {\n return 0;\n }\n }),\n );\n\n return results.reduce((max, value) => Math.max(max, value), 0);\n}\n\n/** Shared instance resolving the project root lazily via git. */\nexport const localFileIssueProvider = new LocalFileIssueProvider();\n","import { githubIssueProvider } from './providers/github.js';\nimport { localFileIssueProvider } from './providers/local.js';\nimport { getRegisteredSources, registerProvider } from './registry.js';\n\n/**\n * Register the providers that ship with issue-flow.\n *\n * Idempotent and non-destructive: an origin that is already registered is left\n * untouched, so a test (or a future plugin) can install its own provider before\n * the first `resolveIssue` call without having it replaced by the built-in one.\n */\nexport function ensureProvidersRegistered(): void {\n const registered = new Set(getRegisteredSources());\n\n if (!registered.has('github')) {\n registerProvider(githubIssueProvider);\n }\n if (!registered.has('local')) {\n registerProvider(localFileIssueProvider);\n }\n}\n","import { createInterface, type Interface as ReadlineInterface } from 'node:readline';\nimport { loadIssuesConfig } from '../config.js';\nimport { actionOf, type ClassifiedFailure, failureOf } from '../resilience/errors.js';\nimport { printInfo, printWarning } from '../ui/logger.js';\nimport { ensureProvidersRegistered } from './bootstrap.js';\nimport type { ProviderAvailability } from './provider.js';\nimport { getProvider, getRegisteredSources } from './registry.js';\nimport type { Issue, IssueSource, IssuesConfig, ResolvedIssue } from './types.js';\n\n/**\n * Failure to settle on an Issue. Carries the exit code the CLI should return,\n * so callers propagate it instead of inventing their own.\n */\nexport class IssueResolutionError extends Error {\n readonly exitCode: number;\n /**\n * The verdict of the origin that failed, when one origin failed for a reason\n * worth naming. It is what tells \"this Issue does not exist\" apart from\n * \"GitHub could not be reached\", which are the same message today.\n */\n readonly failure: ClassifiedFailure | undefined;\n /** What a human has to do, for a failure that waiting cannot fix. */\n readonly action: string | undefined;\n\n constructor(\n message: string,\n exitCode = 1,\n details: { failure?: ClassifiedFailure; action?: string } = {},\n ) {\n super(message);\n this.name = 'IssueResolutionError';\n this.exitCode = exitCode;\n this.failure = details.failure;\n this.action = details.action;\n }\n}\n\nexport interface ResolveIssueOptions {\n /** Already-resolved provider configuration. Defaults to loadIssuesConfig(). */\n config?: IssuesConfig;\n /** Origins to query. Defaults to every registered source. */\n sources?: IssueSource[];\n /**\n * Whether a prompt may be shown. Defaults to a real TTY outside CI, which is\n * what keeps `ask` from ever blocking a headless run.\n */\n interactive?: boolean;\n /** Input stream for the conflict prompt. Defaults to process.stdin. */\n stdin?: NodeJS.ReadableStream;\n /** Output stream for the conflict prompt. 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}\n\n/** How many invalid answers are tolerated before the prompt gives up. */\nconst MAX_PROMPT_ATTEMPTS = 3;\n\n/**\n * Labels shown to users for the built-in origins. An origin registered from\n * outside this package is displayed under its own name, so a new provider needs\n * no entry here.\n */\nconst SOURCE_LABELS: Record<string, string> = {\n github: 'GitHub',\n local: 'local',\n};\n\nfunction sourceLabel(source: IssueSource): string {\n return SOURCE_LABELS[source] ?? source;\n}\n\n/** Same label, capitalized for the numbered prompt ('local' -> 'Local'). */\nfunction promptLabel(source: IssueSource): string {\n const label = sourceLabel(source);\n return label.charAt(0).toUpperCase() + label.slice(1);\n}\n\ninterface Candidate {\n source: IssueSource;\n issue: Issue | null;\n /** Why the origin produced nothing, used only to explain a total miss. */\n reason: string | null;\n /** The classified verdict behind `reason`, when the origin could give one. */\n failure?: ClassifiedFailure;\n /** What a human has to do about it (`gh auth login`), when that applies. */\n action?: string;\n}\n\n/** A candidate that actually produced an Issue. */\ninterface FoundCandidate extends Candidate {\n issue: Issue;\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/**\n * Read one origin without letting it break the others.\n *\n * An unavailable provider or a failed read degrades to \"no candidate\" plus a\n * reason: as long as another origin has the Issue the pipeline keeps going, and\n * when nothing is found the reasons are surfaced in the final error so a broken\n * environment is never reported as a missing Issue.\n */\nasync function fetchCandidate(\n source: IssueSource,\n id: string,\n warn: (message: string) => void,\n): Promise<Candidate> {\n let provider: ReturnType<typeof getProvider>;\n try {\n provider = getProvider(source);\n } catch (err) {\n return { source, issue: null, reason: errorMessage(err) };\n }\n\n let availability: ProviderAvailability;\n try {\n availability = (await provider.checkAvailability?.()) ?? {\n available: await provider.isAvailable(),\n };\n } catch (err) {\n return { source, issue: null, reason: `provider unavailable (${errorMessage(err)})` };\n }\n if (!availability.available) {\n // The classified reason replaces the flat \"provider unavailable\": a\n // credential that expired and a network that blipped were indistinguishable\n // before, and only one of them is worth telling a human about.\n const reason = availability.failure?.message ?? 'provider unavailable';\n return {\n source,\n issue: null,\n reason,\n ...(availability.failure === undefined ? {} : { failure: availability.failure }),\n ...(availability.action === undefined ? {} : { action: availability.action }),\n };\n }\n\n try {\n const issue = await provider.get(id);\n return { source, issue, reason: issue === null ? 'not found' : null };\n } catch (err) {\n const reason = errorMessage(err);\n // A real failure (network, auth, corrupted metadata) must be visible even\n // when the other origin answers.\n warn(`Could not read issue '${id}' from ${sourceLabel(source)}: ${reason}`);\n const failure = failureOf(err);\n const action = actionOf(err);\n return {\n source,\n issue: null,\n reason,\n ...(failure === null ? {} : { failure }),\n ...(action === null ? {} : { action }),\n };\n }\n}\n\nfunction shortHash(hash: string): string {\n const hex = hash.startsWith('sha256:') ? hash.slice('sha256:'.length) : hash;\n return hex.slice(0, 12);\n}\n\n/** One line per origin describing what differs, without dumping every body. */\nfunction describeCandidate(source: IssueSource, issue: Issue): string {\n const lines = issue.body.length === 0 ? 0 : issue.body.split('\\n').length;\n return (\n ` ${sourceLabel(source).padEnd(6)} title: \"${issue.title}\" | body: ${lines} line(s), ` +\n `${issue.body.length} char(s) | updated: ${issue.updatedAt} | hash: ${shortHash(issue.contentHash)}`\n );\n}\n\n/** 'local and GitHub', 'local, GitHub and memory' — origins in query order. */\nfunction listSources(candidates: FoundCandidate[]): string {\n const labels = candidates.map((candidate) => sourceLabel(candidate.source));\n if (labels.length <= 1) {\n return labels.join('');\n }\n return `${labels.slice(0, -1).join(', ')} and ${labels[labels.length - 1]}`;\n}\n\n/**\n * Turn a readline interface into an awaitable line reader.\n *\n * Lines are queued instead of read on demand: `rl.question` drops whatever\n * arrives between two questions, which silently loses a piped answer typed\n * ahead of the prompt. Resolves `null` once the input is exhausted, so EOF ends\n * the prompt instead of hanging the process.\n */\nfunction createLineReader(\n rl: ReadlineInterface,\n output: NodeJS.WritableStream,\n): (query: string) => Promise<string | null> {\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 return (query: string) =>\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 output.write(query);\n pending = resolve;\n });\n}\n\n/**\n * Interactive choice between the divergent versions.\n *\n * The options are numbered from the origins that actually answered, so a third\n * provider simply shows up as `[3] Memory` without this function knowing which\n * origins exist. Cancel is always the last option.\n */\nasync function promptChoice(\n sources: IssueSource[],\n stdin: NodeJS.ReadableStream,\n stdout: NodeJS.WritableStream,\n warn: (message: string) => void,\n): Promise<IssueSource | 'cancel'> {\n const options = sources.map((source, index) => `[${index + 1}] ${promptLabel(source)}`);\n const cancelOption = sources.length + 1;\n const query = `Which version should be used? ${options.join(' ')} [${cancelOption}] Cancel: `;\n const numbers = Array.from({ length: cancelOption }, (_, index) => String(index + 1));\n const accepted = `${numbers.slice(0, -1).join(', ')} or ${numbers[numbers.length - 1]}`;\n\n const rl = createInterface({ input: stdin, output: stdout });\n const ask = createLineReader(rl, stdout);\n try {\n for (let attempt = 0; attempt < MAX_PROMPT_ATTEMPTS; attempt++) {\n const answer = await ask(query);\n if (answer === null) {\n return 'cancel';\n }\n const choice = answer.trim();\n const picked = sources[Number(choice) - 1];\n if (choice.length > 0 && picked !== undefined) {\n return picked;\n }\n if (choice === String(cancelOption)) {\n return 'cancel';\n }\n warn(`Invalid choice: '${choice}'. Enter ${accepted}.`);\n }\n return 'cancel';\n } finally {\n rl.close();\n }\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) && !inCi;\n}\n\nfunction buildResolved(\n issue: Issue,\n source: IssueSource,\n local: Issue | null,\n github: Issue | null,\n divergent: boolean,\n): ResolvedIssue {\n return { issue, source, local, github, divergent };\n}\n\n/** Origin `conflictPolicy` points at, or `null` for 'ask'. */\nfunction policyTarget(policy: IssuesConfig['conflictPolicy']): IssueSource | null {\n if (policy === 'prefer-local') {\n return 'local';\n }\n if (policy === 'prefer-github') {\n return 'github';\n }\n return null;\n}\n\n/**\n * Single entry point every command uses to decide which Issue the pipeline\n * works on.\n *\n * The logic is written against the origins that answered, never against a fixed\n * list of sources: registering a new provider is enough for it to take part in\n * the resolution, be reported in a divergence and appear in the prompt.\n *\n * Scenarios:\n * - only one origin has it -> that one, no questions asked;\n * - several, same contentHash -> equivalence is reported and the preferred\n * origin wins, without a prompt;\n * - several, different content -> the divergence is reported and\n * `conflictPolicy` decides (`ask` prompts on a TTY, falls back to\n * `preferredProvider` with a warning anywhere else);\n * - none -> IssueResolutionError, which carries the CLI exit code.\n */\nexport async function resolveIssue(\n id: string,\n opts: ResolveIssueOptions = {},\n): Promise<ResolvedIssue> {\n const info = opts.info ?? printInfo;\n const warn = opts.warn ?? printWarning;\n const config = opts.config ?? (await loadIssuesConfig({ warn }));\n\n ensureProvidersRegistered();\n const sources = opts.sources ?? getRegisteredSources();\n\n // Each origin is an independent I/O call (network for GitHub, disk for\n // local); querying them concurrently instead of one-by-one keeps the total\n // latency at the slowest origin instead of the sum of all of them, without\n // changing which candidate wins (order is preserved by Promise.all).\n const candidates: Candidate[] = await Promise.all(\n sources.map((source) => fetchCandidate(source, id, warn)),\n );\n\n const issueOf = (source: IssueSource): Issue | null =>\n candidates.find((candidate) => candidate.source === source)?.issue ?? null;\n const local = issueOf('local');\n const github = issueOf('github');\n\n const found = candidates.filter(\n (candidate): candidate is FoundCandidate => candidate.issue !== null,\n );\n\n if (found.length === 0) {\n const details = candidates\n .map((candidate) => `${sourceLabel(candidate.source)}: ${candidate.reason ?? 'not found'}`)\n .join('; ');\n const where = details.length > 0 ? ` (${details})` : '';\n\n // An origin that needs a human is the answer, not a footnote: reporting\n // \"Issue not found\" for an expired credential sends the user looking for\n // the wrong problem, and no amount of retrying would have helped.\n const blocking = candidates.find((candidate) => candidate.action !== undefined);\n if (blocking !== undefined) {\n throw new IssueResolutionError(\n `Cannot read issue '${id}' from ${sourceLabel(blocking.source)}: ${blocking.reason}`,\n 1,\n {\n ...(blocking.failure === undefined ? {} : { failure: blocking.failure }),\n ...(blocking.action === undefined ? {} : { action: blocking.action }),\n },\n );\n }\n\n throw new IssueResolutionError(`Issue '${id}' not found in any registered origin${where}`);\n }\n\n const build = (candidate: FoundCandidate, divergent: boolean): ResolvedIssue =>\n buildResolved(candidate.issue, candidate.source, local, github, divergent);\n\n const firstFound = found[0] as FoundCandidate;\n if (found.length === 1) {\n return build(firstFound, false);\n }\n\n // Several origins have it. The preferred one wins whenever it is among them;\n // otherwise the first origin queried does, so an unrelated preference never\n // leaves the pipeline without an Issue.\n const preferred =\n found.find((candidate) => candidate.source === config.preferredProvider) ?? firstFound;\n\n const distinct = new Set(found.map((candidate) => candidate.issue.contentHash));\n if (distinct.size === 1) {\n info(\n `Issue '${id}' has identical content in ${listSources(found)}; using ${sourceLabel(preferred.source)}.`,\n );\n return build(preferred, false);\n }\n\n info(`Issue '${id}' differs between origins:`);\n for (const candidate of found) {\n info(describeCandidate(candidate.source, candidate.issue));\n }\n\n const target = policyTarget(config.conflictPolicy);\n if (target !== null) {\n const chosen = found.find((candidate) => candidate.source === target);\n if (chosen !== undefined) {\n info(`Conflict policy '${config.conflictPolicy}': using the ${sourceLabel(target)} version.`);\n return build(chosen, true);\n }\n // The policy names an origin that has no version of this Issue: say so\n // instead of silently picking for the user under its name.\n warn(\n `Conflict policy '${config.conflictPolicy}' does not apply to Issue '${id}' ` +\n `(${sourceLabel(target)} has no version of it); using ${sourceLabel(preferred.source)}.`,\n );\n return build(preferred, true);\n }\n\n const interactive = opts.interactive ?? isInteractiveByDefault();\n if (!interactive) {\n warn(\n `Divergent Issue '${id}' and conflict policy 'ask' in a non-interactive environment; ` +\n `using the preferred provider (${sourceLabel(preferred.source)}).`,\n );\n return build(preferred, true);\n }\n\n const choice = await promptChoice(\n found.map((candidate) => candidate.source),\n opts.stdin ?? process.stdin,\n opts.stdout ?? process.stdout,\n warn,\n );\n if (choice === 'cancel') {\n throw new IssueResolutionError(\n `Cancelled: Issue '${id}' diverges between ${listSources(found)}.`,\n );\n }\n const picked = found.find((candidate) => candidate.source === choice) as FoundCandidate;\n return build(picked, true);\n}\n","import { actionOf, classify, requiresHumanAction } from '../resilience/errors.js';\nimport { printError, printWarning } from '../ui/logger.js';\nimport { IssueResolutionError, type ResolveIssueOptions, resolveIssue } from './resolver.js';\nimport type { Issue, ResolvedIssue } from './types.js';\n\n/**\n * Bridge between the resolved Issue and the phase prompts.\n *\n * Commands never fetch the Issue themselves: they resolve it once through\n * `resolveIssue` and hand the content to the template, so every phase sees the\n * exact same title/body regardless of where the demand came from.\n */\n\n/** Path of the markdown file a local Issue lives in. */\nexport function localIssueRef(id: string): string {\n return `issues/${id}/issue.md`;\n}\n\n/**\n * Reference a human (or an agent) can follow to read the Issue: the remote URL\n * when there is one, the local file otherwise.\n */\nexport function issueReference(issue: Issue): string {\n return issue.remoteRef ?? localIssueRef(issue.id);\n}\n\n/** Shown instead of an empty list so the prompt never renders a dangling label. */\nconst NO_LABELS = '(none)';\n\n/**\n * Placeholder values every phase template consumes, replacing the former\n * `gh issue view` instruction.\n */\nexport function issuePlaceholders(resolved: ResolvedIssue): Record<string, string> {\n const { issue } = resolved;\n return {\n __ISSUE_TITLE__: issue.title,\n __ISSUE_BODY__: issue.body,\n __ISSUE_LABELS__: issue.labels.length > 0 ? issue.labels.join(', ') : NO_LABELS,\n __ISSUE_SOURCE__: resolved.source,\n __ISSUE_URL__: issueReference(issue),\n };\n}\n\nexport type CommandIssue = { ok: true; resolved: ResolvedIssue } | { ok: false; code: number };\n\n/**\n * Resolve the Issue a command works on, turning a resolution failure into the\n * exit code the command returns.\n *\n * `preResolved` short-circuits the lookup: the pipeline decides the origin once\n * and passes the decision down, so a phase never re-queries the providers (and\n * never asks the user twice about the same divergence).\n *\n * `options` forwards resolver options — `run.ts` passes the configuration it\n * already loaded so `.issue-flow.json` is read (and warned about) only once.\n */\nexport async function resolveCommandIssue(\n id: string,\n preResolved?: ResolvedIssue,\n options?: ResolveIssueOptions,\n): Promise<CommandIssue> {\n if (preResolved !== undefined) {\n return { ok: true, resolved: preResolved };\n }\n\n try {\n return { ok: true, resolved: await resolveIssue(id, options) };\n } catch (err) {\n if (err instanceof IssueResolutionError) {\n printError(err.message);\n printAction(err.action);\n return { ok: false, code: err.exitCode };\n }\n const message = err instanceof Error ? err.message : String(err);\n printError(`Could not resolve issue '${id}': ${message}`);\n printAction(actionOf(err) ?? requiredAction(message));\n return { ok: false, code: 1 };\n }\n}\n\n/**\n * What a human has to do about a failure that no amount of waiting fixes.\n *\n * The retry budget was already spent inside the providers — every `gh` call\n * goes through `run()` with the `network`/`rate_limit` policy — so by the time\n * a failure reaches here it is final. What is left to decide is whether it is\n * final *because a person has to act*, and that is what turns an opaque exit\n * code into an instruction.\n */\nfunction requiredAction(message: string): string | null {\n const failure = classify({ source: 'github', stderr: message });\n if (!requiresHumanAction(failure.kind)) return null;\n if (failure.kind === 'authentication') {\n return 'Run `gh auth login` to authenticate the GitHub CLI';\n }\n return null;\n}\n\nfunction printAction(action: string | null | undefined): void {\n if (action !== null && action !== undefined && action !== '') {\n printWarning(`Action required: ${action}`);\n }\n}\n"],"mappings":"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","names":["normalizeId"]}
@@ -1 +0,0 @@
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 { DEFAULT_HEADLESS_TIMEOUT_MS, 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() ?? DEFAULT_HEADLESS_TIMEOUT_MS,\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 { run } from '../../utils/shell.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/**\n * The marker that makes a published review addressable.\n *\n * A review round is *republished* more often than it is first published — a\n * retry of the phase, a re-run after a correction cycle, a resume — and without\n * a way to recognise its own previous comment every one of those leaves another\n * copy on the Pull Request. The marker is per round on purpose: round 2 is a\n * different statement from round 1 and gets its own comment, while a second\n * publication of round 1 updates the one that is there.\n *\n * It is an HTML comment, so it is invisible in the rendered body and survives\n * GitHub's Markdown untouched.\n */\nexport function reviewCommentMarker(round: number): string {\n return `<!-- issue-flow:review:${round} -->`;\n}\n\ninterface GitHubComment {\n id: number;\n body: string;\n}\n\nfunction parseComments(stdout: string): GitHubComment[] {\n try {\n const parsed: unknown = JSON.parse(stdout || '[]');\n if (!Array.isArray(parsed)) return [];\n return parsed.filter(\n (entry): entry is GitHubComment =>\n typeof entry === 'object' &&\n entry !== null &&\n typeof (entry as { id?: unknown }).id === 'number' &&\n typeof (entry as { body?: unknown }).body === 'string',\n );\n } catch {\n return [];\n }\n}\n\n/**\n * Publishes the report as a comment on the Pull Request, **updating** the\n * comment of the same round instead of adding a second one.\n *\n * The sequence is: read the existing comments, look for this round's marker,\n * and either `PATCH` that comment or post a new one. A `gh` that cannot answer\n * the first question falls through to posting — the failure mode of a\n * duplicated comment is better than the failure mode of a review nobody sees —\n * and nothing here throws: a Pull Request comment is a report, and a report\n * that fails to publish must not fail the phase.\n */\nexport class GitHubCommentPublisher implements PrReviewPublisher {\n async publish(report: PrReviewReport): Promise<void> {\n const marker = reviewCommentMarker(report.round);\n const body = `${marker}\\n${report.markdown}`;\n const pr = report.pullRequest.number;\n\n const listed = await run('gh', [\n 'api',\n `repos/{owner}/{repo}/issues/${pr}/comments`,\n '--paginate',\n ]);\n\n const existing =\n listed.exitCode === 0\n ? parseComments(listed.stdout).find((comment) => comment.body.includes(marker))\n : undefined;\n\n if (existing !== undefined) {\n await run('gh', [\n 'api',\n '--method',\n 'PATCH',\n `repos/{owner}/{repo}/issues/comments/${existing.id}`,\n '-f',\n `body=${body}`,\n ]);\n return;\n }\n\n await run('gh', ['pr', 'comment', String(pr), '--body', body]);\n }\n}\n\n/** Runs several publishers over one report, in order. */\nexport class CompositePrReviewPublisher implements PrReviewPublisher {\n constructor(private readonly members: readonly PrReviewPublisher[]) {}\n\n async publish(report: PrReviewReport): Promise<void> {\n for (const member of this.members) {\n await member.publish(report);\n }\n }\n}\n\n/**\n * Publishers selectable by configuration.\n *\n * `github` composes rather than replaces: the local artifacts are what the\n * correction cycle and `resume` read, and a Pull Request comment is an\n * additional audience, never a substitute for them.\n */\nexport type PrReviewPublisherKind = 'local' | 'github';\n\nconst PUBLISHERS: Record<PrReviewPublisherKind, (dir: string) => PrReviewPublisher> = {\n local: (dir) => new LocalReportPublisher(dir),\n github: (dir) =>\n new CompositePrReviewPublisher([new LocalReportPublisher(dir), new GitHubCommentPublisher()]),\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,QAAAA,aAAY;;;ACArB,SAAS,uBAAuB;AAkCzB,IAAM,mBAAN,cAA+B,MAAM;AAAA,EACjC;AAAA,EAET,YAAY,SAAiB,WAAW,GAAG;AACzC,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,WAAW;AAAA,EAClB;AACF;AAkCA,IAAM,sBAAsB;AAE5B,IAAM,gBAAmD;AAAA,EACvD,UAAU;AAAA,EACV,SAAS;AAAA,EACT,MAAM;AAAA,EACN,QAAQ;AACV;AAOA,eAAe,oBAAoB,OAA+C;AAChF,MAAI;AACF,UAAM,OAAO,MAAM,cAAc,MAAM,kBAAkB,KAAK,GAAG,SAAS;AAC1E,WAAO,KAAK,eAAe;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,IAAM,iBAAqC;AAAA,EACzC,qBAAqB,MAAM,oBAAoB,EAAE,SAAS,EAAE;AAAA,EAC5D,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,oBAAoB;AACtB;AAMO,SAAS,WAAW,cAA+D;AACxF,MAAI,SAAoC;AACxC,aAAW,MAAM,cAAc;AAC7B,QAAI,WAAW,QAAQ,GAAG,SAAS,OAAO,QAAQ;AAChD,eAAS;AAAA,IACX;AAAA,EACF;AACA,SAAO;AACT;AAOA,SAAS,gBAAgB,KAA4B;AACnD,QAAM,UAAU,IAAI,KAAK;AACzB,QAAM,UAAU,QAAQ,MAAM,eAAe;AAC7C,MAAI,UAAU,CAAC,MAAM,QAAW;AAC9B,WAAO,OAAO,QAAQ,CAAC,CAAC;AAAA,EAC1B;AACA,QAAM,QAAQ,QAAQ,MAAM,WAAW;AACvC,SAAO,QAAQ,CAAC,MAAM,SAAY,OAAO,OAAO,MAAM,CAAC,CAAC;AAC1D;AAEA,SAAS,yBAAkC;AACzC,QAAM,KAAK,QAAQ,IAAI;AACvB,QAAM,OAAO,OAAO,UAAa,OAAO,MAAM,OAAO,OAAO,GAAG,YAAY,MAAM;AACjF,SAAO,QAAQ,QAAQ,MAAM,KAAK,KAAK,QAAQ,QAAQ,OAAO,KAAK,KAAK,CAAC;AAC3E;AAGA,SAAS,SAAS,IAAiC;AACjD,QAAM,QAAQ,CAAC,IAAI,GAAG,MAAM,EAAE;AAC9B,MAAI,GAAG,UAAU,KAAM,OAAM,KAAK,IAAI,GAAG,KAAK,GAAG;AACjD,MAAI,GAAG,eAAe,KAAM,OAAM,KAAK,IAAI,GAAG,UAAU,GAAG;AAC3D,SAAO,MAAM,KAAK,GAAG;AACvB;AAUA,eAAe,QACb,OACA,OACA,QACA,MACkB;AAClB,QAAM,KAAK,gBAAgB,EAAE,OAAO,OAAO,QAAQ,OAAO,CAAC;AAC3D,QAAM,SAAmB,CAAC;AAC1B,MAAI,UAAkD;AACtD,MAAI,SAAS;AAEb,KAAG,GAAG,QAAQ,CAAC,SAAiB;AAC9B,QAAI,YAAY,MAAM;AACpB,YAAM,UAAU;AAChB,gBAAU;AACV,cAAQ,IAAI;AACZ;AAAA,IACF;AACA,WAAO,KAAK,IAAI;AAAA,EAClB,CAAC;AACD,KAAG,GAAG,SAAS,MAAM;AACnB,aAAS;AACT,QAAI,YAAY,MAAM;AACpB,YAAM,UAAU;AAChB,gBAAU;AACV,cAAQ,IAAI;AAAA,IACd;AAAA,EACF,CAAC;AAED,QAAM,MAAM,MACV,IAAI,QAAuB,CAAC,YAAY;AACtC,UAAM,WAAW,OAAO,MAAM;AAC9B,QAAI,aAAa,QAAW;AAC1B,cAAQ,QAAQ;AAChB;AAAA,IACF;AACA,QAAI,QAAQ;AACV,cAAQ,IAAI;AACZ;AAAA,IACF;AACA,WAAO,MAAM,KAAK;AAClB,cAAU;AAAA,EACZ,CAAC;AAEH,MAAI;AACF,aAAS,UAAU,GAAG,UAAU,qBAAqB,WAAW;AAC9D,YAAM,SAAS,MAAM,IAAI;AACzB,UAAI,WAAW,MAAM;AACnB,eAAO;AAAA,MACT;AACA,YAAM,aAAa,OAAO,KAAK,EAAE,YAAY;AAC7C,UAAI,eAAe,MAAM,eAAe,OAAO,eAAe,OAAO;AACnE,eAAO;AAAA,MACT;AACA,UAAI,eAAe,OAAO,eAAe,MAAM;AAC7C,eAAO;AAAA,MACT;AACA,WAAK,oBAAoB,OAAO,KAAK,CAAC,kBAAkB;AAAA,IAC1D;AACA,WAAO;AAAA,EACT,UAAE;AACA,OAAG,MAAM;AAAA,EACX;AACF;AAsBA,eAAsB,mBACpB,OACA,OAAkC,CAAC,GACL;AAC9B,QAAM,OAAO,KAAK,QAAQ;AAC1B,QAAM,OAAO,KAAK,QAAQ;AAC1B,QAAM,UAA8B,EAAE,GAAG,gBAAgB,GAAG,KAAK,QAAQ;AAEzE,MAAI,UAAU,UAAa,MAAM,KAAK,MAAM,IAAI;AAC9C,UAAM,SAAS,gBAAgB,KAAK;AACpC,QAAI,WAAW,MAAM;AACnB,YAAM,IAAI;AAAA,QACR,mCAAmC,MAAM,KAAK,CAAC;AAAA,MAEjD;AAAA,IACF;AACA,UAAM,MAAM,MAAM,KAAK,EAAE,SAAS,QAAQ,IAAI,MAAM,KAAK,IAAI;AAC7D,WAAO,EAAE,QAAQ,KAAK,OAAO,MAAM,YAAY,MAAM,QAAQ,WAAW;AAAA,EAC1E;AAEA,QAAM,aAAa,MAAM,SAAS,SAAS,KAAK,OAAO,IAAI;AAC3D,MAAI,eAAe,MAAM;AACvB,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AAEA,QAAM,cAAc,KAAK,QAAQ,OAAO,QAAS,KAAK,eAAe,uBAAuB;AAC5F,MAAI,CAAC,aAAa;AAChB;AAAA,MACE,0BAA0B,SAAS,UAAU,CAAC,cAAc,cAAc,WAAW,MAAM,CAAC;AAAA,IAC9F;AACA,WAAO;AAAA,EACT;AAEA,OAAK,yBAAyB,cAAc,WAAW,MAAM,CAAC,GAAG;AACjE,OAAK,cAAc,WAAW,MAAM,EAAE;AACtC,OAAK,aAAa,WAAW,SAAS,WAAW,EAAE;AACnD,OAAK,aAAa,WAAW,cAAc,WAAW,EAAE;AAExD,QAAM,WAAW,MAAM;AAAA,IACrB,wBAAwB,WAAW,MAAM;AAAA,IACzC,KAAK,SAAS,QAAQ;AAAA,IACtB,KAAK,UAAU,QAAQ;AAAA,IACvB;AAAA,EACF;AACA,MAAI,CAAC,UAAU;AACb,UAAM,IAAI;AAAA,MACR,4BAA4B,WAAW,MAAM;AAAA,IAE/C;AAAA,EACF;AACA,SAAO;AACT;AAOA,eAAe,SACb,SACA,OACA,MACqC;AACrC,MAAI,UAAU,UAAa,UAAU,IAAI;AACvC,QAAI;AACF,YAAM,WAAW,MAAM,QAAQ,gBAAgB,KAAK;AACpD,UAAI,aAAa,MAAM;AACrB,eAAO;AAAA,UACL,QAAQ,SAAS;AAAA,UACjB,KAAK,SAAS;AAAA,UACd,OAAO;AAAA,UACP,YAAY,SAAS;AAAA,UACrB,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,MAAI,aAAmC,CAAC;AACxC,MAAI;AACF,iBAAa,QAAQ,oBAAoB;AAAA,EAC3C,QAAQ;AAAA,EAER;AACA,QAAM,cAAc,WAAW,UAAU;AACzC,MAAI,gBAAgB,MAAM;AACxB,WAAO;AAAA,MACL,QAAQ,YAAY;AAAA,MACpB,KAAK,YAAY;AAAA,MACjB,OAAO,YAAY;AAAA,MACnB,YAAY;AAAA,MACZ,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,MAAI,SAAS;AACb,MAAI;AACF,aAAS,MAAM,QAAQ,cAAc;AAAA,EACvC,SAAS,KAAK;AACZ;AAAA,MACE,wCAAwC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IAC1F;AAAA,EACF;AACA,MAAI,WAAW,IAAI;AACjB,WAAO;AAAA,EACT;AAEA,MAAI,YAAkC,CAAC;AACvC,MAAI;AACF,gBAAY,MAAM,QAAQ,mBAAmB,MAAM;AAAA,EACrD,SAAS,KAAK;AACZ;AAAA,MACE,4CAA4C,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IAC1G;AACA,WAAO;AAAA,EACT;AACA,QAAM,aAAa,WAAW,SAAS;AACvC,MAAI,eAAe,MAAM;AACvB,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,QAAQ,WAAW;AAAA,IACnB,KAAK,WAAW;AAAA,IAChB,OAAO,WAAW;AAAA,IAClB,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV;AACF;;;ACtXA,SAAS,aAAa;AACtB,SAAS,QAAAC,aAAY;;;ACDrB,SAAS,SAAS,gBAAgB;AAClC,SAAS,YAAY;AA4DrB,IAAM,kBAA0D;AAAA,EAC9D,SAAS;AAAA,EACT,0BAA0B;AAAA,EAC1B,iBAAiB;AACnB;AAQO,SAAS,oBAAoB,QAA+B;AACjE,QAAM,QAAQ,OAAO,MAAM,kDAAkD;AAC7E,QAAM,QAAQ,QAAQ,CAAC,KAAK;AAE5B,QAAM,OAAO,MAAM,MAAM,+BAA+B;AACxD,MAAI,OAAO,CAAC,MAAM,QAAW;AAC3B,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,OAAO,QACH,6DACA;AAAA,IACN;AAAA,EACF;AAIA,QAAM,MAAM,KAAK,CAAC,EACf,KAAK,EACL,QAAQ,SAAS,EAAE,EACnB,QAAQ,eAAe,EAAE,EACzB,KAAK;AACR,QAAM,aAAa,IAAI,YAAY,EAAE,QAAQ,WAAW,GAAG;AAC3D,QAAM,iBAAiB,gBAAgB,UAAU;AACjD,MAAI,mBAAmB,QAAW;AAChC,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,OACE,2BAA2B,GAAG;AAAA,IAElC;AAAA,EACF;AAEA,SAAO,EAAE,IAAI,MAAM,QAAQ,EAAE,gBAAgB,UAAU,cAAc,KAAK,EAAE,EAAE;AAChF;AAGA,SAAS,cAAc,OAAyB;AAC9C,QAAM,QAAQ,MAAM,MAAM,qBAAqB;AAC/C,MAAI,QAAQ,CAAC,MAAM,OAAW,QAAO,CAAC;AAEtC,SAAO,MAAM,CAAC,EACX,MAAM,IAAI,EACV,IAAI,CAAC,UAAU,MAAM,KAAK,CAAC,EAC3B,OAAO,CAAC,UAAU,WAAW,KAAK,KAAK,CAAC,EACxC,IAAI,CAAC,UAAU,MAAM,QAAQ,YAAY,EAAE,EAAE,KAAK,CAAC,EACnD,OAAO,CAAC,UAAU,UAAU,MAAM,CAAC,2BAA2B,KAAK,KAAK,CAAC;AAC9E;AAGO,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AASA,IAAM,kBAAmD;AAAA,EACvD,qBAAqB,CAAC,qBAAqB,WAAW,YAAY,oBAAoB,QAAQ;AAAA,EAC9F,WAAW,CAAC,aAAa,aAAa,cAAc,kBAAkB,kBAAkB;AAAA,EACxF,gBAAgB,CAAC,gBAAgB,UAAU,YAAY,YAAY,uBAAuB;AAAA,EAC1F,0BAA0B;AAAA,IACxB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,8BAA8B;AAAA,IAC5B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,oBAAoB,CAAC,oBAAoB,SAAS,wBAAwB,QAAQ;AAAA,EAClF,yBAAyB;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,wBAAwB;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAGA,SAAS,iBAAiB,SAAyB;AACjD,SAAO,QACJ,UAAU,KAAK,EACf,QAAQ,oBAAoB,EAAE,EAC9B,YAAY,EACZ,QAAQ,eAAe,EAAE,EACzB,QAAQ,WAAW,EAAE,EACrB,QAAQ,aAAa,EAAE,EACvB,QAAQ,QAAQ,GAAG,EACnB,KAAK;AACV;AAEA,SAAS,aAAa,SAAuC;AAC3D,QAAM,aAAa,iBAAiB,OAAO;AAC3C,aAAW,WAAW,iBAAiB;AACrC,QAAI,gBAAgB,OAAO,EAAE,SAAS,UAAU,EAAG,QAAO;AAAA,EAC5D;AACA,SAAO;AACT;AAaA,SAAS,UAAU,MAAyB;AAC1C,QAAM,WAAW,oBAAI,IAA2B;AAChD,QAAM,QAAkB,CAAC;AACzB,MAAI,UAAgC;AACpC,MAAI,SAAmB,CAAC;AAExB,QAAM,QAAQ,MAAM;AAClB,UAAM,UAAU,OAAO,KAAK,IAAI,EAAE,KAAK;AACvC,aAAS,CAAC;AACV,QAAI,YAAY,GAAI;AACpB,QAAI,YAAY,MAAM;AACpB,YAAM,KAAK,OAAO;AAClB;AAAA,IACF;AACA,UAAM,WAAW,SAAS,IAAI,OAAO;AACrC,aAAS,IAAI,SAAS,aAAa,SAAY,UAAU,GAAG,QAAQ;AAAA;AAAA,EAAO,OAAO,EAAE;AAAA,EACtF;AAEA,aAAW,QAAQ,KAAK,MAAM,IAAI,GAAG;AACnC,UAAM,UAAU,KAAK,MAAM,iBAAiB;AAC5C,QAAI,UAAU,CAAC,MAAM,QAAW;AAC9B,aAAO,KAAK,IAAI;AAChB;AAAA,IACF;AACA,UAAM;AACN,UAAM,UAAU,aAAa,QAAQ,CAAC,CAAC;AACvC,cAAU;AACV,QAAI,YAAY,MAAM;AACpB,aAAO,KAAK,IAAI;AAAA,IAClB;AAAA,EACF;AACA,QAAM;AAEN,SAAO,EAAE,UAAU,OAAO,MAAM,KAAK,MAAM,EAAE,KAAK,EAAE;AACtD;AAEA,IAAM,eAAe;AACrB,IAAM,mBAAmB;AAEzB,IAAM,mBAAoD;AAAA,EACxD,SAAS;AAAA,EACT,UAAU;AAAA,EACV,UAAU;AAAA,EACV,MAAM;AAAA,EACN,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,KAAK;AAAA,EACL,OAAO;AAAA,EACP,KAAK;AACP;AASO,SAAS,cAAc,MAAiC;AAC7D,QAAM,EAAE,SAAS,IAAI,UAAU,IAAI;AACnC,QAAM,SAAS,CAAC,SAAS,IAAI,cAAc,GAAG,SAAS,IAAI,uBAAuB,CAAC;AACnF,QAAM,WAA8B,CAAC;AAErC,aAAW,SAAS,QAAQ;AAC1B,QAAI,UAAU,OAAW;AACzB,eAAW,QAAQ,MAAM,MAAM,IAAI,GAAG;AACpC,YAAM,OAAO,KAAK,KAAK,EAAE,MAAM,YAAY;AAC3C,UAAI,OAAO,CAAC,MAAM,UAAa,KAAK,CAAC,MAAM,OAAW;AAEtD,YAAM,WAAW,iBAAiB,KAAK,CAAC,EAAE,YAAY,CAAC;AAGvD,UAAI,aAAa,OAAW;AAE5B,YAAM,OAAO,KAAK,CAAC,EAAE,KAAK;AAC1B,YAAM,WAAW,KAAK,MAAM,gBAAgB;AAC5C,UAAI,WAAW,CAAC,MAAM,UAAa,SAAS,CAAC,MAAM,QAAW;AAC5D,iBAAS,KAAK,EAAE,UAAU,MAAM,MAAM,MAAM,MAAM,OAAO,KAAK,CAAC;AAC/D;AAAA,MACF;AACA,eAAS,KAAK;AAAA,QACZ;AAAA,QACA,MAAM,SAAS,CAAC;AAAA,QAChB,MAAM,SAAS,CAAC,MAAM,SAAY,OAAO,OAAO,SAAS,CAAC,CAAC;AAAA,QAC3D,OAAO,SAAS,CAAC,EAAE,KAAK;AAAA,MAC1B,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO,eAAe,QAAQ;AAChC;AAGA,SAAS,eAAe,UAAgD;AACtE,QAAM,OAAO,oBAAI,IAAY;AAC7B,SAAO,SAAS,OAAO,CAAC,YAAY;AAClC,UAAM,MAAM,GAAG,QAAQ,QAAQ,EAAE,IAAI,QAAQ,QAAQ,EAAE,IAAI,QAAQ,KAAK;AACxE,QAAI,KAAK,IAAI,GAAG,EAAG,QAAO;AAC1B,SAAK,IAAI,GAAG;AACZ,WAAO;AAAA,EACT,CAAC;AACH;AAoBO,SAAS,oBAAoB,OAAiC;AACnE,QAAM,KAAK,MAAM;AACjB,QAAM,QAAkB,CAAC,iCAA4B,GAAG,MAAM,WAAW,MAAM,KAAK,KAAK,EAAE;AAE3F,QAAM,UAAU,GAAG,QAAQ,OAAO,IAAI,GAAG,MAAM,KAAK,KAAK,GAAG,MAAM,KAAK,GAAG,GAAG;AAC7E,QAAM,KAAK,uBAAuB,OAAO,GAAG,GAAG,UAAU,OAAO,KAAK,WAAM,GAAG,KAAK,EAAE,EAAE;AACvF,MAAI,GAAG,eAAe,KAAM,OAAM,KAAK,sBAAsB,GAAG,UAAU,EAAE;AAC5E,MAAI,MAAM,WAAW,KAAM,OAAM,KAAK,mBAAmB,MAAM,OAAO,EAAE;AACxE,QAAM,KAAK,gBAAgB,MAAM,KAAK,EAAE;AACxC,QAAM,KAAK,sBAAsB,MAAM,EAAE,EAAE;AAC3C,QAAM,KAAK,yBAAyB,MAAM,kBAAkB,UAAU,EAAE;AACxE,QAAM,KAAK,EAAE;AAEb,MAAI,MAAM,cAAc,QAAQ,MAAM,eAAe,IAAI;AACvD,UAAM,KAAK,kCAAkC,MAAM,UAAU,IAAI,EAAE;AAAA,EACrE;AAEA,QAAM,EAAE,UAAU,MAAM,IAAI,UAAU,MAAM,IAAI;AAChD,aAAW,WAAW,iBAAiB;AACrC,UAAM,KAAK,MAAM,OAAO,IAAI,IAAI,SAAS,IAAI,OAAO,KAAK,mBAAmB,EAAE;AAAA,EAChF;AAEA,MAAI,MAAM,cAAc,QAAQ,MAAM,eAAe,IAAI;AACvD,UAAM,KAAK,uBAAuB,IAAI,WAAW,MAAM,KAAK,KAAK,GAAG,OAAO,EAAE;AAAA,EAC/E,WAAW,UAAU,IAAI;AACvB,UAAM,KAAK,uBAAuB,IAAI,OAAO,EAAE;AAAA,EACjD;AAEA,SAAO,GAAG,MAAM,KAAK,IAAI,EAAE,QAAQ,CAAC;AAAA;AACtC;AAWA,eAAsB,YAAY,MAAgE;AAChG,QAAM,OACJ,KAAK,UAAU,UAAa,KAAK,UAAU,KACvC,MAAM,KAAK,WAAW,KACtB,KAAK,MAAM,QAAQ,MAAM,EAAE;AACjC,UAAQ,MAAM,kBAAkB,IAAI,GAAG;AACzC;AAEO,SAAS,eAAe,aAAqB,OAAuB;AACzE,SAAO,MAAM,WAAW,UAAU,KAAK;AACzC;AAEA,IAAM,cAAc;AAOpB,eAAsB,kBAAkB,KAA4C;AAClF,MAAI;AACF,UAAM,SAAkB,KAAK,MAAM,MAAM,SAAS,KAAK,KAAK,YAAY,GAAG,OAAO,CAAC;AACnF,QAAI,OAAO,WAAW,YAAY,WAAW,KAAM,QAAO;AAC1D,UAAM,QAAQ;AACd,QAAI,CAAC,MAAM,QAAQ,MAAM,MAAM,KAAK,OAAO,MAAM,gBAAgB,SAAU,QAAO;AAClF,WAAO;AAAA,MACL,eAAe;AAAA,MACf,aAAa,MAAM;AAAA,MACnB,QAAQ,MAAM;AAAA,IAChB;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,eAAe,aAAa,KAAa,aAAwC;AAC/E,MAAI;AACF,YAAQ,MAAM,QAAQ,GAAG,GACtB,IAAI,CAAC,SAAS,KAAK,MAAM,WAAW,CAAC,EACrC,OAAO,CAAC,UAAqC,UAAU,IAAI,EAC3D,OAAO,CAAC,UAAU,OAAO,MAAM,CAAC,CAAC,MAAM,WAAW,EAClD,IAAI,CAAC,UAAU,OAAO,MAAM,CAAC,CAAC,CAAC;AAAA,EACpC,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAOA,eAAsB,aACpB,KACA,aACA,UACiB;AACjB,MAAI,aAAa,OAAW,QAAO;AAEnC,QAAM,QAAQ,MAAM,kBAAkB,GAAG;AACzC,QAAM,QAAQ;AAAA,IACZ,IAAI,OAAO,UAAU,CAAC,GAAG,IAAI,CAAC,UAAU,MAAM,KAAK;AAAA,IACnD,GAAI,MAAM,aAAa,KAAK,WAAW;AAAA,EACzC;AACA,SAAO,MAAM,WAAW,IAAI,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI;AACvD;AAMO,SAAS,YAAY,OAAsB,OAA0C;AAC1F,QAAM,SAAS,MAAM,OAAO,OAAO,CAAC,aAAa,SAAS,UAAU,MAAM,KAAK;AAC/E,SAAO,KAAK,KAAK;AACjB,SAAO,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACvC,SAAO,EAAE,GAAG,OAAO,OAAO;AAC5B;;;AD1YO,IAAM,uBAAN,MAAwD;AAAA,EAC7D,YAA6B,KAAa;AAAb;AAAA,EAAc;AAAA,EAAd;AAAA,EAE7B,MAAM,QAAQ,QAAuC;AACnD,UAAM,MAAM,KAAK,KAAK,EAAE,WAAW,KAAK,CAAC;AAEzC,UAAM,WAAW,eAAe,OAAO,YAAY,QAAQ,OAAO,KAAK;AACvE,UAAM,gBAAgBC,MAAK,KAAK,KAAK,QAAQ,GAAG,OAAO,QAAQ;AAE/D,UAAM,WAAW,MAAM,kBAAkB,KAAK,GAAG;AACjD,UAAM,QAAuB;AAAA,MAC3B,eAAe;AAAA,MACf,aAAa,OAAO;AAAA,MACpB,QAAQ,UAAU,UAAU,CAAC;AAAA,IAC/B;AAEA,UAAM,UAAU,YAAY,OAAO;AAAA,MACjC,OAAO,OAAO;AAAA,MACd,IAAI,OAAO;AAAA,MACX,gBAAgB,OAAO;AAAA,MACvB,SAAS,OAAO;AAAA,MAChB,YAAY;AAAA,MACZ,UAAU,OAAO;AAAA,IACnB,CAAC;AAED,UAAM,gBAAgBA,MAAK,KAAK,KAAK,YAAY,GAAG,GAAG,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAAA,CAAI;AAAA,EAC7F;AACF;AAeO,SAAS,oBAAoB,OAAuB;AACzD,SAAO,0BAA0B,KAAK;AACxC;AAOA,SAAS,cAAc,QAAiC;AACtD,MAAI;AACF,UAAM,SAAkB,KAAK,MAAM,UAAU,IAAI;AACjD,QAAI,CAAC,MAAM,QAAQ,MAAM,EAAG,QAAO,CAAC;AACpC,WAAO,OAAO;AAAA,MACZ,CAAC,UACC,OAAO,UAAU,YACjB,UAAU,QACV,OAAQ,MAA2B,OAAO,YAC1C,OAAQ,MAA6B,SAAS;AAAA,IAClD;AAAA,EACF,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAaO,IAAM,yBAAN,MAA0D;AAAA,EAC/D,MAAM,QAAQ,QAAuC;AACnD,UAAM,SAAS,oBAAoB,OAAO,KAAK;AAC/C,UAAM,OAAO,GAAG,MAAM;AAAA,EAAK,OAAO,QAAQ;AAC1C,UAAM,KAAK,OAAO,YAAY;AAE9B,UAAM,SAAS,MAAM,IAAI,MAAM;AAAA,MAC7B;AAAA,MACA,+BAA+B,EAAE;AAAA,MACjC;AAAA,IACF,CAAC;AAED,UAAM,WACJ,OAAO,aAAa,IAChB,cAAc,OAAO,MAAM,EAAE,KAAK,CAAC,YAAY,QAAQ,KAAK,SAAS,MAAM,CAAC,IAC5E;AAEN,QAAI,aAAa,QAAW;AAC1B,YAAM,IAAI,MAAM;AAAA,QACd;AAAA,QACA;AAAA,QACA;AAAA,QACA,wCAAwC,SAAS,EAAE;AAAA,QACnD;AAAA,QACA,QAAQ,IAAI;AAAA,MACd,CAAC;AACD;AAAA,IACF;AAEA,UAAM,IAAI,MAAM,CAAC,MAAM,WAAW,OAAO,EAAE,GAAG,UAAU,IAAI,CAAC;AAAA,EAC/D;AACF;AAGO,IAAM,6BAAN,MAA8D;AAAA,EACnE,YAA6B,SAAuC;AAAvC;AAAA,EAAwC;AAAA,EAAxC;AAAA,EAE7B,MAAM,QAAQ,QAAuC;AACnD,eAAW,UAAU,KAAK,SAAS;AACjC,YAAM,OAAO,QAAQ,MAAM;AAAA,IAC7B;AAAA,EACF;AACF;AAWA,IAAM,aAAgF;AAAA,EACpF,OAAO,CAAC,QAAQ,IAAI,qBAAqB,GAAG;AAAA,EAC5C,QAAQ,CAAC,QACP,IAAI,2BAA2B,CAAC,IAAI,qBAAqB,GAAG,GAAG,IAAI,uBAAuB,CAAC,CAAC;AAChG;AAOO,SAAS,wBACd,KACA,OAA8B,SACX;AACnB,SAAO,WAAW,IAAI,EAAE,GAAG;AAC7B;;;AFxJA,IAAM,iBAAmC,CAAC,mBAAmB,eAAe,MAAM;AAClF,IAAM,kBAAkC;AAGxC,IAAM,WAAW;AACjB,IAAM,UAAU;AAkBT,SAAS,YACd,gBACA,QACQ;AACR,MAAI,WAAW,QAAQ;AACrB,WAAO;AAAA,EACT;AACA,MAAI,mBAAmB,mBAAmB;AACxC,WAAO;AAAA,EACT;AACA,SAAO,WAAW,iBAAiB,mBAAmB,6BAA6B,IAAI;AACzF;AAEA,SAAS,YAAY,OAAkD;AACrE,MAAI,UAAU,UAAa,UAAU,IAAI;AACvC,WAAO;AAAA,EACT;AACA,QAAM,aAAa,MAAM,KAAK,EAAE,YAAY;AAC5C,SAAO,eAAe,KAAK,CAAC,UAAU,UAAU,UAAU,KAAK;AACjE;AAcA,eAAe,yBAAyB,QAA+C;AACrF,MAAI;AACF,UAAM,SAAS,MAAM,IAAI,MAAM;AAAA,MAC7B;AAAA,MACA;AAAA,MACA,OAAO,MAAM;AAAA,MACb;AAAA,MACA;AAAA,IACF,CAAC;AACD,QAAI,OAAO,aAAa,GAAG;AACzB,aAAO;AAAA,IACT;AACA,UAAM,SAAkB,KAAK,MAAM,OAAO,MAAM;AAChD,WAAO,OAAO,WAAW,YAAY,WAAW,OAAQ,SAA2B;AAAA,EACrF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,iBACP,QACA,MACqB;AACrB,SAAO;AAAA,IACL,QAAQ,OAAO;AAAA,IACf,KAAK,OAAO,OAAO,MAAM,OAAO;AAAA,IAChC,OAAO,OAAO,SAAS,MAAM,SAAS;AAAA,IACtC,YAAY,OAAO,cAAc,MAAM,eAAe;AAAA,EACxD;AACF;AAmBA,eAAe,aAAa,OAAoC;AAC9D,MAAI;AACF,UAAM,OAAO,MAAM,aAAa,MAAM,SAAS;AAC/C,SAAK,SAAS,oBACZ,MAAM,mBAAmB,aAAa,MAAM,mBAAmB;AACjE,SAAK,WAAW;AAAA;AAAA;AAAA,MAGd,SAAS,KAAK,UAAU,WAAW;AAAA,MACnC,mBAAmB,MAAM;AAAA;AAAA,MAEzB,QAAQ,KAAK,IAAI,KAAK,UAAU,UAAU,GAAG,MAAM,KAAK;AAAA,MACxD,oBAAoB,MAAM,kBAAkB,KAAK,UAAU;AAAA,MAC3D,gBAAgB,MAAM;AAAA,IACxB;AACA,UAAM,aAAa,MAAM,WAAW,IAAI;AAAA,EAC1C,QAAQ;AAAA,EAER;AACF;AAEA,eAAsB,YAAY,OAAgB,OAAwB,CAAC,GAAoB;AAC7F,QAAM,SAAS,YAAY,KAAK,MAAM;AACtC,MAAI,WAAW,MAAM;AACnB,eAAW,4BAA4B,KAAK,MAAM,eAAe,eAAe,KAAK,IAAI,CAAC,GAAG;AAC7F,WAAO;AAAA,EACT;AAEA,QAAM,gBAAgB,KAAK,UAAU,SAAY,SAAY,OAAO,KAAK,KAAK;AAC9E,MAAI,kBAAkB,WAAc,CAAC,OAAO,UAAU,aAAa,KAAK,gBAAgB,IAAI;AAC1F,eAAW,0BAA0B,KAAK,KAAK,iCAAiC;AAChF,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,KAAK,OAAO,QAAQ,MAAM,EAAE,EAAE,KAAK;AACpD,QAAM,QAAQ,aAAa,UAAa,aAAa,KAAK,SAAY;AAEtE,MAAI;AACJ,MAAI;AACF,aAAS,MAAM,mBAAmB,OAAO,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC;AAAA,EACnE,SAAS,KAAK;AACZ,QAAI,eAAe,kBAAkB;AACnC,iBAAW,IAAI,OAAO;AACtB,aAAO,IAAI;AAAA,IACb;AACA;AAAA,MACE,qDAAqD,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IACvG;AACA,WAAO;AAAA,EACT;AAIA,QAAM,aAAa,UAAU,SAAY,OAAO,MAAM,kBAAkB,KAAK;AAC7E,QAAM,WAAW,YAAY,YAAY;AACzC,QAAM,YAAY,YAAY,aAAa;AAC3C,QAAM,UAAU,YAAY,WAAW;AAEvC,QAAM,MAAM,MAAM,YAAY,EAAE,OAAO,aAAa,OAAO,OAAO,CAAC;AACnE,QAAM,QAAQ,MAAM,aAAa,KAAK,OAAO,QAAQ,aAAa;AAClE,QAAM,aAAaC,MAAK,KAAK,eAAe,OAAO,QAAQ,KAAK,CAAC;AAEjE,QAAM,WAAW,MAAM,WAAW,WAAW;AAC7C,QAAM,SAAS,kBAAkB,UAAU;AAAA;AAAA;AAAA,IAGzC,GAAI,MAAM,0BAA0B;AAAA,IACpC,eAAe,OAAO,OAAO,MAAM;AAAA,IACnC,kBAAkB,SAAS;AAAA,IAC3B,gBAAgB,aAAa;AAAA,IAC7B,cAAc,WAAW;AAAA,IACzB,iBAAiB;AAAA,IACjB,WAAW,OAAO,KAAK;AAAA,EACzB,CAAC;AAED,QAAM,cAAc,KAAK,IAAI;AAC7B,QAAM,SAAS,MAAM,YAAY;AAAA,IAC/B;AAAA,IACA,UAAU;AAAA,IACV,SAAS,iBAAiB,KAAK;AAAA;AAAA;AAAA,IAG/B,cAAc;AAAA,IACd,cAAc,CAAC,QAAQ,QAAQ,QAAQ,MAAM;AAAA;AAAA;AAAA,IAG7C,SAAS,aAAa,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ;AAAA,IAC9C,eAAe,2BAA2B,OAAO,MAAM,WAAW,KAAK;AAAA,EACzE,CAAC;AAED,sBAAoB,aAAa,OAAO,MAAM,WAAW;AAEzD,MAAI,CAAC,OAAO,SAAS;AACnB,eAAW,qBAAqB,OAAO,KAAK,EAAE;AAC9C,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,oBAAoB,OAAO,MAAM;AAChD,QAAM,iBAAiB,OAAO,KAAK,OAAO,OAAO,iBAAiB;AAClE,QAAM,WAAW,OAAO,KAAK,OAAO,OAAO,WAAW,CAAC;AAEvD,QAAM,OAAO,MAAM,yBAAyB,OAAO,MAAM;AACzD,QAAM,cAAc,iBAAiB,QAAQ,IAAI;AACjD,QAAM,KAAK,OAAO;AAClB,QAAM,UAAU,MAAM,cAAc;AAEpC,QAAM,SAAS;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU,cAAc,OAAO,MAAM;AAAA,IACrC,UAAU,oBAAoB;AAAA,MAC5B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,MAAM,OAAO;AAAA,MACb,YAAY,OAAO,KAAK,OAAO,OAAO;AAAA,IACxC,CAAC;AAAA,EACH;AAIA,QAAM,YACJ,KAAK,aAAa,wBAAwB,MAAM,MAAM,mBAAmB,GAAG,SAAS;AACvF,MAAI;AACF,UAAM,UAAU,QAAQ,MAAM;AAAA,EAChC,SAAS,KAAK;AACZ;AAAA,MACE,wCAAwC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IAC1F;AACA,WAAO;AAAA,EACT;AAEA,MAAI,cAAc,MAAM;AACtB,UAAM,aAAa;AAAA,MACjB;AAAA,MACA,mBAAmB,OAAO;AAAA,MAC1B;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAEA,YAAU,WAAW,UAAU,EAAE;AAIjC,MAAI,CAAC,OAAO,MAAM,mBAAmB,MAAM;AACzC,eAAW,kCAAkC,OAAO,KAAK,sBAAsB,OAAO,KAAK,EAAE;AAC7F,WAAO;AAAA,EACT;AAEA,MAAI,mBAAmB,mBAAmB;AACxC,eAAW,cAAc,OAAO,MAAM,mBAAmB;AACzD,eAAW,WAAW,UAAU;AAC9B,cAAQ,IAAI,OAAO,OAAO,EAAE;AAAA,IAC9B;AACA,WAAO,YAAY,gBAAgB,MAAM;AAAA,EAC3C;AAEA;AAAA,IACE,mBAAmB,YACf,cAAc,OAAO,MAAM,cAC3B,cAAc,OAAO,MAAM;AAAA,EACjC;AACA,SAAO,YAAY,gBAAgB,MAAM;AAC3C;","names":["join","join","join","join"]}