@revisium/orchestrator 0.2.0-alpha.3 → 0.2.0-alpha.5

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 (323) hide show
  1. package/README.md +4 -4
  2. package/control-plane/bootstrap.config.json +108 -550
  3. package/control-plane/default-playbook/catalog/pipelines.json +446 -1123
  4. package/control-plane/default-playbook/catalog/roles.json +1 -1
  5. package/control-plane/default-playbook/catalog/run-profiles.json +338 -0
  6. package/control-plane/default-playbook/package.json +1 -1
  7. package/control-plane/default-playbook/playbook.json +2 -1
  8. package/control-plane/default-playbook/prompts/developer.md +6 -6
  9. package/control-plane/default-playbook/prompts/integrator.md +1 -1
  10. package/dist/__generated__/client/browser.js +18 -0
  11. package/dist/__generated__/client/browser.js.map +1 -0
  12. package/dist/__generated__/client/client.js +32 -0
  13. package/dist/__generated__/client/client.js.map +1 -0
  14. package/dist/__generated__/client/commonInputTypes.js +11 -0
  15. package/dist/__generated__/client/commonInputTypes.js.map +1 -0
  16. package/dist/__generated__/client/enums.js +19 -0
  17. package/dist/__generated__/client/enums.js.map +1 -0
  18. package/dist/__generated__/client/internal/class.js +53 -0
  19. package/dist/__generated__/client/internal/class.js.map +1 -0
  20. package/dist/__generated__/client/internal/prismaNamespace.js +450 -0
  21. package/dist/__generated__/client/internal/prismaNamespace.js.map +1 -0
  22. package/dist/__generated__/client/internal/prismaNamespaceBrowser.js +421 -0
  23. package/dist/__generated__/client/internal/prismaNamespaceBrowser.js.map +1 -0
  24. package/dist/__generated__/client/models/Branch.js +2 -0
  25. package/dist/__generated__/client/models/Branch.js.map +1 -0
  26. package/dist/__generated__/client/models/CostLedgerEntry.js +2 -0
  27. package/dist/__generated__/client/models/CostLedgerEntry.js.map +1 -0
  28. package/dist/__generated__/client/models/FileBlob.js +2 -0
  29. package/dist/__generated__/client/models/FileBlob.js.map +1 -0
  30. package/dist/__generated__/client/models/InboxItem.js +2 -0
  31. package/dist/__generated__/client/models/InboxItem.js.map +1 -0
  32. package/dist/__generated__/client/models/ProjectFileUsage.js +2 -0
  33. package/dist/__generated__/client/models/ProjectFileUsage.js.map +1 -0
  34. package/dist/__generated__/client/models/Revision.js +2 -0
  35. package/dist/__generated__/client/models/Revision.js.map +1 -0
  36. package/dist/__generated__/client/models/RevoProject.js +2 -0
  37. package/dist/__generated__/client/models/RevoProject.js.map +1 -0
  38. package/dist/__generated__/client/models/Row.js +2 -0
  39. package/dist/__generated__/client/models/Row.js.map +1 -0
  40. package/dist/__generated__/client/models/RunAttempt.js +2 -0
  41. package/dist/__generated__/client/models/RunAttempt.js.map +1 -0
  42. package/dist/__generated__/client/models/RunEvent.js +2 -0
  43. package/dist/__generated__/client/models/RunEvent.js.map +1 -0
  44. package/dist/__generated__/client/models/RunOutput.js +2 -0
  45. package/dist/__generated__/client/models/RunOutput.js.map +1 -0
  46. package/dist/__generated__/client/models/RunTask.js +2 -0
  47. package/dist/__generated__/client/models/RunTask.js.map +1 -0
  48. package/dist/__generated__/client/models/Table.js +2 -0
  49. package/dist/__generated__/client/models/Table.js.map +1 -0
  50. package/dist/__generated__/client/models/TableMigration.js +2 -0
  51. package/dist/__generated__/client/models/TableMigration.js.map +1 -0
  52. package/dist/__generated__/client/models/TaskRun.js +2 -0
  53. package/dist/__generated__/client/models/TaskRun.js.map +1 -0
  54. package/dist/__generated__/client/models.js +2 -0
  55. package/dist/__generated__/client/models.js.map +1 -0
  56. package/dist/api/graphql-api/graphql-ws/pubsub.module.js +2 -1
  57. package/dist/api/graphql-api/graphql-ws/pubsub.module.js.map +1 -1
  58. package/dist/api/graphql-api/graphql-ws/subscription-bridge.service.js +110 -28
  59. package/dist/api/graphql-api/graphql-ws/subscription-bridge.service.js.map +1 -1
  60. package/dist/api/graphql-api/graphql-ws/subscription-mappers.js +3 -1
  61. package/dist/api/graphql-api/graphql-ws/subscription-mappers.js.map +1 -1
  62. package/dist/api/graphql-api/inbox/inputs/resolve-gate.input.js +9 -0
  63. package/dist/api/graphql-api/inbox/inputs/resolve-gate.input.js.map +1 -1
  64. package/dist/api/graphql-api/method/inputs/create-run-profile.input.js +54 -0
  65. package/dist/api/graphql-api/method/inputs/create-run-profile.input.js.map +1 -0
  66. package/dist/api/graphql-api/method/inputs/deprecate-run-profile.input.js +37 -0
  67. package/dist/api/graphql-api/method/inputs/deprecate-run-profile.input.js.map +1 -0
  68. package/dist/api/graphql-api/method/inputs/get-run-profile.input.js +32 -0
  69. package/dist/api/graphql-api/method/inputs/get-run-profile.input.js.map +1 -0
  70. package/dist/api/graphql-api/method/inputs/list-run-profiles.input.js +33 -0
  71. package/dist/api/graphql-api/method/inputs/list-run-profiles.input.js.map +1 -0
  72. package/dist/api/graphql-api/method/inputs/update-run-profile.input.js +59 -0
  73. package/dist/api/graphql-api/method/inputs/update-run-profile.input.js.map +1 -0
  74. package/dist/api/graphql-api/method/inputs/validate-run-profile.input.js +33 -0
  75. package/dist/api/graphql-api/method/inputs/validate-run-profile.input.js.map +1 -0
  76. package/dist/api/graphql-api/method/method.resolver.js +76 -1
  77. package/dist/api/graphql-api/method/method.resolver.js.map +1 -1
  78. package/dist/api/graphql-api/method/model/run-profile-connection.model.js +16 -0
  79. package/dist/api/graphql-api/method/model/run-profile-connection.model.js.map +1 -0
  80. package/dist/api/graphql-api/method/model/run-profile-status.model.js +6 -0
  81. package/dist/api/graphql-api/method/model/run-profile-status.model.js.map +1 -0
  82. package/dist/api/graphql-api/method/model/run-profile.model.js +79 -0
  83. package/dist/api/graphql-api/method/model/run-profile.model.js.map +1 -0
  84. package/dist/api/graphql-api/pr/inputs/pr-readiness.input.js +6 -0
  85. package/dist/api/graphql-api/pr/inputs/pr-readiness.input.js.map +1 -1
  86. package/dist/api/graphql-api/registerGraphqlEnums.js +6 -0
  87. package/dist/api/graphql-api/registerGraphqlEnums.js.map +1 -1
  88. package/dist/api/graphql-api/runs/inputs/create-run.input.js +17 -1
  89. package/dist/api/graphql-api/runs/inputs/create-run.input.js.map +1 -1
  90. package/dist/api/graphql-api/runs/inputs/simulate-route.input.js +11 -1
  91. package/dist/api/graphql-api/runs/inputs/simulate-route.input.js.map +1 -1
  92. package/dist/api/graphql-api/runs/model/run-workflow.model.js +5 -0
  93. package/dist/api/graphql-api/runs/model/run-workflow.model.js.map +1 -1
  94. package/dist/api/graphql-api/share/model/issue-action.model.js +7 -0
  95. package/dist/api/graphql-api/share/model/issue-action.model.js.map +1 -0
  96. package/dist/cli/commands/doctor-report.js +1 -13
  97. package/dist/cli/commands/doctor-report.js.map +1 -1
  98. package/dist/cli/commands/lifecycle.js +50 -65
  99. package/dist/cli/commands/lifecycle.js.map +1 -1
  100. package/dist/cli/commands/{revisium-helpers.js → process-helpers.js} +1 -24
  101. package/dist/cli/commands/process-helpers.js.map +1 -0
  102. package/dist/config.js +17 -55
  103. package/dist/config.js.map +1 -1
  104. package/dist/control-plane/bootstrap-json-validator.js +65 -0
  105. package/dist/control-plane/bootstrap-json-validator.js.map +1 -0
  106. package/dist/control-plane/bootstrap.js +25 -38
  107. package/dist/control-plane/bootstrap.js.map +1 -1
  108. package/dist/control-plane/change-notifications.js +45 -14
  109. package/dist/control-plane/change-notifications.js.map +1 -1
  110. package/dist/control-plane/data-access.js +1 -106
  111. package/dist/control-plane/data-access.js.map +1 -1
  112. package/dist/control-plane/default-playbook-policy.js +488 -94
  113. package/dist/control-plane/default-playbook-policy.js.map +1 -1
  114. package/dist/control-plane/definitions.js +4 -5
  115. package/dist/control-plane/definitions.js.map +1 -1
  116. package/dist/control-plane/engine-transport.js +383 -0
  117. package/dist/control-plane/engine-transport.js.map +1 -0
  118. package/dist/control-plane/inbox.js +15 -7
  119. package/dist/control-plane/inbox.js.map +1 -1
  120. package/dist/control-plane/index.js +0 -1
  121. package/dist/control-plane/index.js.map +1 -1
  122. package/dist/control-plane/json-fields.js +1 -1
  123. package/dist/control-plane/json-fields.js.map +1 -1
  124. package/dist/control-plane/merge-override-audit.js +2 -2
  125. package/dist/control-plane/merge-override-audit.js.map +1 -1
  126. package/dist/control-plane/pipeline-coverage-registry.js +486 -0
  127. package/dist/control-plane/pipeline-coverage-registry.js.map +1 -0
  128. package/dist/control-plane/query-types.js +2 -0
  129. package/dist/control-plane/query-types.js.map +1 -0
  130. package/dist/control-plane/run-profiles.js +175 -0
  131. package/dist/control-plane/run-profiles.js.map +1 -0
  132. package/dist/control-plane/schema-migration.js +0 -35
  133. package/dist/control-plane/schema-migration.js.map +1 -1
  134. package/dist/control-plane/seed-default-playbook.js +2 -31
  135. package/dist/control-plane/seed-default-playbook.js.map +1 -1
  136. package/dist/control-plane/tables.js +8 -0
  137. package/dist/control-plane/tables.js.map +1 -1
  138. package/dist/control-plane/transport.js +16 -0
  139. package/dist/control-plane/transport.js.map +1 -0
  140. package/dist/control-plane/versioned-meaning.js +72 -15
  141. package/dist/control-plane/versioned-meaning.js.map +1 -1
  142. package/dist/e2e/kit/agents.js +63 -8
  143. package/dist/e2e/kit/agents.js.map +1 -1
  144. package/dist/e2e/kit/assertions.js +15 -3
  145. package/dist/e2e/kit/assertions.js.map +1 -1
  146. package/dist/e2e/kit/crash.js +8 -4
  147. package/dist/e2e/kit/crash.js.map +1 -1
  148. package/dist/e2e/kit/drive.js +46 -11
  149. package/dist/e2e/kit/drive.js.map +1 -1
  150. package/dist/e2e/kit/env.js +17 -1
  151. package/dist/e2e/kit/env.js.map +1 -1
  152. package/dist/e2e/kit/fake-integrator.js +9 -22
  153. package/dist/e2e/kit/fake-integrator.js.map +1 -1
  154. package/dist/e2e/kit/gh-emulator.js +166 -26
  155. package/dist/e2e/kit/gh-emulator.js.map +1 -1
  156. package/dist/e2e/kit/harness.js +44 -10
  157. package/dist/e2e/kit/harness.js.map +1 -1
  158. package/dist/e2e/kit/index.js +2 -0
  159. package/dist/e2e/kit/index.js.map +1 -1
  160. package/dist/e2e/kit/run-profiles.js +50 -0
  161. package/dist/e2e/kit/run-profiles.js.map +1 -0
  162. package/dist/e2e/kit/scenario.js +207 -0
  163. package/dist/e2e/kit/scenario.js.map +1 -0
  164. package/dist/e2e/kit/scenarios.js +38 -45
  165. package/dist/e2e/kit/scenarios.js.map +1 -1
  166. package/dist/e2e/recovery-crash-child.js +2 -2
  167. package/dist/e2e/recovery-crash-child.js.map +1 -1
  168. package/dist/e2e/recovery-dd-crash-child.js +8 -6
  169. package/dist/e2e/recovery-dd-crash-child.js.map +1 -1
  170. package/dist/engine/dbos.service.js +8 -1
  171. package/dist/engine/dbos.service.js.map +1 -1
  172. package/dist/features/inbox/commands/impl/resolve-gate.command.js.map +1 -1
  173. package/dist/features/method/commands/handlers/method-command.handlers.js +64 -0
  174. package/dist/features/method/commands/handlers/method-command.handlers.js.map +1 -0
  175. package/dist/features/method/commands/impl/create-run-profile.command.js +7 -0
  176. package/dist/features/method/commands/impl/create-run-profile.command.js.map +1 -0
  177. package/dist/features/method/commands/impl/deprecate-run-profile.command.js +7 -0
  178. package/dist/features/method/commands/impl/deprecate-run-profile.command.js.map +1 -0
  179. package/dist/features/method/commands/impl/update-run-profile.command.js +7 -0
  180. package/dist/features/method/commands/impl/update-run-profile.command.js.map +1 -0
  181. package/dist/features/method/commands/index.js +6 -1
  182. package/dist/features/method/commands/index.js.map +1 -1
  183. package/dist/features/method/method-api.service.js +31 -3
  184. package/dist/features/method/method-api.service.js.map +1 -1
  185. package/dist/features/method/queries/handlers/method-query.handlers.js +58 -1
  186. package/dist/features/method/queries/handlers/method-query.handlers.js.map +1 -1
  187. package/dist/features/method/queries/impl/get-run-profile.query.js +7 -0
  188. package/dist/features/method/queries/impl/get-run-profile.query.js.map +1 -0
  189. package/dist/features/method/queries/impl/list-run-profiles.query.js +7 -0
  190. package/dist/features/method/queries/impl/list-run-profiles.query.js.map +1 -0
  191. package/dist/features/method/queries/impl/validate-run-profile.query.js +7 -0
  192. package/dist/features/method/queries/impl/validate-run-profile.query.js.map +1 -0
  193. package/dist/features/method/queries/index.js +4 -1
  194. package/dist/features/method/queries/index.js.map +1 -1
  195. package/dist/features/pr/queries/impl/get-pr-readiness.query.js.map +1 -1
  196. package/dist/features/runs/commands/impl/create-run.command.js.map +1 -1
  197. package/dist/features/runs/queries/impl/simulate-route.query.js.map +1 -1
  198. package/dist/features/shared/connection.js +6 -3
  199. package/dist/features/shared/connection.js.map +1 -1
  200. package/dist/host/daemon.js +32 -8
  201. package/dist/host/daemon.js.map +1 -1
  202. package/dist/host/ensure-host.js +3 -3
  203. package/dist/host/ensure-host.js.map +1 -1
  204. package/dist/host/host-runtime.js.map +1 -1
  205. package/dist/host/host.lifecycle.js +3 -16
  206. package/dist/host/host.lifecycle.js.map +1 -1
  207. package/dist/http/graphql-host.js +17 -8
  208. package/dist/http/graphql-host.js.map +1 -1
  209. package/dist/mcp/mcp-capabilities.js +8 -2
  210. package/dist/mcp/mcp-capabilities.js.map +1 -1
  211. package/dist/mcp/mcp-facade.service.js +53 -10
  212. package/dist/mcp/mcp-facade.service.js.map +1 -1
  213. package/dist/mcp/mcp-tool-result.js +22 -0
  214. package/dist/mcp/mcp-tool-result.js.map +1 -0
  215. package/dist/mcp/mcp-tools.js +108 -1
  216. package/dist/mcp/mcp-tools.js.map +1 -1
  217. package/dist/pipeline/await-human.js +27 -8
  218. package/dist/pipeline/await-human.js.map +1 -1
  219. package/dist/pipeline/data-driven-task.workflow.js +573 -105
  220. package/dist/pipeline/data-driven-task.workflow.js.map +1 -1
  221. package/dist/pipeline/pipeline.service.js +28 -12
  222. package/dist/pipeline/pipeline.service.js.map +1 -1
  223. package/dist/pipeline/route-contract.js +80 -39
  224. package/dist/pipeline/route-contract.js.map +1 -1
  225. package/dist/pipeline-core/condition-scopes.js +15 -0
  226. package/dist/pipeline-core/condition-scopes.js.map +1 -0
  227. package/dist/pipeline-core/index.js +2 -0
  228. package/dist/pipeline-core/index.js.map +1 -1
  229. package/dist/pipeline-core/kit/fixtures.js +81 -44
  230. package/dist/pipeline-core/kit/fixtures.js.map +1 -1
  231. package/dist/pipeline-core/materialize.js +221 -0
  232. package/dist/pipeline-core/materialize.js.map +1 -0
  233. package/dist/pipeline-core/types.js +5 -0
  234. package/dist/pipeline-core/types.js.map +1 -1
  235. package/dist/pipeline-core/validate-loops.js +2 -15
  236. package/dist/pipeline-core/validate-loops.js.map +1 -1
  237. package/dist/pipeline-core/validate-resilience.js +257 -0
  238. package/dist/pipeline-core/validate-resilience.js.map +1 -0
  239. package/dist/pipeline-core/validate.js +2 -0
  240. package/dist/pipeline-core/validate.js.map +1 -1
  241. package/dist/playbook/catalog-loader.js +40 -3
  242. package/dist/playbook/catalog-loader.js.map +1 -1
  243. package/dist/playbook/catalog-schema-validator.js +468 -0
  244. package/dist/playbook/catalog-schema-validator.js.map +1 -0
  245. package/dist/playbook/import-mapper.js +55 -3
  246. package/dist/playbook/import-mapper.js.map +1 -1
  247. package/dist/playbook/manifest.js +5 -0
  248. package/dist/playbook/manifest.js.map +1 -1
  249. package/dist/playbook/playbook-installer.js +17 -1
  250. package/dist/playbook/playbook-installer.js.map +1 -1
  251. package/dist/poller/pr-readiness-core.js +257 -93
  252. package/dist/poller/pr-readiness-core.js.map +1 -1
  253. package/dist/poller/pr-readiness.js +2 -2
  254. package/dist/poller/pr-readiness.js.map +1 -1
  255. package/dist/revisium/inbox.service.js +8 -8
  256. package/dist/revisium/inbox.service.js.map +1 -1
  257. package/dist/revisium/playbooks.service.js +311 -21
  258. package/dist/revisium/playbooks.service.js.map +1 -1
  259. package/dist/revisium/revisium.module.js +8 -8
  260. package/dist/revisium/revisium.module.js.map +1 -1
  261. package/dist/revisium/roles.service.js +3 -1
  262. package/dist/revisium/roles.service.js.map +1 -1
  263. package/dist/revisium/run.service.js +9 -8
  264. package/dist/revisium/run.service.js.map +1 -1
  265. package/dist/revisium/tokens.js +0 -1
  266. package/dist/revisium/tokens.js.map +1 -1
  267. package/dist/run/create-run.js +1 -4
  268. package/dist/run/create-run.js.map +1 -1
  269. package/dist/run/inspect-run.js +33 -29
  270. package/dist/run/inspect-run.js.map +1 -1
  271. package/dist/run/issue-ref.js +97 -2
  272. package/dist/run/issue-ref.js.map +1 -1
  273. package/dist/run/prisma-runtime-data-access.js +616 -0
  274. package/dist/run/prisma-runtime-data-access.js.map +1 -0
  275. package/dist/run/run-outputs.js +39 -14
  276. package/dist/run/run-outputs.js.map +1 -1
  277. package/dist/run/terminal-run-status.js.map +1 -1
  278. package/dist/runners/gh-identity.js +29 -7
  279. package/dist/runners/gh-identity.js.map +1 -1
  280. package/dist/runners/integrator.js +692 -89
  281. package/dist/runners/integrator.js.map +1 -1
  282. package/dist/runners/worktree.service.js +10 -8
  283. package/dist/runners/worktree.service.js.map +1 -1
  284. package/dist/schema/ajv-errors.js +15 -0
  285. package/dist/schema/ajv-errors.js.map +1 -0
  286. package/dist/smoke/isolation.js +7 -16
  287. package/dist/smoke/isolation.js.map +1 -1
  288. package/dist/storage/ensure-storage.js +118 -0
  289. package/dist/storage/ensure-storage.js.map +1 -0
  290. package/dist/storage/revo-database.js +39 -0
  291. package/dist/storage/revo-database.js.map +1 -0
  292. package/dist/storage/revo-prisma.service.js +31 -0
  293. package/dist/storage/revo-prisma.service.js.map +1 -0
  294. package/dist/storage/revo-storage.module.js +18 -0
  295. package/dist/storage/revo-storage.module.js.map +1 -0
  296. package/dist/task-control-plane/pr-readiness.service.js +5 -2
  297. package/dist/task-control-plane/pr-readiness.service.js.map +1 -1
  298. package/dist/task-control-plane/run-watch.service.js +3 -6
  299. package/dist/task-control-plane/run-watch.service.js.map +1 -1
  300. package/dist/task-control-plane/task-control-plane-api.service.js +518 -85
  301. package/dist/task-control-plane/task-control-plane-api.service.js.map +1 -1
  302. package/dist/testing/runtime-data-access.js +175 -0
  303. package/dist/testing/runtime-data-access.js.map +1 -0
  304. package/dist/worker/build-context.js +178 -2
  305. package/dist/worker/build-context.js.map +1 -1
  306. package/dist/worker/claude-code-runner.js +23 -6
  307. package/dist/worker/claude-code-runner.js.map +1 -1
  308. package/dist/worker/codex-runner.js +6 -1
  309. package/dist/worker/codex-runner.js.map +1 -1
  310. package/package.json +20 -7
  311. package/prisma/migrations/20260707080500_initial_schema/migration.sql +259 -0
  312. package/prisma/migrations/20260707103000_prisma_runtime_tables/migration.sql +234 -0
  313. package/prisma/migrations/migration_lock.toml +3 -0
  314. package/prisma/schema.prisma +338 -0
  315. package/prisma.config.ts +11 -0
  316. package/dist/cli/commands/revisium-helpers.js.map +0 -1
  317. package/dist/control-plane/client-transport.js +0 -168
  318. package/dist/control-plane/client-transport.js.map +0 -1
  319. package/dist/engine/ensure-postgres.js +0 -46
  320. package/dist/engine/ensure-postgres.js.map +0 -1
  321. package/dist/host/ensure-revisium.js +0 -135
  322. package/dist/host/ensure-revisium.js.map +0 -1
  323. package/revisium.config.json +0 -10
