@fitlab-ai/agent-infra 0.9.1 → 0.9.3

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 (476) hide show
  1. package/README.md +24 -11
  2. package/README.zh-CN.md +24 -11
  3. package/bin/cli.ts +12 -0
  4. package/dist/bin/cli.js +14 -0
  5. package/dist/bin/internal-cli.js +30 -0
  6. package/dist/lib/agent-client.js +163 -0
  7. package/dist/lib/agent-clients/adapter.js +337 -0
  8. package/dist/lib/agent-clients/adapters/antigravity-cli.js +64 -0
  9. package/dist/lib/agent-clients/adapters/claude-code.js +71 -0
  10. package/dist/lib/agent-clients/adapters/codex-sandbox.js +210 -0
  11. package/dist/lib/agent-clients/adapters/codex.js +63 -0
  12. package/dist/lib/agent-clients/adapters/index.js +20 -0
  13. package/dist/lib/agent-clients/adapters/opencode.js +73 -0
  14. package/dist/lib/agent-clients/config.js +215 -0
  15. package/dist/lib/agent-clients/custom-tuis.js +99 -0
  16. package/dist/lib/agent-clients/invocation.js +9 -0
  17. package/dist/lib/agent-clients/next-steps.js +47 -0
  18. package/dist/lib/agent-clients/project-assets.js +45 -0
  19. package/dist/lib/agent-clients/reconcile.js +236 -0
  20. package/dist/lib/agent-clients/registry.js +78 -0
  21. package/dist/lib/agent-clients/schema.js +75 -0
  22. package/dist/lib/agent-clients/tokens.js +49 -0
  23. package/dist/lib/agent-clients/types.js +29 -0
  24. package/dist/lib/builtin-tuis.js +9 -14
  25. package/dist/lib/defaults.json +85 -73
  26. package/dist/lib/git/review-snapshot.js +25 -10
  27. package/dist/lib/git/reviewed-commit-equivalence.js +110 -0
  28. package/dist/lib/git/workflow.js +54 -1
  29. package/dist/lib/init.js +47 -91
  30. package/dist/lib/internal/agent-client.js +174 -0
  31. package/dist/lib/internal/git-workflow.js +7 -2
  32. package/dist/lib/internal/platform-checks.js +3 -3
  33. package/dist/lib/internal/platform-comment.js +29 -2
  34. package/dist/lib/internal/platform-issue.js +7 -0
  35. package/dist/lib/internal/platform-pr-review.js +132 -0
  36. package/dist/lib/internal/platform-pr.js +20 -2
  37. package/dist/lib/internal/platform-release-notes.js +52 -16
  38. package/dist/lib/internal/pr-review-grade.js +140 -0
  39. package/dist/lib/internal/release-workflow.js +82 -20
  40. package/dist/lib/internal/task-activity.js +73 -0
  41. package/dist/lib/internal/task-event.js +17 -1
  42. package/dist/lib/internal/task-lifecycle.js +7 -0
  43. package/dist/lib/internal/task-orchestration.js +196 -0
  44. package/dist/lib/internal/task-review.js +70 -0
  45. package/dist/lib/merge.js +2 -1
  46. package/dist/lib/platform/adapters.js +45 -1
  47. package/dist/lib/platform/change-request-git-evidence.js +113 -0
  48. package/dist/lib/platform/completion-backfill.js +122 -0
  49. package/dist/lib/platform/issue-comments.js +3 -2
  50. package/dist/lib/platform/merged-pr-equivalence.js +156 -16
  51. package/dist/lib/platform/pr-checks.js +101 -50
  52. package/dist/lib/platform/pr-review.js +166 -0
  53. package/dist/lib/platform/pull-requests.js +432 -7
  54. package/dist/lib/platform/release-notes.js +85 -3
  55. package/dist/lib/platform/releases.js +1 -1
  56. package/dist/lib/platform/verification-required.js +8 -10
  57. package/dist/lib/pr-review/evidence-grading.js +229 -0
  58. package/dist/lib/prompt.js +15 -3
  59. package/dist/lib/release/channels.js +4 -1
  60. package/dist/lib/release/workflow.js +4 -2
  61. package/dist/lib/remove-dir.js +25 -0
  62. package/dist/lib/run/tui.js +11 -7
  63. package/dist/lib/sandbox/agent-client-reconciler.js +234 -0
  64. package/dist/lib/sandbox/clipboard/linux.js +2 -1
  65. package/dist/lib/sandbox/commands/create.js +76 -200
  66. package/dist/lib/sandbox/commands/enter.js +17 -1
  67. package/dist/lib/sandbox/commands/prune.js +2 -2
  68. package/dist/lib/sandbox/commands/rm.js +3 -2
  69. package/dist/lib/sandbox/commands/show.js +1 -1
  70. package/dist/lib/sandbox/config.js +21 -4
  71. package/dist/lib/sandbox/constants.js +3 -0
  72. package/dist/lib/sandbox/dockerfile.js +2 -1
  73. package/dist/lib/sandbox/managed-fs.js +10 -5
  74. package/dist/lib/sandbox/recovery.js +52 -36
  75. package/dist/lib/sandbox/shell.js +1 -1
  76. package/dist/lib/sandbox/tool-types.js +8 -0
  77. package/dist/lib/sandbox/tools.js +2 -126
  78. package/dist/lib/task/activity-intent.js +101 -0
  79. package/dist/lib/task/artifact-lifecycle.js +3 -50
  80. package/dist/lib/task/commands/decisions.js +50 -20
  81. package/dist/lib/task/commands/log.js +15 -14
  82. package/dist/lib/task/delegation-receipts.js +128 -0
  83. package/dist/lib/task/events.js +146 -8
  84. package/dist/lib/task/finalization-artifacts.js +35 -0
  85. package/dist/lib/task/lifecycle-execution.js +46 -0
  86. package/dist/lib/task/orchestration.js +784 -0
  87. package/dist/lib/task/review-artifacts.js +143 -59
  88. package/dist/lib/task/review-finalization.js +186 -0
  89. package/dist/lib/task/review-fingerprint.js +4 -0
  90. package/dist/lib/task/task-execution-lock.js +231 -0
  91. package/dist/lib/task/verification-checks.js +2 -1
  92. package/dist/lib/task/verification-engine.js +372 -28
  93. package/dist/lib/task/verification-review.js +1 -1
  94. package/dist/lib/task/verification.js +5 -2
  95. package/dist/lib/task/workspace-snapshot.js +46 -0
  96. package/dist/lib/update.js +42 -161
  97. package/lib/agent-client.ts +214 -0
  98. package/lib/agent-clients/adapter.ts +534 -0
  99. package/lib/agent-clients/adapters/antigravity-cli.ts +65 -0
  100. package/lib/agent-clients/adapters/claude-code.ts +79 -0
  101. package/lib/agent-clients/adapters/codex-sandbox.ts +245 -0
  102. package/lib/agent-clients/adapters/codex.ts +67 -0
  103. package/lib/agent-clients/adapters/index.ts +27 -0
  104. package/lib/agent-clients/adapters/opencode.ts +75 -0
  105. package/lib/agent-clients/config.ts +323 -0
  106. package/lib/agent-clients/custom-tuis.ts +149 -0
  107. package/lib/agent-clients/invocation.ts +19 -0
  108. package/lib/agent-clients/next-steps.ts +85 -0
  109. package/lib/agent-clients/project-assets.ts +96 -0
  110. package/lib/agent-clients/reconcile.ts +325 -0
  111. package/lib/agent-clients/registry.ts +154 -0
  112. package/lib/agent-clients/schema.ts +80 -0
  113. package/lib/agent-clients/tokens.ts +53 -0
  114. package/lib/agent-clients/types.ts +92 -0
  115. package/lib/benchmark/schemas/v1.0.0/case-manifest.schema.json +120 -0
  116. package/lib/benchmark/schemas/v1.0.0/common.schema.json +109 -0
  117. package/lib/benchmark/schemas/v1.0.0/grader-result.schema.json +160 -0
  118. package/lib/benchmark/schemas/v1.0.0/run-manifest.schema.json +122 -0
  119. package/lib/benchmark/schemas/v1.0.0/subject.schema.json +227 -0
  120. package/lib/builtin-tuis.ts +16 -15
  121. package/lib/defaults.json +24 -12
  122. package/lib/git/review-snapshot.ts +39 -9
  123. package/lib/git/reviewed-commit-equivalence.ts +135 -0
  124. package/lib/git/workflow.ts +66 -2
  125. package/lib/init.ts +53 -121
  126. package/lib/internal/agent-client.ts +186 -0
  127. package/lib/internal/git-workflow.ts +6 -2
  128. package/lib/internal/platform-checks.ts +4 -4
  129. package/lib/internal/platform-comment.ts +17 -2
  130. package/lib/internal/platform-issue.ts +4 -0
  131. package/lib/internal/platform-pr-review.ts +104 -0
  132. package/lib/internal/platform-pr.ts +14 -1
  133. package/lib/internal/platform-release-notes.ts +59 -16
  134. package/lib/internal/pr-review-grade.ts +130 -0
  135. package/lib/internal/release-workflow.ts +79 -20
  136. package/lib/internal/task-activity.ts +55 -0
  137. package/lib/internal/task-event.ts +9 -1
  138. package/lib/internal/task-lifecycle.ts +4 -0
  139. package/lib/internal/task-orchestration.ts +171 -0
  140. package/lib/internal/task-review.ts +70 -0
  141. package/lib/merge.ts +2 -1
  142. package/lib/platform/adapters.ts +169 -3
  143. package/lib/platform/change-request-git-evidence.ts +159 -0
  144. package/lib/platform/completion-backfill.ts +134 -0
  145. package/lib/platform/issue-comments.ts +3 -2
  146. package/lib/platform/merged-pr-equivalence.ts +178 -19
  147. package/lib/platform/pr-checks.ts +121 -66
  148. package/lib/platform/pr-review.ts +201 -0
  149. package/lib/platform/pull-requests.ts +529 -27
  150. package/lib/platform/release-notes.ts +100 -4
  151. package/lib/platform/releases.ts +1 -1
  152. package/lib/platform/verification-required.ts +12 -10
  153. package/lib/pr-review/evidence-grading.ts +318 -0
  154. package/lib/prompt.ts +16 -3
  155. package/lib/release/channels.ts +4 -1
  156. package/lib/release/workflow.ts +4 -3
  157. package/lib/remove-dir.ts +28 -0
  158. package/lib/run/tui.ts +12 -5
  159. package/lib/sandbox/agent-client-reconciler.ts +378 -0
  160. package/lib/sandbox/clipboard/linux.ts +2 -1
  161. package/lib/sandbox/commands/create.ts +111 -212
  162. package/lib/sandbox/commands/enter.ts +23 -1
  163. package/lib/sandbox/commands/prune.ts +2 -2
  164. package/lib/sandbox/commands/rm.ts +3 -2
  165. package/lib/sandbox/commands/show.ts +1 -1
  166. package/lib/sandbox/config.ts +31 -4
  167. package/lib/sandbox/constants.ts +6 -0
  168. package/lib/sandbox/dockerfile.ts +2 -1
  169. package/lib/sandbox/managed-fs.ts +18 -5
  170. package/lib/sandbox/recovery.ts +98 -67
  171. package/lib/sandbox/shell.ts +1 -1
  172. package/lib/sandbox/tool-types.ts +143 -0
  173. package/lib/sandbox/tools.ts +5 -157
  174. package/lib/server/server.schema.json +2 -2
  175. package/lib/task/activity-intent.ts +128 -0
  176. package/lib/task/artifact-lifecycle.ts +3 -39
  177. package/lib/task/commands/decisions.ts +53 -20
  178. package/lib/task/commands/log.ts +15 -14
  179. package/lib/task/delegation-receipts.ts +199 -0
  180. package/lib/task/events.ts +164 -11
  181. package/lib/task/finalization-artifacts.ts +58 -0
  182. package/lib/task/lifecycle-execution.ts +106 -0
  183. package/lib/task/orchestration.ts +1022 -0
  184. package/lib/task/review-artifacts.ts +201 -69
  185. package/lib/task/review-finalization.ts +306 -0
  186. package/lib/task/review-fingerprint.ts +5 -0
  187. package/lib/task/task-execution-lock.ts +296 -0
  188. package/lib/task/verification-checks.ts +2 -1
  189. package/lib/task/verification-engine.ts +424 -28
  190. package/lib/task/verification-review.ts +1 -0
  191. package/lib/task/verification.ts +8 -3
  192. package/lib/task/workspace-snapshot.ts +57 -0
  193. package/lib/update.ts +48 -184
  194. package/package.json +4 -4
  195. package/templates/.agents/QUICKSTART.en.md +25 -8
  196. package/templates/.agents/QUICKSTART.zh-CN.md +25 -8
  197. package/templates/.agents/README.en.md +29 -25
  198. package/templates/.agents/README.zh-CN.md +29 -25
  199. package/templates/.agents/hooks/lifecycle-delegation.js +75 -0
  200. package/templates/.agents/rules/README.en.md +2 -0
  201. package/templates/.agents/rules/README.zh-CN.md +2 -0
  202. package/templates/.agents/rules/commit-and-pr.en.md +1 -0
  203. package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -0
  204. package/templates/.agents/rules/create-issue.github.en.md +4 -2
  205. package/templates/.agents/rules/create-issue.github.zh-CN.md +4 -2
  206. package/templates/.agents/rules/human-decision-context.en.md +14 -12
  207. package/templates/.agents/rules/human-decision-context.zh-CN.md +14 -12
  208. package/templates/.agents/rules/issue-fields.github.en.md +3 -1
  209. package/templates/.agents/rules/issue-fields.github.zh-CN.md +3 -1
  210. package/templates/.agents/rules/issue-pr-commands.en.md +5 -3
  211. package/templates/.agents/rules/issue-pr-commands.github.en.md +5 -3
  212. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +5 -3
  213. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +5 -3
  214. package/templates/.agents/rules/issue-sync.github.en.md +7 -3
  215. package/templates/.agents/rules/issue-sync.github.zh-CN.md +8 -4
  216. package/templates/.agents/rules/lifecycle-orchestration.en.md +24 -0
  217. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +24 -0
  218. package/templates/.agents/rules/milestone-inference.github.en.md +4 -2
  219. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +4 -2
  220. package/templates/.agents/rules/next-step-output.en.md +18 -0
  221. package/templates/.agents/rules/next-step-output.zh-CN.md +18 -0
  222. package/templates/.agents/rules/no-mid-flow-questions.en.md +19 -0
  223. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +19 -0
  224. package/templates/.agents/rules/pr-checks-commands.en.md +3 -3
  225. package/templates/.agents/rules/pr-checks-commands.github.en.md +2 -2
  226. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +2 -2
  227. package/templates/.agents/rules/pr-checks-commands.zh-CN.md +3 -3
  228. package/templates/.agents/rules/pr-sync.en.md +3 -1
  229. package/templates/.agents/rules/pr-sync.github.en.md +11 -1
  230. package/templates/.agents/rules/pr-sync.github.zh-CN.md +11 -1
  231. package/templates/.agents/rules/pr-sync.zh-CN.md +3 -1
  232. package/templates/.agents/rules/release-commands.en.md +1 -1
  233. package/templates/.agents/rules/release-commands.github.en.md +21 -5
  234. package/templates/.agents/rules/release-commands.github.zh-CN.md +21 -5
  235. package/templates/.agents/rules/release-commands.zh-CN.md +1 -1
  236. package/templates/.agents/rules/review-handshake.en.md +4 -4
  237. package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
  238. package/templates/.agents/rules/review-method.en.md +113 -0
  239. package/templates/.agents/rules/review-method.zh-CN.md +98 -0
  240. package/templates/.agents/rules/task-management.en.md +11 -0
  241. package/templates/.agents/rules/task-management.zh-CN.md +11 -0
  242. package/templates/.agents/skills/analyze-task/SKILL.en.md +15 -12
  243. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +15 -12
  244. package/templates/.agents/skills/block-task/SKILL.en.md +11 -9
  245. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +11 -9
  246. package/templates/.agents/skills/cancel-task/SKILL.en.md +10 -8
  247. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +11 -9
  248. package/templates/.agents/skills/check-task/SKILL.en.md +16 -16
  249. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -16
  250. package/templates/.agents/skills/close-codescan/SKILL.en.md +7 -5
  251. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +7 -5
  252. package/templates/.agents/skills/close-dependabot/SKILL.en.md +7 -5
  253. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +7 -5
  254. package/templates/.agents/skills/code-task/SKILL.en.md +10 -7
  255. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +13 -10
  256. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +14 -16
  257. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +14 -16
  258. package/templates/.agents/skills/code-task/reference/output-template.en.md +3 -3
  259. package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +3 -3
  260. package/templates/.agents/skills/commit/SKILL.en.md +13 -1
  261. package/templates/.agents/skills/commit/SKILL.zh-CN.md +13 -1
  262. package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
  263. package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
  264. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
  265. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
  266. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
  267. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
  268. package/templates/.agents/skills/commit/reference/task-status-update.en.md +24 -28
  269. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +24 -28
  270. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +6 -4
  271. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +6 -4
  272. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +1 -1
  273. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +1 -1
  274. package/templates/.agents/skills/complete-task/SKILL.en.md +23 -8
  275. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +23 -8
  276. package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
  277. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
  278. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +17 -0
  279. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +17 -0
  280. package/templates/.agents/skills/create-pr/SKILL.en.md +13 -11
  281. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +13 -11
  282. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +1 -1
  283. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +1 -1
  284. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +3 -3
  285. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +3 -3
  286. package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -16
  287. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +17 -16
  288. package/templates/.agents/skills/create-task/SKILL.en.md +16 -14
  289. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +16 -14
  290. package/templates/.agents/skills/import-codescan/SKILL.en.md +5 -5
  291. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +5 -5
  292. package/templates/.agents/skills/import-dependabot/SKILL.en.md +5 -5
  293. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +5 -5
  294. package/templates/.agents/skills/import-issue/SKILL.en.md +10 -8
  295. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +10 -8
  296. package/templates/.agents/skills/init-labels/SKILL.en.md +4 -4
  297. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +4 -4
  298. package/templates/.agents/skills/init-milestones/SKILL.en.md +4 -4
  299. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -4
  300. package/templates/.agents/skills/plan-task/SKILL.en.md +13 -10
  301. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -10
  302. package/templates/.agents/skills/post-release/SKILL.en.md +2 -0
  303. package/templates/.agents/skills/post-release/SKILL.zh-CN.md +2 -0
  304. package/templates/.agents/skills/refine-title/SKILL.en.md +1 -1
  305. package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +1 -1
  306. package/templates/.agents/skills/release/SKILL.en.md +16 -10
  307. package/templates/.agents/skills/release/SKILL.zh-CN.md +16 -10
  308. package/templates/.agents/skills/restore-task/SKILL.en.md +4 -1
  309. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +4 -1
  310. package/templates/.agents/skills/review-analysis/SKILL.en.md +18 -4
  311. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +19 -5
  312. package/templates/.agents/skills/review-analysis/config/verify.en.json +6 -0
  313. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +6 -0
  314. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +13 -15
  315. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +13 -15
  316. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +51 -5
  317. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +52 -6
  318. package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +25 -22
  319. package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +25 -22
  320. package/templates/.agents/skills/review-code/SKILL.en.md +35 -12
  321. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +35 -12
  322. package/templates/.agents/skills/review-code/config/verify.en.json +7 -0
  323. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +7 -0
  324. package/templates/.agents/skills/review-code/reference/concurrency-risks.en.md +25 -0
  325. package/templates/.agents/skills/review-code/reference/concurrency-risks.zh-CN.md +25 -0
  326. package/templates/.agents/skills/review-code/reference/cross-platform-risks.en.md +27 -0
  327. package/templates/.agents/skills/review-code/reference/cross-platform-risks.zh-CN.md +27 -0
  328. package/templates/.agents/skills/review-code/reference/documentation-antipatterns.en.md +13 -0
  329. package/templates/.agents/skills/review-code/reference/documentation-antipatterns.zh-CN.md +13 -0
  330. package/templates/.agents/skills/review-code/reference/migration-risks.en.md +25 -0
  331. package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +25 -0
  332. package/templates/.agents/skills/review-code/reference/output-templates.en.md +22 -24
  333. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +22 -24
  334. package/templates/.agents/skills/review-code/reference/report-template.en.md +54 -7
  335. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +54 -7
  336. package/templates/.agents/skills/review-code/reference/review-criteria.en.md +29 -24
  337. package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +29 -24
  338. package/templates/.agents/skills/review-code/reference/security-risks.en.md +25 -0
  339. package/templates/.agents/skills/review-code/reference/security-risks.zh-CN.md +25 -0
  340. package/templates/.agents/skills/review-plan/SKILL.en.md +18 -4
  341. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +19 -5
  342. package/templates/.agents/skills/review-plan/config/verify.en.json +6 -0
  343. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +6 -0
  344. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +13 -15
  345. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +13 -15
  346. package/templates/.agents/skills/review-plan/reference/report-template.en.md +61 -5
  347. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +61 -5
  348. package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +21 -24
  349. package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +21 -24
  350. package/templates/.agents/skills/review-pr/SKILL.en.md +158 -0
  351. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +158 -0
  352. package/templates/.agents/skills/review-pr/config/verify.en.json +37 -0
  353. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +37 -0
  354. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +65 -0
  355. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +65 -0
  356. package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +18 -0
  357. package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +18 -0
  358. package/templates/.agents/skills/review-pr/reference/report-template.en.md +85 -0
  359. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +85 -0
  360. package/templates/.agents/skills/run-task/SKILL.en.md +22 -0
  361. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +22 -0
  362. package/templates/.agents/skills/run-task/config/verify.en.json +10 -0
  363. package/templates/.agents/skills/run-task/config/verify.zh-CN.json +10 -0
  364. package/templates/.agents/skills/run-task/reference/host-validation.en.md +20 -0
  365. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +20 -0
  366. package/templates/.agents/skills/test/SKILL.en.md +4 -4
  367. package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -4
  368. package/templates/.agents/skills/test-integration/SKILL.en.md +4 -4
  369. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +4 -4
  370. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -5
  371. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -5
  372. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +664 -246
  373. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +4 -4
  374. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +4 -4
  375. package/templates/.agents/skills/watch-pr/SKILL.en.md +21 -21
  376. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +21 -21
  377. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +27 -6
  378. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +26 -5
  379. package/templates/.agents/templates/task.en.md +1 -1
  380. package/templates/.agents/templates/task.zh-CN.md +1 -1
  381. package/templates/.agents/workflows/bug-fix.en.yaml +3 -3
  382. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +3 -3
  383. package/templates/.agents/workflows/code-review.en.yaml +1 -1
  384. package/templates/.agents/workflows/code-review.zh-CN.yaml +1 -1
  385. package/templates/.agents/workflows/feature-development.en.yaml +4 -4
  386. package/templates/.agents/workflows/feature-development.zh-CN.yaml +4 -4
  387. package/templates/.agents/workflows/refactoring.en.yaml +3 -3
  388. package/templates/.agents/workflows/refactoring.zh-CN.yaml +3 -3
  389. package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +6 -0
  390. package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +6 -0
  391. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +6 -0
  392. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +6 -0
  393. package/templates/.claude/commands/post-release.en.md +1 -0
  394. package/templates/.claude/commands/post-release.zh-CN.md +1 -0
  395. package/templates/.claude/commands/review-pr.en.md +8 -0
  396. package/templates/.claude/commands/review-pr.zh-CN.md +8 -0
  397. package/templates/.claude/commands/run-task.en.md +8 -0
  398. package/templates/.claude/commands/run-task.zh-CN.md +8 -0
  399. package/templates/.claude/settings.json +24 -0
  400. package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +3 -0
  401. package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +3 -0
  402. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +3 -0
  403. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +3 -0
  404. package/templates/.gemini/commands/_project_/review-pr.en.toml +10 -0
  405. package/templates/.gemini/commands/_project_/review-pr.zh-CN.toml +10 -0
  406. package/templates/.opencode/commands/post-release.en.md +2 -0
  407. package/templates/.opencode/commands/post-release.zh-CN.md +2 -0
  408. package/templates/.opencode/commands/review-pr.en.md +11 -0
  409. package/templates/.opencode/commands/review-pr.zh-CN.md +11 -0
  410. package/templates/.opencode/commands/run-task.en.md +11 -0
  411. package/templates/.opencode/commands/run-task.zh-CN.md +11 -0
  412. package/templates/.gemini/commands/_project_/analyze-task.en.toml +0 -10
  413. package/templates/.gemini/commands/_project_/analyze-task.zh-CN.toml +0 -10
  414. package/templates/.gemini/commands/_project_/archive-tasks.en.toml +0 -10
  415. package/templates/.gemini/commands/_project_/archive-tasks.zh-CN.toml +0 -10
  416. package/templates/.gemini/commands/_project_/block-task.en.toml +0 -10
  417. package/templates/.gemini/commands/_project_/block-task.zh-CN.toml +0 -10
  418. package/templates/.gemini/commands/_project_/cancel-task.en.toml +0 -10
  419. package/templates/.gemini/commands/_project_/cancel-task.zh-CN.toml +0 -10
  420. package/templates/.gemini/commands/_project_/check-task.en.toml +0 -10
  421. package/templates/.gemini/commands/_project_/check-task.zh-CN.toml +0 -10
  422. package/templates/.gemini/commands/_project_/close-codescan.en.toml +0 -8
  423. package/templates/.gemini/commands/_project_/close-codescan.zh-CN.toml +0 -8
  424. package/templates/.gemini/commands/_project_/close-dependabot.en.toml +0 -8
  425. package/templates/.gemini/commands/_project_/close-dependabot.zh-CN.toml +0 -8
  426. package/templates/.gemini/commands/_project_/code-task.en.toml +0 -10
  427. package/templates/.gemini/commands/_project_/code-task.zh-CN.toml +0 -10
  428. package/templates/.gemini/commands/_project_/commit.en.toml +0 -10
  429. package/templates/.gemini/commands/_project_/commit.zh-CN.toml +0 -10
  430. package/templates/.gemini/commands/_project_/complete-manual-validation.en.toml +0 -10
  431. package/templates/.gemini/commands/_project_/complete-manual-validation.zh-CN.toml +0 -10
  432. package/templates/.gemini/commands/_project_/complete-task.en.toml +0 -10
  433. package/templates/.gemini/commands/_project_/complete-task.zh-CN.toml +0 -10
  434. package/templates/.gemini/commands/_project_/create-pr.en.toml +0 -10
  435. package/templates/.gemini/commands/_project_/create-pr.zh-CN.toml +0 -10
  436. package/templates/.gemini/commands/_project_/create-release-note.en.toml +0 -8
  437. package/templates/.gemini/commands/_project_/create-release-note.zh-CN.toml +0 -8
  438. package/templates/.gemini/commands/_project_/create-task.en.toml +0 -8
  439. package/templates/.gemini/commands/_project_/create-task.zh-CN.toml +0 -8
  440. package/templates/.gemini/commands/_project_/import-codescan.en.toml +0 -8
  441. package/templates/.gemini/commands/_project_/import-codescan.zh-CN.toml +0 -8
  442. package/templates/.gemini/commands/_project_/import-dependabot.en.toml +0 -8
  443. package/templates/.gemini/commands/_project_/import-dependabot.zh-CN.toml +0 -8
  444. package/templates/.gemini/commands/_project_/import-issue.en.toml +0 -8
  445. package/templates/.gemini/commands/_project_/import-issue.zh-CN.toml +0 -8
  446. package/templates/.gemini/commands/_project_/init-labels.en.toml +0 -8
  447. package/templates/.gemini/commands/_project_/init-labels.zh-CN.toml +0 -8
  448. package/templates/.gemini/commands/_project_/init-milestones.en.toml +0 -10
  449. package/templates/.gemini/commands/_project_/init-milestones.zh-CN.toml +0 -10
  450. package/templates/.gemini/commands/_project_/plan-task.en.toml +0 -10
  451. package/templates/.gemini/commands/_project_/plan-task.zh-CN.toml +0 -10
  452. package/templates/.gemini/commands/_project_/post-release.en.toml +0 -6
  453. package/templates/.gemini/commands/_project_/post-release.zh-CN.toml +0 -6
  454. package/templates/.gemini/commands/_project_/refine-title.en.toml +0 -8
  455. package/templates/.gemini/commands/_project_/refine-title.zh-CN.toml +0 -8
  456. package/templates/.gemini/commands/_project_/release.en.toml +0 -8
  457. package/templates/.gemini/commands/_project_/release.zh-CN.toml +0 -8
  458. package/templates/.gemini/commands/_project_/restore-task.en.toml +0 -8
  459. package/templates/.gemini/commands/_project_/restore-task.zh-CN.toml +0 -8
  460. package/templates/.gemini/commands/_project_/review-analysis.en.toml +0 -10
  461. package/templates/.gemini/commands/_project_/review-analysis.zh-CN.toml +0 -10
  462. package/templates/.gemini/commands/_project_/review-code.en.toml +0 -10
  463. package/templates/.gemini/commands/_project_/review-code.zh-CN.toml +0 -10
  464. package/templates/.gemini/commands/_project_/review-plan.en.toml +0 -10
  465. package/templates/.gemini/commands/_project_/review-plan.zh-CN.toml +0 -10
  466. package/templates/.gemini/commands/_project_/test-integration.en.toml +0 -6
  467. package/templates/.gemini/commands/_project_/test-integration.zh-CN.toml +0 -6
  468. package/templates/.gemini/commands/_project_/test.en.toml +0 -6
  469. package/templates/.gemini/commands/_project_/test.zh-CN.toml +0 -6
  470. package/templates/.gemini/commands/_project_/update-agent-infra.en.toml +0 -6
  471. package/templates/.gemini/commands/_project_/update-agent-infra.zh-CN.toml +0 -6
  472. package/templates/.gemini/commands/_project_/upgrade-dependency.en.toml +0 -8
  473. package/templates/.gemini/commands/_project_/upgrade-dependency.zh-CN.toml +0 -8
  474. package/templates/.gemini/commands/_project_/watch-pr.en.toml +0 -10
  475. package/templates/.gemini/commands/_project_/watch-pr.zh-CN.toml +0 -10
  476. package/templates/.gemini/settings.json +0 -3
