@fitlab-ai/agent-infra 0.9.2 → 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 (318) hide show
  1. package/README.md +10 -9
  2. package/README.zh-CN.md +10 -9
  3. package/dist/bin/internal-cli.js +15 -0
  4. package/dist/lib/agent-clients/adapter.js +45 -2
  5. package/dist/lib/agent-clients/adapters/antigravity-cli.js +64 -0
  6. package/dist/lib/agent-clients/adapters/claude-code.js +10 -1
  7. package/dist/lib/agent-clients/adapters/codex.js +9 -0
  8. package/dist/lib/agent-clients/adapters/index.js +2 -2
  9. package/dist/lib/agent-clients/adapters/opencode.js +9 -0
  10. package/dist/lib/agent-clients/config.js +42 -4
  11. package/dist/lib/agent-clients/next-steps.js +11 -2
  12. package/dist/lib/agent-clients/reconcile.js +2 -1
  13. package/dist/lib/agent-clients/registry.js +7 -1
  14. package/dist/lib/agent-clients/schema.js +18 -0
  15. package/dist/lib/agent-clients/tokens.js +49 -0
  16. package/dist/lib/agent-clients/types.js +1 -1
  17. package/dist/lib/defaults.json +1 -1
  18. package/dist/lib/git/workflow.js +54 -1
  19. package/dist/lib/internal/agent-client.js +61 -4
  20. package/dist/lib/internal/git-workflow.js +6 -1
  21. package/dist/lib/internal/platform-checks.js +3 -3
  22. package/dist/lib/internal/platform-comment.js +29 -2
  23. package/dist/lib/internal/platform-issue.js +7 -0
  24. package/dist/lib/internal/platform-pr-review.js +132 -0
  25. package/dist/lib/internal/platform-pr.js +20 -2
  26. package/dist/lib/internal/platform-release-notes.js +52 -16
  27. package/dist/lib/internal/pr-review-grade.js +140 -0
  28. package/dist/lib/internal/release-workflow.js +13 -6
  29. package/dist/lib/internal/task-activity.js +73 -0
  30. package/dist/lib/internal/task-event.js +17 -1
  31. package/dist/lib/internal/task-lifecycle.js +7 -0
  32. package/dist/lib/internal/task-orchestration.js +51 -6
  33. package/dist/lib/internal/task-review.js +8 -3
  34. package/dist/lib/platform/adapters.js +8 -1
  35. package/dist/lib/platform/completion-backfill.js +122 -0
  36. package/dist/lib/platform/issue-comments.js +3 -2
  37. package/dist/lib/platform/merged-pr-equivalence.js +115 -5
  38. package/dist/lib/platform/pr-checks.js +64 -41
  39. package/dist/lib/platform/pr-review.js +166 -0
  40. package/dist/lib/platform/pull-requests.js +324 -3
  41. package/dist/lib/platform/release-notes.js +85 -3
  42. package/dist/lib/pr-review/evidence-grading.js +229 -0
  43. package/dist/lib/run/tui.js +3 -5
  44. package/dist/lib/sandbox/commands/create.js +0 -20
  45. package/dist/lib/sandbox/commands/show.js +1 -1
  46. package/dist/lib/task/activity-intent.js +101 -0
  47. package/dist/lib/task/artifact-lifecycle.js +2 -1
  48. package/dist/lib/task/commands/log.js +15 -14
  49. package/dist/lib/task/delegation-receipts.js +23 -2
  50. package/dist/lib/task/events.js +67 -28
  51. package/dist/lib/task/finalization-artifacts.js +35 -0
  52. package/dist/lib/task/lifecycle-execution.js +46 -0
  53. package/dist/lib/task/orchestration.js +408 -44
  54. package/dist/lib/task/review-finalization.js +29 -9
  55. package/dist/lib/task/task-execution-lock.js +231 -0
  56. package/dist/lib/task/verification-checks.js +1 -1
  57. package/dist/lib/task/verification-engine.js +218 -2
  58. package/dist/lib/task/verification.js +4 -3
  59. package/dist/lib/task/workspace-snapshot.js +17 -5
  60. package/lib/agent-clients/adapter.ts +75 -2
  61. package/lib/agent-clients/adapters/antigravity-cli.ts +65 -0
  62. package/lib/agent-clients/adapters/claude-code.ts +10 -1
  63. package/lib/agent-clients/adapters/codex.ts +9 -0
  64. package/lib/agent-clients/adapters/index.ts +2 -2
  65. package/lib/agent-clients/adapters/opencode.ts +9 -0
  66. package/lib/agent-clients/config.ts +49 -4
  67. package/lib/agent-clients/next-steps.ts +15 -2
  68. package/lib/agent-clients/reconcile.ts +2 -1
  69. package/lib/agent-clients/registry.ts +10 -0
  70. package/lib/agent-clients/schema.ts +18 -0
  71. package/lib/agent-clients/tokens.ts +53 -0
  72. package/lib/agent-clients/types.ts +15 -1
  73. package/lib/defaults.json +1 -1
  74. package/lib/git/workflow.ts +66 -2
  75. package/lib/internal/agent-client.ts +59 -4
  76. package/lib/internal/git-workflow.ts +5 -1
  77. package/lib/internal/platform-checks.ts +4 -4
  78. package/lib/internal/platform-comment.ts +17 -2
  79. package/lib/internal/platform-issue.ts +4 -0
  80. package/lib/internal/platform-pr-review.ts +104 -0
  81. package/lib/internal/platform-pr.ts +14 -1
  82. package/lib/internal/platform-release-notes.ts +59 -16
  83. package/lib/internal/pr-review-grade.ts +130 -0
  84. package/lib/internal/release-workflow.ts +13 -6
  85. package/lib/internal/task-activity.ts +55 -0
  86. package/lib/internal/task-event.ts +9 -1
  87. package/lib/internal/task-lifecycle.ts +4 -0
  88. package/lib/internal/task-orchestration.ts +45 -6
  89. package/lib/internal/task-review.ts +8 -3
  90. package/lib/platform/adapters.ts +29 -2
  91. package/lib/platform/completion-backfill.ts +134 -0
  92. package/lib/platform/issue-comments.ts +3 -2
  93. package/lib/platform/merged-pr-equivalence.ts +127 -5
  94. package/lib/platform/pr-checks.ts +72 -45
  95. package/lib/platform/pr-review.ts +201 -0
  96. package/lib/platform/pull-requests.ts +395 -2
  97. package/lib/platform/release-notes.ts +100 -4
  98. package/lib/pr-review/evidence-grading.ts +318 -0
  99. package/lib/run/tui.ts +3 -4
  100. package/lib/sandbox/commands/create.ts +0 -20
  101. package/lib/sandbox/commands/show.ts +1 -1
  102. package/lib/server/server.schema.json +2 -2
  103. package/lib/task/activity-intent.ts +128 -0
  104. package/lib/task/artifact-lifecycle.ts +2 -1
  105. package/lib/task/commands/log.ts +15 -14
  106. package/lib/task/delegation-receipts.ts +32 -3
  107. package/lib/task/events.ts +75 -30
  108. package/lib/task/finalization-artifacts.ts +58 -0
  109. package/lib/task/lifecycle-execution.ts +106 -0
  110. package/lib/task/orchestration.ts +543 -64
  111. package/lib/task/review-finalization.ts +40 -9
  112. package/lib/task/task-execution-lock.ts +296 -0
  113. package/lib/task/verification-checks.ts +1 -1
  114. package/lib/task/verification-engine.ts +249 -2
  115. package/lib/task/verification.ts +5 -3
  116. package/lib/task/workspace-snapshot.ts +26 -5
  117. package/package.json +3 -3
  118. package/templates/.agents/QUICKSTART.en.md +25 -8
  119. package/templates/.agents/QUICKSTART.zh-CN.md +25 -8
  120. package/templates/.agents/README.en.md +10 -7
  121. package/templates/.agents/README.zh-CN.md +10 -7
  122. package/templates/.agents/hooks/lifecycle-delegation.js +15 -4
  123. package/templates/.agents/rules/create-issue.github.en.md +4 -2
  124. package/templates/.agents/rules/create-issue.github.zh-CN.md +4 -2
  125. package/templates/.agents/rules/issue-fields.github.en.md +3 -1
  126. package/templates/.agents/rules/issue-fields.github.zh-CN.md +3 -1
  127. package/templates/.agents/rules/issue-pr-commands.en.md +5 -3
  128. package/templates/.agents/rules/issue-pr-commands.github.en.md +5 -3
  129. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +5 -3
  130. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +5 -3
  131. package/templates/.agents/rules/issue-sync.github.en.md +7 -3
  132. package/templates/.agents/rules/issue-sync.github.zh-CN.md +8 -4
  133. package/templates/.agents/rules/lifecycle-orchestration.en.md +5 -2
  134. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +5 -2
  135. package/templates/.agents/rules/milestone-inference.github.en.md +4 -2
  136. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +4 -2
  137. package/templates/.agents/rules/next-step-output.en.md +3 -1
  138. package/templates/.agents/rules/next-step-output.zh-CN.md +3 -1
  139. package/templates/.agents/rules/no-mid-flow-questions.en.md +17 -0
  140. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +17 -0
  141. package/templates/.agents/rules/pr-checks-commands.en.md +3 -3
  142. package/templates/.agents/rules/pr-checks-commands.github.en.md +2 -2
  143. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +2 -2
  144. package/templates/.agents/rules/pr-checks-commands.zh-CN.md +3 -3
  145. package/templates/.agents/rules/pr-sync.en.md +3 -1
  146. package/templates/.agents/rules/pr-sync.github.en.md +11 -1
  147. package/templates/.agents/rules/pr-sync.github.zh-CN.md +11 -1
  148. package/templates/.agents/rules/pr-sync.zh-CN.md +3 -1
  149. package/templates/.agents/rules/release-commands.en.md +1 -1
  150. package/templates/.agents/rules/release-commands.github.en.md +12 -2
  151. package/templates/.agents/rules/release-commands.github.zh-CN.md +12 -2
  152. package/templates/.agents/rules/release-commands.zh-CN.md +1 -1
  153. package/templates/.agents/rules/task-management.en.md +11 -0
  154. package/templates/.agents/rules/task-management.zh-CN.md +11 -0
  155. package/templates/.agents/skills/analyze-task/SKILL.en.md +10 -7
  156. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -7
  157. package/templates/.agents/skills/block-task/SKILL.en.md +7 -5
  158. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +7 -5
  159. package/templates/.agents/skills/cancel-task/SKILL.en.md +6 -4
  160. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +6 -4
  161. package/templates/.agents/skills/close-codescan/SKILL.en.md +3 -1
  162. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -1
  163. package/templates/.agents/skills/close-dependabot/SKILL.en.md +3 -1
  164. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +3 -1
  165. package/templates/.agents/skills/code-task/SKILL.en.md +9 -6
  166. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +11 -8
  167. package/templates/.agents/skills/commit/SKILL.en.md +3 -1
  168. package/templates/.agents/skills/commit/SKILL.zh-CN.md +3 -1
  169. package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
  170. package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
  171. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
  172. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
  173. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
  174. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
  175. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +6 -4
  176. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +6 -4
  177. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +1 -1
  178. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +1 -1
  179. package/templates/.agents/skills/complete-task/SKILL.en.md +21 -6
  180. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +21 -6
  181. package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
  182. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
  183. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +17 -0
  184. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +17 -0
  185. package/templates/.agents/skills/create-pr/SKILL.en.md +3 -1
  186. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +3 -1
  187. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +1 -1
  188. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +1 -1
  189. package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -16
  190. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +17 -16
  191. package/templates/.agents/skills/create-task/SKILL.en.md +5 -3
  192. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -3
  193. package/templates/.agents/skills/import-issue/SKILL.en.md +5 -3
  194. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -3
  195. package/templates/.agents/skills/plan-task/SKILL.en.md +8 -5
  196. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +8 -5
  197. package/templates/.agents/skills/post-release/SKILL.en.md +2 -0
  198. package/templates/.agents/skills/post-release/SKILL.zh-CN.md +2 -0
  199. package/templates/.agents/skills/release/SKILL.en.md +16 -10
  200. package/templates/.agents/skills/release/SKILL.zh-CN.md +16 -10
  201. package/templates/.agents/skills/restore-task/SKILL.en.md +4 -1
  202. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +4 -1
  203. package/templates/.agents/skills/review-analysis/SKILL.en.md +7 -4
  204. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +7 -4
  205. package/templates/.agents/skills/review-code/SKILL.en.md +9 -6
  206. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +9 -6
  207. package/templates/.agents/skills/review-plan/SKILL.en.md +7 -4
  208. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +7 -4
  209. package/templates/.agents/skills/review-pr/SKILL.en.md +158 -0
  210. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +158 -0
  211. package/templates/.agents/skills/review-pr/config/verify.en.json +37 -0
  212. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +37 -0
  213. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +65 -0
  214. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +65 -0
  215. package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +18 -0
  216. package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +18 -0
  217. package/templates/.agents/skills/review-pr/reference/report-template.en.md +85 -0
  218. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +85 -0
  219. package/templates/.agents/skills/run-task/SKILL.en.md +8 -8
  220. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +8 -8
  221. package/templates/.agents/skills/run-task/config/verify.en.json +2 -1
  222. package/templates/.agents/skills/run-task/config/verify.zh-CN.json +2 -1
  223. package/templates/.agents/skills/run-task/reference/host-validation.en.md +20 -0
  224. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +20 -0
  225. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +35 -61
  226. package/templates/.agents/skills/watch-pr/SKILL.en.md +13 -13
  227. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +13 -13
  228. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +27 -6
  229. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +26 -5
  230. package/templates/.agents/templates/task.en.md +1 -1
  231. package/templates/.agents/templates/task.zh-CN.md +1 -1
  232. package/templates/.agents/workflows/bug-fix.en.yaml +3 -3
  233. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +3 -3
  234. package/templates/.agents/workflows/code-review.en.yaml +1 -1
  235. package/templates/.agents/workflows/code-review.zh-CN.yaml +1 -1
  236. package/templates/.agents/workflows/feature-development.en.yaml +4 -4
  237. package/templates/.agents/workflows/feature-development.zh-CN.yaml +4 -4
  238. package/templates/.agents/workflows/refactoring.en.yaml +3 -3
  239. package/templates/.agents/workflows/refactoring.zh-CN.yaml +3 -3
  240. package/templates/.claude/commands/post-release.en.md +1 -0
  241. package/templates/.claude/commands/post-release.zh-CN.md +1 -0
  242. package/templates/.claude/commands/review-pr.en.md +8 -0
  243. package/templates/.claude/commands/review-pr.zh-CN.md +8 -0
  244. package/templates/.gemini/commands/_project_/review-pr.en.toml +10 -0
  245. package/templates/.gemini/commands/_project_/review-pr.zh-CN.toml +10 -0
  246. package/templates/.opencode/commands/post-release.en.md +2 -0
  247. package/templates/.opencode/commands/post-release.zh-CN.md +2 -0
  248. package/templates/.opencode/commands/review-pr.en.md +11 -0
  249. package/templates/.opencode/commands/review-pr.zh-CN.md +11 -0
  250. package/dist/lib/agent-clients/adapters/gemini-cli.js +0 -64
  251. package/lib/agent-clients/adapters/gemini-cli.ts +0 -65
  252. package/templates/.gemini/commands/_project_/analyze-task.en.toml +0 -10
  253. package/templates/.gemini/commands/_project_/analyze-task.zh-CN.toml +0 -10
  254. package/templates/.gemini/commands/_project_/archive-tasks.en.toml +0 -10
  255. package/templates/.gemini/commands/_project_/archive-tasks.zh-CN.toml +0 -10
  256. package/templates/.gemini/commands/_project_/block-task.en.toml +0 -10
  257. package/templates/.gemini/commands/_project_/block-task.zh-CN.toml +0 -10
  258. package/templates/.gemini/commands/_project_/cancel-task.en.toml +0 -10
  259. package/templates/.gemini/commands/_project_/cancel-task.zh-CN.toml +0 -10
  260. package/templates/.gemini/commands/_project_/check-task.en.toml +0 -10
  261. package/templates/.gemini/commands/_project_/check-task.zh-CN.toml +0 -10
  262. package/templates/.gemini/commands/_project_/close-codescan.en.toml +0 -8
  263. package/templates/.gemini/commands/_project_/close-codescan.zh-CN.toml +0 -8
  264. package/templates/.gemini/commands/_project_/close-dependabot.en.toml +0 -8
  265. package/templates/.gemini/commands/_project_/close-dependabot.zh-CN.toml +0 -8
  266. package/templates/.gemini/commands/_project_/code-task.en.toml +0 -10
  267. package/templates/.gemini/commands/_project_/code-task.zh-CN.toml +0 -10
  268. package/templates/.gemini/commands/_project_/commit.en.toml +0 -10
  269. package/templates/.gemini/commands/_project_/commit.zh-CN.toml +0 -10
  270. package/templates/.gemini/commands/_project_/complete-manual-validation.en.toml +0 -10
  271. package/templates/.gemini/commands/_project_/complete-manual-validation.zh-CN.toml +0 -10
  272. package/templates/.gemini/commands/_project_/complete-task.en.toml +0 -10
  273. package/templates/.gemini/commands/_project_/complete-task.zh-CN.toml +0 -10
  274. package/templates/.gemini/commands/_project_/create-pr.en.toml +0 -10
  275. package/templates/.gemini/commands/_project_/create-pr.zh-CN.toml +0 -10
  276. package/templates/.gemini/commands/_project_/create-release-note.en.toml +0 -8
  277. package/templates/.gemini/commands/_project_/create-release-note.zh-CN.toml +0 -8
  278. package/templates/.gemini/commands/_project_/create-task.en.toml +0 -8
  279. package/templates/.gemini/commands/_project_/create-task.zh-CN.toml +0 -8
  280. package/templates/.gemini/commands/_project_/import-codescan.en.toml +0 -8
  281. package/templates/.gemini/commands/_project_/import-codescan.zh-CN.toml +0 -8
  282. package/templates/.gemini/commands/_project_/import-dependabot.en.toml +0 -8
  283. package/templates/.gemini/commands/_project_/import-dependabot.zh-CN.toml +0 -8
  284. package/templates/.gemini/commands/_project_/import-issue.en.toml +0 -8
  285. package/templates/.gemini/commands/_project_/import-issue.zh-CN.toml +0 -8
  286. package/templates/.gemini/commands/_project_/init-labels.en.toml +0 -8
  287. package/templates/.gemini/commands/_project_/init-labels.zh-CN.toml +0 -8
  288. package/templates/.gemini/commands/_project_/init-milestones.en.toml +0 -10
  289. package/templates/.gemini/commands/_project_/init-milestones.zh-CN.toml +0 -10
  290. package/templates/.gemini/commands/_project_/plan-task.en.toml +0 -10
  291. package/templates/.gemini/commands/_project_/plan-task.zh-CN.toml +0 -10
  292. package/templates/.gemini/commands/_project_/post-release.en.toml +0 -6
  293. package/templates/.gemini/commands/_project_/post-release.zh-CN.toml +0 -6
  294. package/templates/.gemini/commands/_project_/refine-title.en.toml +0 -8
  295. package/templates/.gemini/commands/_project_/refine-title.zh-CN.toml +0 -8
  296. package/templates/.gemini/commands/_project_/release.en.toml +0 -8
  297. package/templates/.gemini/commands/_project_/release.zh-CN.toml +0 -8
  298. package/templates/.gemini/commands/_project_/restore-task.en.toml +0 -8
  299. package/templates/.gemini/commands/_project_/restore-task.zh-CN.toml +0 -8
  300. package/templates/.gemini/commands/_project_/review-analysis.en.toml +0 -10
  301. package/templates/.gemini/commands/_project_/review-analysis.zh-CN.toml +0 -10
  302. package/templates/.gemini/commands/_project_/review-code.en.toml +0 -10
  303. package/templates/.gemini/commands/_project_/review-code.zh-CN.toml +0 -10
  304. package/templates/.gemini/commands/_project_/review-plan.en.toml +0 -10
  305. package/templates/.gemini/commands/_project_/review-plan.zh-CN.toml +0 -10
  306. package/templates/.gemini/commands/_project_/run-task.en.toml +0 -8
  307. package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +0 -8
  308. package/templates/.gemini/commands/_project_/test-integration.en.toml +0 -6
  309. package/templates/.gemini/commands/_project_/test-integration.zh-CN.toml +0 -6
  310. package/templates/.gemini/commands/_project_/test.en.toml +0 -6
  311. package/templates/.gemini/commands/_project_/test.zh-CN.toml +0 -6
  312. package/templates/.gemini/commands/_project_/update-agent-infra.en.toml +0 -6
  313. package/templates/.gemini/commands/_project_/update-agent-infra.zh-CN.toml +0 -6
  314. package/templates/.gemini/commands/_project_/upgrade-dependency.en.toml +0 -8
  315. package/templates/.gemini/commands/_project_/upgrade-dependency.zh-CN.toml +0 -8
  316. package/templates/.gemini/commands/_project_/watch-pr.en.toml +0 -10
  317. package/templates/.gemini/commands/_project_/watch-pr.zh-CN.toml +0 -10
  318. package/templates/.gemini/settings.json +0 -3