@@ -12,24 +12,38 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
12
  };
13
13
  import { Inject, Injectable } from '@nestjs/common';
14
14
  import { execFileSync } from 'node:child_process';
15
- import { collectPrReadiness, fetchRequiredCheckNames, } from '../poller/pr-readiness-core.js';
15
+ import { collectPrReadiness, fetchRequiredCheckNames, GITHUB_CHECK_ROLLUP_UNAVAILABLE, } from '../poller/pr-readiness-core.js';
16
16
  import { RunService } from '../revisium/run.service.js';
17
- import { issueRefTag } from '../run/issue-ref.js';
18
- import { resolveGhAccount, resolvePinnedGh } from './gh-identity.js';
17
+ import { hasClosingIssueReference, hasIssueRefToken, issueBodyWithClosingReference, issueRefTag, } from '../run/issue-ref.js';
18
+ import { redactTokens, resolvePinnedGh } from './gh-identity.js';
19
19
  import { gitAbsPath, branchExists, countAhead } from './integrator-git.js';
20
20
  import { resolveOwnerRepo } from './integrator-remote.js';
21
21
  import { branchName } from './integrator-branch-naming.js';
22
22
  export { resolveExecutable } from './integrator-git.js';
23
23
  export { branchName };
24
24
  export { parseOwnerRepo } from './integrator-remote.js';