@@ -1,13 +1,16 @@
1
1
  import fs from 'node:fs';
2
+ import { execFileSync } from 'node:child_process';
2
3
  import { parseTaskFrontmatter } from '../task/frontmatter.js';
3
4
  import { resolveTaskRef } from '../task/resolve-ref.js';
4
5
  import { extractSection } from '../task/sections.js';
5
6
  import { captureTaskWriteMetadata, writeTask } from '../task/write.js';
6
- import { resolvePlatformContext } from './context.js';
7
+ import { inspectPlatformChangeRequest, inspectPlatformIssueClosingChangeRequests, registerPlatformCapabilities } from './adapters.js';
8
+ import { parseGitHubRemote, resolvePlatformContext } from './context.js';
7
9
  import { createGitHubClient } from './github-client.js';
8
10
  import { inspectPlatformIssue } from './issues.js';
9
11
  import { planPullRequestMetadata } from './pull-request-metadata.js';
10
12
  import { platformResult } from './types.js';
13
+ import { inspectCompletionArtifacts } from '../task/finalization-artifacts.js';
11
14
  function result(status, taskId, issueNumber, prNumber, overrides = {}) {
12
15
  return {
13
16
  ...platformResult(status),
@@ -16,6 +19,17 @@ function result(status, taskId, issueNumber, prNumber, overrides = {}) {
16
19
  ...overrides
17
20
  };
18
21
  }
22
+ function externalResult(base, overrides = {}) {
23
+ return {
24
+ ...base,
25
+ mode: null,
26
+ authorization: null,
27
+ candidates: [],
28
+ eligible: [],
29
+ selected: null,
30
+ ...overrides
31
+ };
32
+ }
19
33
  function normalizePullRequest(remote, repository) {
20
34
  const number = Number(remote.number);
21
35
  const headRepository = remote.head?.repo?.full_name;
@@ -23,6 +37,12 @@ function normalizePullRequest(remote, repository) {
23
37
  if (!Number.isInteger(number) || number <= 0 || !remote.node_id || !remote.html_url ||
24
38
  !remote.head?.ref || !remote.head.sha || !headRepository || !remote.base?.ref || !baseRepository)
25
39
  return null;
40
+ const mergeabilityDetail = remote.mergeable_state?.trim().toLowerCase() || null;
41
+ const mergeability = remote.mergeable === false
42
+ ? { state: 'conflicting', detail: mergeabilityDetail }
43
+ : remote.mergeable === true && mergeabilityDetail !== 'dirty'
44
+ ? { state: 'mergeable', detail: mergeabilityDetail }
45
+ : { state: 'unknown', detail: mergeabilityDetail };
26
46
  return {
27
47
  repository,
28
48
  number,
@@ -38,9 +58,141 @@ function normalizePullRequest(remote, repository) {
38
58
  mergeCommitSha: remote.merge_commit_sha || null,
39
59
  labels: (remote.labels || []).map((label) => typeof label === 'string' ? label : label.name || '').filter(Boolean).sort(),
40
60
  assignees: (remote.assignees || []).map((assignee) => assignee.login || '').filter(Boolean).sort(),
41
- milestone: remote.milestone?.title || null
61
+ milestone: remote.milestone?.title || null,
62
+ mergeability
42
63
  };
43
64
  }
65
+ function normalizeClosingPullRequest(node, repository) {
66
+ return normalizePullRequest({
67
+ number: node.number,
68
+ node_id: node.id,
69
+ html_url: node.url,
70
+ state: node.state === 'OPEN' ? 'open' : 'closed',
71
+ title: node.title,
72
+ body: node.body,
73
+ draft: node.isDraft,
74
+ head: { ref: node.headRefName, sha: node.headRefOid, repo: node.headRepository ? { full_name: node.headRepository.nameWithOwner } : null },
75
+ base: { ref: node.baseRefName, sha: node.baseRefOid, repo: node.baseRepository ? { full_name: node.baseRepository.nameWithOwner } : null },
76
+ merged_at: node.mergedAt,
77
+ merge_commit_sha: node.mergeCommit?.oid,
78
+ labels: node.labels?.nodes,
79
+ assignees: node.assignees?.nodes,
80
+ milestone: node.milestone
81
+ }, repository);
82
+ }
83
+ function repositoryKey(repository) {
84
+ return repository.trim().toLowerCase();
85
+ }
86
+ function identitySignature(pullRequest) {
87
+ return [
88
+ pullRequest.repository, pullRequest.number, pullRequest.nodeId, pullRequest.url,
89
+ pullRequest.head.repository, pullRequest.head.ref, pullRequest.head.sha,
90
+ pullRequest.base.repository, pullRequest.base.ref, pullRequest.base.sha,
91
+ pullRequest.mergedAt || '', pullRequest.mergeCommitSha || ''
92
+ ].join('|');
93
+ }
94
+ function hasCompleteExternalIdentity(pullRequest) {
95
+ const required = [
96
+ pullRequest.repository, pullRequest.nodeId, pullRequest.url,
97
+ pullRequest.head.repository, pullRequest.head.ref, pullRequest.head.sha,
98
+ pullRequest.base.repository, pullRequest.base.ref, pullRequest.base.sha
99
+ ];
100
+ return Number.isInteger(pullRequest.number) && pullRequest.number > 0 &&
101
+ required.every((value) => Boolean(value?.trim())) &&
102
+ Boolean(pullRequest.mergedAt) === Boolean(pullRequest.mergeCommitSha);
103
+ }
104
+ function selectExternalPullRequest(candidates, repository, existingPrNumber, explicitPrNumber) {
105
+ const invalid = candidates.find((candidate) => !hasCompleteExternalIdentity(candidate));
106
+ if (invalid)
107
+ return {
108
+ status: 'failed', code: 'PR_IDENTITY_INVALID',
109
+ message: `Closing PR #${invalid.number} lacks required identity`, candidates, eligible: []
110
+ };
111
+ const byNumber = new Map();
112
+ for (const candidate of candidates) {
113
+ const signature = identitySignature(candidate);
114
+ const prior = byNumber.get(candidate.number);
115
+ if (prior && prior !== signature)
116
+ return {
117
+ status: 'failed', code: 'PR_IDENTITY_INVALID',
118
+ message: `Closing PR #${candidate.number} has conflicting identities`, candidates, eligible: []
119
+ };
120
+ byNumber.set(candidate.number, signature);
121
+ }
122
+ const unique = candidates.filter((candidate, index) => candidates.findIndex((other) => other.number === candidate.number) === index);
123
+ const wantedRepository = repositoryKey(repository);
124
+ const eligible = unique.filter((candidate) => repositoryKey(candidate.repository) === wantedRepository &&
125
+ repositoryKey(candidate.base.repository) === wantedRepository &&
126
+ Boolean(candidate.mergedAt && candidate.mergeCommitSha));
127
+ if (explicitPrNumber !== null) {
128
+ const selected = unique.find((candidate) => candidate.number === explicitPrNumber);
129
+ if (!selected)
130
+ return { status: 'failed', code: 'PR_NOT_FOUND', message: `PR #${explicitPrNumber} is not a closing PR`, candidates, eligible };
131
+ if (!eligible.some((candidate) => candidate.number === explicitPrNumber)) {
132
+ return { status: 'failed', code: 'PR_IDENTITY_INVALID', message: `PR #${explicitPrNumber} is not an eligible merged PR`, candidates, eligible };
133
+ }
134
+ if (existingPrNumber !== null && existingPrNumber !== explicitPrNumber) {
135
+ return { status: 'failed', code: 'PR_BIND_CONFLICT', message: `Task is already bound to PR #${existingPrNumber}`, candidates, eligible };
136
+ }
137
+ return { status: 'selected', source: 'explicit', selected, candidates, eligible };
138
+ }
139
+ if (existingPrNumber !== null && eligible.length === 1 && existingPrNumber !== eligible[0].number) {
140
+ return { status: 'failed', code: 'PR_BIND_CONFLICT', message: `Task is already bound to PR #${existingPrNumber}`, candidates, eligible };
141
+ }
142
+ if (eligible.length === 0)
143
+ return { status: 'normal', candidates, eligible: [] };
144
+ if (eligible.length > 1)
145
+ return {
146
+ status: 'failed', code: 'PR_IDENTITY_AMBIGUOUS',
147
+ message: 'Multiple eligible merged closing pull requests were found', candidates, eligible
148
+ };
149
+ return { status: 'selected', source: 'unique', selected: eligible[0], candidates, eligible };
150
+ }
151
+ const CLOSING_PULL_REQUESTS_QUERY = `query($owner:String!,$name:String!,$issue:Int!,$cursor:String){repository(owner:$owner,name:$name){issue(number:$issue){closedByPullRequestsReferences(first:100,after:$cursor){nodes{number id url state title body isDraft headRefName headRefOid headRepository{nameWithOwner} baseRefName baseRefOid baseRepository{nameWithOwner} mergedAt mergeCommit{oid} labels(first:100){nodes{name}} assignees(first:100){nodes{login}} milestone{title}} pageInfo{hasNextPage endCursor}}}}}`;
152
+ function inspectGitHubIssueClosingChangeRequests(client, repository, issueNumber, cwd) {
153
+ const [owner, name] = repository.split('/');
154
+ if (!owner || !name || !Number.isInteger(issueNumber) || issueNumber <= 0)
155
+ return {
156
+ ok: false,
157
+ error: { code: 'PR_IDENTITY_INVALID', message: 'Repository or Issue identity is invalid', retryable: false }
158
+ };
159
+ const candidates = [];
160
+ let cursor = null;
161
+ for (;;) {
162
+ const args = [
163
+ 'api', 'graphql', '-f', `query=${CLOSING_PULL_REQUESTS_QUERY}`,
164
+ '-F', `owner=${owner}`, '-F', `name=${name}`, '-F', `issue=${issueNumber}`
165
+ ];
166
+ if (cursor)
167
+ args.push('-F', `cursor=${cursor}`);
168
+ const response = client.json(args, { cwd });
169
+ if (!response.ok)
170
+ return response;
171
+ const connection = response.value?.data?.repository?.issue?.closedByPullRequestsReferences;
172
+ if (!connection || !Array.isArray(connection.nodes) || !connection.pageInfo)
173
+ return {
174
+ ok: false,
175
+ error: { code: 'PR_IDENTITY_INVALID', message: 'Closing pull request response is incomplete', retryable: false }
176
+ };
177
+ for (const node of connection.nodes) {
178
+ const normalized = normalizeClosingPullRequest(node, repository);
179
+ if (!normalized)
180
+ return {
181
+ ok: false,
182
+ error: { code: 'PR_IDENTITY_INVALID', message: 'Closing pull request identity is incomplete', retryable: false }
183
+ };
184
+ candidates.push(normalized);
185
+ }
186
+ if (!connection.pageInfo.hasNextPage)
187
+ return { ok: true, value: candidates };
188
+ if (!connection.pageInfo.endCursor || connection.pageInfo.endCursor === cursor)
189
+ return {
190
+ ok: false,
191
+ error: { code: 'PR_IDENTITY_INVALID', message: 'Closing pull request pagination cursor is invalid', retryable: false }
192
+ };
193
+ cursor = connection.pageInfo.endCursor;
194
+ }
195
+ }
44
196
  function expectedHead(repository, head) {
45
197
  const colon = head.indexOf(':');
46
198
  if (colon === -1)
@@ -90,6 +242,100 @@ function inspectGitHubPullRequest(client, repository, number, cwd) {
90
242
  ? { ok: true, value: pullRequest }
91
243
  : { ok: false, error: { code: 'PR_IDENTITY_INVALID', message: 'Remote resource is not a valid pull request', retryable: false } };
92
244
  }
245
+ function configuredGitRemotes(cwd) {
246
+ try {
247
+ return execFileSync('git', ['remote'], {
248
+ cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe']
249
+ }).split(/\r?\n/).filter(Boolean).flatMap((name) => {
250
+ try {
251
+ const url = execFileSync('git', ['config', '--get', `remote.${name}.url`], {
252
+ cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe']
253
+ }).trim();
254
+ const repository = parseGitHubRemote(url);
255
+ return repository ? [{ name, url, repository }] : [];
256
+ }
257
+ catch {
258
+ return [];
259
+ }
260
+ });
261
+ }
262
+ catch {
263
+ return [];
264
+ }
265
+ }
266
+ function rewriteGitHubRemote(remote, repository) {
267
+ const match = remote.trim().match(/^(https?:\/\/github\.com\/|ssh:\/\/(?:git@)?github\.com\/|git@github\.com:)[^/\s]+\/[^/\s]+(?:\.git)?$/i);
268
+ return match ? `${match[1]}${repository}.git` : null;
269
+ }
270
+ function resolveGitHubChangeRequestGitEvidence({ cwd, repository, pullRequest }) {
271
+ if (pullRequest.repository !== repository ||
272
+ pullRequest.base.repository !== repository ||
273
+ pullRequest.number <= 0) {
274
+ return {
275
+ ok: false,
276
+ error: {
277
+ code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
278
+ message: 'Pull request repository identity is inconsistent',
279
+ retryable: false
280
+ }
281
+ };
282
+ }
283
+ const reviewedHeadRef = `refs/pull/${pullRequest.number}/head`;
284
+ const targetHeadRef = `refs/heads/${pullRequest.base.ref}`;
285
+ const refValid = (ref) => {
286
+ try {
287
+ execFileSync('git', ['check-ref-format', ref], {
288
+ cwd, encoding: 'utf8', stdio: ['ignore', 'ignore', 'ignore']
289
+ });
290
+ return true;
291
+ }
292
+ catch {
293
+ return false;
294
+ }
295
+ };
296
+ if (!refValid(reviewedHeadRef) || !refValid(targetHeadRef)) {
297
+ return {
298
+ ok: false,
299
+ error: {
300
+ code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
301
+ message: 'Pull request Git refs are invalid',
302
+ retryable: false
303
+ }
304
+ };
305
+ }
306
+ const remotes = configuredGitRemotes(cwd);
307
+ const exact = remotes
308
+ .filter((remote) => remote.repository === repository)
309
+ .sort((left, right) => Number(right.name === 'origin') - Number(left.name === 'origin') ||
310
+ left.name.localeCompare(right.name))[0];
311
+ const origin = remotes.find((remote) => remote.name === 'origin');
312
+ const remoteUrl = exact?.url || (origin ? rewriteGitHubRemote(origin.url, repository) : null);
313
+ if (!remoteUrl) {
314
+ return {
315
+ ok: false,
316
+ error: {
317
+ code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
318
+ message: `No GitHub remote can provide evidence for ${repository}`,
319
+ retryable: false
320
+ }
321
+ };
322
+ }
323
+ return {
324
+ ok: true,
325
+ value: { remoteUrl, reviewedHeadRef, targetHeadRef }
326
+ };
327
+ }
328
+ registerPlatformCapabilities('github', {
329
+ inspectChangeRequest({ client, repository, number, cwd }) {
330
+ return inspectGitHubPullRequest(client || createGitHubClient(), repository, number, cwd);
331
+ },
332
+ inspectIssueClosingChangeRequests({ client, repository, issueNumber, cwd }) {
333
+ return inspectGitHubIssueClosingChangeRequests(client || createGitHubClient(), repository, issueNumber, cwd);
334
+ },
335
+ resolveChangeRequestGitEvidence(context) {
336
+ return resolveGitHubChangeRequestGitEvidence(context);
337
+ }
338
+ });
93
339
  function locatePullRequest(base, head, target) {
94
340
  const repository = base.context.platform.repository;
95
341
  const listed = base.client.json([
@@ -115,17 +361,65 @@ function inspectPlatformPullRequest(taskRef, options = {}) {
115
361
  platform: base.context.platform, capabilities: base.context.capabilities,
116
362
  error: { code: 'PR_NOT_LINKED', message: 'Task has no valid pr_number', retryable: false }
117
363
  });
118
- const fetched = inspectGitHubPullRequest(base.client, base.context.platform.repository, base.prNumber, base.resolved.repoRoot);
119
- if (!fetched.ok)
120
- return result(fetched.error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
364
+ const fetched = inspectPlatformChangeRequest(base.context.platform.type, {
365
+ cwd: base.resolved.repoRoot,
366
+ repository: base.context.platform.repository,
367
+ number: base.prNumber,
368
+ client: options.client || base.client
369
+ });
370
+ if (!fetched.ok || !fetched.value) {
371
+ const error = fetched.error || {
372
+ code: 'PR_INSPECTION_INVALID',
373
+ message: 'Platform adapter returned no change-request snapshot',
374
+ retryable: false
375
+ };
376
+ return result(error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
121
377
  platform: base.context.platform, capabilities: base.context.capabilities,
122
- resource: { kind: 'pull-request', number: base.prNumber }, error: fetched.error
378
+ resource: { kind: 'pull-request', number: base.prNumber }, error
123
379
  });
380
+ }
124
381
  return result('no-op', base.resolved.taskId, base.issueNumber, base.prNumber, {
125
382
  platform: base.context.platform, capabilities: base.context.capabilities,
126
383
  resource: { kind: 'pull-request', number: base.prNumber }, pullRequest: fetched.value, error: null
127
384
  });
128
385
  }
386
+ function inspectPlatformPullRequestByNumber(prNumber, options = {}) {
387
+ const client = options.client || createGitHubClient();
388
+ const context = resolvePlatformContext({ cwd: options.cwd || process.cwd(), client });
389
+ const usable = (context.status === 'no-op' || context.status === 'degraded') && context.platform.repository;
390
+ if (!usable) {
391
+ return result(context.status, null, null, null, {
392
+ platform: context.platform, capabilities: context.capabilities, operations: context.operations, error: context.error
393
+ });
394
+ }
395
+ if (!Number.isInteger(prNumber) || prNumber <= 0) {
396
+ return result('failed', null, null, null, {
397
+ platform: context.platform, capabilities: context.capabilities,
398
+ error: { code: 'PR_NUMBER_INVALID', message: 'PR number must be positive', retryable: false }
399
+ });
400
+ }
401
+ const fetched = inspectPlatformChangeRequest(context.platform.type, {
402
+ cwd: options.cwd || process.cwd(),
403
+ repository: context.platform.repository,
404
+ number: prNumber,
405
+ client
406
+ });
407
+ if (!fetched.ok || !fetched.value) {
408
+ const error = fetched.error || {
409
+ code: 'PR_INSPECTION_INVALID',
410
+ message: 'Platform adapter returned no change-request snapshot',
411
+ retryable: false
412
+ };
413
+ return result(error.retryable ? 'blocked' : 'failed', null, null, prNumber, {
414
+ platform: context.platform, capabilities: context.capabilities,
415
+ resource: { kind: 'pull-request', number: prNumber }, error
416
+ });
417
+ }
418
+ return result('no-op', null, null, prNumber, {
419
+ platform: context.platform, capabilities: context.capabilities,
420
+ resource: { kind: 'pull-request', number: prNumber }, pullRequest: fetched.value, error: null
421
+ });
422
+ }
129
423
  function appendActivity(content, line) {
130
424
  const body = extractSection(content, ['活动日志', 'Activity Log']);
131
425
  return `${body.replace(/\s+$/, '')}${body.trim() ? '\n' : ''}${line}`;
@@ -187,6 +481,137 @@ function bindPlatformPullRequest(taskRef, options) {
187
481
  operations: [{ name: 'task:bind-pr', status: options.dryRun ? 'planned' : written.status === 'no-op' ? 'no-op' : 'applied', reasonCode: null }], error: null
188
482
  });
189
483
  }
484
+ function externalEvidenceNote(issueNumber, source, pullRequest) {
485
+ return [
486
+ `authorization=${source}`,
487
+ externalIdentityEvidenceNote(issueNumber, pullRequest)
488
+ ].join('; ');
489
+ }
490
+ function externalIdentityEvidenceNote(issueNumber, pullRequest) {
491
+ return [
492
+ `issue=#${issueNumber}`,
493
+ `pr=#${pullRequest.number}`,
494
+ `url=${pullRequest.url}`,
495
+ `base=${pullRequest.base.repository}:${pullRequest.base.ref}@${pullRequest.base.sha}`,
496
+ `head=${pullRequest.head.repository}:${pullRequest.head.ref}@${pullRequest.head.sha}`,
497
+ `mergedAt=${pullRequest.mergedAt}`,
498
+ `mergeCommitSha=${pullRequest.mergeCommitSha}`
499
+ ].join('; ');
500
+ }
501
+ function resolveExternalPullRequest(taskRef, options) {
502
+ const base = resolvedContext(taskRef, options);
503
+ if (!base.ok)
504
+ return externalResult(base.output);
505
+ const inventory = inspectCompletionArtifacts(base.resolved.taskId, { repoRoot: base.resolved.repoRoot });
506
+ if (inventory.status === 'failed')
507
+ return externalResult(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
508
+ platform: base.context.platform,
509
+ capabilities: base.context.capabilities,
510
+ error: { code: inventory.error?.code || 'ARTIFACT_TOPOLOGY_CONFLICT', message: inventory.error?.message || 'Completion artifact inventory failed', retryable: false }
511
+ }));
512
+ if (inventory.artifacts.length > 0) {
513
+ if (options.pr !== undefined)
514
+ return externalResult(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
515
+ platform: base.context.platform,
516
+ capabilities: base.context.capabilities,
517
+ error: { code: 'EXTERNAL_DELIVERY_NOT_APPLICABLE', message: 'External PR selection is only valid when the completion inventory is empty', retryable: false }
518
+ }), { mode: 'normal' });
519
+ return externalResult(result('no-op', base.resolved.taskId, base.issueNumber, base.prNumber, {
520
+ platform: base.context.platform,
521
+ capabilities: base.context.capabilities,
522
+ operations: [{ name: 'external-pr:inventory', status: 'no-op', reasonCode: 'CANONICAL_ARTIFACTS_PRESENT' }],
523
+ error: null
524
+ }), { mode: 'normal' });
525
+ }
526
+ if (!base.issueNumber)
527
+ return externalResult(result('failed', base.resolved.taskId, null, base.prNumber, {
528
+ platform: base.context.platform,
529
+ capabilities: base.context.capabilities,
530
+ error: { code: 'EXTERNAL_DELIVERY_ISSUE_REQUIRED', message: 'External delivery requires a valid issue_number', retryable: false }
531
+ }));
532
+ const inspected = inspectPlatformIssueClosingChangeRequests(base.context.platform.type, {
533
+ cwd: base.resolved.repoRoot,
534
+ repository: base.context.platform.repository,
535
+ issueNumber: base.issueNumber,
536
+ client: base.client
537
+ });
538
+ if (!inspected.ok || !inspected.value) {
539
+ const error = inspected.error || { code: 'PR_IDENTITY_INVALID', message: 'Closing PR inspection returned no candidates', retryable: false };
540
+ return externalResult(result(error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
541
+ platform: base.context.platform, capabilities: base.context.capabilities, error
542
+ }));
543
+ }
544
+ const selected = selectExternalPullRequest(inspected.value, base.context.platform.repository, base.prNumber, options.pr ?? null);
545
+ if (selected.status === 'normal')
546
+ return externalResult(result('no-op', base.resolved.taskId, base.issueNumber, base.prNumber, {
547
+ platform: base.context.platform,
548
+ capabilities: base.context.capabilities,
549
+ operations: [{ name: 'external-pr:select', status: 'no-op', reasonCode: 'NO_ELIGIBLE_CANDIDATE' }],
550
+ error: null
551
+ }), { mode: 'normal', candidates: selected.candidates, eligible: selected.eligible });
552
+ if (selected.status === 'failed')
553
+ return externalResult(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
554
+ platform: base.context.platform,
555
+ capabilities: base.context.capabilities,
556
+ error: { code: selected.code, message: selected.message, retryable: false }
557
+ }), { candidates: selected.candidates, eligible: selected.eligible });
558
+ const note = externalEvidenceNote(base.issueNumber, selected.source, selected.selected);
559
+ const identityNote = externalIdentityEvidenceNote(base.issueNumber, selected.selected);
560
+ const activityEntry = `**Bind External PR** by ${options.agent} — ${note}`;
561
+ const auditLines = base.content.split(/\r?\n/).filter((line) => line.includes('**Bind External PR**'));
562
+ const alreadyAudited = auditLines.some((line) => line.endsWith(identityNote));
563
+ if (auditLines.length > 0 && !alreadyAudited)
564
+ return externalResult(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
565
+ platform: base.context.platform,
566
+ capabilities: base.context.capabilities,
567
+ pullRequest: selected.selected,
568
+ error: { code: 'PR_BIND_CONFLICT', message: 'Existing external PR audit evidence conflicts with the selected identity', retryable: false }
569
+ }), { candidates: selected.candidates, eligible: selected.eligible, selected: selected.selected });
570
+ if (alreadyAudited && base.prNumber !== selected.selected.number)
571
+ return externalResult(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
572
+ platform: base.context.platform,
573
+ capabilities: base.context.capabilities,
574
+ pullRequest: selected.selected,
575
+ error: { code: 'PR_BIND_CONFLICT', message: 'External PR audit evidence does not match the task binding', retryable: false }
576
+ }), { candidates: selected.candidates, eligible: selected.eligible, selected: selected.selected });
577
+ if (base.prNumber === selected.selected.number && alreadyAudited) {
578
+ return externalResult(result('no-op', base.resolved.taskId, base.issueNumber, base.prNumber, {
579
+ platform: base.context.platform,
580
+ capabilities: base.context.capabilities,
581
+ resource: { kind: 'pull-request', number: selected.selected.number },
582
+ pullRequest: selected.selected,
583
+ operations: [{ name: 'task:bind-external-pr', status: 'no-op', reasonCode: 'EVIDENCE_ALREADY_RECORDED' }],
584
+ error: null
585
+ }), { mode: 'external', authorization: selected.source, candidates: selected.candidates, eligible: selected.eligible, selected: selected.selected });
586
+ }
587
+ const metadata = captureTaskWriteMetadata();
588
+ const write = writeTask({
589
+ taskRef: base.resolved.taskId,
590
+ expectedState: 'active',
591
+ dryRun: options.dryRun,
592
+ mutations: [
593
+ { kind: 'frontmatter', set: { pr_number: selected.selected.number, pr_status: 'created', assigned_to: options.agent } },
594
+ { kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: '活动日志', body: appendActivity(base.content, `- ${metadata.timestamp} — ${activityEntry}`) }
595
+ ]
596
+ }, { repoRoot: base.resolved.repoRoot, metadataProvider: () => metadata });
597
+ if (write.status === 'failed')
598
+ return externalResult(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
599
+ platform: base.context.platform,
600
+ capabilities: base.context.capabilities,
601
+ pullRequest: selected.selected,
602
+ error: { code: write.error.code, message: write.error.message, retryable: false }
603
+ }), { candidates: selected.candidates, eligible: selected.eligible, selected: selected.selected });
604
+ const operationStatus = options.dryRun ? 'planned' : write.status === 'applied' ? 'applied' : 'no-op';
605
+ return externalResult(result(operationStatus, base.resolved.taskId, base.issueNumber, options.dryRun ? base.prNumber : selected.selected.number, {
606
+ changed: !options.dryRun && write.changed,
607
+ platform: base.context.platform,
608
+ capabilities: base.context.capabilities,
609
+ resource: { kind: 'pull-request', number: selected.selected.number },
610
+ pullRequest: selected.selected,
611
+ operations: [{ name: 'task:bind-external-pr', status: operationStatus, reasonCode: null }],
612
+ error: null
613
+ }), { mode: 'external', authorization: selected.source, candidates: selected.candidates, eligible: selected.eligible, selected: selected.selected });
614
+ }
190
615
  function createPlatformPullRequest(taskRef, options) {
191
616
  const base = resolvedContext(taskRef, options);
192
617
  if (!base.ok)
@@ -336,5 +761,5 @@ function syncPlatformPullRequest(taskRef, options) {
336
761
  operations: operations.map((operation) => operation.status === 'planned' ? { ...operation, status: 'applied' } : operation), error: null
337
762
  });
