issue-flow 0.13.0 → 0.15.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 (163) hide show
  1. package/dist/agent-AT64RXTA.js +30 -0
  2. package/dist/{analyze-WS47UYGN.js → analyze-O2PYDIFK.js} +21 -18
  3. package/dist/{analyze-WS47UYGN.js.map → analyze-O2PYDIFK.js.map} +1 -1
  4. package/dist/{apply-OS62UWRN.js → apply-SSU6QCD4.js} +14 -11
  5. package/dist/{apply-OS62UWRN.js.map → apply-SSU6QCD4.js.map} +1 -1
  6. package/dist/{bench-SA3BOME5.js → bench-ODFPAGW6.js} +30 -26
  7. package/dist/{bench-SA3BOME5.js.map → bench-ODFPAGW6.js.map} +1 -1
  8. package/dist/{chunk-HJGGNLWI.js → chunk-2ZAM45PZ.js} +9 -9
  9. package/dist/{chunk-QCXPNSEL.js → chunk-3BWZO4YI.js} +2 -2
  10. package/dist/{chunk-J62QSVP6.js → chunk-4RXYFWP7.js} +8 -7
  11. package/dist/{chunk-J62QSVP6.js.map → chunk-4RXYFWP7.js.map} +1 -1
  12. package/dist/{chunk-ARFVHC2F.js → chunk-5ZKBIXET.js} +115 -23
  13. package/dist/chunk-5ZKBIXET.js.map +1 -0
  14. package/dist/{chunk-HEF2RMKN.js → chunk-6E2PEBFT.js} +18 -16
  15. package/dist/{chunk-HEF2RMKN.js.map → chunk-6E2PEBFT.js.map} +1 -1
  16. package/dist/{agent-BXRIEKVM.js → chunk-6FXOU2KR.js} +22 -22
  17. package/dist/chunk-6FXOU2KR.js.map +1 -0
  18. package/dist/{chunk-IYV6KNVF.js → chunk-6QYMSNVA.js} +66 -240
  19. package/dist/chunk-6QYMSNVA.js.map +1 -0
  20. package/dist/{chunk-UOCPC2Y2.js → chunk-6SRE4KD7.js} +4 -4
  21. package/dist/{chunk-MIVHK5YI.js → chunk-A35HE4V7.js} +3 -3
  22. package/dist/{chunk-TM67EHSV.js → chunk-ARWNYXFL.js} +19 -19
  23. package/dist/chunk-ARWNYXFL.js.map +1 -0
  24. package/dist/chunk-AYYMW33T.js +163 -0
  25. package/dist/chunk-AYYMW33T.js.map +1 -0
  26. package/dist/chunk-BMHGRYL6.js +890 -0
  27. package/dist/chunk-BMHGRYL6.js.map +1 -0
  28. package/dist/{chunk-PV4OX5TC.js → chunk-F4OK7KEP.js} +11 -11
  29. package/dist/{chunk-QXDNBYQF.js → chunk-FK4IDY6P.js} +21 -10
  30. package/dist/chunk-FK4IDY6P.js.map +1 -0
  31. package/dist/{chunk-KQHB7ZT3.js → chunk-GSZCQUCY.js} +82 -16
  32. package/dist/chunk-GSZCQUCY.js.map +1 -0
  33. package/dist/{chunk-TX5WIVYP.js → chunk-HAKDLR6N.js} +6 -1
  34. package/dist/chunk-HAKDLR6N.js.map +1 -0
  35. package/dist/{chunk-OPNGPUKJ.js → chunk-HGSYD57B.js} +143 -48
  36. package/dist/chunk-HGSYD57B.js.map +1 -0
  37. package/dist/{chunk-PIVGJM6K.js → chunk-KAHQ6F5C.js} +11 -11
  38. package/dist/{chunk-BHJCGWQX.js → chunk-MEFY4B7T.js} +9 -9
  39. package/dist/{chunk-BQDQTIM7.js → chunk-MGCOQU6N.js} +4 -4
  40. package/dist/{chunk-5TB6KLC2.js → chunk-MROS3SKL.js} +8 -8
  41. package/dist/chunk-NVZMBXTE.js +243 -0
  42. package/dist/chunk-NVZMBXTE.js.map +1 -0
  43. package/dist/{chunk-664Y3NDX.js → chunk-PCN7ZQND.js} +8 -8
  44. package/dist/{chunk-IV5SS4JN.js → chunk-QRCGW2ZO.js} +9 -7
  45. package/dist/{chunk-IV5SS4JN.js.map → chunk-QRCGW2ZO.js.map} +1 -1
  46. package/dist/{chunk-7KD66LIM.js → chunk-RPXDBX6R.js} +3 -3
  47. package/dist/{chunk-SNTTOQAD.js → chunk-RWEXK55D.js} +2 -2
  48. package/dist/{chunk-6FDFRNXZ.js → chunk-TBDP6QPJ.js} +25 -20
  49. package/dist/chunk-TBDP6QPJ.js.map +1 -0
  50. package/dist/chunk-UNQYMR3H.js +25 -0
  51. package/dist/chunk-UNQYMR3H.js.map +1 -0
  52. package/dist/{chunk-HQAHNIAR.js → chunk-VR34TXHF.js} +14 -14
  53. package/dist/{chunk-CNBYCMSU.js → chunk-WM3Y7Q4A.js} +27 -18
  54. package/dist/{chunk-CNBYCMSU.js.map → chunk-WM3Y7Q4A.js.map} +1 -1
  55. package/dist/{chunk-NEZZ6KDR.js → chunk-XDKGA6UX.js} +52 -3
  56. package/dist/chunk-XDKGA6UX.js.map +1 -0
  57. package/dist/{chunk-FZDR7CGH.js → chunk-XG2NYDRA.js} +3 -3
  58. package/dist/{chunk-3XLCNVMD.js → chunk-XLVGPXNL.js} +2 -2
  59. package/dist/{chunk-DIG5XKQQ.js → chunk-XQHFC62U.js} +2 -2
  60. package/dist/{chunk-UJ7XWK7J.js → chunk-XRLRKTQA.js} +16 -14
  61. package/dist/{chunk-UJ7XWK7J.js.map → chunk-XRLRKTQA.js.map} +1 -1
  62. package/dist/{chunk-LZ6GFQAG.js → chunk-ZFIXEYUJ.js} +44 -31
  63. package/dist/chunk-ZFIXEYUJ.js.map +1 -0
  64. package/dist/{chunk-37N2RJW2.js → chunk-ZPIVCM7Q.js} +3 -3
  65. package/dist/{chunk-TVN2JVOF.js → chunk-ZXPASFHG.js} +3 -3
  66. package/dist/cli.js +58 -64
  67. package/dist/cli.js.map +1 -1
  68. package/dist/{config-MEM2IWWN.js → config-UMP6DBJB.js} +12 -9
  69. package/dist/{conventions-WV4NDXGH.js → conventions-DKH7AE4T.js} +14 -11
  70. package/dist/{conventions-WV4NDXGH.js.map → conventions-DKH7AE4T.js.map} +1 -1
  71. package/dist/diagnostics-GOGCLTRS.js +23 -0
  72. package/dist/execute-E3BRMDAQ.js +35 -0
  73. package/dist/{generate-MVQI7A45.js → generate-AGBT7EHX.js} +23 -20
  74. package/dist/{generate-MVQI7A45.js.map → generate-AGBT7EHX.js.map} +1 -1
  75. package/dist/{git-YRVRUN3C.js → git-AC542GU2.js} +4 -2
  76. package/dist/init-LLBVJNPE.js +24 -0
  77. package/dist/{operations-LU6JA3BX.js → operations-JQYK2EMT.js} +17 -14
  78. package/dist/{operations-LU6JA3BX.js.map → operations-JQYK2EMT.js.map} +1 -1
  79. package/dist/{permissions-7NMDFU27.js → permissions-6HL52K54.js} +4 -4
  80. package/dist/plan-HSLWDJEZ.js +34 -0
  81. package/dist/{policy-JSYEK5B2.js → policy-IMRXGG7X.js} +13 -10
  82. package/dist/{policy-JSYEK5B2.js.map → policy-IMRXGG7X.js.map} +1 -1
  83. package/dist/pr-6TCZXG6K.js +39 -0
  84. package/dist/pr-review-6MWJO2NP.js +32 -0
  85. package/dist/prd-D6YDYAFX.js +33 -0
  86. package/dist/{ps-TNE44R5V.js → ps-KUUZ6TUL.js} +11 -8
  87. package/dist/{ps-TNE44R5V.js.map → ps-KUUZ6TUL.js.map} +1 -1
  88. package/dist/{recorder-XGF55WXI.js → recorder-BJ2KIAWT.js} +5 -4
  89. package/dist/registry-SB7XTMGE.js +15 -0
  90. package/dist/{resume-QMEPMSZW.js → resume-QNLQSCR5.js} +44 -40
  91. package/dist/{resume-QMEPMSZW.js.map → resume-QNLQSCR5.js.map} +1 -1
  92. package/dist/review-3KJ4BH2V.js +35 -0
  93. package/dist/{routing-2ZUMZPJB.js → routing-TN7GLMGU.js} +13 -10
  94. package/dist/{routing-2ZUMZPJB.js.map → routing-TN7GLMGU.js.map} +1 -1
  95. package/dist/run-5EHIFF2V.js +54 -0
  96. package/dist/run-5EHIFF2V.js.map +1 -0
  97. package/dist/session-publisher-T7EI3CIP.js +11 -0
  98. package/dist/session-publisher-T7EI3CIP.js.map +1 -0
  99. package/dist/{state-manager-X3PT3FGT.js → state-manager-ZEPWTCA6.js} +3 -3
  100. package/dist/state-manager-ZEPWTCA6.js.map +1 -0
  101. package/dist/{usage-VCFLDA5Y.js → usage-QL563I7F.js} +13 -10
  102. package/dist/{usage-VCFLDA5Y.js.map → usage-QL563I7F.js.map} +1 -1
  103. package/dist/{web-XVHLX62B.js → web-PPSMNTQ6.js} +22 -57
  104. package/dist/{web-XVHLX62B.js.map → web-PPSMNTQ6.js.map} +1 -1
  105. package/package.json +1 -1
  106. package/web/AGENTS.md +154 -11
  107. package/web/public/app.css +343 -110
  108. package/web/public/app.js +436 -27
  109. package/web/public/index.html +44 -1
  110. package/dist/agent-BXRIEKVM.js.map +0 -1
  111. package/dist/chunk-6FDFRNXZ.js.map +0 -1
  112. package/dist/chunk-ARFVHC2F.js.map +0 -1
  113. package/dist/chunk-IHIEXUTC.js +0 -483
  114. package/dist/chunk-IHIEXUTC.js.map +0 -1
  115. package/dist/chunk-IYV6KNVF.js.map +0 -1
  116. package/dist/chunk-KQHB7ZT3.js.map +0 -1
  117. package/dist/chunk-LZ6GFQAG.js.map +0 -1
  118. package/dist/chunk-NEZZ6KDR.js.map +0 -1
  119. package/dist/chunk-OPNGPUKJ.js.map +0 -1
  120. package/dist/chunk-QXDNBYQF.js.map +0 -1
  121. package/dist/chunk-TM67EHSV.js.map +0 -1
  122. package/dist/chunk-TX5WIVYP.js.map +0 -1
  123. package/dist/execute-NITIHHCK.js +0 -31
  124. package/dist/init-OKWEC3EV.js +0 -21
  125. package/dist/plan-LEJJF3DE.js +0 -31
  126. package/dist/pr-HPVUZ7S3.js +0 -36
  127. package/dist/pr-review-R2QZBT3F.js +0 -29
  128. package/dist/prd-5O6GYCC6.js +0 -30
  129. package/dist/registry-LGPE53QD.js +0 -15
  130. package/dist/review-YKWD6SNN.js +0 -32
  131. package/dist/run-YU4TR62R.js +0 -50
  132. /package/dist/{config-MEM2IWWN.js.map → agent-AT64RXTA.js.map} +0 -0
  133. /package/dist/{chunk-HJGGNLWI.js.map → chunk-2ZAM45PZ.js.map} +0 -0
  134. /package/dist/{chunk-QCXPNSEL.js.map → chunk-3BWZO4YI.js.map} +0 -0
  135. /package/dist/{chunk-UOCPC2Y2.js.map → chunk-6SRE4KD7.js.map} +0 -0
  136. /package/dist/{chunk-MIVHK5YI.js.map → chunk-A35HE4V7.js.map} +0 -0
  137. /package/dist/{chunk-PV4OX5TC.js.map → chunk-F4OK7KEP.js.map} +0 -0
  138. /package/dist/{chunk-PIVGJM6K.js.map → chunk-KAHQ6F5C.js.map} +0 -0
  139. /package/dist/{chunk-BHJCGWQX.js.map → chunk-MEFY4B7T.js.map} +0 -0
  140. /package/dist/{chunk-BQDQTIM7.js.map → chunk-MGCOQU6N.js.map} +0 -0
  141. /package/dist/{chunk-5TB6KLC2.js.map → chunk-MROS3SKL.js.map} +0 -0
  142. /package/dist/{chunk-664Y3NDX.js.map → chunk-PCN7ZQND.js.map} +0 -0
  143. /package/dist/{chunk-7KD66LIM.js.map → chunk-RPXDBX6R.js.map} +0 -0
  144. /package/dist/{chunk-SNTTOQAD.js.map → chunk-RWEXK55D.js.map} +0 -0
  145. /package/dist/{chunk-HQAHNIAR.js.map → chunk-VR34TXHF.js.map} +0 -0
  146. /package/dist/{chunk-FZDR7CGH.js.map → chunk-XG2NYDRA.js.map} +0 -0
  147. /package/dist/{chunk-3XLCNVMD.js.map → chunk-XLVGPXNL.js.map} +0 -0
  148. /package/dist/{chunk-DIG5XKQQ.js.map → chunk-XQHFC62U.js.map} +0 -0
  149. /package/dist/{chunk-37N2RJW2.js.map → chunk-ZPIVCM7Q.js.map} +0 -0
  150. /package/dist/{chunk-TVN2JVOF.js.map → chunk-ZXPASFHG.js.map} +0 -0
  151. /package/dist/{execute-NITIHHCK.js.map → config-UMP6DBJB.js.map} +0 -0
  152. /package/dist/{git-YRVRUN3C.js.map → diagnostics-GOGCLTRS.js.map} +0 -0
  153. /package/dist/{init-OKWEC3EV.js.map → execute-E3BRMDAQ.js.map} +0 -0
  154. /package/dist/{permissions-7NMDFU27.js.map → git-AC542GU2.js.map} +0 -0
  155. /package/dist/{plan-LEJJF3DE.js.map → init-LLBVJNPE.js.map} +0 -0
  156. /package/dist/{pr-HPVUZ7S3.js.map → permissions-6HL52K54.js.map} +0 -0
  157. /package/dist/{pr-review-R2QZBT3F.js.map → plan-HSLWDJEZ.js.map} +0 -0
  158. /package/dist/{prd-5O6GYCC6.js.map → pr-6TCZXG6K.js.map} +0 -0
  159. /package/dist/{recorder-XGF55WXI.js.map → pr-review-6MWJO2NP.js.map} +0 -0
  160. /package/dist/{registry-LGPE53QD.js.map → prd-D6YDYAFX.js.map} +0 -0
  161. /package/dist/{review-YKWD6SNN.js.map → recorder-BJ2KIAWT.js.map} +0 -0
  162. /package/dist/{run-YU4TR62R.js.map → registry-SB7XTMGE.js.map} +0 -0
  163. /package/dist/{state-manager-X3PT3FGT.js.map → review-3KJ4BH2V.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/shell.ts","../src/resilience/policy.ts","../src/resilience/retry.ts","../src/utils/git.ts"],"sourcesContent":["import { basename } from 'node:path';\nimport { type Options as ExecaOptions, execa } from 'execa';\nimport {\n type ClassifiedFailure,\n classify,\n type FailureSignal,\n type FailureSource,\n} from '../resilience/errors.js';\nimport type { RetryPolicy } from '../resilience/policy.js';\nimport { type RetryPolicyFor, withRetry } from '../resilience/retry.js';\n\nexport interface ExecResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n /**\n * The classified failure of the last attempt. Only ever set on the retry\n * path, and only when that attempt failed — a caller that passes no `retry`\n * gets exactly the three fields it has always got.\n */\n failure?: ClassifiedFailure;\n /** How many times the command actually ran. Only set on the retry path. */\n attempts?: number;\n}\n\n/** What `onRetryAttempt` is told once an attempt has produced a result. */\nexport interface RunRetryAttempt {\n /** 1-based number of the attempt that just finished. */\n attempt: number;\n result: ExecResult;\n /** `null` when the attempt succeeded. */\n failure: ClassifiedFailure | null;\n /** Whether another attempt follows this one. */\n willRetry: boolean;\n /** The wait before that next attempt; `0` when there is none. */\n delayMs: number;\n}\n\nexport interface RunOptions extends ExecaOptions {\n /** Skip global diagnostics when a non-zero exit is an expected probe result. */\n diagnostics?: boolean;\n /**\n * Opt in to retrying this invocation.\n *\n * Absent — the default, and what every call site of this project did before\n * this option existed — means one `execa` call with `reject: false` and no\n * extra attempt, byte for byte.\n *\n * Only pass a policy for a command whose non-zero exit means *it failed*.\n * A command whose non-zero exit is a legitimate answer (`git rev-parse\n * --verify --quiet`, `gh pr view` on a branch with no PR) is safe anyway —\n * an unclassifiable exit lands on `unknown`, which is not a retryable kind —\n * but the intent is worth being explicit about.\n *\n * A function instead of a policy resolves it from the failure that actually\n * happened, which is how one `gh` call gets the `network` budget for a DNS\n * blip and the `rate_limit` budget for a rate limit.\n */\n retry?: RetryPolicy | RetryPolicyFor;\n /** Overrides the subsystem inferred from `command` (`git`, `gh`). */\n source?: FailureSource;\n /**\n * Cuts a pending backoff short. Named apart from execa's own `cancelSignal`\n * because it governs the wait *between* attempts, not the child process.\n */\n retrySignal?: AbortSignal;\n /** Observe each attempt: log it, publish an event, persist the error. */\n onRetryAttempt?: (info: RunRetryAttempt) => void | Promise<void>;\n}\n\n/* ── which subsystem produced the failure ───────────────────────────────── */\n\nconst SOURCE_BY_COMMAND: Readonly<Record<string, FailureSource>> = {\n git: 'git',\n gh: 'github',\n};\n\nfunction inferSource(command: string): FailureSource {\n return SOURCE_BY_COMMAND[basename(command)] ?? 'internal';\n}\n\nfunction diagnoseCommandFailure(command: string, args: string[], result: ExecResult): void {\n if (result.exitCode === 0) return;\n void import('../storage/diagnostics.js').then(({ writeDiagnostic }) => {\n writeDiagnostic({\n level: 'warning',\n message: `Command failed: ${basename(command)}`,\n context: {\n command: basename(command),\n args,\n exitCode: result.exitCode,\n stderr: result.stderr.slice(0, 4_000),\n },\n });\n });\n}\n\n/* ── git invocations that are never repeated ────────────────────────────── */\n\n/**\n * `git` global options that swallow the token after them, so the subcommand of\n * `git -C /repo push --force` is `push` and not `/repo`.\n */\nconst GIT_GLOBAL_OPTIONS_WITH_VALUE: ReadonlySet<string> = new Set([\n '-C',\n '-c',\n '--git-dir',\n '--work-tree',\n '--namespace',\n '--exec-path',\n '--super-prefix',\n]);\n\n/**\n * git invocations no retry policy may repeat, whatever the caller asked for.\n *\n * `true` means the subcommand is never retried; a list means it is destructive\n * only when one of those tokens is present. This is the second of the Epic's\n * three limits — *no destructive operation is run automatically to fix state* —\n * enforced at the one chokepoint every `git` call of the project goes through.\n *\n * It errs on the side of not retrying: a sequencer operation left half-applied\n * (`rebase`, `cherry-pick`, `merge`, `am`, `revert`) is exactly the state a\n * blind second attempt turns into a lost commit, so those are absolute even\n * though a transient failure of one of them is conceivable.\n */\nconst DESTRUCTIVE_GIT_COMMANDS: Readonly<Record<string, true | readonly string[]>> = {\n rebase: true,\n 'cherry-pick': true,\n revert: true,\n merge: true,\n am: true,\n restore: true,\n 'filter-branch': true,\n gc: true,\n prune: true,\n 'update-ref': true,\n push: ['-f', '--force', '--force-with-lease', '--force-if-includes', '--delete', '--mirror'],\n reset: ['--hard', '--merge', '--keep'],\n clean: ['-f', '--force'],\n checkout: ['-f', '--force', '-B'],\n switch: ['-f', '--force', '--discard-changes'],\n branch: ['-d', '-D', '--delete', '-M'],\n tag: ['-d', '--delete', '-f', '--force'],\n stash: ['drop', 'clear', 'pop'],\n worktree: ['remove', 'prune'],\n submodule: ['deinit'],\n};\n\nfunction gitSubcommand(args: readonly string[]): { name: string; rest: readonly string[] } | null {\n for (let index = 0; index < args.length; index++) {\n const arg = args[index];\n if (arg === undefined) continue;\n if (GIT_GLOBAL_OPTIONS_WITH_VALUE.has(arg)) {\n index++;\n continue;\n }\n if (arg.startsWith('-')) continue;\n return { name: arg, rest: args.slice(index + 1) };\n }\n return null;\n}\n\n/**\n * Whether `rest` carries `token`. Long options also match their `=value` form,\n * and a short option also matches bundled into another (`git clean -fd` is\n * `-f -d`), which is how `-f` is actually written in practice.\n */\nfunction hasToken(rest: readonly string[], token: string): boolean {\n if (token.startsWith('--')) {\n return rest.some((arg) => arg === token || arg.startsWith(`${token}=`));\n }\n if (token.startsWith('-')) {\n const letter = token.slice(1);\n return rest.some((arg) => /^-[A-Za-z]+$/.test(arg) && arg.slice(1).includes(letter));\n }\n return rest.includes(token);\n}\n\n/** Whether this invocation may be retried at all, whatever the policy says. */\nexport function isRetryableInvocation(command: string, args: readonly string[]): boolean {\n if (basename(command) !== 'git') return true;\n\n const subcommand = gitSubcommand(args);\n if (subcommand === null) return true;\n\n const rule = DESTRUCTIVE_GIT_COMMANDS[subcommand.name];\n if (rule === undefined) return true;\n if (rule === true) return false;\n\n return !rule.some((token) => hasToken(subcommand.rest, token));\n}\n\n/* ── the failure signal ─────────────────────────────────────────────────── */\n\n/**\n * The fields execa puts on a failed result when `reject: false` is set. They\n * are not on its success type, hence the narrowing: `code` is where the spawn\n * errno lands (`ENOENT`, `ETIMEDOUT`, `ECONNRESET`, `EAI_AGAIN`), and\n * `originalMessage` is the only place the reason shows up when the child never\n * started and therefore wrote nothing to stderr.\n */\ninterface ExecaFailureFields {\n code?: unknown;\n signal?: unknown;\n timedOut?: unknown;\n originalMessage?: unknown;\n shortMessage?: unknown;\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' && value !== '' ? value : undefined;\n}\n\nfunction toFailureSignal(raw: unknown, result: ExecResult, source: FailureSource): FailureSignal {\n const fields = (raw ?? {}) as ExecaFailureFields;\n const errno = asString(fields.code);\n const signal = asString(fields.signal);\n // A command that never started has an empty stderr; execa's own message is\n // then the only evidence there is, so it stands in for the missing output.\n const stderr =\n result.stderr.trim() !== ''\n ? result.stderr\n : (asString(fields.originalMessage) ?? asString(fields.shortMessage) ?? '');\n\n return {\n source,\n exitCode: result.exitCode,\n stdout: result.stdout,\n stderr,\n ...(errno === undefined ? {} : { errno }),\n ...(signal === undefined ? {} : { signal }),\n ...(fields.timedOut === true ? { timedOut: true } : {}),\n };\n}\n\n/* ── the executor ───────────────────────────────────────────────────────── */\n\nasync function execOnce(\n command: string,\n args: string[],\n options: ExecaOptions,\n): Promise<{ result: ExecResult; raw: unknown }> {\n const raw = await execa(command, args, {\n reject: false,\n ...options,\n });\n\n return {\n raw,\n result: {\n stdout: raw.stdout?.toString() ?? '',\n stderr: raw.stderr?.toString() ?? '',\n exitCode: raw.exitCode ?? 1,\n },\n };\n}\n\n/**\n * Execute a command with arguments and capture its output.\n * Uses execa which calls execFile internally (no shell injection risk).\n * Does not throw on non-zero exit codes.\n *\n * With `options.retry` it becomes the retrying chokepoint every `gh` and `git`\n * call of the project already passes through: the failure is classified from\n * structured evidence (errno, signal, `timedOut`, exit code — text last) and\n * handed to `withRetry`, the single retry executor. Without it nothing changes.\n */\nexport async function run(\n command: string,\n args: string[] = [],\n options?: RunOptions,\n): Promise<ExecResult> {\n const {\n retry,\n source,\n retrySignal,\n onRetryAttempt,\n diagnostics = true,\n ...execaOptions\n } = options ?? {};\n\n if (retry === undefined || !isRetryableInvocation(command, args)) {\n const result = (await execOnce(command, args, execaOptions)).result;\n if (diagnostics) diagnoseCommandFailure(command, args, result);\n return result;\n }\n\n const failureSource = source ?? inferSource(command);\n\n const outcome = await withRetry(\n async () => {\n const { result, raw } = await execOnce(command, args, execaOptions);\n const failure =\n result.exitCode === 0 ? null : classify(toFailureSignal(raw, result, failureSource));\n return { result, failure };\n },\n {\n policy: retry,\n evaluate: (attempt) => attempt.failure,\n ...(retrySignal === undefined ? {} : { signal: retrySignal }),\n ...(onRetryAttempt === undefined\n ? {}\n : {\n onAttempt: (info) =>\n onRetryAttempt({\n attempt: info.attempt,\n result: info.value.result,\n failure: info.value.failure,\n willRetry: info.willRetry,\n delayMs: info.delayMs,\n }),\n }),\n },\n );\n\n const result = {\n ...outcome.value.result,\n attempts: outcome.attempts,\n ...(outcome.failure === null ? {} : { failure: outcome.failure }),\n };\n if (diagnostics) diagnoseCommandFailure(command, args, result);\n return result;\n}\n","/**\n * Retry policy: how long to wait, how many times, and what to do when the\n * budget runs out.\n *\n * `errors.ts` answers *what went wrong*; this module answers *what to do about\n * it*. It is pure — no I/O, no clock of its own, no knowledge of phases — so\n * every decision here is reproducible from its arguments. `retry.ts` is the\n * only executor that acts on what this module returns.\n *\n * The run-level state machine lives here too, for the same reason: whether a\n * run may go from `blocked` to `running` is a policy question, and there must\n * be exactly one answer to it in the codebase.\n *\n * The invariants of this layer are in `src/resilience/AGENTS.md`.\n */\n\nimport { type FailureKind, requiresHumanAction } from './errors.js';\n\n/* ── the policy ─────────────────────────────────────────────────────────── */\n\n/** What to do with the provider once a kind keeps failing. */\nexport type FailoverMode = 'never' | 'after_attempts' | 'immediate';\n\n/** What a failure becomes once the attempt budget is spent. */\nexport type ExhaustedAction = 'fail' | 'block';\n\nexport interface RetryPolicy {\n /** `0` means the kind is never retried by this layer. */\n maxAttempts: number;\n initialDelayMs: number;\n /** The ceiling of the backoff — honoured even under `retryForever`. */\n maxDelayMs: number;\n backoffFactor: number;\n jitter: 'none' | 'full';\n retryForever: boolean;\n failover: FailoverMode;\n /** Attempts to burn before `failover: 'after_attempts'` kicks in. */\n failoverAfterAttempts: number;\n onExhausted: ExhaustedAction;\n}\n\n/** A partial policy, as a configuration layer supplies it. */\nexport type RetryPolicyOverride = Partial<RetryPolicy>;\n\n/* ── configuration ──────────────────────────────────────────────────────── */\n\nexport type ResilienceProfile = 'default' | 'continuous';\n\n/**\n * The configuration keys of `resilience.retry`, one per `FailureKind` in the\n * camelCase spelling the JSON uses.\n */\nexport type RetryConfigKey =\n | 'network'\n | 'timeout'\n | 'stalled'\n | 'rateLimit'\n | 'providerDown'\n | 'providerCrash'\n | 'authentication'\n | 'configuration'\n | 'repositoryState'\n | 'taskExecution'\n | 'internal'\n | 'unknown';\n\nconst CONFIG_KEYS: Readonly<Record<FailureKind, RetryConfigKey>> = {\n network: 'network',\n timeout: 'timeout',\n stalled: 'stalled',\n rate_limit: 'rateLimit',\n provider_down: 'providerDown',\n provider_crash: 'providerCrash',\n authentication: 'authentication',\n configuration: 'configuration',\n repository_state: 'repositoryState',\n task_execution: 'taskExecution',\n internal: 'internal',\n unknown: 'unknown',\n};\n\n/** The configuration key `resilience.retry` uses for `kind`. */\nexport function retryConfigKey(kind: FailureKind): RetryConfigKey {\n return CONFIG_KEYS[kind];\n}\n\n/**\n * Every key `resilience.retry` accepts, for the layers that have to iterate\n * them (the schema in `storage/schemas.ts`, the merge in `config.ts`). Derived\n * from `CONFIG_KEYS` rather than written a second time, so a new `FailureKind`\n * cannot reach the configuration surface half-declared.\n */\nexport const RETRY_CONFIG_KEYS: readonly RetryConfigKey[] = Object.values(CONFIG_KEYS);\n\n/**\n * The slice of the `resilience` configuration this module reads. The full key\n * (providers, queue, watchdog, journal, decompose) is a superset of this, and\n * satisfies it structurally.\n */\nexport interface PolicyConfig {\n profile?: ResilienceProfile;\n /**\n * Whether a credential failure may migrate to another provider. Explicit on\n * purpose, never a default: switching provider because the main one lost its\n * credential hides exactly what the user needs to be told.\n */\n failoverOnAuth?: boolean;\n retry?: Partial<Record<RetryConfigKey, RetryPolicyOverride>>;\n}\n\n/* ── the defaults ───────────────────────────────────────────────────────── */\n\nconst SECOND = 1000;\n\nfunction policy(overrides: RetryPolicyOverride): RetryPolicy {\n return {\n maxAttempts: 0,\n initialDelayMs: 0,\n maxDelayMs: 0,\n backoffFactor: 2,\n jitter: 'full',\n retryForever: false,\n failover: 'never',\n failoverAfterAttempts: 2,\n onExhausted: 'fail',\n ...overrides,\n };\n}\n\n/**\n * The table of the PRD, verbatim. Changing a number here changes documented\n * behaviour: `policy.test.ts` asserts this table cell by cell on purpose.\n */\nconst BASE_POLICIES: Readonly<Record<FailureKind, RetryPolicy>> = {\n network: policy({ maxAttempts: 8, initialDelayMs: 2 * SECOND, maxDelayMs: 120 * SECOND }),\n timeout: policy({\n maxAttempts: 2,\n initialDelayMs: 30 * SECOND,\n maxDelayMs: 120 * SECOND,\n failover: 'after_attempts',\n }),\n stalled: policy({\n maxAttempts: 2,\n initialDelayMs: 15 * SECOND,\n maxDelayMs: 15 * SECOND,\n failover: 'after_attempts',\n }),\n rate_limit: policy({\n maxAttempts: 6,\n initialDelayMs: 60 * SECOND,\n maxDelayMs: 900 * SECOND,\n failover: 'after_attempts',\n }),\n provider_down: policy({\n maxAttempts: 4,\n initialDelayMs: 10 * SECOND,\n maxDelayMs: 300 * SECOND,\n failover: 'after_attempts',\n }),\n provider_crash: policy({\n maxAttempts: 3,\n initialDelayMs: 5 * SECOND,\n maxDelayMs: 60 * SECOND,\n failover: 'after_attempts',\n }),\n authentication: policy({ maxAttempts: 0, onExhausted: 'block' }),\n configuration: policy({ maxAttempts: 0 }),\n repository_state: policy({ maxAttempts: 0, onExhausted: 'block' }),\n task_execution: policy({ maxAttempts: 0 }),\n internal: policy({ maxAttempts: 2, initialDelayMs: 5 * SECOND, maxDelayMs: 5 * SECOND }),\n unknown: policy({ maxAttempts: 2, initialDelayMs: 5 * SECOND, maxDelayMs: 5 * SECOND }),\n};\n\n/**\n * The `continuous` profile — autonomous, long-running execution. It only ever\n * *widens* a budget: what is not retryable under `default` is not retryable\n * here either.\n */\nconst CONTINUOUS_OVERRIDES: Readonly<Partial<Record<FailureKind, RetryPolicyOverride>>> = {\n network: { retryForever: true },\n timeout: { maxAttempts: 3 },\n stalled: { maxAttempts: 3 },\n rate_limit: { retryForever: true },\n provider_down: { retryForever: true, failover: 'after_attempts' },\n provider_crash: { maxAttempts: 5 },\n};\n\n/**\n * Resolve the policy of a `FailureKind`: base table → profile → user\n * configuration, in that order.\n *\n * The kinds that need a human (`authentication`, `configuration`,\n * `repository_state`, `task_execution`) are clamped **after** the user layer:\n * no configuration, no profile and no `retryForever` can buy them an attempt.\n * That clamp is the golden rule of `AGENTS.md` made unbypassable.\n */\nexport function resolvePolicy(kind: FailureKind, config: PolicyConfig = {}): RetryPolicy {\n const base = BASE_POLICIES[kind];\n const profile = config.profile === 'continuous' ? CONTINUOUS_OVERRIDES[kind] : undefined;\n const user = config.retry?.[retryConfigKey(kind)];\n\n const resolved: RetryPolicy = { ...base, ...profile, ...user };\n\n if (!requiresHumanAction(kind)) return sanitize(resolved);\n\n return sanitize({\n ...resolved,\n maxAttempts: 0,\n retryForever: false,\n failover:\n kind === 'authentication' && config.failoverOnAuth === true ? resolved.failover : 'never',\n });\n}\n\n/** Guard against a configuration layer supplying a number that breaks the math. */\nfunction sanitize(value: RetryPolicy): RetryPolicy {\n return {\n ...value,\n maxAttempts: Math.max(0, Math.trunc(value.maxAttempts)),\n initialDelayMs: Math.max(0, value.initialDelayMs),\n maxDelayMs: Math.max(0, value.maxDelayMs),\n backoffFactor: value.backoffFactor >= 1 ? value.backoffFactor : 1,\n failoverAfterAttempts: Math.max(1, Math.trunc(value.failoverAfterAttempts)),\n };\n}\n\n/* ── the decisions ──────────────────────────────────────────────────────── */\n\n/**\n * Whether another attempt is allowed after `attemptsMade` attempts have\n * already failed.\n */\nexport function shouldRetry(policy: RetryPolicy, attemptsMade: number): boolean {\n if (policy.maxAttempts <= 0) return false;\n if (policy.retryForever) return true;\n return attemptsMade < policy.maxAttempts;\n}\n\n/** Whether the next attempt should be handed to a different provider. */\nexport function shouldFailover(policy: RetryPolicy, attemptsMade: number): boolean {\n if (policy.failover === 'never') return false;\n if (policy.failover === 'immediate') return true;\n return attemptsMade >= policy.failoverAfterAttempts;\n}\n\nexport interface DelayOptions {\n /** Injectable RNG, so the jitter is deterministic under test. */\n random?: () => number;\n /** What the server asked for, when it asked (`Retry-After`). */\n retryAfterMs?: number;\n}\n\n/**\n * The delay before attempt number `attemptsMade + 1`.\n *\n * `ceiling = min(maxDelayMs, initialDelayMs * backoffFactor^(attemptsMade-1))`,\n * and full jitter — the default — draws uniformly from `[0, ceiling)`. The\n * ceiling applies under `retryForever` exactly as it does otherwise: waiting\n * forever is allowed, hammering is not.\n *\n * A server-supplied `Retry-After` wins outright, un-jittered and uncapped: the\n * point of honouring it is not to come back before it says, and capping it by\n * `maxDelayMs` would do precisely that.\n */\nexport function computeDelayMs(\n policy: RetryPolicy,\n attemptsMade: number,\n options: DelayOptions = {},\n): number {\n if (options.retryAfterMs !== undefined && options.retryAfterMs >= 0) {\n return Math.round(options.retryAfterMs);\n }\n\n const exponent = Math.max(0, attemptsMade - 1);\n const growth = policy.initialDelayMs * policy.backoffFactor ** exponent;\n const ceiling = Math.min(policy.maxDelayMs, growth);\n if (ceiling <= 0) return 0;\n\n if (policy.jitter === 'none') return Math.round(ceiling);\n\n const random = options.random ?? Math.random;\n return Math.round(random() * ceiling);\n}\n\n/* ── the abortable delay ────────────────────────────────────────────────── */\n\n/**\n * Wait `ms`, unless `signal` fires first.\n *\n * Resolves `true` when the full delay elapsed and `false` when it was cut\n * short — a caller that waited fifteen minutes and a caller that was\n * interrupted must not take the same next step. It never throws: an abort is\n * an expected outcome here, not an error.\n */\nexport function abortableDelay(\n ms: number,\n options: { signal?: AbortSignal } = {},\n): Promise<boolean> {\n const { signal } = options;\n if (signal?.aborted === true) return Promise.resolve(false);\n if (ms <= 0) return Promise.resolve(true);\n\n return new Promise((resolve) => {\n const onAbort = () => {\n clearTimeout(timer);\n resolve(false);\n };\n\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve(true);\n }, ms);\n\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n\n/* ── the run state machine ──────────────────────────────────────────────── */\n\n/**\n * The state of a run — a queued issue in `execution-plan.json`, or the single\n * issue of a `tasks.json`.\n */\nexport type RunStatus =\n | 'queued'\n | 'running'\n | 'waiting'\n | 'retrying'\n | 'paused'\n | 'blocked'\n | 'failed'\n | 'completed'\n | 'cancelled';\n\n/**\n * Every legal transition, in one place.\n *\n * `completed` and `cancelled` are terminal: nothing leaves them. `blocked` has\n * exits, but none the pipeline may take on its own — see `canTransition`.\n */\nexport const RUN_TRANSITIONS: Readonly<Record<RunStatus, readonly RunStatus[]>> = {\n queued: ['running', 'blocked', 'cancelled'],\n running: ['waiting', 'retrying', 'paused', 'blocked', 'failed', 'completed', 'cancelled'],\n waiting: ['running', 'retrying', 'paused', 'blocked', 'failed', 'cancelled'],\n retrying: ['running', 'waiting', 'paused', 'blocked', 'failed', 'cancelled'],\n paused: ['running', 'blocked', 'cancelled'],\n blocked: ['running', 'cancelled'],\n failed: ['running', 'cancelled'],\n completed: [],\n cancelled: [],\n};\n\n/** Nothing leaves these, by anyone. */\nconst TERMINAL_STATUSES: ReadonlySet<RunStatus> = new Set<RunStatus>(['completed', 'cancelled']);\n\n/** Whether `status` is terminal. */\nexport function isTerminalRunStatus(status: RunStatus): boolean {\n return TERMINAL_STATUSES.has(status);\n}\n\n/** Who is attempting the transition. The pipeline is the default. */\nexport type TransitionActor = 'pipeline' | 'human';\n\n/**\n * Whether `from → to` is legal for `actor`.\n *\n * `blocked` exists precisely to stop the machine until someone looks at it, so\n * the pipeline may enter it but never leave it: only `actor: 'human'` — a\n * `resume`, a `cancel` — can. A pipeline able to unblock itself would spin on\n * the same missing credential forever.\n */\nexport function canTransition(\n from: RunStatus,\n to: RunStatus,\n options: { actor?: TransitionActor } = {},\n): boolean {\n if (!RUN_TRANSITIONS[from].includes(to)) return false;\n if (from === 'blocked' && (options.actor ?? 'pipeline') !== 'human') return false;\n return true;\n}\n","/**\n * The single retry executor of the project.\n *\n * `errors.ts` says *what went wrong*, `policy.ts` says *what to do about it*,\n * and this module is the only place that actually does it: one loop, one\n * backoff, one veto. Before it existed the codebase had two independent retry\n * loops — `core/phase-runner.ts` for single-shot phases and the `execute` loop\n * of `core/engine.ts` — with their own counters, their own delays and no way\n * to keep them in step. Both now delegate here.\n *\n * The loop performs no I/O and knows nothing about phases, sessions or\n * publishers: everything observable is handed back through `onAttempt`, so the\n * caller keeps ownership of its own logging, events and persistence.\n *\n * The invariants of this layer are in `src/resilience/AGENTS.md`.\n */\n\nimport { type ClassifiedFailure, requiresHumanAction } from './errors.js';\nimport { abortableDelay, computeDelayMs, type RetryPolicy, shouldRetry } from './policy.js';\n\n/** What `onAttempt` is told once an attempt has produced a value. */\nexport interface RetryAttemptInfo<T> {\n /** 1-based number of the attempt that just finished. */\n attempt: number;\n /** Whatever `fn` returned, failure or not. */\n value: T;\n /** `null` when the attempt succeeded. */\n failure: ClassifiedFailure | null;\n /** Whether another attempt follows this one. */\n willRetry: boolean;\n /** The wait before that next attempt; `0` when there is none. */\n delayMs: number;\n}\n\n/**\n * A policy chosen from the failure that just happened.\n *\n * The kind is only known *after* an attempt, so a call site that spans several\n * kinds — every `gh` invocation is a network failure, a rate limit or an\n * authentication failure depending on the day — hands over the resolver rather\n * than one frozen policy. `resolvePolicy(failure.kind, config)` is what a\n * caller normally passes.\n */\nexport type RetryPolicyFor = (failure: ClassifiedFailure) => RetryPolicy;\n\nexport interface WithRetryOptions<T> {\n /** One policy, or one chosen per classified failure. */\n policy: RetryPolicy | RetryPolicyFor;\n /**\n * The verdict on one attempt's value: `null` for success, a classified\n * failure otherwise.\n *\n * It belongs to the caller because only the caller knows what a failure\n * looks like in its own return type — an exit code, an `ok: false`, a\n * rejected HTTP response. Callers holding structured evidence should build\n * the failure with `classify()` rather than re-deriving it from text.\n */\n evaluate: (value: T, attempt: number) => ClassifiedFailure | null;\n /** Cuts a pending backoff short; see `abortableDelay`. */\n signal?: AbortSignal;\n /**\n * Called once per attempt, before the backoff, with the decision already\n * taken. This is where a caller publishes its `retry` event, prints, or\n * persists an error — awaited, so those writes finish before the wait.\n */\n onAttempt?: (info: RetryAttemptInfo<T>) => void | Promise<void>;\n /** Injectable RNG for the jitter, so a test can assert the exact curve. */\n random?: () => number;\n /** Injectable wait, so a test never sleeps for real. */\n delay?: (ms: number, options: { signal?: AbortSignal }) => Promise<boolean>;\n}\n\nexport interface RetryOutcome<T> {\n /** The value of the last attempt — the successful one, or the final failure. */\n value: T;\n /** How many times `fn` ran. */\n attempts: number;\n /** `null` when the last attempt succeeded. */\n failure: ClassifiedFailure | null;\n /** The failure was retryable and the budget ran out. */\n exhausted: boolean;\n /** `signal` fired during a backoff, so the next attempt never happened. */\n aborted: boolean;\n}\n\n/**\n * Run `fn` until it succeeds, until the policy's budget is spent, or until\n * `signal` fires.\n *\n * Three separate conditions have to hold for another attempt, and all three\n * are checked here rather than by the caller:\n *\n * 1. the failure is retryable at all (`ClassifiedFailure.retryable`);\n * 2. the kind is not one that needs a human — `task_execution`,\n * `authentication`, `configuration` and `repository_state` are vetoed\n * outright, whatever the policy says and even under `retryForever`. This is\n * the golden rule of `AGENTS.md`, enforced a second time here: a caller can\n * hand-build a `RetryPolicy` without going through `resolvePolicy()`, so\n * the clamp there is not on its own enough;\n * 3. the policy still has budget (`shouldRetry`).\n */\nexport async function withRetry<T>(\n fn: (attempt: number) => Promise<T>,\n options: WithRetryOptions<T>,\n): Promise<RetryOutcome<T>> {\n const { policy, evaluate, signal, onAttempt, random } = options;\n const delay = options.delay ?? abortableDelay;\n const policyFor: RetryPolicyFor = typeof policy === 'function' ? policy : () => policy;\n\n for (let attempt = 1; ; attempt++) {\n const value = await fn(attempt);\n const failure = evaluate(value, attempt);\n\n if (failure === null) {\n await onAttempt?.({ attempt, value, failure, willRetry: false, delayMs: 0 });\n return { value, attempts: attempt, failure: null, exhausted: false, aborted: false };\n }\n\n const active = policyFor(failure);\n const eligible = failure.retryable && !requiresHumanAction(failure.kind);\n const willRetry = eligible && shouldRetry(active, attempt);\n const delayMs = willRetry\n ? computeDelayMs(active, attempt, { random, retryAfterMs: failure.retryAfterMs })\n : 0;\n\n await onAttempt?.({ attempt, value, failure, willRetry, delayMs });\n\n if (!willRetry) {\n return { value, attempts: attempt, failure, exhausted: eligible, aborted: false };\n }\n\n if (!(await delay(delayMs, { signal }))) {\n return { value, attempts: attempt, failure, exhausted: false, aborted: true };\n }\n }\n}\n\n/**\n * A fixed policy in the shape the two legacy call sites express their settings\n * in: a number of attempts and a base/max delay in seconds.\n *\n * Deliberately un-jittered. Both call sites have always used a bare\n * `base * 2^(n-1)` and both publish that number in a `retry` event and print\n * it; adding jitter here would change today's behaviour with no configuration\n * asking for it. Jitter arrives with the `resilience` key that configures it.\n */\nexport function fixedBackoffPolicy(\n maxAttempts: number,\n baseSeconds: number,\n maxSeconds: number,\n overrides: Partial<RetryPolicy> = {},\n): RetryPolicy {\n return {\n maxAttempts,\n initialDelayMs: baseSeconds * 1000,\n maxDelayMs: maxSeconds * 1000,\n backoffFactor: 2,\n jitter: 'none',\n retryForever: false,\n failover: 'never',\n failoverAfterAttempts: 2,\n onExhausted: 'fail',\n ...overrides,\n };\n}\n","import { type ExecResult, run } from './shell.js';\n\n/**\n * Get the root directory of the current git repository.\n * Throws if not inside a git repository.\n */\nexport async function getProjectRoot(): Promise<string> {\n const result = await run('git', ['rev-parse', '--show-toplevel']);\n\n if (result.exitCode !== 0) {\n throw new Error(\n 'Not inside a git repository. Please run issue-flow from within a git project.',\n );\n }\n\n return result.stdout.trim();\n}\n\n/**\n * Get the current git branch name.\n * Returns an empty string if in detached HEAD state.\n */\nexport async function getCurrentBranch(): Promise<string> {\n const result = await run('git', ['branch', '--show-current']);\n\n if (result.exitCode !== 0) {\n throw new Error(\n 'Failed to detect git branch. Ensure git is installed and you are inside a repository.',\n );\n }\n\n return result.stdout.trim();\n}\n\n/** Whether a local branch exists, without consulting or mutating remotes. */\nexport async function localBranchExists(name: string, cwd?: string): Promise<boolean> {\n if (name.trim() === '') return false;\n const result = await run('git', ['show-ref', '--verify', '--quiet', `refs/heads/${name}`], {\n ...(cwd === undefined ? {} : { cwd }),\n diagnostics: false,\n });\n return result.exitCode === 0;\n}\n\nexport interface CommitInfo {\n hash: string;\n subject: string;\n committedAt?: string | null;\n storyId?: string | null;\n}\n\n/**\n * Get the base branch of the repository: the remote HEAD (origin/HEAD) when\n * available, otherwise the first of main/master that exists locally.\n * Never throws; defaults to 'main'.\n */\nexport async function getBaseBranch(): Promise<string> {\n const remoteHead = await run('git', ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD'], {\n diagnostics: false,\n });\n if (remoteHead.exitCode === 0) {\n const name = remoteHead.stdout.trim().replace(/^origin\\//, '');\n if (name) return name;\n }\n\n for (const candidate of ['main', 'master']) {\n const check = await run(\n 'git',\n ['rev-parse', '--verify', '--quiet', `refs/heads/${candidate}`],\n { diagnostics: false },\n );\n if (check.exitCode === 0) return candidate;\n }\n\n return 'main';\n}\n\n/**\n * Get the URL of the `origin` remote.\n * Never throws: a missing remote, a non-zero exit code, an empty stdout or a\n * git binary that cannot even be spawned all resolve to `null` — the same\n * discipline as {@link getBaseBranch}, so callers can treat \"no remote\" as an\n * ordinary state instead of an error path.\n *\n * `cwd` selects which repository is queried; omitting it falls back to\n * `process.cwd()`, which is almost never what a caller resolving a specific\n * `projectRoot` wants — pass it explicitly whenever one is available.\n */\nexport async function getRemoteUrl(cwd?: string): Promise<string | null> {\n let result: ExecResult;\n try {\n result = await run('git', ['remote', 'get-url', 'origin'], { cwd, diagnostics: false });\n } catch {\n return null;\n }\n\n if (result.exitCode !== 0) return null;\n\n const url = result.stdout.trim();\n return url === '' ? null : url;\n}\n\n/**\n * Get the abbreviated hash of the current HEAD commit.\n * Never throws, same discipline as {@link getRemoteUrl}: a repository with no\n * commits yet, a non-zero exit code, an empty stdout or a git binary that\n * cannot be spawned all resolve to `null`, so callers can treat \"no HEAD\" as\n * an ordinary state instead of an error path.\n *\n * `cwd` selects which repository is queried; omitting it falls back to\n * `process.cwd()`.\n */\nexport async function getHeadCommit(cwd?: string): Promise<string | null> {\n let result: ExecResult;\n try {\n result = await run('git', ['rev-parse', '--short', 'HEAD'], { cwd, diagnostics: false });\n } catch {\n return null;\n }\n\n if (result.exitCode !== 0) return null;\n\n const hash = result.stdout.trim();\n return hash === '' ? null : hash;\n}\n\n/**\n * Reduce a git remote URL to a canonical `host/path` identity, so that every\n * way of addressing the same repository collapses to the same string:\n *\n * ```\n * https://github.com/org/repo.git -> github.com/org/repo\n * git@github.com:org/repo.git -> github.com/org/repo\n * ssh://git@github.com:22/org/repo.git -> github.com/org/repo\n * https://user:token@github.com/org/repo -> github.com/org/repo\n * https://github.com/Org/Repo/ -> github.com/org/repo\n * ```\n *\n * Pure function: it never shells out, so it is testable without a git\n * repository. Returns `null` when no host and path can be extracted.\n *\n * Known limitation: both host and path are lowercased. Hostnames are\n * case-insensitive by definition, but repository paths are case-sensitive on\n * some self-hosted servers — so `host/org/Repo` and `host/org/repo` normalize\n * to the same identity even if that server considers them distinct. This is a\n * deliberate trade: matching the same repository across clones (where users\n * routinely type the wrong case) matters more than telling apart two repos\n * whose paths differ only by case.\n */\nexport function normalizeRemoteUrl(url: string | null | undefined): string | null {\n if (typeof url !== 'string') return null;\n\n const trimmed = url.trim();\n if (trimmed === '') return null;\n\n let authority: string;\n let path: string;\n\n const scheme = /^[a-zA-Z][a-zA-Z0-9+.-]*:\\/\\//.exec(trimmed);\n if (scheme) {\n const rest = trimmed.slice(scheme[0].length);\n const slash = rest.indexOf('/');\n if (slash === -1) return null;\n authority = rest.slice(0, slash);\n path = rest.slice(slash + 1);\n } else {\n // scp-like syntax: [user@]host:path — everything after the first colon is\n // the path, which is how git itself reads it (no port is possible here).\n const colon = trimmed.indexOf(':');\n if (colon === -1) return null;\n authority = trimmed.slice(0, colon);\n path = trimmed.slice(colon + 1);\n }\n\n // Drop embedded credentials (user, user:token) and the ssh user.\n const at = authority.lastIndexOf('@');\n if (at !== -1) authority = authority.slice(at + 1);\n\n const host = authority.replace(/:\\d+$/, '').toLowerCase();\n if (host === '') return null;\n\n const normalizedPath = path\n .replace(/[?#].*$/, '')\n .replace(/\\/+/g, '/')\n .replace(/^\\/+|\\/+$/g, '')\n .replace(/\\.git$/i, '')\n .replace(/\\/+$/, '')\n .toLowerCase();\n if (normalizedPath === '') return null;\n\n return `${host}/${normalizedPath}`;\n}\n\n/**\n * Remove Basic-Auth-style credentials (`user:token@`, `user@`) embedded in an\n * `http(s)` remote URL, without touching anything else about it — case, port,\n * the `.git` suffix, query string and trailing slashes are all preserved.\n *\n * Deliberately scoped to `http`/`https`: that is the only place a git remote\n * can carry a real secret (a PAT is routinely embedded as\n * `https://x-access-token:TOKEN@host/...` by CI and automation), and the URL\n * stays a valid, clonable remote once the userinfo is gone. SSH has no\n * equivalent syntax — both `ssh://user@host/path` and the scp-like shorthand\n * `user@host:path` require that user segment to connect at all (it is almost\n * always the fixed, non-secret `git` service account), so this function\n * leaves any other scheme, the scp-like form and local paths untouched.\n *\n * Unlike {@link normalizeRemoteUrl}, which collapses a URL to a lossy\n * `host/path` identity, this keeps the URL \"as configured\" — it exists for\n * surfaces (like the web monitor's `repository.remoteUrl`) that want to show\n * the real remote, minus whatever secret an HTTPS token commonly embeds in\n * it.\n *\n * Pure function, never throws. Returns `null` for a nullish or empty input.\n */\nexport function stripRemoteUrlCredentials(url: string | null | undefined): string | null {\n if (typeof url !== 'string') return null;\n\n const trimmed = url.trim();\n if (trimmed === '') return null;\n\n const httpScheme = /^https?:\\/\\//i.exec(trimmed);\n if (!httpScheme) return trimmed;\n\n const rest = trimmed.slice(httpScheme[0].length);\n const slash = rest.indexOf('/');\n const authority = slash === -1 ? rest : rest.slice(0, slash);\n const remainder = slash === -1 ? '' : rest.slice(slash);\n\n const at = authority.lastIndexOf('@');\n if (at === -1) return trimmed;\n return `${httpScheme[0]}${authority.slice(at + 1)}${remainder}`;\n}\n\n/**\n * List commits on HEAD that are not on the given base branch (base..HEAD),\n * most recent first. Never throws; returns [] when the range cannot be\n * resolved (e.g. unknown base branch or shallow clone).\n */\nexport async function getCommitsSince(base: string): Promise<CommitInfo[]> {\n const result = await run('git', [\n 'log',\n '--pretty=format:%h%x09%cI%x09%s%x09%b%x1e',\n `${base}..HEAD`,\n ]);\n if (result.exitCode !== 0) return [];\n\n if (!result.stdout.includes('\\x1e')) {\n return result.stdout\n .split('\\n')\n .filter((line) => line.trim() !== '')\n .map((line) => {\n const tab = line.indexOf('\\t');\n return tab === -1\n ? { hash: line.trim(), subject: '' }\n : { hash: line.slice(0, tab), subject: line.slice(tab + 1) };\n });\n }\n const records = result.stdout.split('\\x1e');\n return records.flatMap((raw) => {\n const record = raw.trim();\n if (record === '') return [];\n const fields = record.split('\\t');\n const [hash = '', committedAt = '', subject = '', ...bodyParts] = fields;\n const body = bodyParts.join('\\t');\n const storyId = /(?:Story:\\s*|\\[)(US-\\d+)/i.exec(`${subject}\\n${body}`)?.[1] ?? null;\n return [\n {\n hash,\n subject,\n committedAt: committedAt || null,\n storyId: storyId?.toUpperCase() ?? null,\n },\n ];\n });\n}\n\n/* ── repository preflight ───────────────────────────────────────────────── */\n\n/**\n * A repository state that stops the pipeline.\n *\n * The whole point of this type is that it is *reported*, never acted on: the\n * second explicit limit of the resilience Epic is that **no destructive\n * operation is ever run automatically to fix state**. A repository mid-rebase\n * is a person's unfinished work, and an `--abort` issued by a tool at 3am is\n * indistinguishable from data loss.\n */\nexport type PreflightBlockKind =\n | 'rebase_in_progress'\n | 'merge_in_progress'\n | 'cherry_pick_in_progress'\n | 'revert_in_progress'\n | 'unmerged_paths'\n | 'detached_head'\n | 'branch_mismatch'\n | 'dirty_tree';\n\nexport interface PreflightBlock {\n kind: PreflightBlockKind;\n /** What is wrong, in one line. */\n message: string;\n /** The command a human would run. Printed, never executed. */\n suggestion: string;\n}\n\nexport interface PreflightResult {\n ok: boolean;\n blocks: PreflightBlock[];\n /** Current branch, or `null` on a detached HEAD. */\n branch: string | null;\n /** Whether the working tree has uncommitted changes. */\n dirty: boolean;\n}\n\n/** What the caller is about to do, which decides how a dirty tree is read. */\nexport type PreflightIntent =\n | /** Continuing the phase that produced the changes. */ 'resume-same-phase'\n | /** Starting a different phase, or a different issue. */ 'new-phase';\n\nexport interface PreflightOptions {\n /** Branch the plan says this work belongs to. `null` skips the check. */\n expectedBranch?: string | null;\n /** Default `new-phase`, the strict reading. */\n intent?: PreflightIntent;\n cwd?: string;\n}\n\n/** `git rev-parse --verify --quiet <ref>` — exit 0 means the ref exists. */\nasync function refExists(ref: string, cwd?: string): Promise<boolean> {\n const result = await run('git', ['rev-parse', '--verify', '--quiet', ref], {\n ...(cwd === undefined ? {} : { cwd }),\n diagnostics: false,\n });\n return result.exitCode === 0;\n}\n\n/** The in-progress sequencer operations, by the ref each one leaves behind. */\nconst SEQUENCER_REFS: readonly {\n ref: string;\n kind: PreflightBlockKind;\n name: string;\n suggestion: string;\n}[] = [\n {\n ref: 'REBASE_HEAD',\n kind: 'rebase_in_progress',\n name: 'a rebase',\n suggestion: 'git rebase --continue (or git rebase --abort)',\n },\n {\n ref: 'MERGE_HEAD',\n kind: 'merge_in_progress',\n name: 'a merge',\n suggestion: 'git merge --continue (or git merge --abort)',\n },\n {\n ref: 'CHERRY_PICK_HEAD',\n kind: 'cherry_pick_in_progress',\n name: 'a cherry-pick',\n suggestion: 'git cherry-pick --continue (or git cherry-pick --abort)',\n },\n {\n ref: 'REVERT_HEAD',\n kind: 'revert_in_progress',\n name: 'a revert',\n suggestion: 'git revert --continue (or git revert --abort)',\n },\n];\n\n/**\n * Describe the repository and decide whether it is safe to write to it.\n *\n * Every check is a *read*: `rev-parse`, `symbolic-ref`, `diff --name-only`,\n * `status --porcelain`, `branch --show-current`. Nothing here checks anything\n * out, resets anything, aborts anything or stashes anything — not on a resume,\n * not under a continuous profile, not ever. A repository in an ambiguous state\n * escalates to a human, which is the only correct answer for a tool that did\n * not put it there.\n *\n * A **dirty tree is not always a problem**: resuming the phase that produced\n * those changes is exactly the case where uncommitted work is expected. It\n * blocks only when the run is about to move on to something else, where the\n * changes would be carried into work they do not belong to.\n */\nexport async function preflightRepository(\n options: PreflightOptions = {},\n): Promise<PreflightResult> {\n const { cwd, expectedBranch, intent = 'new-phase' } = options;\n const at = cwd === undefined ? {} : { cwd };\n const blocks: PreflightBlock[] = [];\n\n for (const sequencer of SEQUENCER_REFS) {\n if (await refExists(sequencer.ref, cwd)) {\n blocks.push({\n kind: sequencer.kind,\n message: `The repository is in the middle of ${sequencer.name} (${sequencer.ref} is present).`,\n suggestion: sequencer.suggestion,\n });\n }\n }\n\n const unmerged = await run('git', ['diff', '--name-only', '--diff-filter=U'], at);\n const conflicted = unmerged.stdout\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => line !== '');\n if (conflicted.length > 0) {\n blocks.push({\n kind: 'unmerged_paths',\n message: `${conflicted.length} file(s) still have unresolved conflicts: ${conflicted.slice(0, 5).join(', ')}${conflicted.length > 5 ? ', …' : ''}.`,\n suggestion: 'Resolve them, then git add the files',\n });\n }\n\n // `symbolic-ref -q HEAD` fails on a detached HEAD, which is the whole test.\n const symbolic = await run('git', ['symbolic-ref', '-q', 'HEAD'], at);\n const detached = symbolic.exitCode !== 0;\n const branch = detached ? null : symbolic.stdout.trim().replace(/^refs\\/heads\\//, '');\n\n if (detached) {\n blocks.push({\n kind: 'detached_head',\n message: 'HEAD is detached, so a commit would belong to no branch.',\n suggestion: `git switch ${expectedBranch ?? '<branch>'}`,\n });\n } else if (\n expectedBranch !== undefined &&\n expectedBranch !== null &&\n expectedBranch !== '' &&\n branch !== expectedBranch\n ) {\n blocks.push({\n kind: 'branch_mismatch',\n message: `The plan works on '${expectedBranch}' but the repository is on '${branch}'.`,\n suggestion: `git switch ${expectedBranch}`,\n });\n }\n\n const status = await run('git', ['status', '--porcelain'], at);\n const dirty = status.stdout.trim() !== '';\n if (dirty && intent !== 'resume-same-phase') {\n blocks.push({\n kind: 'dirty_tree',\n message: 'The working tree has uncommitted changes that do not belong to this phase.',\n suggestion: 'Commit or stash them yourself, then run again',\n });\n }\n\n return { ok: blocks.length === 0, blocks, branch, dirty };\n}\n\n/** The report a caller prints when the preflight blocks. Never a fix. */\nexport function describePreflight(result: PreflightResult): string[] {\n return result.blocks.map((block) => `${block.message} Suggested: ${block.suggestion}`);\n}\n\n/* ── story-level idempotence ────────────────────────────────────────────── */\n\n/** A story id as it appears in a commit subject: `feat(issue-63): US-012 - …`. */\nconst STORY_ID_IN_SUBJECT = /\\bUS-\\d+\\b/g;\n\n/**\n * The story ids already committed on this branch.\n *\n * The execute prompt commits one story per commit, with the id in the subject\n * (`<type>(scope): US-001 - Title`), so the branch's own history is a record of\n * what is done — and it is the *only* record that survives a crash between the\n * commit and the agent writing `passes: true` into the plan.\n *\n * Read-only, and deliberately so: this answers a question, it never fixes\n * anything. Never throws — an unreadable history means \"nothing known\", which\n * leaves the engine doing exactly what it did before.\n */\nexport async function committedStoryIds(baseBranch: string, cwd?: string): Promise<Set<string>> {\n const at = cwd === undefined ? {} : { cwd };\n const range = baseBranch === '' ? 'HEAD' : `${baseBranch}..HEAD`;\n const result = await run('git', ['log', range, '--format=%s'], at);\n if (result.exitCode !== 0) return new Set();\n\n const ids = new Set<string>();\n for (const subject of result.stdout.split('\\n')) {\n for (const match of subject.matchAll(STORY_ID_IN_SUBJECT)) {\n ids.add(match[0]);\n }\n }\n return ids;\n}\n\n/** Whether the working tree has no uncommitted change. Never throws. */\nexport async function isWorkingTreeClean(cwd?: string): Promise<boolean> {\n const at = cwd === undefined ? {} : { cwd };\n const result = await run('git', ['status', '--porcelain'], at);\n if (result.exitCode !== 0) return false;\n return result.stdout.trim() === '';\n}\n"],"mappings":"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","names":["policy","policy","result"]}
@@ -7,6 +7,7 @@ import { basename, join, resolve } from "path";
7
7
  var GLOBAL_DIR_NAME = ".issue-flow";
8
8
  var GLOBAL_ROOT_ENV = "ISSUE_FLOW_HOME";
9
9
  var PROJECTS_DIR_NAME = "projects";
10
+ var LOGS_DIR_NAME = "logs";
10
11
  var ISSUES_DIR_NAME = "issues";
11
12
  var QUEUES_DIR_NAME = "queues";
12
13
  var RUN_LOCK_FILENAME = "run.lock";
@@ -36,6 +37,9 @@ function getGlobalRoot(options = {}) {
36
37
  }
37
38
  return join(home, GLOBAL_DIR_NAME);
38
39
  }