25
- function issueBoundTitle(title, issueRef, ownerRepo) {
25
+ function issueBoundTitle(title, issueRef, ownerRepo, issueAction = issueRef ? 'close' : 'none') {
26
+ if (!issueRef || issueAction === 'none')
27
+ return title;
28
+ if (hasIssueRefToken(title, issueRef, ownerRepo))
29
+ return title;
26
30
  const tag = issueRefTag(issueRef, ownerRepo);
27
31
  return tag ? `${tag} ${title}` : title;
28
32
  }
29
- function commitMessage(title, issueRef, ownerRepo) {
33
+ function commitMessage(title, issueRef, ownerRepo, issueAction = issueRef ? 'close' : 'none') {
34
+ if (!issueRef || issueAction === 'none')
35
+ return `feat: ${title}`;
30
36
  const tag = issueRefTag(issueRef, ownerRepo);
31
37
  return tag ? `feat: ${tag} ${title}` : `feat: ${title}`;
32
38
  }
39
+ function prBody(body, issueRef, ownerRepo, issueAction) {
40
+ if (issueAction !== 'close')
41
+ return body ?? '';
42
+ return issueBodyWithClosingReference(body, issueRef, ownerRepo);
43
+ }
44
+ function resolvedIssueAction(issueRef, issueAction) {
45
+ return issueAction ?? (issueRef ? 'close' : undefined);
46
+ }
33
47
  function parsePrList(raw) {
34
48
  try {
35
49
  return JSON.parse(raw);
@@ -38,6 +52,22 @@ function parsePrList(raw) {
38
52
  throw new Error(`gh pr list returned non-JSON: ${raw.slice(0, 200)}`);
39
53
  }
40
54
  }
55
+ function prAuthorLogin(author) {
56
+ if (typeof author === 'string' && author.trim().length > 0)
57
+ return author.trim();
58
+ if (author && typeof author === 'object' && typeof author.login === 'string' && author.login.trim().length > 0) {
59
+ return author.login.trim();
60
+ }
61
+ return undefined;
62
+ }
63
+ function asRecord(value) {
64
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
65
+ return undefined;
66
+ return value;
67
+ }
68
+ function nonBlank(value) {
69
+ return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
70
+ }
41
71
  function matchingOpenPr(ownerRepo, branch, base, execGh, jsonFields) {
42
72
  const raw = execGh([
43
73
  'pr',
@@ -56,7 +86,14 @@ function matchingOpenPr(ownerRepo, branch, base, execGh, jsonFields) {
56
86
  const pr = matching[0];
57
87
  if (!pr)
58
88
  throw new Error('unexpected empty match');
59
- return { prUrl: pr.url, prNumber: pr.number, ...(pr.headRefOid ? { headSha: pr.headRefOid } : {}) };
89
+ return {
90
+ prUrl: pr.url,
91
+ prNumber: pr.number,
92
+ ...(pr.headRefOid ? { headSha: pr.headRefOid } : {}),
93
+ ...(pr.title !== undefined ? { title: pr.title } : {}),
94
+ ...(pr.body !== undefined ? { body: pr.body } : {}),
95
+ ...(prAuthorLogin(pr.author) ? { author: prAuthorLogin(pr.author) } : {}),
96
+ };
60
97
  }
61
98
  if (matching.length > 1) {
62
99
  const candidates = matching.map((p) => `#${p.number}`).join(', ');
@@ -67,7 +104,9 @@ function matchingOpenPr(ownerRepo, branch, base, execGh, jsonFields) {
67
104
  }
68
105
  return null;
69
106
  }
70
- function createPr(ownerRepo, branch, base, title, issueRef, execGh) {
107
+ function createPr(ownerRepo, branch, base, title, issueRef, issueAction, execGh) {
108
+ const resolvedTitle = issueBoundTitle(title, issueRef, ownerRepo, issueAction);
109
+ const resolvedBody = prBody(undefined, issueRef, ownerRepo, issueAction);
71
110
  const createOut = execGh([
72
111
  'pr',
73
112
  'create',
@@ -79,9 +118,9 @@ function createPr(ownerRepo, branch, base, title, issueRef, execGh) {
79
118
  '--head',
80
119
  branch,
81
120
  '--title',
82
- issueBoundTitle(title, issueRef, ownerRepo),
121
+ resolvedTitle,
83
122
  '--body',
84
- '',
123
+ resolvedBody,
85
124
  ]);
86
125
  const createdUrl = createOut.trim();
87
126
  const viewRaw = execGh([
@@ -104,19 +143,67 @@ function createPr(ownerRepo, branch, base, title, issueRef, execGh) {
104
143
  'check if the PR was created and update the run manually',
105
144
  };
106
145
  }
107
- return { prUrl: viewData.url, prNumber: viewData.number };
146
+ return { prUrl: viewData.url, prNumber: viewData.number, title: resolvedTitle, body: resolvedBody };
147
+ }
148
+ function repairPr(ownerRepo, prNumber, issueRef, issueAction, title, body, desiredTitle, desiredBody, execGh) {
149
+ if (!issueRef && issueAction !== 'close')
150
+ return;
151
+ const args = ['pr', 'edit', String(prNumber), '--repo', ownerRepo];
152
+ if ((title ?? '') !== desiredTitle)
153
+ args.push('--title', desiredTitle);
154
+ if ((body ?? '') !== desiredBody)
155
+ args.push('--body', desiredBody);
156
+ if (args.length > 5)
157
+ execGh(args);
108
158
  }
109
- function findOrCreatePr(ownerRepo, branch, base, title, issueRef, execGh) {
110
- const existing = matchingOpenPr(ownerRepo, branch, base, execGh, 'number,url,baseRefName');
159
+ function findOrCreatePr(ownerRepo, branch, base, title, issueRef, issueAction, execGh) {
160
+ const existing = matchingOpenPr(ownerRepo, branch, base, execGh, 'number,url,baseRefName,title,body');
111
161
  if (existing && !('needsHuman' in existing)) {
162
+ repairPr(ownerRepo, existing.prNumber, issueRef, issueAction, existing.title, existing.body, issueBoundTitle(existing.title || title, issueRef, ownerRepo, issueAction), prBody(existing.body, issueRef, ownerRepo, issueAction), execGh);
112
163
  return { prUrl: existing.prUrl, prNumber: existing.prNumber };
113
164
  }
114
165
  if (existing)
115
166
  return existing;
116
- return createPr(ownerRepo, branch, base, title, issueRef, execGh);
167
+ return createPr(ownerRepo, branch, base, title, issueRef, issueAction, execGh);
117
168
  }
118
169
  function findExistingPrWithHead(ownerRepo, branch, base, execGh) {
119
- return matchingOpenPr(ownerRepo, branch, base, execGh, 'number,url,baseRefName,headRefOid');
170
+ return matchingOpenPr(ownerRepo, branch, base, execGh, 'number,url,baseRefName,headRefOid,title,body,author');
171
+ }
172
+ function foreignPrProvenance(author, integratorAccount) {
173
+ if (!integratorAccount)
174
+ return {};
175
+ if (!author || author.toLowerCase() === integratorAccount.toLowerCase())
176
+ return {};
177
+ return { foreignPr: true, prAuthor: author, integratorAccount };
178
+ }
179
+ function repairProducedChangePr(context, existing, provenance) {
180
+ if (provenance.foreignPr)
181
+ return;
182
+ repairPr(context.ownerRepo, existing.prNumber, context.issueRef, context.issueAction, existing.title, existing.body, issueBoundTitle(existing.title || context.title, context.issueRef, context.ownerRepo, context.issueAction), prBody(existing.body, context.issueRef, context.ownerRepo, context.issueAction), context.execGh);
183
+ }
184
+ function existingProducedChangeOutput(context, existing, provenance, status) {
185
+ return {
186
+ prUrl: existing.prUrl,
187
+ branch: context.branch,
188
+ prNumber: existing.prNumber,
189
+ ...(context.issueRef ? { issueRef: context.issueRef } : {}),
190
+ headSha: context.change.headSha,
191
+ status,
192
+ ...(status === 'noop' ? { message: 'nothing to integrate — produced head already pushed and equals PR head' } : {}),
193
+ ...provenance,
194
+ };
195
+ }
196
+ function reuseExistingProducedChangePr(context, existing) {
197
+ if (existing.headSha !== context.change.headSha)
198
+ return null;
199
+ const provenance = foreignPrProvenance(existing.author, context.integratorAccount);
200
+ repairProducedChangePr(context, existing, provenance);
201
+ return existingProducedChangeOutput(context, existing, provenance, 'noop');
202
+ }
203
+ function updateExistingProducedChangePr(context, existing) {
204
+ const provenance = foreignPrProvenance(existing.author, context.integratorAccount);
205
+ repairProducedChangePr(context, existing, provenance);
206
+ return existingProducedChangeOutput(context, existing, provenance, 'pushed');
120
207
  }
121
208
  export async function preflightLive(taskId, base, deps) {
122
209
  const { execGit, resolveTaskCwd } = deps;
@@ -172,20 +259,13 @@ export async function preflightLive(taskId, base, deps) {
172
259
  }
173
260
  return { ok: true };
174
261
  }
175
- export function stubIntegrate(input) {
176
- return {
177
- prUrl: 'stub://pr/placeholder',
178
- branch: `feat/${input.taskId}-stub`,
179
- prNumber: 0,
180
- ...(input.issueRef ? { issueRef: input.issueRef } : {}),
181
- };
182
- }
183
262
  export async function captureProducedChange(input, deps) {
184
263
  const { execGit: git, resolveRunCwd } = deps;
185
264
  const cwd = await resolveRunCwd(input.runId, input.taskId);
186
265
  const branch = branchName(input.taskId, input.title, input.issueRef);
187
266
  const ownerRepoResult = resolveOwnerRepo(git, cwd);
188
267
  const ownerRepo = 'needsHuman' in ownerRepoResult ? undefined : ownerRepoResult.ownerRepo;
268
+ const issueAction = resolvedIssueAction(input.issueRef, input.issueAction);
189
269
  if (branchExists(git, cwd, branch)) {
190
270
  git(['switch', branch], cwd);
191
271
  }
@@ -194,13 +274,14 @@ export async function captureProducedChange(input, deps) {
194
274
  }
195
275
  git(['add', '-A'], cwd);
196
276
  if (stagedDiffPresent(git, cwd)) {
197
- git(['commit', '-m', commitMessage(input.title, input.issueRef, ownerRepo)], cwd);
277
+ git(['commit', '-m', commitMessage(input.title, input.issueRef, ownerRepo, issueAction)], cwd);
198
278
  }
199
279
  const headSha = git(['rev-parse', 'HEAD'], cwd).trim();
200
280
  return {
201
281
  branch,
202
282
  headSha,
203
283
  ...(input.issueRef ? { issueRef: input.issueRef } : {}),
284
+ ...(issueAction ? { issueAction } : {}),
204
285
  worktreePath: cwd,
205
286
  ...(input.artifactRef ? { artifactRef: input.artifactRef } : {}),
206
287
  };
@@ -224,6 +305,7 @@ export async function integrate(input, deps) {
224
305
  if ('needsHuman' in ownerRepoResult)
225
306
  return ownerRepoResult;
226
307
  const { ownerRepo } = ownerRepoResult;
308
+ const issueAction = resolvedIssueAction(input.issueRef, input.issueAction);
227
309
  git(['fetch', 'origin', input.base], cwd);
228
310
  if (branchExists(git, cwd, branch)) {
229
311
  git(['switch', branch], cwd);
@@ -233,7 +315,7 @@ export async function integrate(input, deps) {
233
315
  }
234
316
  git(['add', '-A'], cwd);
235
317
  if (stagedDiffPresent(git, cwd)) {
236
- const commitMsg = commitMessage(input.title, input.issueRef, ownerRepo);
318
+ const commitMsg = commitMessage(input.title, input.issueRef, ownerRepo, issueAction);
237
319
  git(['commit', '-m', commitMsg], cwd);
238
320
  }
239
321
  else {
@@ -256,7 +338,7 @@ export async function integrate(input, deps) {
256
338
  }
257
339
  }
258
340
  git(['push', '-u', 'origin', branch], cwd);
259
- const prResult = findOrCreatePr(ownerRepo, branch, input.base, input.title, input.issueRef, gh);
341
+ const prResult = findOrCreatePr(ownerRepo, branch, input.base, input.title, input.issueRef, issueAction, gh);
260
342
  if ('needsHuman' in prResult)
261
343
  return prResult;
262
344
  return { prUrl: prResult.prUrl, branch, prNumber: prResult.prNumber, ...(input.issueRef ? { issueRef: input.issueRef } : {}) };
@@ -266,6 +348,7 @@ async function integrateProducedChange(input, deps, change) {
266
348
  const cwd = change.worktreePath ?? await deps.resolveRunCwd(input.runId, input.taskId);
267
349
  const branch = change.branch;
268
350
  const issueRef = change.issueRef ?? input.issueRef;
351
+ const issueAction = resolvedIssueAction(issueRef, change.issueAction ?? input.issueAction);
269
352
  const ownerRepoResult = resolveOwnerRepo(git, cwd);
270
353
  if ('needsHuman' in ownerRepoResult)
271
354
  return ownerRepoResult;
@@ -274,16 +357,11 @@ async function integrateProducedChange(input, deps, change) {
274
357
  const existing = findExistingPrWithHead(ownerRepo, branch, input.base, gh);
275
358
  if (existing && 'needsHuman' in existing)
276
359
  return existing;
277
- if (existing?.headSha === change.headSha) {
278
- return {
279
- prUrl: existing.prUrl,
280
- branch,
281
- prNumber: existing.prNumber,
282
- ...(issueRef ? { issueRef } : {}),
283
- headSha: change.headSha,
284
- status: 'noop',
285
- message: 'nothing to integrate — produced head already pushed and equals PR head',
286
- };
360
+ const prContext = { ownerRepo, branch, title: input.title, integratorAccount: input.githubAccount, issueRef, issueAction, change, execGh: gh };
361
+ if (existing) {
362
+ const reused = reuseExistingProducedChangePr(prContext, existing);
363
+ if (reused)
364
+ return reused;
287
365
  }
288
366
  if (!existing && countAhead(git, cwd, change.headSha, input.base) === 0) {
289
367
  return {
@@ -294,16 +372,9 @@ async function integrateProducedChange(input, deps, change) {
294
372
  }
295
373
  git(['push', 'origin', `${change.headSha}:refs/heads/${branch}`], cwd);
296
374
  if (existing) {
297
- return {
298
- prUrl: existing.prUrl,
299
- branch,
300
- prNumber: existing.prNumber,
301
- ...(issueRef ? { issueRef } : {}),
302
- headSha: change.headSha,
303
- status: 'pushed',
304
- };
375
+ return updateExistingProducedChangePr(prContext, existing);
305
376
  }
306
- const created = createPr(ownerRepo, branch, input.base, input.title, issueRef, gh);
377
+ const created = createPr(ownerRepo, branch, input.base, input.title, issueRef, issueAction, gh);
307
378
  if ('needsHuman' in created)
308
379
  return created;
309
380
  return {
@@ -315,6 +386,33 @@ async function integrateProducedChange(input, deps, change) {
315
386
  status: 'pushed',
316
387
  };
317
388
  }
389
+ function mergeabilityForConfirmMerge(pr, overrideAccepted) {
390
+ if (!overrideAccepted) {
391
+ return pr.mergeStateStatus === 'CLEAN' ? 'clean' : 'blocked';
392
+ }
393
+ const mergeable = pr.mergeable ?? (pr.mergeStateStatus === 'CLEAN' ? 'MERGEABLE' : undefined);
394
+ return mergeSignal(pr.mergeStateStatus, mergeable);
395
+ }
396
+ function readyFailureIsBenign(message) {
397
+ return /not a draft|already ready(?: for review)?/i.test(message);
398
+ }
399
+ function markPrReadyForReview(input) {
400
+ try {
401
+ input.gh(['pr', 'ready', input.branch, '--repo', input.ownerRepo]);
402
+ return undefined;
403
+ }
404
+ catch (err) {
405
+ const raw = err instanceof Error ? err.message : String(err);
406
+ if (readyFailureIsBenign(raw))
407
+ return undefined;
408
+ const account = input.githubAccount ?? 'resolved-host-account';
409
+ return {
410
+ needsHuman: true,
411
+ lesson: `failed to mark PR #${input.prNumber ?? 'unknown'} ready for review ` +
412
+ `(githubAccount=${account}, repo=${input.ownerRepo}, branch=${input.branch}): ${redactTokens(raw || 'gh pr ready failed')}`,
413
+ };
414
+ }
415
+ }
318
416
  export async function confirmMerge(input, deps) {
319
417
  const { execGit: git, execGh: gh, resolveRunCwd } = deps;
320
418
  const cwd = await resolveRunCwd(input.runId, input.taskId);
@@ -323,8 +421,9 @@ export async function confirmMerge(input, deps) {
323
421
  if ('needsHuman' in ownerRepoResult)
324
422
  return ownerRepoResult;
325
423
  const { ownerRepo } = ownerRepoResult;
424
+ const issueAction = resolvedIssueAction(input.issueRef, input.issueAction);
326
425
  const view = () => {
327
- const raw = gh(['pr', 'view', branch, '--repo', ownerRepo, '--json', 'number,url,state,isDraft,mergeStateStatus']);
426
+ const raw = gh(['pr', 'view', branch, '--repo', ownerRepo, '--json', 'number,url,state,isDraft,mergeStateStatus,mergeable,closingIssuesReferences']);
328
427
  try {
329
428
  return JSON.parse(raw);
330
429
  }
@@ -338,7 +437,9 @@ export async function confirmMerge(input, deps) {
338
437
  if (pr.state !== 'OPEN') {
339
438
  return { needsHuman: true, lesson: `PR #${pr.number} is ${pr.state} (not OPEN) and not merged — resolve manually` };
340
439
  }
341
- if (pr.mergeStateStatus !== 'CLEAN') {
440
+ const overrideAccepted = input.mergeReadiness?.override?.accepted === true;
441
+ const mergeability = mergeabilityForConfirmMerge(pr, overrideAccepted);
442
+ if (mergeability !== 'clean') {
342
443
  return {
343
444
  needsHuman: true,
344
445
  lesson: `PR #${pr.number} is not auto-mergeable (mergeStateStatus=${pr.mergeStateStatus}) — CI not green, ` +
@@ -352,8 +453,22 @@ export async function confirmMerge(input, deps) {
352
453
  lesson: `PR #${pr.number} merge requires a fresh merge readiness headSha guard — re-run readiness before approving merge`,
353
454
  };
354
455
  }
456
+ if (issueAction === 'close' && input.issueRef && !hasClosingIssueReference(pr.closingIssuesReferences, input.issueRef)) {
457
+ return {
458
+ needsHuman: true,
459
+ lesson: `PR #${pr.number} is expected to close ${issueRefTag(input.issueRef, ownerRepo)} but GitHub closingIssuesReferences does not include it`,
460
+ };
461
+ }
355
462
  if (pr.isDraft) {
356
- gh(['pr', 'ready', branch, '--repo', ownerRepo]);
463
+ const readyFailure = markPrReadyForReview({
464
+ gh,
465
+ branch,
466
+ ownerRepo,
467
+ prNumber: pr.number,
468
+ ...(input.githubAccount ? { githubAccount: input.githubAccount } : {}),
469
+ });
470
+ if (readyFailure)
471
+ return readyFailure;
357
472
  }
358
473
  try {
359
474
  gh(['pr', 'merge', branch, '--repo', ownerRepo, '--squash', '--delete-branch', '--match-head-commit', expectedHeadSha]);
@@ -375,14 +490,22 @@ function envInt(name, fallback) {
375
490
  const n = Number.parseInt(process.env[name] ?? '', 10);
376
491
  return Number.isFinite(n) && n > 0 ? n : fallback;
377
492
  }
378
- function defaultCollect(repo, branch, base, execGh, issueRef) {
379
- return collectPrReadiness({ repo, headBranch: branch, baseBranch: base, issueRef, includeReviewThreads: true, includeComments: false }, execGh).then((r) => ({
493
+ function defaultCollect(repo, branch, base, execGh, issueRef, issueAction) {
494
+ return collectPrReadiness({ repo, headBranch: branch, baseBranch: base, issueRef, issueAction, includeReviewThreads: true, includeComments: false }, execGh).then((r) => ({
380
495
  pr: { number: r.pr.number, headSha: r.pr.headSha },
381
496
  checks: { pending: r.checks.pending, fail: r.checks.fail, list: r.checks.list },
382
- reviewThreads: { items: r.reviewThreads.items },
497
+ reviewThreads: {
498
+ items: r.reviewThreads.items,
499
+ unresolvedCount: r.reviewThreads.unresolvedCount,
500
+ ...(r.reviewThreads.truncated ? { truncated: true } : {}),
501
+ },
383
502
  readinessVerdict: r.verdict,
384
503
  nextAction: r.nextAction,
385
504
  evidence: r.evidence,
505
+ mergeStateStatus: r.pr.mergeState,
506
+ mergeable: r.ciSummary.mergeable,
507
+ draft: r.pr.draft,
508
+ feedback: r.feedback,
386
509
  }));
387
510
  }
388
511
  const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -428,8 +551,364 @@ function unsettledReadinessFeedback(input, readiness, reason) {
428
551
  verdict: 'recheck',
429
552
  ciFailures: ciFailuresFrom(readiness),
430
553
  reviewThreads,
554
+ ...(readiness.mergeStateStatus !== undefined ? { mergeStateStatus: readiness.mergeStateStatus } : {}),
555
+ ...(readiness.mergeable !== undefined ? { mergeable: readiness.mergeable } : {}),
556
+ };
557
+ }
558
+ // UNSTABLE/HAS_HOOKS: GitHub marks a PR mergeable even when non-required checks are unsettled.
559
+ // Required-check gating is handled upstream; this quirk is intentional for advisory-only scenarios.
560
+ export function mergeSignal(mergeStateStatus, mergeable) {
561
+ const ms = (mergeStateStatus ?? '').toUpperCase();
562
+ const mg = (mergeable ?? '').toUpperCase();
563
+ if (mg === 'CONFLICTING' || ms === 'DIRTY' || ms === 'BLOCKED' || ms === 'BEHIND')
564
+ return 'blocked';
565
+ if (mg === 'MERGEABLE' && (ms === 'CLEAN' || ms === 'UNSTABLE' || ms === 'HAS_HOOKS'))
566
+ return 'clean';
567
+ return 'unknown';
568
+ }
569
+ function mergeOverrideGate(input) {
570
+ const resolution = asRecord(input.gateResolution);
571
+ const audit = asRecord(resolution?.['mergeOverrideAudit']);
572
+ const note = nonBlank(resolution?.['note']);
573
+ if (!resolution || !note || !audit) {
574
+ return {
575
+ needsHuman: true,
576
+ lesson: 'override_merge requires a non-empty note and mergeOverrideAudit from the merge gate',
577
+ };
578
+ }
579
+ const actor = nonBlank(audit.actor) ?? nonBlank(resolution['resolvedBy']) ?? 'operator';
580
+ const trustedHeadSha = nonBlank(resolution['trustedGateHeadSha']);
581
+ return {
582
+ note,
583
+ audit,
584
+ actor,
585
+ ...(trustedHeadSha ? { trustedHeadSha } : {}),
586
+ source: { gate: 'mergeGate', inboxId: nonBlank(resolution['inboxId']) ?? '' },
587
+ };
588
+ }
589
+ function prReviewThreads(readiness) {
590
+ return readiness.reviewThreads.items.map((thread) => ({
591
+ threadId: thread.id,
592
+ path: thread.path,
593
+ line: thread.line,
594
+ author: thread.author,
595
+ body: thread.body,
596
+ }));
597
+ }
598
+ function checkFact(severity, kind, name, result) {
599
+ return { severity, kind, name, summary: `${name}: ${result}`, evidence: result };
600
+ }
601
+ function emptyFactBuckets() {
602
+ return { hard: [], advisory: [] };
603
+ }
604
+ function pushFact(buckets, fact) {
605
+ buckets[fact.severity].push(fact);
606
+ }
607
+ function reviewThreadFact(thread) {
608
+ return {
609
+ severity: 'advisory',
610
+ kind: 'review_thread',
611
+ threadId: thread.id,
612
+ summary: thread.body,
613
+ evidence: [thread.path, thread.line].filter((item) => item !== undefined).join(':') || thread.url || thread.id,
614
+ };
615
+ }
616
+ function feedbackEvidence(item) {
617
+ return item.evidence ?? item.location ?? item.author ?? item.summary;
618
+ }
619
+ function classifyFeedbackFacts(readiness) {
620
+ const facts = emptyFactBuckets();
621
+ const feedback = readiness.feedback;
622
+ if (!feedback)
623
+ return facts;
624
+ for (const fix of feedback.developerFixes) {
625
+ if (fix.source === 'ci' || fix.source === 'review_thread')
626
+ continue;
627
+ const fact = {
628
+ severity: 'advisory',
629
+ kind: fix.source,
630
+ summary: fix.summary,
631
+ evidence: feedbackEvidence(fix),
632
+ };
633
+ pushFact(facts, fix.source === 'human_review' ? { ...fact, severity: 'hard' } : fact);
634
+ }
635
+ for (const question of feedback.reviewerQuestions) {
636
+ pushFact(facts, {
637
+ severity: 'hard',
638
+ kind: 'reviewer_question',
639
+ summary: question.summary,
640
+ evidence: feedbackEvidence(question),
641
+ });
642
+ }
643
+ for (const decision of feedback.humanDecisions) {
644
+ pushFact(facts, {
645
+ severity: 'hard',
646
+ kind: decision.source,
647
+ summary: decision.summary,
648
+ });
649
+ }
650
+ for (const wait of feedback.providerWait) {
651
+ pushFact(facts, {
652
+ severity: 'advisory',
653
+ kind: `provider_wait:${wait.provider}`,
654
+ summary: wait.evidence,
655
+ evidence: wait.reason,
656
+ });
657
+ }
658
+ return facts;
659
+ }
660
+ function checkStatusFact(check, required, status) {
661
+ const severity = required ? 'hard' : 'advisory';
662
+ const kind = `${required ? 'required' : 'non_required'}_check_${status}`;
663
+ return checkFact(severity, kind, check.name, check.result);
664
+ }
665
+ function pushCheckStatusFact(facts, check, names, required, status) {
666
+ if (!names.includes(check.name))
667
+ return;
668
+ pushFact(facts, checkStatusFact(check, required, status));
669
+ }
670
+ function factsFromRequiredChecks(readiness, required) {
671
+ const facts = emptyFactBuckets();
672
+ for (const check of readiness.checks.list) {
673
+ const isRequired = required.has(check.name);
674
+ pushCheckStatusFact(facts, check, readiness.checks.fail, isRequired, 'failed');
675
+ pushCheckStatusFact(facts, check, readiness.checks.pending, isRequired, 'pending');
676
+ }
677
+ if (readiness.checks.list.length === 0) {
678
+ pushFact(facts, {
679
+ severity: 'advisory',
680
+ kind: 'checks_none_registered',
681
+ summary: 'No registered checks were reported for the PR.',
682
+ evidence: 'checks: none registered',
683
+ });
684
+ }
685
+ return facts;
686
+ }
687
+ function checkRollupUnavailableFact(readiness) {
688
+ const unavailable = readiness.checks.pending.includes(GITHUB_CHECK_ROLLUP_UNAVAILABLE)
689
+ || readiness.evidence.some((item) => item.includes(GITHUB_CHECK_ROLLUP_UNAVAILABLE));
690
+ if (!unavailable)
691
+ return undefined;
692
+ return {
693
+ severity: 'hard',
694
+ kind: 'check_rollup_unavailable',
695
+ summary: 'GitHub check rollup is unavailable; override cannot distinguish pending checks from no registered checks.',
696
+ evidence: GITHUB_CHECK_ROLLUP_UNAVAILABLE,
697
+ };
698
+ }
699
+ function reviewThreadsIncompleteFact(readiness) {
700
+ if (readiness.reviewThreads.truncated !== true)
701
+ return undefined;
702
+ return {
703
+ severity: 'hard',
704
+ kind: 'review_threads_incomplete',
705
+ summary: 'Review thread data is incomplete; override cannot safely resolve every unresolved thread.',
706
+ evidence: `unresolvedCount=${readiness.reviewThreads.unresolvedCount ?? readiness.reviewThreads.items.length}`,
707
+ };
708
+ }
709
+ function mergeOverrideOutput(input) {
710
+ const readiness = input.readiness;
711
+ const evidence = [
712
+ ...(readiness?.evidence ?? []),
713
+ ...(readiness ? readinessEvidence(readiness) : []),
714
+ ...input.facts.map((fact) => `${fact.severity} ${fact.kind}: ${fact.summary}`),
715
+ input.reason,
716
+ `PR headSha=${readiness?.pr.headSha ?? input.fallbackHeadSha ?? input.gate.audit.headSha}`,
717
+ `overrideMerge verdict=${input.verdict}`,
718
+ ].filter((item) => typeof item === 'string' && item.length > 0);
719
+ return {
720
+ prNumber: readiness?.pr.number ?? null,
721
+ headSha: readiness?.pr.headSha ?? input.fallbackHeadSha ?? input.gate.audit.headSha,
722
+ evidence,
723
+ verdict: input.verdict,
724
+ ciFailures: readiness ? ciFailuresFrom(readiness) : [],
725
+ reviewThreads: readiness ? prReviewThreads(readiness) : [],
726
+ ...(readiness?.mergeStateStatus !== undefined ? { mergeStateStatus: readiness.mergeStateStatus } : {}),
727
+ ...(readiness?.mergeable !== undefined ? { mergeable: readiness.mergeable } : {}),
728
+ override: {
729
+ accepted: input.accepted,
730
+ actor: input.gate.actor,
731
+ note: input.gate.note,
732
+ audit: {
733
+ reason: input.gate.audit.reason,
734
+ risk: input.gate.audit.risk,
735
+ verificationResponsibility: input.gate.audit.verificationResponsibility,
736
+ headSha: input.gate.audit.headSha,
737
+ },
738
+ source: input.gate.source,
739
+ facts: input.facts,
740
+ replied: input.response?.replied ?? 0,
741
+ resolved: input.response?.resolved ?? 0,
742
+ ...(input.reason ? { reason: input.reason } : {}),
743
+ },
744
+ };
745
+ }
746
+ function hardMergeabilityFact(readiness) {
747
+ const signal = mergeSignal(readiness.mergeStateStatus, readiness.mergeable);
748
+ if (signal === 'clean')
749
+ return undefined;
750
+ return {
751
+ severity: 'hard',
752
+ kind: signal === 'blocked' ? 'mergeability_blocked' : 'mergeability_unknown',
753
+ summary: `mergeStateStatus=${readiness.mergeStateStatus ?? ''} mergeable=${readiness.mergeable ?? ''}`,
754
+ evidence: `mergeStateStatus=${readiness.mergeStateStatus ?? ''}; mergeable=${readiness.mergeable ?? ''}`,
431
755
  };
432
756
  }
757
+ function movedHeadFact(readiness, gate) {
758
+ const approvedHeadSha = gate.trustedHeadSha;
759
+ if (!approvedHeadSha) {
760
+ return {
761
+ severity: 'hard',
762
+ kind: 'trusted_gate_head_unavailable',
763
+ summary: 'Trusted merge gate head is unavailable.',
764
+ evidence: `audit=${gate.audit.headSha}; fresh=${readiness.pr.headSha}`,
765
+ };
766
+ }
767
+ if (readiness.pr.headSha === approvedHeadSha)
768
+ return undefined;
769
+ return {
770
+ severity: 'hard',
771
+ kind: 'head_moved',
772
+ summary: `approved head ${approvedHeadSha} no longer matches fresh head ${readiness.pr.headSha}`,
773
+ evidence: `approved=${approvedHeadSha}; audit=${gate.audit.headSha}; fresh=${readiness.pr.headSha}`,
774
+ };
775
+ }
776
+ function auditHeadMismatchFact(gate) {
777
+ if (!gate.trustedHeadSha)
778
+ return undefined;
779
+ if (gate.audit.headSha === gate.trustedHeadSha)
780
+ return undefined;
781
+ return {
782
+ severity: 'hard',
783
+ kind: 'audit_head_mismatch',
784
+ summary: `audit head ${gate.audit.headSha} does not match trusted merge gate head ${gate.trustedHeadSha}`,
785
+ evidence: `audit=${gate.audit.headSha}; approved=${gate.trustedHeadSha}`,
786
+ };
787
+ }
788
+ function initialMergeOverrideHardFacts(readiness, gate, unavailableRollup) {
789
+ return [
790
+ readiness.draft === true ? { severity: 'hard', kind: 'draft_pr', summary: 'PR is still draft.' } : undefined,
791
+ auditHeadMismatchFact(gate),
792
+ movedHeadFact(readiness, gate),
793
+ hardMergeabilityFact(readiness),
794
+ unavailableRollup,
795
+ reviewThreadsIncompleteFact(readiness),
796
+ ].filter((fact) => fact !== undefined);
797
+ }
798
+ function appendFeedbackFacts(buckets, readiness) {
799
+ for (const thread of readiness.reviewThreads.items) {
800
+ buckets.advisory.push(reviewThreadFact(thread));
801
+ }
802
+ const feedbackFacts = classifyFeedbackFacts(readiness);
803
+ buckets.hard.push(...feedbackFacts.hard);
804
+ buckets.advisory.push(...feedbackFacts.advisory);
805
+ }
806
+ function classifyAvailableCheckFacts(readiness, ownerRepo, execGh, requiredChecks) {
807
+ if (readiness.checks.fail.length === 0 && readiness.checks.pending.length === 0) {
808
+ return readiness.checks.list.length === 0 ? factsFromRequiredChecks(readiness, new Set()) : emptyFactBuckets();
809
+ }
810
+ if (readiness.pr.number === null) {
811
+ return {
812
+ hard: [{ severity: 'hard', kind: 'pr_number_unavailable', summary: 'Cannot classify required checks without a PR number.' }],
813
+ advisory: [],
814
+ };
815
+ }
816
+ try {
817
+ return factsFromRequiredChecks(readiness, requiredChecks(ownerRepo, readiness.pr.number, execGh));
818
+ }
819
+ catch (err) {
820
+ return {
821
+ hard: [{
822
+ severity: 'hard',
823
+ kind: 'required_check_names_unavailable',
824
+ summary: 'Required check names are unavailable.',
825
+ evidence: err instanceof Error ? err.message : String(err),
826
+ }],
827
+ advisory: [],
828
+ };
829
+ }
830
+ }
831
+ async function classifyMergeOverrideFacts(readiness, gate, ownerRepo, execGh, requiredChecks) {
832
+ const unavailableRollup = checkRollupUnavailableFact(readiness);
833
+ const facts = {
834
+ hard: initialMergeOverrideHardFacts(readiness, gate, unavailableRollup),
835
+ advisory: [],
836
+ };
837
+ if (!unavailableRollup) {
838
+ const checkFacts = classifyAvailableCheckFacts(readiness, ownerRepo, execGh, requiredChecks);
839
+ facts.hard.push(...checkFacts.hard);
840
+ facts.advisory.push(...checkFacts.advisory);
841
+ }
842
+ appendFeedbackFacts(facts, readiness);
843
+ return facts;
844
+ }
845
+ function overrideThreadTriage(readiness, note) {
846
+ return {
847
+ items: readiness.reviewThreads.items.map((thread) => ({
848
+ threadId: thread.id,
849
+ decision: 'wontfix',
850
+ replyText: `merged by operator override: ${note}`,
851
+ })),
852
+ };
853
+ }
854
+ export async function overrideMerge(input, deps) {
855
+ const gate = mergeOverrideGate(input);
856
+ if ('needsHuman' in gate)
857
+ return gate;
858
+ const { execGit: git, execGh: gh, resolveRunCwd } = deps;
859
+ const cwd = await resolveRunCwd(input.runId, input.taskId);
860
+ const branch = branchName(input.taskId, input.title, input.issueRef);
861
+ const ownerRepoResult = resolveOwnerRepo(git, cwd);
862
+ if ('needsHuman' in ownerRepoResult)
863
+ return ownerRepoResult;
864
+ const { ownerRepo } = ownerRepoResult;
865
+ let readiness;
866
+ try {
867
+ readiness = await (deps.collect ?? defaultCollect)(ownerRepo, branch, input.base, gh, input.issueRef, input.issueAction);
868
+ }
869
+ catch (err) {
870
+ const fact = {
871
+ severity: 'hard',
872
+ kind: 'readiness_transport_degraded',
873
+ summary: 'Fresh override reverify could not read PR readiness.',
874
+ evidence: err instanceof Error ? err.message : String(err),
875
+ };
876
+ return mergeOverrideOutput({
877
+ gate,
878
+ verdict: 'recheck',
879
+ facts: [fact],
880
+ accepted: false,
881
+ reason: fact.summary,
882
+ });
883
+ }
884
+ if (readiness.readinessVerdict === 'merged') {
885
+ return mergeOverrideOutput({ gate, readiness, verdict: 'merged', facts: [], accepted: false, reason: 'PR already merged externally.' });
886
+ }
887
+ if (readiness.readinessVerdict === 'closed') {
888
+ const fact = { severity: 'hard', kind: 'pr_closed_externally', summary: 'PR is closed without being merged.' };
889
+ return mergeOverrideOutput({ gate, readiness, verdict: 'closed', facts: [fact], accepted: false, reason: fact.summary });
890
+ }
891
+ const facts = await classifyMergeOverrideFacts(readiness, gate, ownerRepo, gh, deps.requiredChecks ?? fetchRequiredCheckNames);
892
+ if (facts.hard.length > 0) {
893
+ return mergeOverrideOutput({
894
+ gate,
895
+ readiness,
896
+ verdict: 'recheck',
897
+ facts: facts.hard,
898
+ accepted: false,
899
+ reason: 'override_merge refused because hard blockers remain',
900
+ });
901
+ }
902
+ const response = await respondThreads(overrideThreadTriage(readiness, gate.note), { execGh: gh });
903
+ return mergeOverrideOutput({
904
+ gate,
905
+ readiness,
906
+ verdict: 'clean',
907
+ facts: facts.advisory,
908
+ accepted: true,
909
+ response,
910
+ });
911
+ }
433
912
  export async function pollPr(input, deps) {
434
913
  const { execGit: git, execGh: gh, resolveRunCwd } = deps;
435
914
  const collect = deps.collect ?? defaultCollect;
@@ -445,10 +924,27 @@ export async function pollPr(input, deps) {
445
924
  const { ownerRepo } = ownerRepoResult;
446
925
  let readiness;
447
926
  let lastReadiness;
927
+ let markedReadyForReview = false;
448
928
  for (let i = 0; i < maxPolls; i++) {
449
- readiness = await collect(ownerRepo, branch, input.base, gh, input.issueRef);
929
+ readiness = await collect(ownerRepo, branch, input.base, gh, input.issueRef, input.issueAction);
930
+ if (readiness.draft === true
931
+ && readiness.readinessVerdict !== 'merged'
932
+ && readiness.readinessVerdict !== 'closed'
933
+ && ciFailuresFrom(readiness).length === 0) {
934
+ const readyFailure = markPrReadyForReview({
935
+ gh,
936
+ branch,
937
+ ownerRepo,
938
+ prNumber: readiness.pr.number,
939
+ ...(input.githubAccount ? { githubAccount: input.githubAccount } : {}),
940
+ });
941
+ if (readyFailure)
942
+ return readyFailure;
943
+ markedReadyForReview = true;
944
+ readiness = await collect(ownerRepo, branch, input.base, gh, input.issueRef, input.issueAction);
945
+ }
450
946
  lastReadiness = readiness;
451
- if (readiness.checks.pending.length === 0 && readiness.checks.list.length > 0)
947
+ if (readiness.checks.pending.length === 0)
452
948
  break;
453
949
  readiness = undefined;
454
950
  if (i < maxPolls - 1)
@@ -468,24 +964,80 @@ export async function pollPr(input, deps) {
468
964
  return { needsHuman: true, lesson: `pollPr timed out after ${maxPolls} polls before reading PR readiness for ${branch}` };
469
965
  }
470
966
  let settled = readiness;
967
+ const makeTerminalFeedback = (verdict) => ({
968
+ prNumber: settled.pr.number ?? null,
969
+ headSha: settled.pr.headSha,
970
+ evidence: [...settled.evidence, ...readinessEvidence(settled), `PR headSha=${settled.pr.headSha}`, `pollPr verdict=${verdict}`],
971
+ ...(input.issueRef ? { issueRef: input.issueRef } : {}),
972
+ verdict,
973
+ ciFailures: ciFailuresFrom(settled),
974
+ reviewThreads: [],
975
+ ...(settled.mergeStateStatus !== undefined ? { mergeStateStatus: settled.mergeStateStatus } : {}),
976
+ ...(settled.mergeable !== undefined ? { mergeable: settled.mergeable } : {}),
977
+ });
978
+ if (settled.readinessVerdict === 'merged')
979
+ return makeTerminalFeedback('merged');
980
+ if (settled.readinessVerdict === 'closed')
981
+ return makeTerminalFeedback('closed');
982
+ const standardCheckResults = new Set('ACTION_REQUIRED CANCELLED ERROR EXPECTED FAILURE IN_PROGRESS NEUTRAL PENDING QUEUED SKIPPED STALE STARTUP_FAILURE SUCCESS TIMED_OUT UNKNOWN'.split(' '));
983
+ const standardMergeStateStatuses = new Set('BEHIND BLOCKED CLEAN DIRTY DRAFT HAS_HOOKS UNKNOWN UNSTABLE'.split(' '));
984
+ const standardMergeableStates = new Set('CONFLICTING MERGEABLE UNKNOWN'.split(' '));
985
+ const addUnclassifiableState = (states, label, value, known) => {
986
+ const state = (value ?? '').trim().toUpperCase();
987
+ if (state !== '' && !known.has(state))
988
+ states.push(`${label} ${state}`);
989
+ };
990
+ const unclassifiablePollState = (snapshot) => {
991
+ const states = [];
992
+ for (const check of snapshot.checks.list) {
993
+ addUnclassifiableState(states, 'check result', check.result, standardCheckResults);
994
+ }
995
+ addUnclassifiableState(states, 'mergeStateStatus', snapshot.mergeStateStatus, standardMergeStateStatuses);
996
+ addUnclassifiableState(states, 'mergeable', snapshot.mergeable, standardMergeableStates);
997
+ return states;
998
+ };
999
+ const unclassifiableReadinessBlock = (snapshot, states) => ({
1000
+ needsHuman: true,
1001
+ lesson: [
1002
+ ...snapshot.evidence,
1003
+ ...readinessEvidence(snapshot),
1004
+ `pollPr unclassifiable readiness state: ${states.join(', ')}`,
1005
+ `PR headSha=${snapshot.pr.headSha}`,
1006
+ ].join('; '),
1007
+ });
1008
+ const unclassifiable = unclassifiablePollState(settled);
1009
+ if (unclassifiable.length > 0) {
1010
+ return unclassifiableReadinessBlock(settled, unclassifiable);
1011
+ }
471
1012
  const initialCiFailures = ciFailuresFrom(settled);
472
1013
  if (initialCiFailures.length === 0) {
473
- try {
474
- gh(['pr', 'ready', branch, '--repo', ownerRepo]);
475
- }
476
- catch {
1014
+ if (!markedReadyForReview) {
1015
+ const readyFailure = markPrReadyForReview({
1016
+ gh,
1017
+ branch,
1018
+ ownerRepo,
1019
+ prNumber: settled.pr.number,
1020
+ ...(input.githubAccount ? { githubAccount: input.githubAccount } : {}),
1021
+ });
1022
+ if (readyFailure)
1023
+ return readyFailure;
477
1024
  }
478
1025
  const reviewGracePolls = deps.reviewGracePolls ?? envInt('REVO_POLL_PR_REVIEW_GRACE_POLLS', 4);
479
1026
  for (let i = 0; i < reviewGracePolls && settled.reviewThreads.items.length === 0; i++) {
480
1027
  await sleep(intervalMs);
481
- settled = await collect(ownerRepo, branch, input.base, gh, input.issueRef);
1028
+ settled = await collect(ownerRepo, branch, input.base, gh, input.issueRef, input.issueAction);
482
1029
  }
483
1030
  }
484
- if (settled.checks.pending.length > 0 || settled.checks.list.length === 0) {
485
- const detail = settled.checks.pending.length > 0
486
- ? `pending checks: ${settled.checks.pending.join(', ')}`
487
- : 'no checks registered';
488
- return unsettledReadinessFeedback(input, settled, `pollPr found unsettled readiness after readying ${branch}: ${detail}`);
1031
+ if (settled.readinessVerdict === 'merged')
1032
+ return makeTerminalFeedback('merged');
1033
+ if (settled.readinessVerdict === 'closed')
1034
+ return makeTerminalFeedback('closed');
1035
+ const finalUnclassifiable = unclassifiablePollState(settled);
1036
+ if (finalUnclassifiable.length > 0) {
1037
+ return unclassifiableReadinessBlock(settled, finalUnclassifiable);
1038
+ }
1039
+ if (settled.checks.pending.length > 0) {
1040
+ return unsettledReadinessFeedback(input, settled, `pollPr found unsettled readiness after readying ${branch}: pending checks: ${settled.checks.pending.join(', ')}`);
489
1041
  }
490
1042
  const reviewThreads = settled.reviewThreads.items.map((t) => ({
491
1043
  threadId: t.id,
@@ -496,27 +1048,51 @@ export async function pollPr(input, deps) {
496
1048
  }));
497
1049
  const ciFailures = ciFailuresFrom(settled);
498
1050
  let ciVerdictFailures = ciFailures;
1051
+ let requiredCheckFetchFailure;
499
1052
  if (ciFailures.length > 0 && settled.pr.number !== null) {
500
1053
  try {
501
1054
  const required = requiredChecks(ownerRepo, settled.pr.number, gh);
502
1055
  if (required.size > 0)
503
1056
  ciVerdictFailures = ciFailures.filter((f) => required.has(f.name));
504
1057
  }
505
- catch {
1058
+ catch (err) {
1059
+ requiredCheckFetchFailure = err instanceof Error ? err.message : String(err);
506
1060
  }
507
1061
  }
508
- const verdict = reviewThreads.length > 0 || readinessRequiresReview(settled)
509
- ? 'review_changes'
510
- : ciVerdictFailures.length > 0 ? 'ci_changes' : 'clean';
511
- return {
1062
+ const makeFeedback = (verdict, extraEvidence = []) => ({
512
1063
  prNumber: settled.pr.number ?? null,
513
1064
  headSha: settled.pr.headSha,
514
- evidence: [...settled.evidence, ...readinessEvidence(settled), `PR headSha=${settled.pr.headSha}`, `pollPr verdict=${verdict}`],
1065
+ evidence: [...settled.evidence, ...readinessEvidence(settled), ...extraEvidence, `PR headSha=${settled.pr.headSha}`, `pollPr verdict=${verdict}`],
515
1066
  ...(input.issueRef ? { issueRef: input.issueRef } : {}),
516
1067
  verdict,
517
1068
  ciFailures,
518
1069
  reviewThreads,
519
- };
1070
+ ...(settled.mergeStateStatus !== undefined ? { mergeStateStatus: settled.mergeStateStatus } : {}),
1071
+ ...(settled.mergeable !== undefined ? { mergeable: settled.mergeable } : {}),
1072
+ });
1073
+ if (reviewThreads.length > 0 || readinessRequiresReview(settled))
1074
+ return makeFeedback('review_changes');
1075
+ if (requiredCheckFetchFailure) {
1076
+ return makeFeedback('recheck', [`Required check names unavailable: ${requiredCheckFetchFailure}`]);
1077
+ }
1078
+ if (ciVerdictFailures.length > 0)
1079
+ return makeFeedback('ci_changes');
1080
+ const signal = mergeSignal(settled.mergeStateStatus, settled.mergeable);
1081
+ if (signal === 'blocked') {
1082
+ return {
1083
+ needsHuman: true,
1084
+ lesson: [
1085
+ ...settled.evidence,
1086
+ ...readinessEvidence(settled),
1087
+ `pollPr merge readiness blocked (mergeStateStatus=${settled.mergeStateStatus ?? ''} mergeable=${settled.mergeable ?? ''})`,
1088
+ `PR headSha=${settled.pr.headSha}`,
1089
+ ].join('; '),
1090
+ };
1091
+ }
1092
+ if (signal === 'unknown') {
1093
+ return unsettledReadinessFeedback(input, settled, `pollPr merge readiness unknown (mergeable=${settled.mergeable ?? 'undefined'})`);
1094
+ }
1095
+ return makeFeedback('clean');
520
1096
  }
521
1097
  const TRIAGE_DECISIONS = new Set(['fix', 'wontfix', 'question']);
522
1098
  export function asTriage(value) {
@@ -531,6 +1107,37 @@ export function asTriage(value) {
531
1107
  : [];
532
1108
  return { items };
533
1109
  }
1110
+ function questionGateResolution(value) {
1111
+ if (value === null || typeof value !== 'object')
1112
+ return undefined;
1113
+ const outcome = value.outcome;
1114
+ if (outcome !== 'fix' && outcome !== 'wontfix')
1115
+ return undefined;
1116
+ const rawNote = value.note;
1117
+ const note = typeof rawNote === 'string' ? rawNote.trim() : '';
1118
+ return { decision: outcome, ...(note ? { note } : {}) };
1119
+ }
1120
+ function replyTextForQuestionResolution(decision, note) {
1121
+ if (decision === 'fix')
1122
+ return note ? `Addressed: ${note}` : 'Addressed.';
1123
+ return note ? `Won't fix: ${note}` : "Won't fix.";
1124
+ }
1125
+ export function triageForRespondThreads(input) {
1126
+ const triage = asTriage(input.triage);
1127
+ const resolution = questionGateResolution(input.gateResolution);
1128
+ if (!resolution)
1129
+ return triage;
1130
+ return {
1131
+ ...triage,
1132
+ items: triage.items.map((item) => item.decision === 'question'
1133
+ ? {
1134
+ ...item,
1135
+ decision: resolution.decision,
1136
+ replyText: replyTextForQuestionResolution(resolution.decision, resolution.note),
1137
+ }
1138
+ : item),
1139
+ };
1140
+ }
534
1141
  export async function respondThreads(triage, deps) {
535
1142
  const { execGh: gh } = deps;
536
1143
  let replied = 0;
@@ -570,27 +1177,21 @@ let IntegratorService = class IntegratorService {
570
1177
  };
571
1178
  }
572
1179
  runIntegrate = (input) => {
573
- const pinned = resolvePinnedGh();
1180
+ const pinned = resolvePinnedGh({ account: input.githubAccount });
574
1181
  if ('needsHuman' in pinned) {
575
1182
  console.warn(`[integrator] ${pinned.lesson}`);
576
1183
  return Promise.resolve(pinned);
577
1184
  }
578
- console.log(`[integrator] gh pinned to account '${resolveGhAccount()}' (GH_TOKEN, not ambient)`);
579
- return integrate(input, { ...this.deps, execGh: pinned.execGh });
580
- };
581
- runStub = (input) => {
582
- return stubIntegrate(input);
1185
+ console.log(`[integrator] gh pinned to account '${pinned.account}' (GH_TOKEN, not ambient)`);
1186
+ return integrate({ ...input, githubAccount: pinned.account }, { ...this.deps, execGh: pinned.execGh });
583
1187
  };
584
1188
  runConfirmMerge = (input) => {
585
- const pinned = resolvePinnedGh();
1189
+ const pinned = resolvePinnedGh({ account: input.githubAccount });
586
1190
  if ('needsHuman' in pinned) {
587
1191
  console.warn(`[confirm-merge] ${pinned.lesson}`);
588
1192
  return Promise.resolve(pinned);
589
1193
  }
590
- return confirmMerge(input, { ...this.deps, execGh: pinned.execGh });
591
- };
592
- runConfirmStub = (input) => {
593
- return { merged: true, prNumber: 0, prUrl: `stub://pr/${input.taskId}/merged` };
1194
+ return confirmMerge({ ...input, githubAccount: pinned.account }, { ...this.deps, execGh: pinned.execGh });
594
1195
  };
595
1196
  runPreflight = (taskId, base) => {
596
1197
  return preflightLive(taskId, base, this.deps);
@@ -599,26 +1200,28 @@ let IntegratorService = class IntegratorService {
599
1200
  return captureProducedChange(input, this.deps);
600
1201
  };
601
1202
  runPollPr = (input) => {
602
- const pinned = resolvePinnedGh();
1203
+ const pinned = resolvePinnedGh({ account: input.githubAccount });
603
1204
  if ('needsHuman' in pinned) {
604
1205
  console.warn(`[poll-pr] ${pinned.lesson}`);
605
1206
  return Promise.resolve(pinned);
606
1207
  }
607
- return pollPr(input, { ...this.deps, execGh: pinned.execGh });
1208
+ return pollPr({ ...input, githubAccount: pinned.account }, { ...this.deps, execGh: pinned.execGh });
608
1209
  };
609
- runPollStub = (_input) => {
610
- return { prNumber: null, headSha: 'stub', evidence: ['stub pollPr readiness: clean'], verdict: 'clean', ciFailures: [], reviewThreads: [] };
1210
+ runOverrideMerge = (input) => {
1211
+ const pinned = resolvePinnedGh({ account: input.githubAccount });
1212
+ if ('needsHuman' in pinned) {
1213
+ console.warn(`[override-merge] ${pinned.lesson}`);
1214
+ return Promise.resolve(pinned);
1215
+ }
1216
+ return overrideMerge({ ...input, githubAccount: pinned.account }, { ...this.deps, execGh: pinned.execGh });
611
1217
  };
612
1218
  runRespondThreads = (input) => {
613
- const pinned = resolvePinnedGh();
1219
+ const pinned = resolvePinnedGh({ account: input.githubAccount });
614
1220
  if ('needsHuman' in pinned) {
615
1221
  console.warn(`[respond-threads] ${pinned.lesson}`);
616
1222
  return Promise.resolve(pinned);
617
1223
  }
618
- return respondThreads(asTriage(input.triage), { execGh: pinned.execGh });
619
- };
620
- runRespondStub = (_input) => {
621
- return { replied: 0, resolved: 0 };
1224
+ return respondThreads(triageForRespondThreads(input), { execGh: pinned.execGh });
622
1225
  };
623
1226
  };
624
1227
  IntegratorService = __decorate([