@@ -1,4 +1,7 @@
1
1
  import { execFileSync, spawnSync } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
2
5
 
3
6
  import type { PullRequestSnapshot } from "./pull-requests.ts";
4
7
 
@@ -45,6 +48,117 @@ function isAncestor(gitRoot: string, ancestor: string, descendant: string):
45
48
  return { status: "error" };
46
49
  }
47
50
 
51
+ function blocked(message: string): ReviewedHeadRelation {
52
+ return { status: "blocked", code: "PR_MERGE_GIT_EVIDENCE_UNAVAILABLE", message };
53
+ }
54
+
55
+ function resolveUniqueMergeBase(
56
+ gitRoot: string,
57
+ reviewedHead: string,
58
+ squashParent: string
59
+ ): { status: "ok"; mergeBase: string } | { status: "failed" | "blocked"; relation: ReviewedHeadRelation } {
60
+ const result = spawnSync("git", ["merge-base", "--all", reviewedHead, squashParent], {
61
+ cwd: gitRoot,
62
+ encoding: "utf8",
63
+ stdio: ["ignore", "pipe", "pipe"]
64
+ });
65
+ if (result.error || result.status === null || result.status > 1) {
66
+ return { status: "blocked", relation: blocked("Unable to compute the reviewed PR merge base") };
67
+ }
68
+ const candidates = result.stdout.trim() === "" ? [] : result.stdout.trim().split(/\s+/);
69
+ if (result.status === 1 || candidates.length !== 1) {
70
+ return {
71
+ status: "failed",
72
+ relation: {
73
+ status: "failed",
74
+ code: "PR_MERGE_TOPOLOGY_INVALID",
75
+ message: "Reviewed head and squash parent do not have a unique merge base"
76
+ }
77
+ };
78
+ }
79
+ const [candidate] = candidates;
80
+ if (!candidate || !SHA_PATTERN.test(candidate)) {
81
+ return { status: "blocked", relation: blocked("Git returned an invalid reviewed PR merge base") };
82
+ }
83
+ return { status: "ok", mergeBase: candidate };
84
+ }
85
+
86
+ function compareReplayedTree(input: {
87
+ gitRoot: string;
88
+ mergeBase: string;
89
+ reviewedHead: string;
90
+ squashParent: string;
91
+ squashCommit: string;
92
+ }): ReviewedHeadRelation | null {
93
+ const { gitRoot, mergeBase, reviewedHead, squashParent, squashCommit } = input;
94
+ let tempDir: string | undefined;
95
+ let relation: ReviewedHeadRelation | null = null;
96
+ try {
97
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-infra-merge-equivalence-"));
98
+ const env = { ...process.env, GIT_INDEX_FILE: path.join(tempDir, "index") };
99
+ const readTree = spawnSync("git", ["read-tree", squashParent], {
100
+ cwd: gitRoot, env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"]
101
+ });
102
+ if (readTree.error || readTree.status !== 0) {
103
+ relation = blocked("Unable to initialize isolated squash merge evidence");
104
+ } else {
105
+ const patchResult = spawnSync(
106
+ "git",
107
+ ["diff", "--no-renames", "--binary", "--full-index", mergeBase, reviewedHead],
108
+ { cwd: gitRoot, env, encoding: "utf8", maxBuffer: 32 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] }
109
+ );
110
+ if (patchResult.error || patchResult.status !== 0) {
111
+ relation = blocked("Unable to generate the reviewed PR patch");
112
+ } else if (patchResult.stdout !== "") {
113
+ const applyResult = spawnSync("git", ["apply", "--cached", "--3way", "--binary", "-q"], {
114
+ cwd: gitRoot,
115
+ env,
116
+ encoding: "utf8",
117
+ input: patchResult.stdout,
118
+ maxBuffer: 32 * 1024 * 1024,
119
+ stdio: ["pipe", "pipe", "pipe"]
120
+ });
121
+ if (applyResult.status === 1) {
122
+ relation = {
123
+ status: "failed",
124
+ code: "PR_MERGE_CONTENT_MISMATCH",
125
+ message: "Reviewed PR patch cannot be replayed without conflicts, so content equivalence cannot be established"
126
+ };
127
+ } else if (applyResult.error || applyResult.status !== 0) {
128
+ relation = blocked("Unable to replay the reviewed PR patch");
129
+ }
130
+ }
131
+ if (relation === null) {
132
+ const compareResult = spawnSync(
133
+ "git",
134
+ ["diff", "--cached", "--quiet", "--no-ext-diff", squashCommit, "--"],
135
+ { cwd: gitRoot, env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }
136
+ );
137
+ if (compareResult.status === 1) {
138
+ relation = {
139
+ status: "failed",
140
+ code: "PR_MERGE_CONTENT_MISMATCH",
141
+ message: "Replayed reviewed PR content differs from the squash merge tree"
142
+ };
143
+ } else if (compareResult.error || compareResult.status !== 0) {
144
+ relation = blocked("Unable to compare replayed PR content with the squash merge tree");
145
+ }
146
+ }
147
+ }
148
+ } catch {
149
+ relation = blocked("Unable to create isolated squash merge evidence");
150
+ } finally {
151
+ if (tempDir) {
152
+ try {
153
+ fs.rmSync(tempDir, { recursive: true, force: true });
154
+ } catch {
155
+ relation = blocked("Unable to clean up isolated squash merge evidence");
156
+ }
157
+ }
158
+ }
159
+ return relation;
160
+ }
161
+
48
162
  export function resolveReviewedHeadRelation(input: {
49
163
  gitRoot: string;
50
164
  comparisonHead: string;
@@ -87,12 +201,20 @@ export function resolveReviewedHeadRelation(input: {
87
201
  if (parents.length !== 2) {
88
202
  return { status: "failed", code: "PR_MERGE_TOPOLOGY_INVALID", message: "PR merge commit is not a single-parent squash commit" };
89
203
  }
90
- const diffArgs = ["diff", "--no-renames", "--binary", "--full-index"];
91
- const reviewedDiff = git(gitRoot, [...diffArgs, pullRequest.base.sha, pullRequest.head.sha]);
92
- const mergedDiff = git(gitRoot, [...diffArgs, `${pullRequest.mergeCommitSha}^`, pullRequest.mergeCommitSha]);
93
- if (reviewedDiff !== mergedDiff) {
94
- return { status: "failed", code: "PR_MERGE_CONTENT_MISMATCH", message: "Squash merge content differs from the reviewed PR changes" };
204
+ const squashParent = parents[1];
205
+ if (!squashParent) {
206
+ return { status: "failed", code: "PR_MERGE_TOPOLOGY_INVALID", message: "PR merge commit is not a single-parent squash commit" };
95
207
  }
208
+ const mergeBase = resolveUniqueMergeBase(gitRoot, pullRequest.head.sha, squashParent);
209
+ if (mergeBase.status !== "ok") return mergeBase.relation;
210
+ const replayFailure = compareReplayedTree({
211
+ gitRoot,
212
+ mergeBase: mergeBase.mergeBase,
213
+ reviewedHead: pullRequest.head.sha,
214
+ squashParent,
215
+ squashCommit: pullRequest.mergeCommitSha
216
+ });
217
+ if (replayFailure) return replayFailure;
96
218
  } catch {
97
219
  return { status: "blocked", code: "PR_MERGE_GIT_EVIDENCE_UNAVAILABLE", message: "Unable to verify PR merge topology or content" };
98
220
  }
@@ -19,10 +19,13 @@ type CheckBucket = 'pass' | 'fail' | 'pending' | 'cancel';
19
19
  type CheckState = 'passed' | 'failed' | 'pending' | 'timed-out' | 'cancelled' | 'no-required';
20
20
  type CheckSnapshot = PlatformCheckSnapshot;
21
21
  type ChecksSnapshot = { state: Exclude<CheckState, 'timed-out'>; required: CheckSnapshot[] };
22
+ type ReadinessState = 'ready' | 'conflicting' | 'checks-failed' | 'pending' | 'timed-out' | 'cancelled';
23
+ type ReadinessSnapshot = { state: ReadinessState; headSha: string };
22
24
  type RunCandidate = { id: number; name: string; headSha: string; jobId?: number | null };
23
25
  type ChecksResult = PlatformResult & {
24
26
  pullRequest: PullRequestSnapshot | null;
25
27
  checks: { state: CheckState; required: CheckSnapshot[] };
28
+ readiness?: ReadinessSnapshot;
26
29
  resolution?: { status: 'resolved' | 'missing' | 'ambiguous'; runId: number | null; jobId: number | null };
27
30
  logs?: { runId: number; jobId?: number; text: string };
28
31
  };
@@ -37,25 +40,15 @@ function classifyRequiredChecks(required: CheckSnapshot[]): ChecksSnapshot {
37
40
  return { state: 'passed', required };
38
41
  }
39
42
 
40
- async function watchRequiredChecks(options: {
41
- inspect: () => Promise<ChecksSnapshot>;
42
- intervalMs: number;
43
- deadlineMs: number;
44
- now?: () => number;
45
- sleep?: (delayMs: number) => Promise<void>;
46
- signal?: AbortSignal;
47
- }): Promise<{ state: CheckState; required: CheckSnapshot[] }> {
48
- const now = options.now || (() => performance.now());
49
- const sleep = options.sleep || ((delay) => new Promise<void>((resolve) => setTimeout(resolve, delay)));
50
- const started = now();
51
- while (true) {
52
- if (options.signal?.aborted) return { state: 'cancelled', required: [] };
53
- const snapshot = await options.inspect();
54
- if (snapshot.state !== 'pending') return snapshot;
55
- const elapsed = now() - started;
56
- if (elapsed >= options.deadlineMs) return { state: 'timed-out', required: snapshot.required };
57
- await sleep(Math.min(options.intervalMs, options.deadlineMs - elapsed));
58
- }
43
+ function classifyPullRequestReadiness(input: {
44
+ headSha: string;
45
+ mergeability: 'mergeable' | 'conflicting' | 'unknown';
46
+ checks: ChecksSnapshot;
47
+ }): ReadinessSnapshot {
48
+ if (input.mergeability === 'conflicting') return { state: 'conflicting', headSha: input.headSha };
49
+ if (input.checks.state === 'failed' || input.checks.state === 'cancelled') return { state: 'checks-failed', headSha: input.headSha };
50
+ if (input.mergeability === 'unknown' || input.checks.state === 'pending') return { state: 'pending', headSha: input.headSha };
51
+ return { state: 'ready', headSha: input.headSha };
59
52
  }
60
53
 
61
54
  function parseRunJobIdentity(detailsUrl: string): { runId: number; jobId: number | null } | null {
@@ -143,6 +136,13 @@ function resolvedTask(taskRef: string, options: InspectionOptions) {
143
136
  function inspectRequiredChecks(taskRef: string, options: InspectionOptions = {}): ChecksResult {
144
137
  const base = resolvedTask(taskRef, options);
145
138
  if (!base.ok) return base.output;
139
+ return inspectChecksForResolvedTask(base, options);
140
+ }
141
+
142
+ function inspectChecksForResolvedTask(
143
+ base: Extract<ReturnType<typeof resolvedTask>, { ok: true }>,
144
+ options: InspectionOptions
145
+ ): ChecksResult {
146
146
  const repository = base.context.platform.repository!;
147
147
  const inspected = inspectPlatformRequiredChecks(base.context.platform.type, {
148
148
  cwd: base.resolved.repoRoot,
@@ -179,36 +179,62 @@ function inspectRequiredChecks(taskRef: string, options: InspectionOptions = {})
179
179
  });
180
180
  }
181
181
 
182
- async function watchPlatformChecks(taskRef: string, options: InspectionOptions & {
182
+ function inspectPullRequestReadiness(taskRef: string, options: InspectionOptions = {}): ChecksResult {
183
+ const base = resolvedTask(taskRef, options);
184
+ if (!base.ok) return base.output;
185
+ const checked = inspectChecksForResolvedTask(base, options);
186
+ const classifiedCodes = new Set(['REQUIRED_CHECKS_PENDING', 'REQUIRED_CHECKS_FAILED', 'REQUIRED_CHECKS_CANCELLED']);
187
+ if (checked.error && !classifiedCodes.has(checked.error.code)) return checked;
188
+ const readiness = classifyPullRequestReadiness({
189
+ headSha: base.pullRequest.head.sha,
190
+ mergeability: base.pullRequest.mergeability?.state ?? 'unknown',
191
+ checks: checked.checks as ChecksSnapshot
192
+ });
193
+ const status = readiness.state === 'ready' ? 'no-op'
194
+ : readiness.state === 'conflicting' || readiness.state === 'checks-failed' ? 'failed' : 'blocked';
195
+ return checksResult(status, {
196
+ ...checked,
197
+ status,
198
+ changed: false,
199
+ readiness,
200
+ error: status === 'no-op' ? null : {
201
+ code: readiness.state === 'conflicting' ? 'PR_MERGE_CONFLICT'
202
+ : readiness.state === 'checks-failed' ? 'REQUIRED_CHECKS_FAILED' : 'PR_READINESS_PENDING',
203
+ message: `Pull request readiness is ${readiness.state}`,
204
+ retryable: readiness.state === 'pending'
205
+ }
206
+ });
207
+ }
208
+
209
+ async function watchPullRequestReadiness(taskRef: string, options: InspectionOptions & {
183
210
  intervalSeconds: number;
184
211
  deadlineSeconds: number;
185
212
  signal?: AbortSignal;
186
213
  now?: () => number;
187
214
  sleep?: (delayMs: number) => Promise<void>;
188
215
  }): Promise<ChecksResult> {
216
+ const now = options.now || (() => performance.now());
217
+ const sleep = options.sleep || ((delay: number) => new Promise<void>((resolve) => setTimeout(resolve, delay)));
218
+ const started = now();
189
219
  let last: ChecksResult | null = null;
190
- const watched = await watchRequiredChecks({
191
- intervalMs: options.intervalSeconds * 1000,
192
- deadlineMs: options.deadlineSeconds * 1000,
193
- signal: options.signal,
194
- now: options.now,
195
- sleep: options.sleep,
196
- inspect: async () => {
197
- last = inspectRequiredChecks(taskRef, options);
198
- if (last.checks.state === 'pending' && last.error?.code === 'REQUIRED_CHECKS_PENDING') return last.checks as ChecksSnapshot;
199
- return last.checks as ChecksSnapshot;
200
- }
201
- });
202
- if (!last) return checksResult('blocked', { checks: watched, error: { code: 'REQUIRED_CHECKS_UNAVAILABLE', message: 'No checks snapshot was produced', retryable: true } });
203
- const snapshot = last as ChecksResult;
204
- if (watched.state === 'timed-out' || watched.state === 'cancelled') return checksResult('blocked', {
205
- ...snapshot,
206
- status: 'blocked',
207
- changed: false,
208
- checks: watched,
209
- error: { code: watched.state === 'timed-out' ? 'REQUIRED_CHECKS_TIMEOUT' : 'REQUIRED_CHECKS_CANCELLED', message: `Required checks ${watched.state}`, retryable: watched.state === 'timed-out' }
210
- });
211
- return { ...snapshot, checks: watched };
220
+ while (true) {
221
+ if (options.signal?.aborted) return checksResult('blocked', {
222
+ ...last,
223
+ status: 'blocked', changed: false,
224
+ readiness: { state: 'cancelled', headSha: last?.pullRequest?.head.sha ?? '' },
225
+ error: { code: 'PR_READINESS_CANCELLED', message: 'Pull request readiness watch was cancelled', retryable: false }
226
+ });
227
+ last = inspectPullRequestReadiness(taskRef, options);
228
+ if (last.readiness?.state !== 'pending' || last.error?.code !== 'PR_READINESS_PENDING') return last;
229
+ const elapsed = now() - started;
230
+ if (elapsed >= options.deadlineSeconds * 1000) return checksResult('blocked', {
231
+ ...last,
232
+ status: 'blocked', changed: false,
233
+ readiness: { state: 'timed-out', headSha: last.pullRequest?.head.sha ?? '' },
234
+ error: { code: 'PR_READINESS_TIMEOUT', message: 'Pull request readiness watch timed out', retryable: true }
235
+ });
236
+ await sleep(Math.min(options.intervalSeconds * 1000, options.deadlineSeconds * 1000 - elapsed));
237
+ }
212
238
  }
213
239
 
214
240
  function resolvePlatformCheckRun(taskRef: string, options: SharedOptions & { checkName: string; detailsUrl?: string }): ChecksResult {
@@ -262,14 +288,15 @@ function fetchPlatformCheckLogs(taskRef: string, options: SharedOptions & { run:
262
288
  }
263
289
 
264
290
  export {
291
+ classifyPullRequestReadiness,
265
292
  classifyRequiredChecks,
266
293
  fetchCheckLogText,
267
294
  fetchPlatformCheckLogs,
268
295
  inspectRequiredChecks,
296
+ inspectPullRequestReadiness,
269
297
  parseRunJobIdentity,
270
298
  resolvePlatformCheckRun,
271
299
  resolveRunCandidate,
272
- watchPlatformChecks,
273
- watchRequiredChecks
300
+ watchPullRequestReadiness
274
301
  };
275
- export type { CheckBucket, CheckSnapshot, ChecksResult, ChecksSnapshot, CheckState, RunCandidate };
302
+ export type { CheckBucket, CheckSnapshot, ChecksResult, ChecksSnapshot, CheckState, ReadinessSnapshot, ReadinessState, RunCandidate };
@@ -0,0 +1,201 @@
1
+ import { createGitHubClient } from './github-client.ts';
2
+ import type { GitHubClient } from './github-client.ts';
3
+ import { resolvePlatformContext } from './context.ts';
4
+ import { platformResult } from './types.ts';
5
+ import type { PlatformOperation, PlatformResult } from './types.ts';
6
+
7
+ export type PrReviewEvent = 'COMMENT' | 'APPROVE' | 'REQUEST_CHANGES';
8
+ export type PrReviewIdentity = { scope: string; round: number; commitSha: string };
9
+ export type PrReviewListEntry = { id: number; commitId: string; body: string; url: string };
10
+ export type PrReviewListResult = PlatformResult & { reviews: PrReviewListEntry[] };
11
+
12
+ type RemoteReview = {
13
+ id?: number;
14
+ node_id?: string;
15
+ commit_id?: string;
16
+ body?: string | null;
17
+ state?: string;
18
+ html_url?: string;
19
+ };
20
+
21
+ const REVIEW_EVENTS: readonly PrReviewEvent[] = ['COMMENT', 'APPROVE', 'REQUEST_CHANGES'];
22
+ // Marker-safe scope: a task id (`TASK-YYYYMMDD-HHMMSS`) or a bare PR number
23
+ // (`pr{N}`). Any other value (e.g. one containing `\r\n` or `-->`) would break
24
+ // the first-line marker idempotency contract of `reviewMarker` (PL-6).
25
+ const REVIEW_SCOPE_PATTERN = /^(?:pr\d+|TASK-\d{8}-\d{6})$/;
26
+
27
+ export function reviewMarker(identity: PrReviewIdentity): string {
28
+ return `<!-- review-pr:${identity.scope}:r${identity.round} -->`;
29
+ }
30
+
31
+ export function reviewedCommitMarker(sha: string): string {
32
+ return `<!-- reviewed-commit: ${sha} -->`;
33
+ }
34
+
35
+ function normalizeBody(body: string): string {
36
+ return String(body || '').replace(/\r\n/g, '\n').replace(/\n+$/, '\n');
37
+ }
38
+
39
+ function firstLine(body: string): string {
40
+ return normalizeBody(String(body || '')).split('\n', 1)[0] || '';
41
+ }
42
+
43
+ function hasUsableContext(context: PlatformResult): boolean {
44
+ return (context.status === 'no-op' || context.status === 'degraded') && context.platform.repository !== null;
45
+ }
46
+
47
+ function invalidPrNumber(prNumber: number): boolean {
48
+ return !Number.isInteger(prNumber) || prNumber <= 0;
49
+ }
50
+
51
+ export function listPrReviews(prNumber: number, options: { cwd?: string; client?: GitHubClient } = {}): PrReviewListResult {
52
+ const client = options.client || createGitHubClient();
53
+ const context = resolvePlatformContext({ cwd: options.cwd || process.cwd(), client });
54
+ if (!hasUsableContext(context)) {
55
+ return { ...platformResult(context.status, { platform: context.platform, capabilities: context.capabilities, error: context.error }), reviews: [] };
56
+ }
57
+ const repository = context.platform.repository!;
58
+ if (invalidPrNumber(prNumber)) {
59
+ return {
60
+ ...platformResult('failed', {
61
+ platform: context.platform, capabilities: context.capabilities,
62
+ error: { code: 'PR_NUMBER_INVALID', message: 'PR number must be positive', retryable: false }
63
+ }),
64
+ reviews: []
65
+ };
66
+ }
67
+ const fetched = client.json<RemoteReview[]>(['api', '--paginate', '--slurp', `repos/${repository}/pulls/${prNumber}/reviews?per_page=100`], { cwd: options.cwd || process.cwd() });
68
+ if (!fetched.ok) {
69
+ return {
70
+ ...platformResult(fetched.error.retryable ? 'blocked' : 'failed', {
71
+ platform: context.platform, capabilities: context.capabilities,
72
+ resource: { kind: 'pull-request', number: prNumber }, error: fetched.error
73
+ }),
74
+ reviews: []
75
+ };
76
+ }
77
+ const flat = (fetched.value || []).flatMap((entry) => Array.isArray(entry) ? entry : [entry]);
78
+ const reviews: PrReviewListEntry[] = flat
79
+ .map((review) => ({
80
+ id: review.id ?? 0,
81
+ commitId: review.commit_id ?? '',
82
+ body: review.body ?? '',
83
+ url: review.html_url ?? ''
84
+ }))
85
+ .filter((review) => review.body !== '');
86
+ return {
87
+ ...platformResult('no-op', {
88
+ platform: context.platform, capabilities: context.capabilities,
89
+ resource: { kind: 'pull-request', number: prNumber }, error: null
90
+ }),
91
+ reviews
92
+ };
93
+ }
94
+
95
+ export function publishPrReview(options: {
96
+ cwd?: string;
97
+ client?: GitHubClient;
98
+ dryRun?: boolean;
99
+ prNumber: number;
100
+ identity: PrReviewIdentity;
101
+ event: PrReviewEvent;
102
+ body: string;
103
+ }): PlatformResult {
104
+ const client = options.client || createGitHubClient();
105
+ const context = resolvePlatformContext({ cwd: options.cwd || process.cwd(), client });
106
+ if (!hasUsableContext(context)) {
107
+ return platformResult(context.status, { platform: context.platform, capabilities: context.capabilities, error: context.error });
108
+ }
109
+ const repository = context.platform.repository!;
110
+ if (invalidPrNumber(options.prNumber)) {
111
+ return platformResult('failed', {
112
+ platform: context.platform, capabilities: context.capabilities,
113
+ error: { code: 'PR_NUMBER_INVALID', message: 'PR number must be positive', retryable: false }
114
+ });
115
+ }
116
+ if (!REVIEW_EVENTS.includes(options.event)) {
117
+ return platformResult('failed', {
118
+ platform: context.platform, capabilities: context.capabilities,
119
+ resource: { kind: 'pull-request', number: options.prNumber },
120
+ error: { code: 'REVIEW_EVENT_INVALID', message: `review event must be one of ${REVIEW_EVENTS.join('|')}`, retryable: false }
121
+ });
122
+ }
123
+ if (!REVIEW_SCOPE_PATTERN.test(options.identity.scope) || !/^[0-9a-f]{7,40}$/i.test(options.identity.commitSha)) {
124
+ return platformResult('failed', {
125
+ platform: context.platform, capabilities: context.capabilities,
126
+ resource: { kind: 'pull-request', number: options.prNumber },
127
+ error: { code: 'REVIEW_IDENTITY_INVALID', message: 'review scope must be a task id or pr{N}; commitSha is required', retryable: false }
128
+ });
129
+ }
130
+
131
+ const marker = reviewMarker(options.identity);
132
+ const listed = listPrReviews(options.prNumber, { cwd: options.cwd, client });
133
+ if (listed.status === 'failed' || listed.status === 'blocked') return listed;
134
+ const existing = listed.reviews.find((review) => firstLine(review.body) === marker) ?? null;
135
+ if (existing) {
136
+ if (existing.commitId === options.identity.commitSha) {
137
+ return platformResult('no-op', {
138
+ platform: context.platform, capabilities: context.capabilities,
139
+ resource: { kind: 'pull-request', number: options.prNumber },
140
+ operations: [{ name: 'review:publish', status: 'no-op', reasonCode: null }], error: null
141
+ });
142
+ }
143
+ return platformResult('failed', {
144
+ platform: context.platform, capabilities: context.capabilities,
145
+ resource: { kind: 'pull-request', number: options.prNumber },
146
+ operations: [{ name: 'review:publish', status: 'failed', reasonCode: 'REVIEW_MARKER_CONFLICT' }],
147
+ error: { code: 'REVIEW_MARKER_CONFLICT', message: 'A review with the same marker targets a different commit; start a new round', retryable: false }
148
+ });
149
+ }
150
+
151
+ const wrappedBody = [
152
+ marker,
153
+ reviewedCommitMarker(options.identity.commitSha),
154
+ '',
155
+ String(options.body || '').replace(/\r?\n/g, '\n').replace(/\n+$/, ''),
156
+ ''
157
+ ].join('\n');
158
+
159
+ if (options.dryRun) {
160
+ return platformResult('planned', {
161
+ platform: context.platform, capabilities: context.capabilities,
162
+ resource: { kind: 'pull-request', number: options.prNumber },
163
+ operations: [{ name: 'review:publish', status: 'planned', reasonCode: null }], error: null
164
+ });
165
+ }
166
+
167
+ const posted = client.json<RemoteReview>(['api', `repos/${repository}/pulls/${options.prNumber}/reviews`, '-X', 'POST', '--input', '-'], {
168
+ cwd: options.cwd || process.cwd(),
169
+ method: 'POST',
170
+ input: JSON.stringify({ commit_id: options.identity.commitSha, body: wrappedBody, event: options.event })
171
+ });
172
+ if (!posted.ok) {
173
+ if (posted.error.retryable) {
174
+ const reconciled = listPrReviews(options.prNumber, { cwd: options.cwd, client });
175
+ const found = reconciled.reviews.find((review) => firstLine(review.body) === marker);
176
+ if (found) {
177
+ return platformResult('applied', {
178
+ changed: true, platform: context.platform, capabilities: context.capabilities,
179
+ resource: { kind: 'pull-request', number: options.prNumber },
180
+ operations: [{ name: 'review:publish', status: 'applied', reasonCode: 'CREATE_RECONCILED' }], error: null
181
+ });
182
+ }
183
+ return platformResult('blocked', {
184
+ platform: context.platform, capabilities: context.capabilities,
185
+ resource: { kind: 'pull-request', number: options.prNumber },
186
+ operations: [{ name: 'review:publish', status: 'failed', reasonCode: 'REVIEW_CREATE_OUTCOME_UNKNOWN' }],
187
+ error: { code: 'REVIEW_CREATE_OUTCOME_UNKNOWN', message: posted.error.message, retryable: true }
188
+ });
189
+ }
190
+ return platformResult('failed', {
191
+ platform: context.platform, capabilities: context.capabilities,
192
+ resource: { kind: 'pull-request', number: options.prNumber },
193
+ operations: [{ name: 'review:publish', status: 'failed', reasonCode: posted.error.code }], error: posted.error
194
+ });
195
+ }
196
+ return platformResult('applied', {
197
+ changed: true, platform: context.platform, capabilities: context.capabilities,
198
+ resource: { kind: 'pull-request', number: options.prNumber },
199
+ operations: [{ name: 'review:publish', status: 'applied', reasonCode: null }], error: null
200
+ });
201
+ }