@wardby/cli 0.6.0 → 0.7.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 (576) hide show
  1. package/deploy/local/docker-compose.quickstart-coding.yml +13 -0
  2. package/dist/claude-coding-worker/driver.d.ts +21 -3
  3. package/dist/claude-coding-worker/driver.js +125 -17
  4. package/dist/claude-coding-worker/main.js +34 -6
  5. package/dist/claude-coding-worker/sdk.js +12 -1
  6. package/dist/claude-coding-worker/tool-socket.d.ts +27 -0
  7. package/dist/claude-coding-worker/tool-socket.js +111 -0
  8. package/dist/claude-coding-worker/transcript.d.ts +18 -0
  9. package/dist/claude-coding-worker/transcript.js +77 -0
  10. package/dist/cli-help.d.ts +1 -1
  11. package/dist/cli-help.js +1 -0
  12. package/dist/cli.js +24 -10
  13. package/dist/coding/continuation-wording.d.ts +35 -0
  14. package/dist/coding/continuation-wording.js +73 -0
  15. package/dist/coding/linked-issue-note.d.ts +23 -0
  16. package/dist/coding/linked-issue-note.js +47 -0
  17. package/dist/coding/merge-wording.d.ts +28 -0
  18. package/dist/coding/merge-wording.js +78 -0
  19. package/dist/coding/pr-description-note.d.ts +9 -0
  20. package/dist/coding/pr-description-note.js +32 -0
  21. package/dist/coding/profile.d.ts +46 -126
  22. package/dist/coding/profile.js +45 -4
  23. package/dist/coding/protected-path-wording.d.ts +8 -0
  24. package/dist/coding/protected-path-wording.js +1 -1
  25. package/dist/coding/protocol.d.ts +264 -346
  26. package/dist/coding/protocol.js +235 -12
  27. package/dist/coding/redact.d.ts +3 -0
  28. package/dist/coding/redact.js +12 -0
  29. package/dist/coding/registry/allowlist.d.ts +12 -0
  30. package/dist/coding/registry/allowlist.js +14 -1
  31. package/dist/coding/registry/audit-mode.d.ts +153 -0
  32. package/dist/coding/registry/audit-mode.js +304 -0
  33. package/dist/coding/registry/lockfile-shared.d.ts +36 -0
  34. package/dist/coding/registry/lockfile-shared.js +128 -0
  35. package/dist/coding/registry/lockfiles.js +1 -1
  36. package/dist/coding/registry/npm-lockfile-formats.d.ts +11 -0
  37. package/dist/coding/registry/npm-lockfile-formats.js +41 -0
  38. package/dist/coding/registry/npm-lockfile.d.ts +8 -4
  39. package/dist/coding/registry/npm-lockfile.js +4 -6
  40. package/dist/coding/registry/npm.js +78 -8
  41. package/dist/coding/registry/pnpm-lockfile.d.ts +17 -0
  42. package/dist/coding/registry/pnpm-lockfile.js +189 -0
  43. package/dist/coding/registry/pypi-plan.d.ts +50 -0
  44. package/dist/coding/registry/pypi-plan.js +406 -0
  45. package/dist/coding/registry/pypi-requirements.d.ts +58 -0
  46. package/dist/coding/registry/pypi-requirements.js +155 -0
  47. package/dist/coding/registry/pypi.d.ts +3 -27
  48. package/dist/coding/registry/pypi.js +40 -154
  49. package/dist/coding/registry/refusal-hints.d.ts +20 -0
  50. package/dist/coding/registry/refusal-hints.js +61 -0
  51. package/dist/coding/registry/refusal-wording.d.ts +11 -0
  52. package/dist/coding/registry/refusal-wording.js +56 -0
  53. package/dist/coding/registry/report.d.ts +70 -8
  54. package/dist/coding/registry/report.js +112 -5
  55. package/dist/coding/registry/types.d.ts +58 -8
  56. package/dist/coding/registry/yarn-lockfile.d.ts +30 -0
  57. package/dist/coding/registry/yarn-lockfile.js +367 -0
  58. package/dist/coding/services/catalog.d.ts +37 -351
  59. package/dist/coding/transcript/claude-code.d.ts +13 -0
  60. package/dist/coding/transcript/claude-code.js +273 -0
  61. package/dist/coding/transcript/download-protocol.d.ts +30 -0
  62. package/dist/coding/transcript/download-protocol.js +18 -0
  63. package/dist/coding/transcript/format.d.ts +20 -0
  64. package/dist/coding/transcript/format.js +32 -0
  65. package/dist/coding/transcript/recorder.d.ts +51 -0
  66. package/dist/coding/transcript/recorder.js +225 -0
  67. package/dist/coding/transcript/resume/checkout.d.ts +31 -0
  68. package/dist/coding/transcript/resume/checkout.js +203 -0
  69. package/dist/coding/transcript/resume/command.d.ts +17 -0
  70. package/dist/coding/transcript/resume/command.js +176 -0
  71. package/dist/coding/transcript/resume/download.d.ts +20 -0
  72. package/dist/coding/transcript/resume/download.js +153 -0
  73. package/dist/coding/transcript/resume/launch.d.ts +38 -0
  74. package/dist/coding/transcript/resume/launch.js +128 -0
  75. package/dist/coding/transcript/resume/session-file.d.ts +13 -0
  76. package/dist/coding/transcript/resume/session-file.js +79 -0
  77. package/dist/coding/transcript/resume/stand-in.d.ts +11 -0
  78. package/dist/coding/transcript/resume/stand-in.js +49 -0
  79. package/dist/coding-worker/artifact.d.ts +8 -3
  80. package/dist/coding-worker/artifact.js +3 -1
  81. package/dist/coding-worker/debug-trace.d.ts +6 -5
  82. package/dist/coding-worker/debug-trace.js +13 -18
  83. package/dist/coding-worker/dependency-cache.d.mts +229 -0
  84. package/dist/coding-worker/dependency-cache.mjs +2123 -0
  85. package/dist/coding-worker/driver.d.ts +4 -3
  86. package/dist/coding-worker/driver.js +116 -38
  87. package/dist/coding-worker/errors.d.ts +1 -0
  88. package/dist/coding-worker/errors.js +32 -0
  89. package/dist/coding-worker/task-note.d.ts +11 -0
  90. package/dist/coding-worker/task-note.js +13 -0
  91. package/dist/coding-worker/types.d.ts +11 -0
  92. package/dist/config/dependency-cache.d.ts +23 -0
  93. package/dist/config/dependency-cache.js +32 -0
  94. package/dist/config/providers.d.ts +12 -3
  95. package/dist/config/providers.js +55 -5
  96. package/dist/config/test-simulators.d.ts +82 -0
  97. package/dist/config/test-simulators.js +305 -0
  98. package/dist/core/async-context.d.ts +2 -0
  99. package/dist/core/async-context.js +12 -0
  100. package/dist/core/budget-events.d.ts +108 -0
  101. package/dist/core/budget-events.js +346 -0
  102. package/dist/core/budget-groups.d.ts +50 -1
  103. package/dist/core/budget-groups.js +236 -27
  104. package/dist/core/budget-stopped-head.d.ts +46 -0
  105. package/dist/core/budget-stopped-head.js +81 -0
  106. package/dist/core/budget-topup.d.ts +51 -0
  107. package/dist/core/budget-topup.js +156 -0
  108. package/dist/core/budget-wording.d.ts +16 -2
  109. package/dist/core/budget-wording.js +40 -2
  110. package/dist/core/coding-queue.d.ts +2 -1
  111. package/dist/core/coding-queue.js +7 -0
  112. package/dist/core/cron.d.ts +19 -1
  113. package/dist/core/cron.js +17 -0
  114. package/dist/core/datastores.js +1 -1
  115. package/dist/core/deferred-coding.d.ts +14 -0
  116. package/dist/core/deferred-coding.js +62 -0
  117. package/dist/core/delegation-child.d.ts +25 -0
  118. package/dist/core/delegation-child.js +37 -0
  119. package/dist/core/dispatch.d.ts +42 -0
  120. package/dist/core/dispatch.js +131 -56
  121. package/dist/core/engine-native.js +1 -1
  122. package/dist/core/error-code.d.ts +38 -0
  123. package/dist/core/error-code.js +168 -0
  124. package/dist/core/grants.d.ts +19 -6
  125. package/dist/core/grants.js +55 -6
  126. package/dist/core/host-events.d.ts +18 -2
  127. package/dist/core/host-events.js +284 -24
  128. package/dist/core/host-status.d.ts +85 -3
  129. package/dist/core/host-status.js +298 -20
  130. package/dist/core/issue-bridge.d.ts +50 -6
  131. package/dist/core/issue-bridge.js +124 -33
  132. package/dist/core/issue-events.d.ts +1 -1
  133. package/dist/core/issue-events.js +4 -10
  134. package/dist/core/issue-status.d.ts +1 -1
  135. package/dist/core/issue-status.js +54 -26
  136. package/dist/core/logger.d.ts +5 -0
  137. package/dist/core/logger.js +14 -1
  138. package/dist/core/lost-run-children.d.ts +6 -0
  139. package/dist/core/lost-run-children.js +32 -0
  140. package/dist/core/mark-ready.d.ts +34 -0
  141. package/dist/core/mark-ready.js +152 -0
  142. package/dist/core/markdown-code.d.ts +8 -0
  143. package/dist/core/markdown-code.js +107 -0
  144. package/dist/core/memory-tools.js +7 -0
  145. package/dist/core/merge-order-check.d.ts +2 -1
  146. package/dist/core/merge-order-check.js +8 -1
  147. package/dist/core/notification-templates.d.ts +1 -7
  148. package/dist/core/notification-templates.js +36 -2
  149. package/dist/core/pull-request-state-sync.js +2 -2
  150. package/dist/core/reconciler.d.ts +4 -3
  151. package/dist/core/reconciler.js +8 -1
  152. package/dist/core/related-pull-requests.d.ts +11 -3
  153. package/dist/core/related-pull-requests.js +27 -14
  154. package/dist/core/reply-parts.d.ts +21 -0
  155. package/dist/core/reply-parts.js +73 -0
  156. package/dist/core/review-fix.d.ts +1 -1
  157. package/dist/core/review-fix.js +2 -1
  158. package/dist/core/review-host-checks.d.ts +1 -1
  159. package/dist/core/review-host-tools.d.ts +3 -0
  160. package/dist/core/review-host-tools.js +99 -13
  161. package/dist/core/review-reads.d.ts +55 -0
  162. package/dist/core/review-reads.js +233 -0
  163. package/dist/core/run-status.d.ts +12 -0
  164. package/dist/core/run-status.js +12 -0
  165. package/dist/core/runner.d.ts +80 -34
  166. package/dist/core/runner.js +439 -144
  167. package/dist/core/schedule-start.d.ts +12 -0
  168. package/dist/core/schedule-start.js +14 -0
  169. package/dist/core/secret-broker-config.d.ts +10 -74
  170. package/dist/core/secret-guard.d.ts +46 -0
  171. package/dist/core/secret-guard.js +104 -0
  172. package/dist/core/secret-guarded-stores.d.ts +12 -0
  173. package/dist/core/secret-guarded-stores.js +41 -0
  174. package/dist/core/secret-spans.d.ts +50 -0
  175. package/dist/core/secret-spans.js +94 -0
  176. package/dist/core/stop-run.d.ts +46 -0
  177. package/dist/core/stop-run.js +61 -0
  178. package/dist/core/workflow-events.d.ts +14 -2
  179. package/dist/core/workflow-events.js +4 -0
  180. package/dist/core/workflow-run-events.d.ts +5 -3
  181. package/dist/core/workflow-run-events.js +43 -8
  182. package/dist/core/zod-json-schema.d.ts +4 -0
  183. package/dist/core/zod-json-schema.js +39 -0
  184. package/dist/core/zod3-window.d.ts +35 -0
  185. package/dist/core/zod3-window.js +124 -0
  186. package/dist/generated/prisma/browser.d.ts +34 -3
  187. package/dist/generated/prisma/client.d.ts +34 -3
  188. package/dist/generated/prisma/commonInputTypes.d.ts +220 -60
  189. package/dist/generated/prisma/enums.d.ts +10 -0
  190. package/dist/generated/prisma/enums.js +8 -0
  191. package/dist/generated/prisma/internal/class.d.ts +44 -0
  192. package/dist/generated/prisma/internal/class.js +4 -4
  193. package/dist/generated/prisma/internal/prismaNamespace.d.ts +428 -9
  194. package/dist/generated/prisma/internal/prismaNamespace.js +92 -1
  195. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +95 -0
  196. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +92 -1
  197. package/dist/generated/prisma/models/Agent.d.ts +106 -8
  198. package/dist/generated/prisma/models/AgentRepository.d.ts +42 -1
  199. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +229 -1
  200. package/dist/generated/prisma/models/CodingProxyRequest.d.ts +207 -3
  201. package/dist/generated/prisma/models/CodingProxySession.d.ts +114 -1
  202. package/dist/generated/prisma/models/CodingRun.d.ts +1637 -1
  203. package/dist/generated/prisma/models/DependencySnapshot.d.ts +1668 -0
  204. package/dist/generated/prisma/models/DependencySnapshot.js +1 -0
  205. package/dist/generated/prisma/models/IssuePullRequest.d.ts +6 -2
  206. package/dist/generated/prisma/models/LocalReview.d.ts +40 -1
  207. package/dist/generated/prisma/models/NativeGatewayCall.d.ts +39 -1
  208. package/dist/generated/prisma/models/NativeGatewaySession.d.ts +35 -1
  209. package/dist/generated/prisma/models/RegistryAllowance.d.ts +41 -1
  210. package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +75 -1
  211. package/dist/generated/prisma/models/RegistryAuditRecord.d.ts +1364 -0
  212. package/dist/generated/prisma/models/RegistryAuditRecord.js +1 -0
  213. package/dist/generated/prisma/models/Run.d.ts +738 -2
  214. package/dist/generated/prisma/models/RunBudgetEvent.d.ts +1211 -0
  215. package/dist/generated/prisma/models/RunBudgetEvent.js +1 -0
  216. package/dist/generated/prisma/models/RunTranscript.d.ts +1339 -0
  217. package/dist/generated/prisma/models/RunTranscript.js +1 -0
  218. package/dist/generated/prisma/models.d.ts +4 -0
  219. package/dist/help-index.json +1320 -69
  220. package/dist/import/create.js +32 -3
  221. package/dist/import/neutral-schema.d.ts +50 -281
  222. package/dist/import/neutral-schema.js +5 -4
  223. package/dist/knowledge/check.d.ts +1 -1
  224. package/dist/knowledge/cli.d.ts +11 -1
  225. package/dist/knowledge/cli.js +37 -4
  226. package/dist/knowledge/concept.d.ts +3 -19
  227. package/dist/knowledge/location.d.ts +31 -0
  228. package/dist/knowledge/location.js +78 -0
  229. package/dist/mcp/auth/access.d.ts +10 -2
  230. package/dist/mcp/auth/access.js +15 -1
  231. package/dist/mcp/auth/ownership.d.ts +7 -7
  232. package/dist/mcp/auth/self-hosted/credentials.d.ts +3 -3
  233. package/dist/mcp/auth/self-hosted/session.d.ts +2 -2
  234. package/dist/mcp/host-events/github-ingress.d.ts +2 -0
  235. package/dist/mcp/host-events/github-ingress.js +2 -1
  236. package/dist/mcp/index.d.ts +6 -0
  237. package/dist/mcp/index.js +58 -10
  238. package/dist/mcp/server.d.ts +13 -0
  239. package/dist/mcp/server.js +33 -14
  240. package/dist/mcp/tools/agents.d.ts +4 -0
  241. package/dist/mcp/tools/agents.js +198 -16
  242. package/dist/mcp/tools/datastore.js +17 -8
  243. package/dist/mcp/tools/grants.js +9 -3
  244. package/dist/mcp/tools/memory.js +11 -1
  245. package/dist/mcp/tools/notification-channels.js +2 -2
  246. package/dist/mcp/tools/packages.d.ts +2 -0
  247. package/dist/mcp/tools/packages.js +195 -0
  248. package/dist/mcp/tools/repositories.js +18 -5
  249. package/dist/mcp/tools/run-access.d.ts +16 -0
  250. package/dist/mcp/tools/run-access.js +25 -0
  251. package/dist/mcp/tools/run-transcript-download.d.ts +30 -0
  252. package/dist/mcp/tools/run-transcript-download.js +129 -0
  253. package/dist/mcp/tools/run-transcript.d.ts +19 -0
  254. package/dist/mcp/tools/run-transcript.js +70 -0
  255. package/dist/mcp/tools/runs.js +34 -12
  256. package/dist/mcp/tools/scheduling.js +3 -1
  257. package/dist/mcp/tools/secret-elicitation-form.js +5 -0
  258. package/dist/mcp/tools/secret-elicitation-server.d.ts +6 -0
  259. package/dist/mcp/tools/secret-elicitation-server.js +10 -61
  260. package/dist/mcp/tools/secrets.js +2 -2
  261. package/dist/mcp/tools/stop-run.d.ts +2 -0
  262. package/dist/mcp/tools/stop-run.js +82 -0
  263. package/dist/mcp/tools/subagents.js +2 -2
  264. package/dist/mcp/tools/tools.d.ts +1 -1
  265. package/dist/mcp/tools/tools.js +25 -9
  266. package/dist/mcp/transport/stdio-link-host.d.ts +20 -0
  267. package/dist/mcp/transport/stdio-link-host.js +67 -0
  268. package/dist/mcp/transport/streamable-http.d.ts +13 -0
  269. package/dist/mcp/transport/streamable-http.js +41 -1
  270. package/dist/native-worker/docker-launcher.d.ts +38 -2
  271. package/dist/native-worker/docker-launcher.js +65 -5
  272. package/dist/native-worker/gateway-server.d.ts +41 -3
  273. package/dist/native-worker/gateway-server.js +124 -27
  274. package/dist/native-worker/gateway.d.ts +27 -1
  275. package/dist/native-worker/gateway.js +70 -4
  276. package/dist/native-worker/http-server.d.ts +3 -1
  277. package/dist/native-worker/http-server.js +11 -7
  278. package/dist/native-worker/http-transport.js +1 -1
  279. package/dist/native-worker/kubernetes-launcher.d.ts +7 -3
  280. package/dist/native-worker/kubernetes-launcher.js +43 -3
  281. package/dist/native-worker/launch.d.ts +18 -1
  282. package/dist/native-worker/launch.js +6 -1
  283. package/dist/native-worker/ledger.d.ts +18 -2
  284. package/dist/native-worker/ledger.js +57 -4
  285. package/dist/native-worker/loopback.js +3 -8
  286. package/dist/native-worker/main.d.ts +2 -1
  287. package/dist/native-worker/main.js +14 -3
  288. package/dist/native-worker/protocol.d.ts +129 -577
  289. package/dist/native-worker/protocol.js +47 -1
  290. package/dist/native-worker/sandbox-executor.d.ts +17 -2
  291. package/dist/native-worker/sandbox-executor.js +78 -4
  292. package/dist/native-worker/stderr-redaction.d.ts +77 -0
  293. package/dist/native-worker/stderr-redaction.js +237 -0
  294. package/dist/native-worker/stdio.d.ts +1 -1
  295. package/dist/native-worker/stdio.js +6 -5
  296. package/dist/native-worker/warm-pool.d.ts +2 -2
  297. package/dist/native-worker/warm-pool.js +4 -4
  298. package/dist/native-worker/worker.js +28 -2
  299. package/dist/observability/metrics.js +6 -1
  300. package/dist/providers/auth/self-hosted.d.ts +1 -1
  301. package/dist/providers/coding-proxy/dependency-cache/eviction.d.ts +29 -0
  302. package/dist/providers/coding-proxy/dependency-cache/eviction.js +87 -0
  303. package/dist/providers/coding-proxy/dependency-cache/key.d.ts +33 -0
  304. package/dist/providers/coding-proxy/dependency-cache/key.js +86 -0
  305. package/dist/providers/coding-proxy/dependency-cache/model-request-seal.d.ts +18 -0
  306. package/dist/providers/coding-proxy/dependency-cache/model-request-seal.js +40 -0
  307. package/dist/providers/coding-proxy/dependency-cache/prisma-store.d.ts +25 -0
  308. package/dist/providers/coding-proxy/dependency-cache/prisma-store.js +131 -0
  309. package/dist/providers/coding-proxy/dependency-cache/service.d.ts +70 -0
  310. package/dist/providers/coding-proxy/dependency-cache/service.js +409 -0
  311. package/dist/providers/coding-proxy/dependency-cache/store.d.ts +85 -0
  312. package/dist/providers/coding-proxy/dependency-cache/store.js +95 -0
  313. package/dist/providers/coding-proxy/hold.d.ts +32 -0
  314. package/dist/providers/coding-proxy/hold.js +86 -0
  315. package/dist/providers/coding-proxy/interrupted.d.ts +70 -0
  316. package/dist/providers/coding-proxy/interrupted.js +104 -0
  317. package/dist/providers/coding-proxy/memory-ledger.d.ts +15 -2
  318. package/dist/providers/coding-proxy/memory-ledger.js +84 -8
  319. package/dist/providers/coding-proxy/metering.d.ts +16 -0
  320. package/dist/providers/coding-proxy/metering.js +27 -4
  321. package/dist/providers/coding-proxy/prisma-ledger.d.ts +15 -2
  322. package/dist/providers/coding-proxy/prisma-ledger.js +179 -67
  323. package/dist/providers/coding-proxy/proxy.d.ts +14 -1
  324. package/dist/providers/coding-proxy/proxy.js +166 -14
  325. package/dist/providers/coding-proxy/registry/audit.d.ts +53 -10
  326. package/dist/providers/coding-proxy/registry/audit.js +94 -17
  327. package/dist/providers/coding-proxy/registry/lockfile-parse-worker.d.ts +36 -0
  328. package/dist/providers/coding-proxy/registry/lockfile-parse-worker.js +67 -0
  329. package/dist/providers/coding-proxy/registry/lockfile-parse.d.ts +16 -0
  330. package/dist/providers/coding-proxy/registry/lockfile-parse.js +110 -0
  331. package/dist/providers/coding-proxy/registry/plan.d.ts +9 -0
  332. package/dist/providers/coding-proxy/registry/plan.js +201 -48
  333. package/dist/providers/coding-proxy/registry/prisma-store.d.ts +11 -5
  334. package/dist/providers/coding-proxy/registry/prisma-store.js +120 -17
  335. package/dist/providers/coding-proxy/registry/service.d.ts +97 -16
  336. package/dist/providers/coding-proxy/registry/service.js +579 -112
  337. package/dist/providers/coding-proxy/registry/store.d.ts +84 -9
  338. package/dist/providers/coding-proxy/registry/store.js +54 -1
  339. package/dist/providers/coding-proxy/runtime.d.ts +4 -0
  340. package/dist/providers/coding-proxy/runtime.js +78 -16
  341. package/dist/providers/coding-proxy/server.d.ts +6 -1
  342. package/dist/providers/coding-proxy/server.js +45 -1
  343. package/dist/providers/coding-proxy/types.d.ts +65 -1
  344. package/dist/providers/datastore/types.d.ts +5 -0
  345. package/dist/providers/engine/types.d.ts +17 -2
  346. package/dist/providers/engine/types.js +17 -0
  347. package/dist/providers/executor/composition.d.ts +10 -3
  348. package/dist/providers/executor/composition.js +47 -7
  349. package/dist/providers/executor/container.d.ts +361 -8
  350. package/dist/providers/executor/container.js +1136 -91
  351. package/dist/providers/executor/dbos-status.d.ts +31 -0
  352. package/dist/providers/executor/dbos-status.js +64 -0
  353. package/dist/providers/executor/dbos.d.ts +50 -1
  354. package/dist/providers/executor/dbos.js +207 -20
  355. package/dist/providers/executor/deferred.d.ts +12 -24
  356. package/dist/providers/executor/deferred.js +10 -29
  357. package/dist/providers/executor/in-process.d.ts +9 -1
  358. package/dist/providers/executor/in-process.js +14 -4
  359. package/dist/providers/executor/linked-issue-reader.d.ts +16 -0
  360. package/dist/providers/executor/linked-issue-reader.js +30 -0
  361. package/dist/providers/executor/routing.d.ts +2 -0
  362. package/dist/providers/executor/routing.js +4 -0
  363. package/dist/providers/executor/types.d.ts +13 -0
  364. package/dist/providers/index.d.ts +2 -1
  365. package/dist/providers/issue-tracker/types.d.ts +17 -0
  366. package/dist/providers/issue-tracker/types.js +39 -1
  367. package/dist/providers/jobs/claude-tool-setup.d.ts +4 -0
  368. package/dist/providers/jobs/claude-tool-setup.js +5 -2
  369. package/dist/providers/jobs/docker-isolation.d.ts +8 -0
  370. package/dist/providers/jobs/docker-isolation.js +17 -0
  371. package/dist/providers/jobs/docker.d.ts +1 -0
  372. package/dist/providers/jobs/docker.js +27 -2
  373. package/dist/providers/jobs/fake-kubernetes-api.d.ts +2 -0
  374. package/dist/providers/jobs/fake-kubernetes-api.js +3 -0
  375. package/dist/providers/jobs/kubernetes-api.d.ts +8 -0
  376. package/dist/providers/jobs/kubernetes-api.js +10 -0
  377. package/dist/providers/jobs/kubernetes-client.js +10 -1
  378. package/dist/providers/jobs/kubernetes-isolation.d.ts +2 -0
  379. package/dist/providers/jobs/kubernetes-isolation.js +17 -1
  380. package/dist/providers/jobs/kubernetes.d.ts +6 -1
  381. package/dist/providers/jobs/kubernetes.js +105 -16
  382. package/dist/providers/jobs/types.d.ts +33 -0
  383. package/dist/providers/jobs/types.js +13 -1
  384. package/dist/providers/llm/anthropic.js +5 -1
  385. package/dist/providers/llm/bedrock.d.ts +0 -12
  386. package/dist/providers/llm/bedrock.js +4 -0
  387. package/dist/providers/llm/claude-messages.d.ts +3 -1
  388. package/dist/providers/llm/claude-messages.js +4 -1
  389. package/dist/providers/llm/claude-provider.d.ts +2 -2
  390. package/dist/providers/llm/claude-provider.js +2 -2
  391. package/dist/providers/llm/openai.d.ts +2 -2
  392. package/dist/providers/llm/openai.js +6 -2
  393. package/dist/providers/llm/routing.d.ts +2 -2
  394. package/dist/providers/llm/routing.js +2 -2
  395. package/dist/providers/llm/types.d.ts +12 -2
  396. package/dist/providers/review-host/github-events.d.ts +9 -1
  397. package/dist/providers/review-host/github-events.js +34 -12
  398. package/dist/providers/review-host/github.d.ts +29 -2
  399. package/dist/providers/review-host/github.js +224 -29
  400. package/dist/providers/review-host/local.d.ts +8 -4
  401. package/dist/providers/review-host/local.js +49 -31
  402. package/dist/providers/review-host/patch-pages.d.ts +41 -0
  403. package/dist/providers/review-host/patch-pages.js +176 -0
  404. package/dist/providers/review-host/review-format.d.ts +19 -1
  405. package/dist/providers/review-host/review-format.js +38 -7
  406. package/dist/providers/review-host/types.d.ts +134 -2
  407. package/dist/providers/review-host/types.js +8 -0
  408. package/dist/providers/storage/gcs.d.ts +63 -0
  409. package/dist/providers/storage/gcs.js +69 -0
  410. package/dist/providers/storage/index.d.ts +15 -0
  411. package/dist/providers/storage/index.js +14 -0
  412. package/dist/providers/storage/local.d.ts +24 -0
  413. package/dist/providers/storage/local.js +92 -0
  414. package/dist/providers/storage/meter.d.ts +14 -0
  415. package/dist/providers/storage/meter.js +34 -0
  416. package/dist/providers/storage/s3.d.ts +41 -0
  417. package/dist/providers/storage/s3.js +107 -0
  418. package/dist/providers/storage/types.d.ts +37 -3
  419. package/dist/providers/storage/types.js +21 -4
  420. package/dist/providers/vcs/git.d.ts +33 -2
  421. package/dist/providers/vcs/git.js +324 -22
  422. package/dist/providers/vcs/github-remote.d.ts +26 -3
  423. package/dist/providers/vcs/github-remote.js +124 -14
  424. package/dist/providers/vcs/github.d.ts +214 -6
  425. package/dist/providers/vcs/github.js +447 -45
  426. package/dist/providers/vcs/index.js +8 -1
  427. package/dist/providers/vcs/pr-description.d.ts +127 -0
  428. package/dist/providers/vcs/pr-description.js +270 -0
  429. package/dist/providers/vcs/remote.d.ts +33 -1
  430. package/dist/providers/vcs/routing.d.ts +4 -2
  431. package/dist/providers/vcs/routing.js +6 -0
  432. package/dist/providers/vcs/types.d.ts +80 -3
  433. package/dist/quickstart/coding-seed.d.ts +14 -3
  434. package/dist/quickstart/coding-seed.js +213 -4
  435. package/dist/quickstart/reviewer-prompt.d.ts +1 -1
  436. package/dist/quickstart/reviewer-prompt.js +6 -6
  437. package/dist/quickstart-images.json +1 -1
  438. package/dist/sandbox/generated/zod.bundle.js +62 -1
  439. package/dist/sandbox/generated/zod3.bundle.js +5 -0
  440. package/dist/sandbox/host-functions.d.ts +1 -0
  441. package/dist/sandbox/host-functions.js +6 -11
  442. package/dist/sandbox/secret-broker.js +3 -4
  443. package/dist/sandbox/tool-capabilities.d.ts +10 -30
  444. package/dist/sandbox/zod-params.d.ts +37 -5
  445. package/dist/sandbox/zod-params.js +181 -26
  446. package/dist/serve.js +8 -1
  447. package/dist/viewer/api-schema.d.ts +349 -2627
  448. package/dist/viewer/api-schema.js +7 -0
  449. package/dist/viewer/build-schemas.js +6 -2
  450. package/dist/viewer/openapi.js +2 -4
  451. package/dist/wardby-bin.js +10 -0
  452. package/docs/agent-recipes.md +58 -20
  453. package/docs/code-review-agents.md +551 -41
  454. package/docs/coding-agent-setup.md +489 -7
  455. package/docs/coding-packages.md +1033 -90
  456. package/docs/coding-services.md +27 -12
  457. package/docs/coding-worker-byo-images.md +20 -0
  458. package/docs/coding-worker-isolation.md +105 -30
  459. package/docs/getting-started-gke.md +124 -137
  460. package/docs/getting-started-identity-provider.md +10 -1
  461. package/docs/getting-started.md +9 -0
  462. package/docs/jira-agents.md +17 -8
  463. package/docs/knowledge/blob-not-found-semantics.md +41 -0
  464. package/docs/knowledge/bounded-file-descriptor.md +52 -0
  465. package/docs/knowledge/delegation-admission-slots.md +52 -0
  466. package/docs/knowledge/http-dispatcher-bootstrap.md +61 -0
  467. package/docs/knowledge/import-rename-target.md +52 -0
  468. package/docs/knowledge/index.md +57 -0
  469. package/docs/knowledge/log.md +9 -0
  470. package/docs/knowledge/mcp-cancelled-task-precedence.md +41 -0
  471. package/docs/knowledge/mcp-http-prompt-discovery.md +41 -0
  472. package/docs/knowledge/mcp-schema-module-boundary.md +41 -0
  473. package/docs/knowledge/nullable-tool-uniqueness.md +52 -0
  474. package/docs/knowledge/overview-cli.md +74 -0
  475. package/docs/knowledge/overview-coding-proxy.md +72 -0
  476. package/docs/knowledge/overview-coding.md +72 -0
  477. package/docs/knowledge/overview-config.md +60 -0
  478. package/docs/knowledge/overview-core.md +72 -0
  479. package/docs/knowledge/overview-deploy.md +83 -0
  480. package/docs/knowledge/overview-mcp.md +61 -0
  481. package/docs/knowledge/overview-prisma.md +72 -0
  482. package/docs/knowledge/overview-providers.md +89 -0
  483. package/docs/knowledge/overview-sandbox.md +83 -0
  484. package/docs/knowledge/overview-surfaces.md +83 -0
  485. package/docs/knowledge/private-datastore-shared-boundary.md +54 -0
  486. package/docs/knowledge/proxy-settlement-projections.md +41 -0
  487. package/docs/knowledge/run-pricing-first-writer.md +43 -0
  488. package/docs/knowledge/schedule-claim-transaction.md +41 -0
  489. package/docs/knowledge/secret-entry-replica-contract.md +52 -0
  490. package/docs/knowledge/terminal-run-first-writer.md +41 -0
  491. package/docs/knowledge/transactional-agent-authorization.md +52 -0
  492. package/docs/knowledge/viewer-notify-write-isolation.md +30 -0
  493. package/docs/knowledge/warm-input-publication.md +30 -0
  494. package/docs/knowledge/workspace-staging-failure.md +54 -0
  495. package/docs/knowledge.md +54 -14
  496. package/docs/native-sandbox.md +119 -23
  497. package/docs/observability.md +4 -0
  498. package/docs/security-deployment.md +33 -2
  499. package/docs/slack-notifications.md +35 -19
  500. package/docs/tool-secrets.md +42 -10
  501. package/docs/viewer-api.openapi.yml +1 -1
  502. package/help/admin-viewer.md +24 -2
  503. package/help/architecture-agent.md +11 -6
  504. package/help/build-worker-image.md +3 -1
  505. package/help/builder-agent.md +19 -6
  506. package/help/code-review-agents.md +177 -6
  507. package/help/coding-packages.md +156 -6
  508. package/help/cost-attribution.md +9 -5
  509. package/help/creating-agents.md +14 -1
  510. package/help/dependency-cache.md +143 -0
  511. package/help/deploy-gke.md +15 -8
  512. package/help/errors/budget-exhausted.md +187 -0
  513. package/help/errors/budget-group-exhausted.md +5 -1
  514. package/help/errors/coding-stream-quota-exceeded.md +33 -0
  515. package/help/errors/coding-workspace-disk-exceeds-limit.md +39 -0
  516. package/help/errors/continuation-closed.md +9 -12
  517. package/help/errors/delegation-conflict.md +24 -0
  518. package/help/errors/file-list-truncated.md +45 -0
  519. package/help/errors/full-read-required.md +49 -0
  520. package/help/errors/github-account-not-allowed.md +4 -0
  521. package/help/errors/graph-incomplete.md +43 -0
  522. package/help/errors/graph-limit.md +43 -0
  523. package/help/errors/merge-conflict.md +49 -0
  524. package/help/errors/native-sandbox-gateway-error.md +20 -0
  525. package/help/errors/native-sandbox-launch-abandoned.md +21 -0
  526. package/help/errors/native-sandbox-worker-exited.md +3 -2
  527. package/help/errors/package-not-allowed-pending.md +52 -0
  528. package/help/errors/package-not-allowed.md +51 -0
  529. package/help/errors/package-refusals.md +43 -0
  530. package/help/errors/patch-cursor-malformed.md +31 -0
  531. package/help/errors/run-lost.md +39 -0
  532. package/help/errors/run-transcript-download.md +44 -0
  533. package/help/errors/secret-in-value.md +69 -0
  534. package/help/errors/target-merge.md +40 -0
  535. package/help/fix-merge-conflicts.md +73 -0
  536. package/help/github-app-setup.md +132 -0
  537. package/help/github.md +22 -0
  538. package/help/identity-and-access.md +2 -0
  539. package/help/jira.md +23 -6
  540. package/help/knowledge.md +16 -7
  541. package/help/local-repositories.md +1 -1
  542. package/help/low-power-mode.md +7 -1
  543. package/help/merge-order-check.md +5 -0
  544. package/help/native-capabilities.md +29 -1
  545. package/help/native-sandbox.md +15 -1
  546. package/help/operating-agents.md +5 -0
  547. package/help/package-audit-mode.md +91 -0
  548. package/help/related-pull-requests.md +10 -7
  549. package/help/resume-runs-locally.md +131 -0
  550. package/help/review-fix-rounds.md +33 -1
  551. package/help/sharing-agents.md +59 -0
  552. package/help/slack-notifications.md +19 -5
  553. package/help/status-comments.md +31 -0
  554. package/help/stop-run.md +58 -0
  555. package/help/troubleshooting/budgets.md +28 -0
  556. package/help/troubleshooting/coding-workers.md +78 -7
  557. package/package.json +17 -10
  558. package/prisma/migrations/20261016000000_registry_approved_accepted_advisories/migration.sql +3 -0
  559. package/prisma/migrations/20261016000001_mark_ready_when_passing/migration.sql +3 -0
  560. package/prisma/migrations/20261017000000_debug_trace_max_mb/migration.sql +4 -0
  561. package/prisma/migrations/20261018000000_dependency_cache/migration.sql +53 -0
  562. package/prisma/migrations/20261019000000_claude_max_output_tokens/migration.sql +5 -0
  563. package/prisma/migrations/20261021000000_pr_description_hash/migration.sql +4 -0
  564. package/prisma/migrations/20261022000000_dependency_snapshot_validation_metadata/migration.sql +4 -0
  565. package/prisma/migrations/20261023000000_package_audit_mode/migration.sql +27 -0
  566. package/prisma/migrations/20261024000000_budget_events/migration.sql +26 -0
  567. package/prisma/migrations/20261025000000_cache_aware_holds/migration.sql +9 -0
  568. package/prisma/migrations/20261026000000_budget_overrun/migration.sql +4 -0
  569. package/prisma/migrations/20261027000000_coding_run_request/migration.sql +1 -0
  570. package/prisma/migrations/20261028000000_native_gateway_review_reads/migration.sql +6 -0
  571. package/prisma/migrations/20261029000000_lead_survives_deploy/migration.sql +6 -0
  572. package/prisma/migrations/20261030000000_local_review_covered_sha/migration.sql +2 -0
  573. package/prisma/migrations/20261031000000_coding_run_budget_limit/migration.sql +9 -0
  574. package/prisma/migrations/20261101000000_run_transcripts/migration.sql +22 -0
  575. package/prisma/schema.prisma +311 -97
  576. package/dist/sandbox/generated/zod-to-json-schema.bundle.js +0 -5