40
+ function getDiagnosticsDir(options = {}) {
41
+ return join(getGlobalRoot(options), LOGS_DIR_NAME);
42
+ }
39
43
  var SLUG_MAX_LENGTH = 32;
40
44
  var HASH_LENGTH = 12;
41
45
  var FALLBACK_SLUG = "project";
@@ -121,9 +125,10 @@ export {
121
125
  ROTATED_EVENTS_FILENAME,
122
126
  VERIFY_FILENAME,
123
127
  getGlobalRoot,
128
+ getDiagnosticsDir,
124
129
  projectIdFromRemote,
125
130
  getProjectDir,
126
131
  getQueuePaths,
127
132
  getIssuePaths
128
133
  };
129
- //# sourceMappingURL=chunk-TX5WIVYP.js.map
134
+ //# sourceMappingURL=chunk-HAKDLR6N.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/storage/paths.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { homedir } from 'node:os';\nimport { basename, join, resolve } from 'node:path';\nimport { getRemoteUrl, normalizeRemoteUrl } from '../utils/git.js';\n\n/** Directory created under the user's home when no override is provided. */\nexport const GLOBAL_DIR_NAME = '.issue-flow';\n\n/** Environment variable that relocates the whole global storage tree. */\nexport const GLOBAL_ROOT_ENV = 'ISSUE_FLOW_HOME';\n\n/** Directory under the global root holding one folder per project. */\nexport const PROJECTS_DIR_NAME = 'projects';\n\n/** Machine-wide structured diagnostic logs, independent of any project. */\nexport const LOGS_DIR_NAME = 'logs';\n\n/** Directory under a project holding one folder per issue. */\nexport const ISSUES_DIR_NAME = 'issues';\n\n/** Directory under a project holding one folder per multi-issue execution queue. */\nexport const QUEUES_DIR_NAME = 'queues';\n\n/** Run-ownership lock, a sibling of `issues/` inside the project directory. */\nexport const RUN_LOCK_FILENAME = 'run.lock';\n\n/** Persisted agent-provider health, shared by every run of one project. */\nexport const PROVIDERS_HEALTH_FILENAME = 'providers.json';\n\n/** Projection and journal filenames shared by storage and the web reader. */\nexport const SESSION_FILENAME = 'session.json';\nexport const EVENTS_FILENAME = 'events.jsonl';\nexport const ROTATED_EVENTS_FILENAME = 'events.1.jsonl';\n/**\n * Acceptance-contract evidence. Named here — and not at the call site — because\n * `verify/run-issue.ts` also writes it in standalone mode, where there is no\n * `IssuePaths` to ask: one constant, two callers, no second spelling.\n */\nexport const VERIFY_FILENAME = 'verify.json';\n\nexport const RUN_LOG_FILENAME = 'run.log';\nexport const ROTATED_RUN_LOG_FILENAME = 'run.log.1';\n\nexport interface GetGlobalRootOptions {\n /** Environment source. Defaults to process.env. */\n env?: NodeJS.ProcessEnv;\n}\n\n/**\n * Resolve the root directory of the global storage tree (`~/.issue-flow`).\n *\n * Every path under the global storage MUST be derived from this function: it is\n * the single seam where `ISSUE_FLOW_HOME` takes effect, which is what lets\n * tests, CI and sandboxes point the whole tree at a temporary directory instead\n * of touching the real `$HOME`. A call site that joins `homedir()` by hand\n * silently opts out of that isolation.\n *\n * Pure and synchronous: it never creates directories nor touches the\n * filesystem — callers decide when (and whether) the directory should exist.\n */\nexport function getGlobalRoot(options: GetGlobalRootOptions = {}): string {\n const env = options.env ?? process.env;\n\n const override = env[GLOBAL_ROOT_ENV]?.trim();\n if (override) {\n // A relative override is resolved against the CWD so that every consumer\n // gets an absolute path, exactly as in the homedir() branch below.\n return resolve(override);\n }\n\n // The only failure mode: an environment where the home directory cannot be\n // determined (some containers and CI images). The override is the escape\n // hatch, so the error names it.\n let home: string;\n try {\n home = homedir();\n } catch {\n home = '';\n }\n if (home.trim() === '') {\n throw new Error(\n `Unable to resolve the home directory for the global storage. Set ${GLOBAL_ROOT_ENV} to an explicit path.`,\n );\n }\n\n return join(home, GLOBAL_DIR_NAME);\n}\n\nexport function getDiagnosticsDir(options: GetGlobalRootOptions = {}): string {\n return join(getGlobalRoot(options), LOGS_DIR_NAME);\n}\n\n/** Maximum length of the human-readable half of a project id. */\nconst SLUG_MAX_LENGTH = 32;\n\n/** Number of hex characters kept from the sha256 of the seed. */\nconst HASH_LENGTH = 12;\n\n/** Used when the repository name has no character that survives sanitization. */\nconst FALLBACK_SLUG = 'project';\n\n/**\n * Reduce an arbitrary repository name to a path-safe slug: lowercase, only\n * `[a-z0-9-]`, runs of separators collapsed, truncated to {@link SLUG_MAX_LENGTH}.\n */\nfunction slugify(name: string): string {\n const slug = name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, SLUG_MAX_LENGTH)\n .replace(/-+$/g, '');\n\n return slug === '' ? FALLBACK_SLUG : slug;\n}\n\n/**\n * Derive a stable identifier for a project, in the form `<slug>-<hash12>`.\n *\n * The seed is canonical rather than local, so the same repository yields the\n * same id on any machine: `remote:<normalizedRemote>` whenever an `origin`\n * remote exists, falling back to `path:<absoluteProjectRoot>` when it does not.\n * That is what makes moving or renaming the local folder harmless for a cloned\n * repository — the history stays attached to the remote identity.\n *\n * The `remote:` / `path:` prefix is part of the hashed seed, so a project\n * identified by path can never collide with one identified by remote even if\n * the two strings were to coincide. The slug is only cosmetic (it makes the\n * directory recognizable); the hash carries the identity.\n *\n * Known limitation of the path fallback: for a repository with no `origin`\n * remote, the absolute path *is* the identity. Moving or renaming that folder\n * produces a different id, and the previous history is left behind under the\n * old one. Configuring a remote before adopting the global storage avoids it.\n */\nexport async function getProjectId(projectRoot: string): Promise<string> {\n const remote = normalizeRemoteUrl(await getRemoteUrl(projectRoot));\n return projectIdFromRemote(remote, projectRoot);\n}\n\n/**\n * Pure half of {@link getProjectId}: derive the id once the normalized remote\n * (or `null`) is already known.\n *\n * Exported so a caller that already resolved the remote for another reason\n * (e.g. {@link resolveStorageMode} in `compat.ts`, which also persists it into\n * `metadata.json`) can compute the id without shelling out to git a second\n * time.\n */\nexport function projectIdFromRemote(remote: string | null, projectRoot: string): string {\n let seed: string;\n let name: string;\n if (remote) {\n // `host/org/repo` -> the repository name is the last path segment.\n seed = `remote:${remote}`;\n name = remote.split('/').pop() ?? '';\n } else {\n const absolute = resolve(projectRoot);\n seed = `path:${absolute}`;\n name = basename(absolute);\n }\n\n const hash = createHash('sha256').update(seed).digest('hex').slice(0, HASH_LENGTH);\n\n return `${slugify(name)}-${hash}`;\n}\n\n/**\n * Absolute directory of a project inside the global storage tree.\n *\n * `projectId` is expected to come from {@link getProjectId}, which already\n * guarantees a single safe path segment.\n */\nexport function getProjectDir(projectId: string, options: GetGlobalRootOptions = {}): string {\n return join(getGlobalRoot(options), PROJECTS_DIR_NAME, projectId);\n}\n\n/**\n * Every artifact of one issue, resolved from a single place.\n *\n * Note that `prdFile` is the requirements document (`prd.md`) and `tasksFile`\n * is the task plan (`tasks.json`) — unlike `ResolvedPaths.prdFile` in\n * `types.ts`, which historically points at `tasks.json`.\n */\nexport interface IssuePaths {\n issueDir: string;\n issueFile: string;\n metadataFile: string;\n prdFile: string;\n tasksFile: string;\n progressFile: string;\n analysisFile: string;\n sessionFile: string;\n eventsFile: string;\n rotatedEventsFile: string;\n runLogFile: string;\n rotatedRunLogFile: string;\n decompositionFile: string;\n verifyFile: string;\n lastBranchFile: string;\n archiveDir: string;\n prReviewDir: string;\n}\n\n/**\n * Artifacts of one multi-issue execution queue.\n *\n * A queue coordinates several issues that share a branch; each issue keeps its\n * own directory under {@link ISSUES_DIR_NAME} exactly as before, and only the\n * coordination state (order, per-issue status, shared branch, consolidated Pull\n * Request) lives here. That is what keeps a single-issue run byte-identical: it\n * never creates a queue at all.\n */\nexport interface QueuePaths {\n queueDir: string;\n planFile: string;\n}\n\n/**\n * Resolve the paths of one execution queue under the global storage.\n *\n * The queue id is the identifier of the primary issue — the first one the user\n * asked for — so re-running the same command finds the same queue and resumes\n * it instead of starting a parallel one.\n *\n * Pure and synchronous like every other helper here: nothing is created.\n */\nexport function getQueuePaths(\n projectId: string,\n queueId: string | number,\n options: GetGlobalRootOptions = {},\n): QueuePaths {\n const queueDir = join(\n getProjectDir(projectId, options),\n QUEUES_DIR_NAME,\n normalizeIssueNumber(queueId),\n );\n\n return { queueDir, planFile: join(queueDir, 'execution-plan.json') };\n}\n\n/**\n * Issue identifiers become path segments, so anything that could escape the\n * issues directory is rejected before it reaches `join` — the same discipline\n * as `normalizeId()` in `issues/providers/local.ts`.\n *\n * Non-numeric identifiers (`auth-refactor`, `pr-184`) are first-class here:\n * the local provider already supports them, so the global storage must too.\n */\nfunction normalizeIssueNumber(issueNumber: string | number): string {\n const normalized = String(issueNumber).trim().replace(/^#/, '');\n\n if (normalized.length === 0) {\n throw new Error('Issue identifier cannot be empty');\n }\n if (/[/\\\\]/.test(normalized) || normalized === '.' || normalized === '..') {\n throw new Error(`Invalid issue identifier: '${issueNumber}'`);\n }\n\n return normalized;\n}\n\n/**\n * Resolve every path of a single issue under the global storage.\n *\n * This is the only place allowed to know the layout of an issue directory:\n * call sites ask for the artifact they need instead of joining names by hand,\n * so renaming or adding an artifact stays a one-file change.\n *\n * Pure and synchronous: nothing here creates directories or touches the\n * filesystem.\n */\nexport function getIssuePaths(\n projectId: string,\n issueNumber: string | number,\n options: GetGlobalRootOptions = {},\n): IssuePaths {\n const issueDir = join(\n getProjectDir(projectId, options),\n ISSUES_DIR_NAME,\n normalizeIssueNumber(issueNumber),\n );\n\n return {\n issueDir,\n issueFile: join(issueDir, 'issue.md'),\n metadataFile: join(issueDir, 'metadata.json'),\n prdFile: join(issueDir, 'prd.md'),\n tasksFile: join(issueDir, 'tasks.json'),\n progressFile: join(issueDir, 'progress.txt'),\n analysisFile: join(issueDir, 'analysis.md'),\n sessionFile: join(issueDir, SESSION_FILENAME),\n // The journal, and the single generation kept when it rotates. The\n // snapshot above is the projection; this pair is the history.\n eventsFile: join(issueDir, EVENTS_FILENAME),\n rotatedEventsFile: join(issueDir, ROTATED_EVENTS_FILENAME),\n runLogFile: join(issueDir, RUN_LOG_FILENAME),\n rotatedRunLogFile: join(issueDir, ROTATED_RUN_LOG_FILENAME),\n // \"This issue looks larger than one run\": written only when the signals\n // agree, and read by a person rather than by the pipeline.\n decompositionFile: join(issueDir, 'decomposition.md'),\n verifyFile: join(issueDir, VERIFY_FILENAME),\n lastBranchFile: join(issueDir, '.last-branch'),\n archiveDir: join(issueDir, 'archive'),\n prReviewDir: join(issueDir, 'pr-review'),\n };\n}\n"],"mappings":"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","names":[]}
@@ -3,10 +3,19 @@ import {
3
3
  formatPhaseLine,
4
4
  loadPhaseTiming,
5
5
  snapshotTimingFields
6
- } from "./chunk-37N2RJW2.js";
6
+ } from "./chunk-ZPIVCM7Q.js";
7
+ import {
8
+ mostRecent,
9
+ prReviewDir,
10
+ readPrReviewIndex,
11
+ runPrReview
12
+ } from "./chunk-XRLRKTQA.js";
13
+ import {
14
+ runPrd
15
+ } from "./chunk-2ZAM45PZ.js";
7
16
  import {
8
17
  runReview
9
- } from "./chunk-QXDNBYQF.js";
18
+ } from "./chunk-FK4IDY6P.js";
10
19
  import {
11
20
  PIPELINE_PHASES,
12
21
  PIPELINE_PHASES_NO_BRANCH,
@@ -28,58 +37,49 @@ import {
28
37
  saveExecutionPlan,
29
38
  setQueueBranch,
30
39
  setQueuePullRequest
31
- } from "./chunk-7KD66LIM.js";
40
+ } from "./chunk-RPXDBX6R.js";
32
41
  import {
33
42
  runInit
34
- } from "./chunk-BHJCGWQX.js";
43
+ } from "./chunk-MEFY4B7T.js";
35
44
  import {
36
45
  printQueueSummary,
37
46
  printRunSummary,
38
47
  runExecute,
39
48
  stripMarkdown
40
- } from "./chunk-LZ6GFQAG.js";
49
+ } from "./chunk-ZFIXEYUJ.js";
41
50
  import {
42
51
  ensureWebMonitor
43
- } from "./chunk-IHIEXUTC.js";
52
+ } from "./chunk-BMHGRYL6.js";
44
53
  import {
45
54
  runPlan
46
- } from "./chunk-TM67EHSV.js";
55
+ } from "./chunk-ARWNYXFL.js";
47
56
  import {
48
57
  runPr
49
- } from "./chunk-PIVGJM6K.js";
50
- import {
51
- mostRecent,
52
- prReviewDir,
53
- readPrReviewIndex,
54
- runPrReview
55
- } from "./chunk-UJ7XWK7J.js";
58
+ } from "./chunk-KAHQ6F5C.js";
56
59
  import {
57
60
  listPullRequests,
58
61
  publishGitState
59
- } from "./chunk-J62QSVP6.js";
60
- import {
61
- runPrd
62
- } from "./chunk-HJGGNLWI.js";
62
+ } from "./chunk-4RXYFWP7.js";
63
63
  import {
64
64
  resolveCommandIssue
65
- } from "./chunk-FZDR7CGH.js";
65
+ } from "./chunk-XG2NYDRA.js";
66
66
  import {
67
67
  getProvider
68
- } from "./chunk-PV4OX5TC.js";
68
+ } from "./chunk-F4OK7KEP.js";
69
69
  import {
70
70
  beginUsageScope,
71
71
  getRunUsageTotals
72
- } from "./chunk-664Y3NDX.js";
72
+ } from "./chunk-PCN7ZQND.js";
73
73
  import {
74
74
  assessDecomposition,
75
75
  buildDecompositionReport,
76
76
  proposeSubIssues,
77
77
  resetAgentInvocationState
78
- } from "./chunk-ARFVHC2F.js";
78
+ } from "./chunk-5ZKBIXET.js";
79
79
  import {
80
80
  JournalPublisher,
81
81
  MultiPublisher
82
- } from "./chunk-QCXPNSEL.js";
82
+ } from "./chunk-3BWZO4YI.js";
83
83
  import {
84
84
  describeRunAgents,
85
85
  getActiveResilienceConfig,
@@ -87,19 +87,16 @@ import {
87
87
  loadIssuesConfig,
88
88
  loadWebConfig,
89
89
  onShutdown
90
- } from "./chunk-CNBYCMSU.js";
90
+ } from "./chunk-WM3Y7Q4A.js";
91
91
  import {
92
92
  resolveIssuePaths,
93
93
  resolveProjectPaths,
94
94
  resolveQueuePaths
95
- } from "./chunk-HQAHNIAR.js";
95
+ } from "./chunk-VR34TXHF.js";
96
96
  import {
97
- FilePublisher,
98
- MemoryPublisher,
99
97
  formatDuration,
100
98
  getIcons,
101
99
  getInactivityTimeout,
102
- getSessionPublisher,
103
100
  isVerbose,
104
101
  printError,
105
102
  printInfo,
@@ -108,26 +105,40 @@ import {
108
105
  setActivityCallback,
109
106
  setInactivityTimeout,
110
107
  setOutputCallback,
111
- setSessionPublisher,
112
108
  setStoryStageCallback,
113
109
  setStoryUpdateCallback
114
- } from "./chunk-IYV6KNVF.js";
110
+ } from "./chunk-NVZMBXTE.js";
115
111
  import {
116
- isoNow,
117
- loadTaskPlan,
118
- saveTaskPlan
119
- } from "./chunk-DIG5XKQQ.js";
112
+ FilePublisher,
113
+ MemoryPublisher,
114
+ getSessionPublisher,
115
+ setSessionPublisher
116
+ } from "./chunk-6QYMSNVA.js";
120
117
  import {
121
- acquireRunLock,
122
- describeRunLockOwner
123
- } from "./chunk-NEZZ6KDR.js";
118
+ bindDiagnosticContext,
119
+ flushDiagnostics,
120
+ writeDiagnostic
121
+ } from "./chunk-AYYMW33T.js";
124
122
  import {
125
123
  committedStoryIds,
126
124
  describePreflight,
127
125
  getBaseBranch,
126
+ getCurrentBranch,
127
+ getHeadCommit,
128
+ getProjectRoot,
129
+ localBranchExists,
128
130
  preflightRepository,
129
131
  run
130
- } from "./chunk-KQHB7ZT3.js";
132
+ } from "./chunk-GSZCQUCY.js";
133
+ import {
134
+ isoNow,
135
+ loadTaskPlan,
136
+ saveTaskPlan
137
+ } from "./chunk-XQHFC62U.js";
138
+ import {
139
+ acquireRunLock,
140
+ describeRunLockOwner
141
+ } from "./chunk-XDKGA6UX.js";
131
142
 