338
763
  }
339
- export { bindPlatformPullRequest, createPlatformPullRequest, inspectGitHubPullRequest, inspectPlatformPullRequest, normalizePullRequest, selectPullRequest, syncPlatformPullRequest };
764
+ export { bindPlatformPullRequest, createPlatformPullRequest, inspectGitHubPullRequest, inspectGitHubIssueClosingChangeRequests, inspectPlatformPullRequest, inspectPlatformPullRequestByNumber, normalizePullRequest, resolveGitHubChangeRequestGitEvidence, resolveExternalPullRequest, selectExternalPullRequest, selectPullRequest, syncPlatformPullRequest };
340
765
  //# sourceMappingURL=pull-requests.js.map
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { execFileSync } from 'node:child_process';
4
+ import { createHash } from 'node:crypto';
4
5
  import { resolvePlatformContext } from './context.js';
5
6
  import { fetchGitHubReleaseNoteData, publishGitHubReleaseNotes } from './github-release-notes.js';
6
7
  const unsupportedError = {
@@ -8,6 +9,77 @@ const unsupportedError = {
8
9
  message: 'Release notes are not supported for the configured platform',
9
10
  retryable: false
10
11
  };
12
+ const SHA256_PATTERN = /^sha256:[0-9a-f]{64}$/;
13
+ function releaseNoteFailure(code, message) {
14
+ return {
15
+ status: 'failed',
16
+ changed: false,
17
+ operation: null,
18
+ url: null,
19
+ error: { code, message, retryable: false }
20
+ };
21
+ }
22
+ function normalizeReleaseNoteBytes(input) {
23
+ const text = new TextDecoder('utf-8', { fatal: true }).decode(input);
24
+ const normalized = text.replace(/\r\n?/g, '\n').replace(/\n*$/, '') + '\n';
25
+ return Buffer.from(normalized, 'utf8');
26
+ }
27
+ function releaseNoteSha256(bytes) {
28
+ return `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
29
+ }
30
+ function isWithin(parent, candidate) {
31
+ const relative = path.relative(parent, candidate);
32
+ return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
33
+ }
34
+ function stageReleaseNotes(input, options = {}) {
35
+ if (!input.notesFile || input.notesFile === '-') {
36
+ return { status: 'failed', changed: false, notesFile: null, sha256: null, byteLength: null, error: { code: 'RELEASE_NOTES_INPUT_INVALID', message: 'notes-file must identify an existing external file', retryable: false } };
37
+ }
38
+ const requestedPath = path.resolve(input.notesFile);
39
+ let notesFile;
40
+ let sourceStat;
41
+ try {
42
+ if (fs.lstatSync(requestedPath).isSymbolicLink())
43
+ throw new Error('symbolic links are not allowed');
44
+ notesFile = fs.realpathSync(requestedPath);
45
+ sourceStat = fs.statSync(notesFile);
46
+ const worktree = fs.realpathSync(path.resolve(options.cwd || process.cwd()));
47
+ if (!sourceStat.isFile() || isWithin(worktree, notesFile))
48
+ throw new Error('path is not an external regular file');
49
+ }
50
+ catch {
51
+ return { status: 'failed', changed: false, notesFile: null, sha256: null, byteLength: null, error: { code: 'RELEASE_NOTES_PATH_INVALID', message: 'notes-file must be a regular file outside the current worktree and must not be a symbolic link', retryable: false } };
52
+ }
53
+ let original;
54
+ let normalized;
55
+ try {
56
+ original = fs.readFileSync(notesFile);
57
+ normalized = normalizeReleaseNoteBytes(original);
58
+ }
59
+ catch {
60
+ return { status: 'failed', changed: false, notesFile, sha256: null, byteLength: null, error: { code: 'RELEASE_NOTES_CONTENT_INVALID', message: 'notes-file must contain valid UTF-8', retryable: false } };
61
+ }
62
+ const changed = !original.equals(normalized);
63
+ if (changed) {
64
+ const temporary = path.join(path.dirname(notesFile), `.${path.basename(notesFile)}.${process.pid}.${Date.now()}.tmp`);
65
+ try {
66
+ fs.writeFileSync(temporary, normalized, { flag: 'wx', mode: sourceStat.mode });
67
+ fs.renameSync(temporary, notesFile);
68
+ }
69
+ catch {
70
+ fs.rmSync(temporary, { force: true });
71
+ return { status: 'failed', changed: false, notesFile, sha256: null, byteLength: null, error: { code: 'RELEASE_NOTES_STAGE_FAILED', message: 'notes-file could not be staged atomically', retryable: false } };
72
+ }
73
+ }
74
+ return {
75
+ status: changed ? 'applied' : 'no-op',
76
+ changed,
77
+ notesFile,
78
+ sha256: releaseNoteSha256(normalized),
79
+ byteLength: normalized.byteLength,
80
+ error: null
81
+ };
82
+ }
11
83
  function baseResult(status) {
12
84
  return {
13
85
  status,
@@ -126,8 +198,18 @@ function releaseNoteContext(input, options = {}) {
126
198
  };
127
199
  }
128
200
  function publishReleaseNotes(input, options = {}) {
129
- if (!input.tag || !input.title || !input.notesFile) {
130
- return { status: 'failed', changed: false, operation: null, url: null, error: { code: 'RELEASE_NOTES_INPUT_INVALID', message: 'tag, title, and notes-file are required', retryable: false } };
201
+ if (!input.tag || !input.title || !input.notesFile || !SHA256_PATTERN.test(input.expectedSha256)) {
202
+ return releaseNoteFailure('RELEASE_NOTES_INPUT_INVALID', 'tag, title, notes-file, and expected-sha256 are required');
203
+ }
204
+ let actualSha256;
205
+ try {
206
+ actualSha256 = releaseNoteSha256(fs.readFileSync(input.notesFile));
207
+ }
208
+ catch {
209
+ return releaseNoteFailure('RELEASE_NOTES_FILE_UNREADABLE', 'notes-file could not be read');
210
+ }
211
+ if (actualSha256 !== input.expectedSha256) {
212
+ return releaseNoteFailure('RELEASE_NOTES_DIGEST_MISMATCH', 'notes-file does not match the confirmed SHA-256 digest');
131
213
  }
132
214
  const cwd = path.resolve(options.cwd || process.cwd());
133
215
  const platformType = options.platformType ?? configuredPlatform(cwd);
@@ -139,5 +221,5 @@ function publishReleaseNotes(input, options = {}) {
139
221
  }
140
222
  return publishGitHubReleaseNotes({ ...input, repository: context.platform.repository }, { cwd, client: options.client });
141
223
  }
142
- export { publishReleaseNotes, releaseNoteContext };
224
+ export { normalizeReleaseNoteBytes, publishReleaseNotes, releaseNoteContext, releaseNoteSha256, stageReleaseNotes };
143
225
  //# sourceMappingURL=release-notes.js.map
@@ -18,7 +18,7 @@ function inspectPlatformRelease(tag, options = {}) {
18
18
  return { ...platformResult(result.error.retryable ? 'blocked' : 'failed'), platform: context.platform, release: null, workflows: [], error: result.error };
19
19
  }
20
20
  const release = { tag: String(result.value.tagName || tag), published: !Boolean(result.value.isDraft), draft: Boolean(result.value.isDraft), url: result.value.url ? String(result.value.url) : null };
21
- const runs = client.json(['run', 'list', '--repo', context.platform.repository, '--limit', '100', '--json', 'name,headBranch,status,conclusion,url'], { cwd: options.cwd });
21
+ const runs = client.json(['run', 'list', '--repo', context.platform.repository, '--limit', '100', '--json', 'name,workflowName,displayTitle,event,headBranch,headSha,status,conclusion,createdAt,databaseId,attempt,url'], { cwd: options.cwd });
22
22
  return { ...platformResult(runs.ok ? 'no-op' : runs.error.retryable ? 'blocked' : 'degraded'), platform: context.platform, release, workflows: runs.ok ? runs.value : [], error: runs.ok ? null : runs.error };
23
23
  }
24
24
  function upsertPlatformRelease(input, options = {}) {