@@ -1,7 +1,13 @@
1
1
  import { createPrivateKey } from "node:crypto";
2
2
  import { importPKCS8, SignJWT } from "jose";
3
3
  import { isGitHubLogin } from "../../config/github-allowed-accounts.js";
4
- import { normalizeGitHubRepository, normalizeGitRef } from "../../coding/protocol.js";
4
+ import { testSimulatorBase } from "../../config/test-simulators.js";
5
+ import { ACCEPTED_ADVISORY_ID } from "../../coding/registry/allowlist.js";
6
+ import { packageAuditRows, packageAuditWording, summarizePackageAudit, } from "../../coding/registry/audit-mode.js";
7
+ import { refusalMeaning } from "../../coding/registry/refusal-hints.js";
8
+ import { DEPENDENCY_CACHE_PATH, normalizeGitHubRepository, normalizeGitRef, truncateText, } from "../../coding/protocol.js";
9
+ import { LATEST_RUN_MARKER_PREFIX, RELATED_SECTION_END, RELATED_SECTION_START, assessFinalMessage, descriptionAppliedComment, descriptionFallbackComment, descriptionSha256, extractRelatedBlock, findDescriptionComments, hasClosingKeyword, isUneditedDescription, supersededDescriptionComment, } from "./pr-description.js";
10
+ export { RELATED_SECTION_END, RELATED_SECTION_START };
5
11
  const DEFAULT_API_BASE_URL = "https://api.github.com";