132
143
  // src/commands/run.ts
133
144
  import { randomUUID } from "crypto";
@@ -1015,6 +1026,7 @@ async function runPipeline(issue, mode, from, noBranch, prReview, options = {})
1015
1026
  prReview,
1016
1027
  requested,
1017
1028
  runOptions: options,
1029
+ restartWeb: options.restartWeb,
1018
1030
  ...ownership.interruptedBy === null ? {} : { interruptedBy: ownership.interruptedBy }
1019
1031
  });
1020
1032
  if (first.queue === void 0) {
@@ -1058,6 +1070,21 @@ async function claimRunOwnership(target, detached = false) {
1058
1070
  async function runIssueSession(issueNumber, mode, input) {
1059
1071
  resetAgentInvocationState();
1060
1072
  const paths = await resolveIssuePaths(issueNumber);
1073
+ try {
1074
+ const project = await resolveProjectPaths();
1075
+ bindDiagnosticContext({
1076
+ project: project.projectId,
1077
+ projectRoot: await getProjectRoot(),
1078
+ issue: issueNumber,
1079
+ sessionId: null,
1080
+ executionId: null,
1081
+ phase: null,
1082
+ story: null,
1083
+ harness: null,
1084
+ model: null
1085
+ });
1086
+ } catch {
1087
+ }
1061
1088
  const resilience = await initResilienceConfig();
1062
1089
  const configuredInactivity = resilience.watchdog?.inactivityTimeoutMs;
1063
1090
  if (configuredInactivity !== void 0 && getInactivityTimeout() === void 0) {
@@ -1099,12 +1126,15 @@ async function runIssueSession(issueNumber, mode, input) {
1099
1126
  });
1100
1127
  }
1101
1128
  if (webConfig.enabled) {
1102
- await ensureWebMonitor({
1103
- publisher,
1104
- port: webConfig.port,
1105
- host: webConfig.host,
1106
- refreshSeconds: webConfig.refreshSeconds
1107
- });
1129
+ await ensureWebMonitor(
1130
+ {
1131
+ publisher,
1132
+ port: webConfig.port,
1133
+ host: webConfig.host,
1134
+ refreshSeconds: webConfig.refreshSeconds
1135
+ },
1136
+ { restart: input.restartWeb === true }
1137
+ );
1108
1138
  }
1109
1139
  let result = {
1110
1140
  code: 1,
@@ -1149,6 +1179,12 @@ async function runIssueSession(issueNumber, mode, input) {
1149
1179
  });
1150
1180
  }
1151
1181
  await publisher.close();
1182
+ writeDiagnostic({
1183
+ level: result.code === 0 ? "info" : "error",
1184
+ message: `Issue Flow session ${result.code === 0 ? "completed" : "failed"}`,
1185
+ context: { code: result.code, failedPhase: result.failedPhase }
1186
+ });
1187
+ await flushDiagnostics();
1152
1188
  setSessionPublisher(void 0);
1153
1189
  }
1154
1190
  }
@@ -1210,7 +1246,7 @@ async function maybeCreateSubIssues(issueNumber, plan, reportFile) {
1210
1246
  printInfo("Nothing left to split: no pending story.");
1211
1247
  return;
1212
1248
  }
1213
- const { runGenerate } = await import("./generate-MVQI7A45.js");
1249
+ const { runGenerate } = await import("./generate-AGBT7EHX.js");
1214
1250
  for (const proposal of proposals) {
1215
1251
  const instruction = [
1216
1252
  `Create a sub-issue of #${issueNumber}: ${proposal.title}.`,
@@ -1275,6 +1311,19 @@ async function runPipelinePhases(issueNumber, paths, mode, publisher, input) {
1275
1311
  };
1276
1312
  const tasksPath = paths.tasksFile;
1277
1313
  const sessionId = randomUUID();
1314
+ bindDiagnosticContext({
1315
+ sessionId,
1316
+ issue: issueNumber,
1317
+ executionId: null,
1318
+ phase: null,
1319
+ story: null,
1320
+ harness: null,
1321
+ model: null
1322
+ });
1323
+ const [initialBranch, initialCommit] = await Promise.all([
1324
+ getCurrentBranch().catch(() => ""),
1325
+ getHeadCommit()
1326
+ ]);
1278
1327
  let publishedIssueNumber = toIssueNumber(issueNumber);
1279
1328
  const publishSessionStart = (phases, at, info) => {
1280
1329
  publisher.publish({
@@ -1284,7 +1333,10 @@ async function runPipelinePhases(issueNumber, paths, mode, publisher, input) {
1284
1333
  issueNumber: publishedIssueNumber,
1285
1334
  issueUrl: info?.issueUrl,
1286
1335
  branch: info?.branch,
1336
+ branchCreated: info?.branchCreated,
1337
+ startCommit: info?.startCommit,
1287
1338
  phases: [...phases],
1339
+ configuration: configurationSnapshot,
1288
1340
  environment: {
1289
1341
  node: process.version,
1290
1342
  platform: process.platform,
@@ -1296,6 +1348,29 @@ async function runPipelinePhases(issueNumber, paths, mode, publisher, input) {
1296
1348
  const agentSummary = await describeRunAgents(
1297
1349
  prReview ? ["prd", "plan", "execute", "review", "pr", "pr-review"] : ["prd", "plan", "execute", "review", "pr"]
1298
1350
  );
1351
+ const fallbacks = getActiveResilienceConfig().providers?.chain ?? [];
1352
+ const configurationSnapshot = {
1353
+ precedence: ["default", "global", "project", "env", "cli", "step override"],
1354
+ defaultProvider: {
1355
+ value: agentSummary.defaultProvider,
1356
+ source: agentSummary.defaultOrigin.provider
1357
+ },
1358
+ defaultModel: {
1359
+ value: agentSummary.defaultModel,
1360
+ source: agentSummary.defaultOrigin.model
1361
+ },
1362
+ phases: Object.entries(agentSummary.byPhase).map(([phase, resolved]) => ({
1363
+ phase,
1364
+ provider: { value: resolved.provider, source: resolved.origin.provider },
1365
+ model: { value: resolved.model, source: resolved.origin.model }
1366
+ })),
1367
+ fallbacks,
1368
+ overrides: Object.entries(agentSummary.byPhase).filter(
1369
+ ([, resolved]) => resolved.provider !== agentSummary.defaultProvider || resolved.model !== agentSummary.defaultModel
1370
+ ).map(
1371
+ ([phase, resolved]) => `${phase}: ${resolved.provider}${resolved.model ? ` \xB7 ${resolved.model}` : ""}`
1372
+ )
1373
+ };
1299
1374
  printInfo(
1300
1375
  `Starting pipeline for issue #${issueNumber} (mode: ${mode}, agent: ${agentSummary.label})`
1301
1376
  );
@@ -1394,6 +1469,8 @@ async function runPipelinePhases(issueNumber, paths, mode, publisher, input) {
1394
1469
  const phaseOrder = finalPr ? effectivePrReview ? RUNNABLE_QUEUE_PR_PHASES_WITH_REVIEW : RUNNABLE_QUEUE_PR_PHASES : inQueue ? RUNNABLE_PHASES_NO_BRANCH : effectiveNoBranch ? RUNNABLE_PHASES_NO_BRANCH : effectivePrReview ? RUNNABLE_PHASES_WITH_PR_REVIEW : RUNNABLE_PHASES;
1395
1470
  const queueCommitScope = queue === void 0 ? void 0 : `issue-${issueNumber}`;
1396
1471
  let producedBranch = null;
1472
+ let plannedExecutionBranch = planBranch ?? null;
1473
+ let branchExistedBeforeExecution = effectiveNoBranch || planBranch === void 0 || planBranch === null ? null : await localBranchExists(planBranch).catch(() => null);
1397
1474
  if (queue !== void 0 && finalPr === void 0 && !effectiveNoBranch) {
1398
1475
  producedBranch = await adoptQueueBranch(tasksPath, queue.plan.branchName);
1399
1476
  if (producedBranch !== null) {
@@ -1402,7 +1479,9 @@ async function runPipelinePhases(issueNumber, paths, mode, publisher, input) {
1402
1479
  }
1403
1480
  publishSessionStart(activePhases, sessionStartedAt, {
1404
1481
  issueUrl: planIssueUrl ?? resolvedIssue.issue.remoteRef ?? void 0,
1405
- branch: planBranch
1482
+ branch: effectiveNoBranch ? initialBranch : planBranch,
1483
+ branchCreated: effectiveNoBranch ? false : branchExistedBeforeExecution === true ? false : null,
1484
+ startCommit: initialCommit
1406
1485
  });
1407
1486
  publishIssueDetails(publisher, resolvedIssue.issue, sessionStartedAt);
1408
1487
  publishStorySeed(publisher, planStories, sessionStartedAt);
@@ -1462,11 +1541,24 @@ async function runPipelinePhases(issueNumber, paths, mode, publisher, input) {
1462
1541
  prd: makeRunner(() => runPrd(issueNumber, resolvedIssue), "prd"),
1463
1542
  plan: async () => {
1464
1543
  await makeRunner(
1465
- () => runPlan(issueNumber, resolvedIssue, { continueFlag: continueNumbering, startUs }),
1544
+ () => runPlan(issueNumber, resolvedIssue, {
1545
+ continueFlag: continueNumbering,
1546
+ startUs,
1547
+ ...effectiveNoBranch && initialBranch ? { branchName: initialBranch } : {}
1548
+ }),
1466
1549
  "plan"
1467
1550
  )();
1468
1551
  try {
1469
1552
  const plan = await loadTaskPlan(tasksPath);
1553
+ if (!effectiveNoBranch) {
1554
+ plannedExecutionBranch = plan.branchName;
1555
+ branchExistedBeforeExecution = await localBranchExists(plan.branchName).catch(() => null);
1556
+ publisher.publish({
1557
+ type: "git:update",
1558
+ at: isoNow(),
1559
+ branchCreated: branchExistedBeforeExecution === true ? false : null
1560
+ });
1561
+ }
1470
1562
  publishStorySeed(publisher, plan.userStories, isoNow());
1471
1563
  if (effectiveNoBranch || effectivePrReview) {
1472
1564
  if (effectiveNoBranch) plan.noBranch = true;
@@ -1546,6 +1638,9 @@ async function runPipelinePhases(issueNumber, paths, mode, publisher, input) {
1546
1638
  publisher.publish({ type: "phase:start", at: isoNow(), phase });
1547
1639
  try {
1548
1640
  await fn();
1641
+ if (phase === "execute" && branchExistedBeforeExecution === false && plannedExecutionBranch !== null && await getCurrentBranch().catch(() => "") === plannedExecutionBranch) {
1642
+ publisher.publish({ type: "git:update", at: isoNow(), branchCreated: true });
1643
+ }
1549
1644
  await publishGitState(publisher);
1550
1645
  await publishInstrumentedPhaseEnd(publisher, phase, issueNumber, true);
1551
1646
  } catch (err) {
@@ -2011,4 +2106,4 @@ export {
2011
2106
  publishIssueDetails,
2012
2107
  runPipeline
2013
2108
  };
2014
- //# sourceMappingURL=chunk-OPNGPUKJ.js.map
2109
+ //# sourceMappingURL=chunk-HGSYD57B.js.map