6
12
  const DEFAULT_API_VERSION = "2026-03-10";
7
13
  const RUN_MARKER_PREFIX = "<!-- wardby:";
@@ -40,6 +46,8 @@ export function isSafeGitHubInstallationToken(value) {
40
46
  * executor validates with the shared one.
41
47
  */
42
48
  export const PR_ISSUE_KEY = /^[A-Z][A-Z0-9_]{0,254}-[1-9]\d{0,9}$/;
49
+ /** Same shape as GITHUB_ISSUE_KEY (issue-tracker/types.ts), duplicated likewise. Only the Related section shows one. */
50
+ export const PR_GITHUB_ISSUE_KEY = /^[a-z0-9](?:[a-z0-9-]{0,37}[a-z0-9])?\/[a-z0-9._-]{1,100}#[1-9]\d{0,9}$/;
43
51
  /** The issue key when it is well-formed; a malformed one is dropped everywhere it would be rendered. */
44
52
  function validIssueKey(input) {
45
53
  const key = input.issue?.key;
@@ -50,22 +58,29 @@ export function pullRequestTitle(input) {
50
58
  const key = validIssueKey(input);
51
59
  return key ? `[${key}] ${base}` : base;
52
60
  }
53
- /** Validated issue parts; the tracker prefix is "" or "<Name> ". */
54
- function issueParts(issue) {
61
+ /**
62
+ * Validated issue parts; the tracker prefix is "" or "<Name> ". A GitHub issue
63
+ * key is accepted only with `orGitHub` (the Related section): the title and
64
+ * the body's issue line stay tracker-only.
65
+ */
66
+ function issueParts(issue, orGitHub = false) {
55
67
  const key = issue?.key;
56
- if (typeof key !== "string" || !PR_ISSUE_KEY.test(key))
68
+ if (typeof key !== "string" || !(PR_ISSUE_KEY.test(key) || (orGitHub && PR_GITHUB_ISSUE_KEY.test(key)))) {
57
69
  return undefined;
70
+ }
58
71
  const url = issue?.url;
59
72
  const safeUrl = typeof url === "string" && url.startsWith("https://") && !/[\s<>()]/.test(url) ? url : undefined;
60
73
  const name = issue?.trackerName;
61
74
  const tracker = typeof name === "string" && /^[A-Za-z][A-Za-z0-9 .-]{0,39}$/.test(name) ? `${name.trim()} ` : "";
62
75
  return { key, ...(safeUrl ? { url: safeUrl } : {}), tracker };
63
76
  }
77
+ /** The body's issue line: a tracker issue's link, else the same-repository issue the run came from. */
64
78
  function issueLine(input) {
65
79
  const parts = issueParts(input.issue);
66
- if (!parts?.url)
67
- return undefined;
68
- return `Resolves ${parts.tracker}issue [${parts.key}](${parts.url})`;
80
+ if (parts?.url)
81
+ return `Resolves ${parts.tracker}issue [${parts.key}](${parts.url})`;
82
+ const number = input.issueNumber;
83
+ return typeof number === "number" && Number.isSafeInteger(number) && number > 0 ? `From issue #${number}` : undefined;
69
84
  }
70
85
  /**
71
86
  * Package names/versions/reasons come from registry metadata, not from
@@ -78,8 +93,17 @@ function issueLine(input) {
78
93
  function isSafeMarkdownFragment(value) {
79
94
  return !value.includes("`") && !value.includes("\n") && !value.includes("\r");
80
95
  }
81
- export const RELATED_SECTION_START = "<!-- wardby-related:start -->";
82
- export const RELATED_SECTION_END = "<!-- wardby-related:end -->";
96
+ /** Whether a comment was written by this GitHub App (its bot user, through this App's id). */
97
+ function isThisAppsComment(comment, appId) {
98
+ const user = comment.user;
99
+ const app = comment.performed_via_github_app;
100
+ return (!!user &&
101
+ typeof user === "object" &&
102
+ user.type === "Bot" &&
103
+ !!app &&
104
+ typeof app === "object" &&
105
+ app.id === appId);
106
+ }
83
107
  /** At most this many pull requests are listed; the rest are counted. */
84
108
  export const MAX_RELATED_PULL_REQUESTS = 20;
85
109
  /** GitHub rejects a pull request body longer than this. */
@@ -128,7 +152,7 @@ export function renderRelatedSection(input) {
128
152
  const valid = input.entries.filter((entry) => relatedLine(entry, "-") !== undefined);
129
153
  if (!valid.some((entry) => !entry.self))
130
154
  return undefined;
131
- const issue = issueParts(input.issue);
155
+ const issue = issueParts(input.issue, true);
132
156
  const issueText = issue ? ` for ${issue.tracker}issue ${issue.url ? `[${issue.key}](${issue.url})` : issue.key}` : "";
133
157
  const open = valid.filter((entry) => !isDone(entry)).slice(0, MAX_RELATED_PULL_REQUESTS);
134
158
  const done = valid.filter(isDone).slice(0, MAX_RELATED_PULL_REQUESTS - open.length);
@@ -185,8 +209,8 @@ export function renderRelatedSection(input) {
185
209
  RELATED_SECTION_END,
186
210
  ].join("\n");
187
211
  }
188
- /** The run marker, then optionally blank lines and the "Resolves …" line: the section goes right after. */
189
- const BODY_HEAD = /^[^\S\n]*<!-- (?:wardby|reevo-run):\S+ -->[^\S\n]*(?:\n|$)(?:[^\S\n]*\n)*(?:Resolves [^\n]*(?:\n|$))?/;
212
+ /** The run marker, then optionally blank lines and the issue line ("Resolves … issue …" or "From issue #N"): the section goes right after. */
213
+ const BODY_HEAD = /^[^\S\n]*<!-- (?:wardby|reevo-run):\S+ -->[^\S\n]*(?:\n|$)(?:[^\S\n]*\n)*(?:(?:Resolves |From issue #\d)[^\n]*(?:\n|$))?/;
190
214
  /**
191
215
  * The body with `block` in place of the existing marked section, or inserted
192
216
  * after the run marker (and the issue line) when there is none. Null — do not
@@ -223,13 +247,16 @@ export const MAX_PR_PACKAGE_LINES = 100;
223
247
  /** A hard ceiling on each list's rendered size, whatever the entries
224
248
  * contain, so the section can never push the body past GitHub's limit. */
225
249
  const MAX_PR_PACKAGE_LIST_CHARS = 16 * 1024;
226
- /** Renders at most MAX_PR_PACKAGE_LINES lines (and at most
227
- * MAX_PR_PACKAGE_LIST_CHARS characters) of `lines`, then a count of the rest. */
228
- function cappedList(lines) {
250
+ /** The accepted-advisories list's ceiling: smaller, so adding it keeps the
251
+ * whole section's worst case well inside the body limit. */
252
+ const MAX_PR_WAIVER_LIST_CHARS = 8 * 1024;
253
+ /** Renders at most MAX_PR_PACKAGE_LINES lines (and at most `maxChars`
254
+ * characters) of `lines`, then a count of the rest. */
255
+ function cappedList(lines, maxChars = MAX_PR_PACKAGE_LIST_CHARS) {
229
256
  const shown = [];
230
257
  let chars = 0;
231
258
  for (const line of lines.slice(0, MAX_PR_PACKAGE_LINES)) {
232
- if (chars + line.length + 1 > MAX_PR_PACKAGE_LIST_CHARS)
259
+ if (chars + line.length + 1 > maxChars)
233
260
  break;
234
261
  shown.push(line);
235
262
  chars += line.length + 1;
@@ -237,6 +264,14 @@ function cappedList(lines) {
237
264
  const more = lines.length - shown.length;
238
265
  return more > 0 ? [...shown, `- …and ${more} more — see get_run for the full list`] : shown;
239
266
  }
267
+ const MAX_DEPENDENCY_CACHE_FOLDERS = 4;
268
+ function dependencyCacheLine(folders) {
269
+ const shown = [...new Set(folders ?? [])]
270
+ .filter((folder) => DEPENDENCY_CACHE_PATH.test(folder))
271
+ .slice(0, MAX_DEPENDENCY_CACHE_FOLDERS)
272
+ .map((folder) => (folder === "." ? "the repo root" : `\`${folder}\``));
273
+ return shown.length > 0 ? `Dependency cache: restored \`node_modules\` for ${shown.join(", ")}.` : undefined;
274
+ }
240
275
  function packagesSection(input) {
241
276
  const packages = [
242
277
  ...new Map((input.packages ?? [])
@@ -252,11 +287,23 @@ function packagesSection(input) {
252
287
  isSafeMarkdownFragment(refusal.reason))
253
288
  .map((refusal) => [`${refusal.ecosystem}\0${refusal.name}\0${refusal.reason}`, refusal])).values(),
254
289
  ];
290
+ const restored = dependencyCacheLine(input.restoredDependencies);
255
291
  if (packages.length === 0 && refusals.length === 0)
256
- return undefined;
292
+ return restored;
293
+ // Only ids safe in a Markdown line; a package whose ids are all unsafe is
294
+ // not listed here (it is still listed as installed).
295
+ const waivers = packages.flatMap((pkg) => {
296
+ // Ids come from OSV (a waiver can match via alias), so keep only the
297
+ // policy id shape and render each in a code span.
298
+ const ids = (pkg.acceptedAdvisories ?? []).filter((id) => ACCEPTED_ADVISORY_ID.test(id));
299
+ return ids.length > 0 ? [`- \`${pkg.name}@${pkg.version}\`: ${ids.map((id) => `\`${id}\``).join(", ")}`] : [];
300
+ });
257
301
  return [
302
+ ...(restored ? [restored, ""] : []),
258
303
  "<details>",
259
- `<summary>Packages installed during this run (${packages.length})</summary>`,
304
+ input.packagesScope === "pull_request"
305
+ ? `<summary>Packages installed by wardby's runs on this pull request (${packages.length})</summary>`
306
+ : `<summary>Packages installed during this run (${packages.length})</summary>`,
260
307
  "",
261
308
  ...cappedList(packages.map((pkg) => `- ${pkg.ecosystem} \`${pkg.name}@${pkg.version}\``)),
262
309
  ...(refusals.length > 0
@@ -266,6 +313,13 @@ function packagesSection(input) {
266
313
  ...cappedList(refusals.map((refusal) => `- ${refusal.ecosystem} \`${refusal.name}\`: ${refusal.reason}`)),
267
314
  ]
268
315
  : []),
316
+ ...(waivers.length > 0
317
+ ? [
318
+ "",
319
+ "**Accepted advisories** (waived by this agent's package policy):",
320
+ ...cappedList(waivers, MAX_PR_WAIVER_LIST_CHARS),
321
+ ]
322
+ : []),
269
323
  "</details>",
270
324
  ].join("\n");
271
325
  }
@@ -282,15 +336,6 @@ const LOCKFILE_NAMES = new Set([
282
336
  export function isLockfilePath(path) {
283
337
  return LOCKFILE_NAMES.has(path.slice(path.lastIndexOf("/") + 1));
284
338
  }
285
- /** What each refusal code means to someone reading the PR, without the registry's internals. */
286
- const REFUSAL_MEANING = {
287
- wardby_package_not_allowed: "not on the allowlist, or only reachable through a refused package",
288
- wardby_version_filtered: "withheld: newer than the release-age limit, or a high-severity advisory",
289
- wardby_graph_incomplete: "its dependencies could not be confirmed (usually transient)",
290
- wardby_lockfile_integrity_mismatch: "the lock file's integrity does not match the registry's",
291
- wardby_upstream_error: "the upstream registry failed (transient)",
292
- wardby_audit_unavailable: "the advisory check was unavailable (transient)",
293
- };
294
339
  /** At most this many refused packages are named in the warning; the full list stays in the details. */
295
340
  const MAX_WARNING_NAMES = 8;
296
341
  /**
@@ -320,7 +365,7 @@ function refusalWarning(input) {
320
365
  const listed = [...names];
321
366
  const shown = listed.slice(0, MAX_WARNING_NAMES).join(", ");
322
367
  const more = listed.length > MAX_WARNING_NAMES ? ` and ${listed.length - MAX_WARNING_NAMES} more` : "";
323
- const meaning = REFUSAL_MEANING[reason];
368
+ const meaning = refusalMeaning(reason);
324
369
  lines.push(`> - ${shown}${more}: \`${reason}\`${meaning ? ` (${meaning})` : ""}`);
325
370
  }
326
371
  const lockfiles = (input.changedLockfiles ?? []).filter(isSafeMarkdownFragment);
@@ -329,8 +374,51 @@ function refusalWarning(input) {
329
374
  }
330
375
  return lines.join("\n");
331
376
  }
377
+ /** The banner when the audit report can't be rendered: fixed text, no record data. */
378
+ const PACKAGE_AUDIT_FALLBACK_BANNER = "> [!CAUTION]\n> **Package audit mode was on for this run.** Wardby's registry may have installed packages outside this agent's allowlist; see `get_run` (`packageAudit`) before merging.";
379
+ /**
380
+ * The package audit block: built only from registry records (names and
381
+ * versions through codeSpan, advisory ids by their policy shape), so neither
382
+ * the model nor a package's metadata can remove or soften it.
383
+ */
384
+ function packageAuditSection(input) {
385
+ if (!input.packageAudit)
386
+ return undefined;
387
+ const summary = summarizePackageAudit(packageAuditRows(input.packageAudit.entries));
388
+ return packageAuditWording(input.packageAudit.mode, summary, { format: "pr" });
389
+ }
390
+ /**
391
+ * The pull request body, within GitHub's MAX_PULL_REQUEST_BODY_CHARS. When it
392
+ * would be longer, the informational Packages section goes first; if that is
393
+ * not enough, the agent's summary is truncated to fit (fitSummary keeps its
394
+ * closing-keyword line). The other sections are bounded and stay whole.
395
+ */
396
+ export function pullRequestBody(input, options = {}) {
397
+ const body = buildPullRequestBody(input, options);
398
+ if (body.length <= MAX_PULL_REQUEST_BODY_CHARS)
399
+ return body;
400
+ const withoutPackages = options.omitPackages ? body : buildPullRequestBody(input, { omitPackages: true });
401
+ if (withoutPackages.length <= MAX_PULL_REQUEST_BODY_CHARS || !input.summary)
402
+ return withoutPackages;
403
+ const room = input.summary.length - (withoutPackages.length - MAX_PULL_REQUEST_BODY_CHARS);
404
+ return buildPullRequestBody({ ...input, summary: fitSummary(input.summary, room) }, { omitPackages: true });
405
+ }
406
+ /**
407
+ * `summary` cut to `max` characters. A last line with a closing keyword
408
+ * (`Resolves #12`) is kept after the cut, so merging still closes the issue.
409
+ */
410
+ function fitSummary(summary, max) {
411
+ const measure = (value) => value.length;
412
+ const lines = summary.trimEnd().split("\n");
413
+ const last = lines[lines.length - 1];
414
+ if (lines.length > 1 && last.length <= 200 && hasClosingKeyword(last)) {
415
+ const kept = truncateText(lines.slice(0, -1).join("\n"), max - last.length - 2, measure);
416
+ return `${kept}\n\n${last}`;
417
+ }
418
+ return truncateText(summary, max, measure);
419
+ }
332
420
  /** The hidden run marker stays first and unconditional: createOrFindDraftPullRequest's idempotent lookup depends on it. */
333
- export function pullRequestBody(input) {
421
+ function buildPullRequestBody(input, options) {
334
422
  const sections = [`${RUN_MARKER_PREFIX}${input.runId} -->`];
335
423
  // Hidden marker stays first; the issue link is the first visible line.
336
424
  const issue = issueLine(input);
@@ -352,6 +440,17 @@ export function pullRequestBody(input) {
352
440
  catch {
353
441
  // omitted, like the packages section
354
442
  }
443
+ try {
444
+ const audit = packageAuditSection(input);
445
+ if (audit)
446
+ sections.push(audit);
447
+ }
448
+ catch {
449
+ // Unlike the packages section, the audit banner fails closed: a report
450
+ // that can't be rendered still says the mode was on.
451
+ if (input.packageAudit)
452
+ sections.push(PACKAGE_AUDIT_FALLBACK_BANNER);
453
+ }
355
454
  if (input.summary)
356
455
  sections.push(input.summary);
357
456
  if (input.tests?.length) {
@@ -360,13 +459,16 @@ export function pullRequestBody(input) {
360
459
  // The packages section is informational: a failure rendering it (e.g. a
361
460
  // malformed report) omits the section and never fails finalization.
362
461
  try {
363
- const packages = packagesSection(input);
462
+ const packages = options.omitPackages ? undefined : packagesSection(input);
364
463
  if (packages)
365
464
  sections.push(packages);
366
465
  }
367
466
  catch {
368
467
  // omitted
369
468
  }
469
+ if (input.latestRunId && SAFE_RUN_ID.test(input.latestRunId)) {
470
+ sections.push(`${LATEST_RUN_MARKER_PREFIX}${input.latestRunId} -->`);
471
+ }
370
472
  return sections.join("\n\n");
371
473
  }
372
474
  /** The hidden marker stays first and unconditional: findStatusComment's lookup depends on it. */
@@ -404,7 +506,9 @@ export class GitHubAppClient {
404
506
  throw new Error("github_app_id_invalid");
405
507
  if (!config.privateKey.trim())
406
508
  throw new Error("github_app_private_key_invalid");
407
- this.apiBaseUrl = normalizeApiBaseUrl(config.apiBaseUrl ?? DEFAULT_API_BASE_URL);
509
+ // WARDBY_TEST_SIMULATORS (test only, refused with production configuration): every call
510
+ // goes to the simulator instead, all or nothing.
511
+ this.apiBaseUrl = testSimulatorBase("github-api") ?? normalizeApiBaseUrl(config.apiBaseUrl ?? DEFAULT_API_BASE_URL);
408
512
  this.apiVersion = config.apiVersion ?? DEFAULT_API_VERSION;
409
513
  if (!/^\d{4}-\d{2}-\d{2}$/.test(this.apiVersion))
410
514
  throw new Error("github_api_version_invalid");
@@ -611,11 +715,14 @@ export class GitHubAppClient {
611
715
  }
612
716
  async completeContinuationCheckRun(input) {
613
717
  const repository = normalizeGitHubRepository(input.repository);
614
- if (!SAFE_RUN_ID.test(input.runId) || !SAFE_COMMIT_SHA.test(input.headSha)) {
718
+ if (!SAFE_RUN_ID.test(input.runId) || (input.headSha !== undefined && !SAFE_COMMIT_SHA.test(input.headSha))) {
615
719
  throw new Error("github_check_run_input_invalid");
616
720
  }
721
+ const ref = input.headSha ?? (input.headRef === undefined ? undefined : normalizeGitRef(input.headRef));
722
+ if (!ref)
723
+ throw new Error("github_check_run_input_invalid");
617
724
  await this.withChecksToken(repository, async (token) => {
618
- const existing = await this.findCheckRun(token, repository, input.headSha, input.runId);
725
+ const existing = await this.findCheckRun(token, repository, ref, input.runId);
619
726
  if (!existing)
620
727
  return;
621
728
  const [owner, name] = repository.split("/");
@@ -653,8 +760,14 @@ export class GitHubAppClient {
653
760
  draft: true,
654
761
  }),
655
762
  }, [201, 422]);
656
- if (response.status === 201)
657
- return this.parsePullRequest(await response.json(), repository);
763
+ if (response.status === 201) {
764
+ const payload = record(await response.json());
765
+ const created = this.parsePullRequest(payload, repository);
766
+ // Hash what GitHub stored, not what was sent.
767
+ return typeof payload.body === "string"
768
+ ? { ...created, descriptionSha256: descriptionSha256(payload.body) }
769
+ : created;
770
+ }
658
771
  const raced = await this.findPullRequest(token, { ...input, repository, baseRef, headRef });
659
772
  if (raced)
660
773
  return raced;
@@ -675,6 +788,270 @@ export class GitHubAppClient {
675
788
  throw new Error("github_pull_request_input_invalid");
676
789
  return this.withRepositoryToken(repository, (token) => this.findPullRequest(token, { runId: input.runId, repository, baseRef, headRef, acceptReadyForReview: true }));
677
790
  }
791
+ /** See GitHubRepositoryAccess.openPullRequestDraft: GitHub's own `draft` flag on the marked open PR. */
792
+ async openPullRequestDraft(input) {
793
+ const repository = normalizeGitHubRepository(input.repository);
794
+ const baseRef = normalizeGitRef(input.baseRef);
795
+ const headRef = normalizeGitRef(input.headRef);
796
+ if (!SAFE_RUN_ID.test(input.runId))
797
+ throw new Error("github_pull_request_input_invalid");
798
+ return this.withRepositoryToken(repository, async (token) => {
799
+ const found = await this.findMarkedPullRequest(token, {
800
+ runId: input.runId,
801
+ repository,
802
+ baseRef,
803
+ headRef,
804
+ acceptReadyForReview: true,
805
+ });
806
+ return found ? found.draft : null;
807
+ });
808
+ }
809
+ /** See GitHubRepositoryAccess.openPullRequestBase: the marked open PR's current base, same repository only. */
810
+ async openPullRequestBase(input) {
811
+ const repository = normalizeGitHubRepository(input.repository);
812
+ const baseRef = normalizeGitRef(input.baseRef);
813
+ const headRef = normalizeGitRef(input.headRef);
814
+ if (!SAFE_RUN_ID.test(input.runId))
815
+ throw new Error("github_pull_request_input_invalid");
816
+ return this.withRepositoryToken(repository, async (token) => {
817
+ const found = await this.findMarkedPullRequest(token, {
818
+ runId: input.runId,
819
+ repository,
820
+ baseRef,
821
+ headRef,
822
+ acceptReadyForReview: true,
823
+ });
824
+ if (!found)
825
+ return null;
826
+ const base = record(found.raw.base);
827
+ const head = record(found.raw.head);
828
+ const sameRepository = (side) => {
829
+ const name = side.repo && typeof side.repo === "object" ? side.repo.full_name : null;
830
+ return typeof name === "string" && name.toLowerCase() === repository.toLowerCase();
831
+ };
832
+ // A fork's branch is never fetched: wardby's own heads live in the PR's repository.
833
+ if (!sameRepository(base) || !sameRepository(head))
834
+ return null;
835
+ if (typeof base.ref !== "string")
836
+ throw new Error("github_api_invalid_response");
837
+ try {
838
+ return { number: found.pr.number, baseRef: normalizeGitRef(base.ref) };
839
+ }
840
+ catch (error) {
841
+ throw new Error("github_api_invalid_response", { cause: error });
842
+ }
843
+ });
844
+ }
845
+ /**
846
+ * A fix round's description update (see GitHubRepositoryAccess). One
847
+ * repository token; every failure becomes `skipped:error`.
848
+ */
849
+ async updatePullRequestDescription(input) {
850
+ try {
851
+ const target = this.descriptionTarget(input);
852
+ return await this.withRepositoryToken(target.repository, async (token) => {
853
+ const state = await this.descriptionState(token, input, target);
854
+ if (!state)
855
+ return { kind: "skipped", reason: "not_open" };
856
+ const { pr, verdict, full, proposed } = state;
857
+ const { repository } = target;
858
+ if (!state.writable) {
859
+ // The comment truncates a body too long for it, so the full one goes in.
860
+ const commentPosted = await this.upsertDescriptionComment(token, repository, pr.number, input, full, state)
861
+ .then(() => true)
862
+ .catch(() => false);
863
+ return { kind: "edited_by_hand", commentPosted };
864
+ }
865
+ if (proposed === undefined)
866
+ return { kind: "skipped", reason: "too_long" };
867
+ const agentTextRunId = verdict.ok ? input.runId : input.baseline.agentTextRunId;
868
+ const outcome = verdict.ok
869
+ ? { kind: "replaced" }
870
+ : { kind: "refreshed", reason: verdict.reason };
871
+ const proposedSha256 = descriptionSha256(proposed);
872
+ if (proposedSha256 === input.baseline.recordedSha256) {
873
+ return { kind: "unchanged", sha256: proposedSha256, agentTextRunId: input.baseline.agentTextRunId };
874
+ }
875
+ const [owner, name] = repository.split("/");
876
+ // Known, accepted race: a person editing between our read and this PATCH is overwritten (GitHub has no If-Match); the store's compare-and-set only covers wardby runs racing each other.
877
+ const response = await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/pulls/${pr.number}`, token, { method: "PATCH", body: JSON.stringify({ body: proposed }) }, [200]);
878
+ const written = record(await response.json()).body;
879
+ if (typeof written !== "string")
880
+ throw new Error("github_api_invalid_response");
881
+ // A proposal left from an earlier round is stale now; a failure here never undoes the write.
882
+ await this.markDescriptionCommentApplied(token, repository, pr.number, input.runId, state).catch(() => undefined);
883
+ return { ...outcome, sha256: descriptionSha256(written), agentTextRunId };
884
+ });
885
+ }
886
+ catch {
887
+ return { kind: "skipped", reason: "error" };
888
+ }
889
+ }
890
+ /** See GitHubRepositoryAccess.proposeDescriptionComment. */
891
+ async proposeDescriptionComment(input) {
892
+ try {
893
+ if (!input.headSha || !/^[0-9a-f]{40}$/.test(input.headSha))
894
+ return false;
895
+ const target = this.descriptionTarget(input);
896
+ return await this.withRepositoryToken(target.repository, async (token) => {
897
+ const state = await this.descriptionState(token, input, target);
898
+ if (!state || state.writable)
899
+ return false;
900
+ await this.upsertDescriptionComment(token, target.repository, state.pr.number, input, state.full, state);
901
+ return true;
902
+ });
903
+ }
904
+ catch {
905
+ return false;
906
+ }
907
+ }
908
+ /** The validated repository and refs; throws unless this is wardby's own pull request branch. */
909
+ descriptionTarget(input) {
910
+ const repository = normalizeGitHubRepository(input.repository);
911
+ const baseRef = normalizeGitRef(input.baseRef);
912
+ const headRef = normalizeGitRef(input.headRef);
913
+ if (!SAFE_RUN_ID.test(input.runId) ||
914
+ !SAFE_RUN_ID.test(input.rootRunId) ||
915
+ !SAFE_RUN_ID.test(input.baseline.agentTextRunId) ||
916
+ (input.headSha !== undefined && !/^[0-9a-f]{40}$/.test(input.headSha)) ||
917
+ // Only wardby's own pull request: its branch is the root run's.
918
+ headRef !== `wardby/run-${input.rootRunId}`) {
919
+ throw new Error("github_pull_request_description_input_invalid");
920
+ }
921
+ return { repository, baseRef, headRef };
922
+ }
923
+ /**
924
+ * The open pull request, the description this run proposes, and whether
925
+ * wardby may write it directly: it is unedited (recorded or rebuilt hash),
926
+ * or a person copied wardby's latest proposal in (taken back). Null when
927
+ * the pull request isn't open. `comments` is set when it was read.
928
+ */
929
+ async descriptionState(token, input, target) {
930
+ const { repository, baseRef, headRef } = target;
931
+ const found = await this.findMarkedPullRequest(token, {
932
+ runId: input.rootRunId,
933
+ repository,
934
+ baseRef,
935
+ headRef,
936
+ acceptReadyForReview: true,
937
+ });
938
+ if (!found)
939
+ return null;
940
+ const { pr, body: currentBody } = found;
941
+ const verdict = assessFinalMessage(input.summary, input.baseline.previousAgentText);
942
+ const summary = verdict.ok ? input.summary : input.baseline.previousAgentText;
943
+ const { full, proposed } = this.proposedDescription(input, repository, baseRef, headRef, summary, currentBody);
944
+ const unedited = isUneditedDescription({
945
+ currentBody,
946
+ rootRunId: input.rootRunId,
947
+ recordedSha256: input.baseline.recordedSha256,
948
+ ...(input.baseline.legacySha256 ? { legacySha256: input.baseline.legacySha256 } : {}),
949
+ previousAgentText: input.baseline.previousAgentText,
950
+ });
951
+ if (unedited)
952
+ return { pr, currentBody, verdict, full, proposed, writable: true };
953
+ const comments = await this.findDescriptionComments(token, repository, pr.number);
954
+ const latest = comments.newest?.parsed.proposed;
955
+ // The author copied wardby's latest proposal in: the description is wardby's again.
956
+ const takenBack = latest !== undefined &&
957
+ isUneditedDescription({
958
+ currentBody,
959
+ rootRunId: input.rootRunId,
960
+ recordedSha256: descriptionSha256(latest),
961
+ });
962
+ return { pr, currentBody, verdict, full, proposed, writable: takenBack, comments };
963
+ }
964
+ /**
965
+ * The rebuilt description: the generated sections from this run with
966
+ * `summary` as the agent text, the latest-run marker last, and the current
967
+ * body's Related block re-inserted verbatim. `proposed` drops the Packages
968
+ * section when `full` exceeds GitHub's limit, and is undefined when it still
969
+ * would.
970
+ */
971
+ proposedDescription(input, repository, baseRef, headRef, summary, currentBody) {
972
+ const related = extractRelatedBlock(currentBody.replace(/\r\n/g, "\n"));
973
+ const build = (omitPackages) => {
974
+ const body = pullRequestBody({
975
+ repository,
976
+ baseRef,
977
+ headRef,
978
+ runId: input.rootRunId,
979
+ latestRunId: input.runId,
980
+ summary,
981
+ tests: input.tests,
982
+ tag: input.tag,
983
+ packages: input.packages,
984
+ packagesScope: "pull_request",
985
+ packageRefusals: input.packageRefusals,
986
+ packageAudit: input.packageAudit,
987
+ restoredDependencies: input.restoredDependencies,
988
+ changedLockfiles: input.changedLockfiles,
989
+ issue: input.issue,
990
+ issueNumber: input.issueNumber,
991
+ }, { omitPackages });
992
+ return related ? (upsertRelatedSection(body, related) ?? body) : body;
993
+ };
994
+ const full = build(false);
995
+ if (full.length <= MAX_PULL_REQUEST_BODY_CHARS)
996
+ return { full, proposed: full };
997
+ const trimmed = build(true);
998
+ return { full, proposed: trimmed.length <= MAX_PULL_REQUEST_BODY_CHARS ? trimmed : undefined };
999
+ }
1000
+ /**
1001
+ * Creates the pull request's one description comment, or edits the newest
1002
+ * one this App wrote (unchanged text is not re-sent); older duplicates are
1003
+ * rewritten to point at it. A comment anyone else wrote is never edited.
1004
+ */
1005
+ async upsertDescriptionComment(token, repository, pullRequestNumber, input, proposedBody, state) {
1006
+ const body = descriptionFallbackComment(input.runId, proposedBody, input.headSha);
1007
+ const { newest, older } = state.comments ?? (await this.findDescriptionComments(token, repository, pullRequestNumber));
1008
+ const [owner, name] = repository.split("/");
1009
+ const commentPath = (id) => `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/issues/comments/${id}`;
1010
+ let url = newest?.url;
1011
+ if (newest) {
1012
+ if (newest.body.replace(/\r\n/g, "\n") !== body) {
1013
+ await this.requestJson(commentPath(newest.id), token, { method: "PATCH", body: JSON.stringify({ body }) }, [200]);
1014
+ }
1015
+ }
1016
+ else {
1017
+ const response = await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/issues/${pullRequestNumber}/comments`, token, { method: "POST", body: JSON.stringify({ body }) }, [201]);
1018
+ const created = record(await response.json()).html_url;
1019
+ url = typeof created === "string" ? created : undefined;
1020
+ }
1021
+ await this.supersedeDescriptionComments(token, commentPath, older, url);
1022
+ }
1023
+ /** Once a run applied its description, a pending proposal in the description comment is marked applied. */
1024
+ async markDescriptionCommentApplied(token, repository, pullRequestNumber, runId, state) {
1025
+ const { newest, older } = state.comments ?? (await this.findDescriptionComments(token, repository, pullRequestNumber));
1026
+ if (!newest)
1027
+ return;
1028
+ const [owner, name] = repository.split("/");
1029
+ const commentPath = (id) => `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/issues/comments/${id}`;
1030
+ if (!newest.parsed.applied) {
1031
+ await this.requestJson(commentPath(newest.id), token, { method: "PATCH", body: JSON.stringify({ body: descriptionAppliedComment(runId) }) }, [200]);
1032
+ }
1033
+ await this.supersedeDescriptionComments(token, commentPath, older, newest.url);
1034
+ }
1035
+ /** Rewrites duplicate description comments to a pointer at the one in use. */
1036
+ async supersedeDescriptionComments(token, commentPath, older, url) {
1037
+ for (const duplicate of older) {
1038
+ await this.requestJson(commentPath(duplicate.id), token, { method: "PATCH", body: JSON.stringify({ body: supersededDescriptionComment(url) }) }, [200]);
1039
+ }
1040
+ }
1041
+ /** The description comments this App wrote (see findDescriptionComments; the reviewer reads them the same way). */
1042
+ async findDescriptionComments(token, repository, pullRequestNumber) {
1043
+ const { id: appId } = await this.appIdentity();
1044
+ const [owner, name] = repository.split("/");
1045
+ return findDescriptionComments(async (page) => {
1046
+ const response = await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/issues/${pullRequestNumber}/comments?per_page=100&page=${page}`, token);
1047
+ const payload = await response.json();
1048
+ if (!Array.isArray(payload))
1049
+ throw new Error("github_api_invalid_response");
1050
+ return payload;
1051
+ },
1052
+ // Only this App's own comments: never a person's, nor another App's or bot's.
1053
+ (comment) => isThisAppsComment(comment, appId));
1054
+ }
678
1055
  async appJwt() {
679
1056
  let keyObject;
680
1057
  try {
@@ -740,11 +1117,19 @@ export class GitHubAppClient {
740
1117
  await this.requestJson("/installation/token", token, { method: "DELETE" }, [204]);
741
1118
  }
742
1119
  async findPullRequest(token, input) {
1120
+ return (await this.findMarkedPullRequest(token, input))?.pr ?? null;
1121
+ }
1122
+ /**
1123
+ * findPullRequest, plus the matched pull request's raw body and payload. Not
1124
+ * filtered by base: a person may retarget the PR, and the head branch plus the
1125
+ * run marker already identify it. When several open PRs match, the one on
1126
+ * `baseRef` wins.
1127
+ */
1128
+ async findMarkedPullRequest(token, input) {
743
1129
  const [owner, name] = input.repository.split("/");
744
1130
  const query = new URLSearchParams({
745
1131
  state: "open",
746
1132
  head: `${owner}:${input.headRef}`,
747
- base: input.baseRef,
748
1133
  per_page: "100",
749
1134
  });
750
1135
  const response = await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/pulls?${query.toString()}`, token);
@@ -752,13 +1137,26 @@ export class GitHubAppClient {
752
1137
  if (!Array.isArray(payload))
753
1138
  throw new Error("github_api_invalid_response");
754
1139
  const marker = `${RUN_MARKER_PREFIX}${input.runId} -->`;
1140
+ let first;
755
1141
  for (const item of payload) {
756
1142
  const candidate = record(item);
757
- if (typeof candidate.body === "string" && candidate.body.includes(marker)) {
758
- return this.parsePullRequest(candidate, input.repository, input.acceptReadyForReview === true);
1143
+ if (typeof candidate.body !== "string" || !candidate.body.includes(marker))
1144
+ continue;
1145
+ const base = candidate.base && typeof candidate.base === "object" ? candidate.base : {};
1146
+ if (base.ref === input.baseRef) {
1147
+ first = candidate;
1148
+ break;
759
1149
  }
1150
+ first ??= candidate;
760
1151
  }
761
- return null;
1152
+ if (!first)
1153
+ return null;
1154
+ return {
1155
+ pr: this.parsePullRequest(first, input.repository, input.acceptReadyForReview === true),
1156
+ body: first.body,
1157
+ draft: first.draft === true,
1158
+ raw: first,
1159
+ };
762
1160
  }
763
1161
  parsePullRequest(value, repository, acceptReadyForReview = false) {
764
1162
  const payload = record(value);
@@ -788,23 +1186,27 @@ export class GitHubAppClient {
788
1186
  return { number, url: expectedUrl };
789
1187
  }
790
1188
  async findStatusComment(token, repository, pullRequestNumber, runId) {
1189
+ const marker = `${STATUS_COMMENT_MARKER_PREFIX}${runId} -->`;
1190
+ return this.findMarkedComment(token, repository, pullRequestNumber, (body) => body.includes(marker));
1191
+ }
1192
+ /** The first comment on the pull request (first page) whose body `matches`. */
1193
+ async findMarkedComment(token, repository, pullRequestNumber, matches) {
791
1194
  const [owner, name] = repository.split("/");
792
1195
  const response = await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/issues/${pullRequestNumber}/comments?per_page=100`, token);
793
1196
  const payload = await response.json();
794
1197
  if (!Array.isArray(payload))
795
1198
  throw new Error("github_api_invalid_response");
796
- const marker = `${STATUS_COMMENT_MARKER_PREFIX}${runId} -->`;
797
1199
  for (const item of payload) {
798
1200
  const candidate = record(item);
799
- if (typeof candidate.body === "string" && candidate.body.includes(marker)) {
800
- return { id: positiveInteger(candidate.id) };
801
- }
1201
+ if (typeof candidate.body !== "string" || !matches(candidate.body))
1202
+ continue;
1203
+ return { id: positiveInteger(candidate.id), body: candidate.body };
802
1204
  }
803
1205
  return null;
804
1206
  }
805
- async findCheckRun(token, repository, headSha, runId) {
1207
+ async findCheckRun(token, repository, ref, runId) {
806
1208
  const [owner, name] = repository.split("/");
807
- const response = await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/commits/${headSha}/check-runs?per_page=100`, token);
1209
+ const response = await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/commits/${encodeURIComponent(ref)}/check-runs?per_page=100`, token);
808
1210
  const payload = record(await response.json());
809
1211
  const checkRuns = Array.isArray(payload.check_runs) ? payload.check_runs.map(record) : [];
810
1212
  for (const candidate of checkRuns) {