@fitlab-ai/agent-infra 0.9.2 → 0.9.4

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 (428) hide show
  1. package/README.md +12 -9
  2. package/README.zh-CN.md +12 -9
  3. package/bin/cli.ts +12 -0
  4. package/dist/bin/cli.js +14 -0
  5. package/dist/bin/internal-cli.js +134 -108
  6. package/dist/lib/agent-clients/adapter.js +137 -4
  7. package/dist/lib/agent-clients/adapters/antigravity-cli.js +64 -0
  8. package/dist/lib/{sandbox/credentials.js → agent-clients/adapters/claude-code-credentials.js} +4 -14
  9. package/dist/lib/agent-clients/adapters/claude-code-refresh.js +85 -0
  10. package/dist/lib/agent-clients/adapters/claude-code-sandbox.js +247 -0
  11. package/dist/lib/agent-clients/adapters/claude-code.js +27 -4
  12. package/dist/lib/agent-clients/adapters/codex.js +9 -0
  13. package/dist/lib/agent-clients/adapters/index.js +2 -2
  14. package/dist/lib/agent-clients/adapters/opencode-sandbox.js +108 -0
  15. package/dist/lib/agent-clients/adapters/opencode.js +24 -3
  16. package/dist/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
  17. package/dist/lib/agent-clients/config.js +42 -4
  18. package/dist/lib/agent-clients/next-steps.js +11 -2
  19. package/dist/lib/agent-clients/reconcile.js +2 -1
  20. package/dist/lib/agent-clients/registry.js +16 -2
  21. package/dist/lib/agent-clients/schema.js +18 -0
  22. package/dist/lib/agent-clients/tokens.js +49 -0
  23. package/dist/lib/agent-clients/types.js +1 -1
  24. package/dist/lib/defaults.json +1 -1
  25. package/dist/lib/git/workflow.js +54 -1
  26. package/dist/lib/internal/agent-client.js +61 -4
  27. package/dist/lib/internal/git-workflow.js +6 -1
  28. package/dist/lib/internal/platform-checks.js +3 -3
  29. package/dist/lib/internal/platform-comment.js +29 -2
  30. package/dist/lib/internal/platform-issue.js +7 -0
  31. package/dist/lib/internal/platform-pr-review.js +132 -0
  32. package/dist/lib/internal/platform-pr.js +20 -2
  33. package/dist/lib/internal/platform-release-notes.js +52 -16
  34. package/dist/lib/internal/pr-review-grade.js +140 -0
  35. package/dist/lib/internal/release-workflow.js +13 -6
  36. package/dist/lib/internal/sandbox-control.js +24 -0
  37. package/dist/lib/internal/task-activity.js +105 -0
  38. package/dist/lib/internal/task-event.js +17 -1
  39. package/dist/lib/internal/task-ledger.js +12 -3
  40. package/dist/lib/internal/task-lifecycle.js +26 -1
  41. package/dist/lib/internal/task-orchestration.js +167 -18
  42. package/dist/lib/internal/task-review.js +8 -3
  43. package/dist/lib/platform/adapters.js +8 -1
  44. package/dist/lib/platform/completion-backfill.js +122 -0
  45. package/dist/lib/platform/github-client.js +19 -2
  46. package/dist/lib/platform/issue-comments.js +3 -2
  47. package/dist/lib/platform/merged-pr-equivalence.js +115 -5
  48. package/dist/lib/platform/pr-checks.js +65 -42
  49. package/dist/lib/platform/pr-review.js +166 -0
  50. package/dist/lib/platform/pull-requests.js +371 -4
  51. package/dist/lib/platform/release-notes.js +85 -3
  52. package/dist/lib/platform/releases.js +13 -5
  53. package/dist/lib/pr-review/evidence-grading.js +229 -0
  54. package/dist/lib/process-data/index.js +239 -0
  55. package/dist/lib/process-data/normalize.js +198 -0
  56. package/dist/lib/process-data/privacy.js +33 -0
  57. package/dist/lib/process-data/reconcile.js +95 -0
  58. package/dist/lib/process-data/repair.js +58 -0
  59. package/dist/lib/process-data/sources.js +345 -0
  60. package/dist/lib/process-data/store.js +282 -0
  61. package/dist/lib/process-data/types.js +2 -0
  62. package/dist/lib/run/index.js +4 -3
  63. package/dist/lib/run/tui.js +10 -13
  64. package/dist/lib/sandbox/agent-client-reconciler.js +26 -2
  65. package/dist/lib/sandbox/capture.js +7 -2
  66. package/dist/lib/sandbox/commands/create.js +109 -248
  67. package/dist/lib/sandbox/commands/enter.js +15 -47
  68. package/dist/lib/sandbox/commands/list-running.js +16 -6
  69. package/dist/lib/sandbox/commands/ls.js +10 -6
  70. package/dist/lib/sandbox/commands/rebuild.js +4 -3
  71. package/dist/lib/sandbox/commands/refresh.js +4 -84
  72. package/dist/lib/sandbox/commands/rm.js +24 -3
  73. package/dist/lib/sandbox/commands/show.js +13 -4
  74. package/dist/lib/sandbox/commands/start.js +7 -4
  75. package/dist/lib/sandbox/config.js +2 -0
  76. package/dist/lib/sandbox/constants.js +6 -0
  77. package/dist/lib/sandbox/control/client.js +44 -0
  78. package/dist/lib/sandbox/control/protocol.js +35 -0
  79. package/dist/lib/sandbox/control/server.js +109 -0
  80. package/dist/lib/sandbox/dockerfile.js +36 -13
  81. package/dist/lib/sandbox/mounts.js +17 -4
  82. package/dist/lib/sandbox/recovery.js +128 -5
  83. package/dist/lib/sandbox/redaction.js +12 -0
  84. package/dist/lib/sandbox/runtimes/base.dockerfile +24 -74
  85. package/dist/lib/sandbox/workspace-identity.js +95 -0
  86. package/dist/lib/sandbox/workspace-view.js +103 -0
  87. package/dist/lib/task/activity-intent.js +307 -0
  88. package/dist/lib/task/artifact-lifecycle.js +2 -1
  89. package/dist/lib/task/commands/log.js +15 -14
  90. package/dist/lib/task/commit-finalization.js +267 -0
  91. package/dist/lib/task/commit-intent.js +165 -0
  92. package/dist/lib/task/decision-intents.js +31 -17
  93. package/dist/lib/task/delegation-receipts.js +25 -4
  94. package/dist/lib/task/events.js +67 -28
  95. package/dist/lib/task/finalization-artifacts.js +35 -0
  96. package/dist/lib/task/implementation-inputs.js +54 -7
  97. package/dist/lib/task/ledger-intents.js +35 -1
  98. package/dist/lib/task/lifecycle-execution.js +46 -0
  99. package/dist/lib/task/orchestration.js +753 -54
  100. package/dist/lib/task/resolve-ref.js +41 -1
  101. package/dist/lib/task/review-finalization.js +29 -9
  102. package/dist/lib/task/task-execution-lock.js +231 -0
  103. package/dist/lib/task/verification-checks.js +1 -1
  104. package/dist/lib/task/verification-engine.js +218 -2
  105. package/dist/lib/task/verification.js +4 -3
  106. package/dist/lib/task/workspace-snapshot.js +17 -5
  107. package/lib/agent-clients/adapter.ts +201 -4
  108. package/lib/agent-clients/adapters/antigravity-cli.ts +65 -0
  109. package/lib/{sandbox/credentials.ts → agent-clients/adapters/claude-code-credentials.ts} +3 -18
  110. package/lib/agent-clients/adapters/claude-code-refresh.ts +128 -0
  111. package/lib/agent-clients/adapters/claude-code-sandbox.ts +295 -0
  112. package/lib/agent-clients/adapters/claude-code.ts +31 -4
  113. package/lib/agent-clients/adapters/codex.ts +9 -0
  114. package/lib/agent-clients/adapters/index.ts +2 -2
  115. package/lib/agent-clients/adapters/opencode-sandbox.ts +136 -0
  116. package/lib/agent-clients/adapters/opencode.ts +27 -3
  117. package/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
  118. package/lib/agent-clients/config.ts +49 -4
  119. package/lib/agent-clients/next-steps.ts +15 -2
  120. package/lib/agent-clients/reconcile.ts +2 -1
  121. package/lib/agent-clients/registry.ts +19 -1
  122. package/lib/agent-clients/schema.ts +18 -0
  123. package/lib/agent-clients/tokens.ts +53 -0
  124. package/lib/agent-clients/types.ts +15 -1
  125. package/lib/defaults.json +1 -1
  126. package/lib/git/workflow.ts +66 -2
  127. package/lib/internal/agent-client.ts +59 -4
  128. package/lib/internal/git-workflow.ts +5 -1
  129. package/lib/internal/platform-checks.ts +4 -4
  130. package/lib/internal/platform-comment.ts +17 -2
  131. package/lib/internal/platform-issue.ts +4 -0
  132. package/lib/internal/platform-pr-review.ts +104 -0
  133. package/lib/internal/platform-pr.ts +14 -1
  134. package/lib/internal/platform-release-notes.ts +59 -16
  135. package/lib/internal/pr-review-grade.ts +130 -0
  136. package/lib/internal/release-workflow.ts +13 -6
  137. package/lib/internal/sandbox-control.ts +24 -0
  138. package/lib/internal/task-activity.ts +91 -0
  139. package/lib/internal/task-event.ts +9 -1
  140. package/lib/internal/task-ledger.ts +12 -3
  141. package/lib/internal/task-lifecycle.ts +26 -1
  142. package/lib/internal/task-orchestration.ts +123 -14
  143. package/lib/internal/task-review.ts +8 -3
  144. package/lib/platform/adapters.ts +29 -2
  145. package/lib/platform/completion-backfill.ts +134 -0
  146. package/lib/platform/github-client.ts +20 -2
  147. package/lib/platform/issue-comments.ts +3 -2
  148. package/lib/platform/merged-pr-equivalence.ts +127 -5
  149. package/lib/platform/pr-checks.ts +73 -46
  150. package/lib/platform/pr-review.ts +201 -0
  151. package/lib/platform/pull-requests.ts +444 -3
  152. package/lib/platform/release-notes.ts +100 -4
  153. package/lib/platform/releases.ts +16 -4
  154. package/lib/pr-review/evidence-grading.ts +318 -0
  155. package/lib/process-data/index.ts +224 -0
  156. package/lib/process-data/normalize.ts +202 -0
  157. package/lib/process-data/privacy.ts +41 -0
  158. package/lib/process-data/reconcile.ts +110 -0
  159. package/lib/process-data/repair.ts +64 -0
  160. package/lib/process-data/sources.ts +354 -0
  161. package/lib/process-data/store.ts +315 -0
  162. package/lib/process-data/types.ts +135 -0
  163. package/lib/run/index.ts +5 -3
  164. package/lib/run/tui.ts +14 -12
  165. package/lib/sandbox/agent-client-reconciler.ts +44 -2
  166. package/lib/sandbox/capture.ts +9 -2
  167. package/lib/sandbox/commands/create.ts +126 -280
  168. package/lib/sandbox/commands/enter.ts +13 -59
  169. package/lib/sandbox/commands/list-running.ts +23 -6
  170. package/lib/sandbox/commands/ls.ts +22 -6
  171. package/lib/sandbox/commands/rebuild.ts +4 -3
  172. package/lib/sandbox/commands/refresh.ts +4 -126
  173. package/lib/sandbox/commands/rm.ts +24 -3
  174. package/lib/sandbox/commands/show.ts +30 -4
  175. package/lib/sandbox/commands/start.ts +6 -4
  176. package/lib/sandbox/config.ts +4 -0
  177. package/lib/sandbox/constants.ts +8 -0
  178. package/lib/sandbox/control/client.ts +55 -0
  179. package/lib/sandbox/control/protocol.ts +75 -0
  180. package/lib/sandbox/control/server.ts +117 -0
  181. package/lib/sandbox/dockerfile.ts +66 -13
  182. package/lib/sandbox/mounts.ts +24 -4
  183. package/lib/sandbox/recovery.ts +163 -6
  184. package/lib/sandbox/redaction.ts +14 -0
  185. package/lib/sandbox/runtimes/base.dockerfile +24 -74
  186. package/lib/sandbox/tool-types.ts +16 -0
  187. package/lib/sandbox/workspace-identity.ts +123 -0
  188. package/lib/sandbox/workspace-view.ts +143 -0
  189. package/lib/server/server.schema.json +2 -2
  190. package/lib/task/activity-intent.ts +417 -0
  191. package/lib/task/artifact-lifecycle.ts +2 -1
  192. package/lib/task/commands/log.ts +15 -14
  193. package/lib/task/commit-finalization.ts +356 -0
  194. package/lib/task/commit-intent.ts +230 -0
  195. package/lib/task/decision-intents.ts +32 -12
  196. package/lib/task/delegation-receipts.ts +34 -5
  197. package/lib/task/events.ts +75 -30
  198. package/lib/task/finalization-artifacts.ts +58 -0
  199. package/lib/task/implementation-inputs.ts +62 -8
  200. package/lib/task/ledger-intents.ts +43 -3
  201. package/lib/task/lifecycle-execution.ts +106 -0
  202. package/lib/task/orchestration.ts +1019 -72
  203. package/lib/task/resolve-ref.ts +46 -1
  204. package/lib/task/review-finalization.ts +40 -9
  205. package/lib/task/task-execution-lock.ts +296 -0
  206. package/lib/task/verification-checks.ts +1 -1
  207. package/lib/task/verification-engine.ts +249 -2
  208. package/lib/task/verification.ts +5 -3
  209. package/lib/task/workspace-snapshot.ts +26 -5
  210. package/package.json +3 -3
  211. package/templates/.agents/QUICKSTART.en.md +25 -8
  212. package/templates/.agents/QUICKSTART.zh-CN.md +25 -8
  213. package/templates/.agents/README.en.md +10 -7
  214. package/templates/.agents/README.zh-CN.md +10 -7
  215. package/templates/.agents/hooks/lifecycle-delegation.js +15 -4
  216. package/templates/.agents/rules/README.en.md +1 -1
  217. package/templates/.agents/rules/README.zh-CN.md +1 -1
  218. package/templates/.agents/rules/create-issue.github.en.md +4 -2
  219. package/templates/.agents/rules/create-issue.github.zh-CN.md +4 -2
  220. package/templates/.agents/rules/human-decision-context.en.md +11 -2
  221. package/templates/.agents/rules/human-decision-context.zh-CN.md +11 -2
  222. package/templates/.agents/rules/issue-fields.github.en.md +3 -1
  223. package/templates/.agents/rules/issue-fields.github.zh-CN.md +3 -1
  224. package/templates/.agents/rules/issue-pr-commands.en.md +5 -3
  225. package/templates/.agents/rules/issue-pr-commands.github.en.md +5 -3
  226. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +5 -3
  227. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +5 -3
  228. package/templates/.agents/rules/issue-sync.github.en.md +7 -3
  229. package/templates/.agents/rules/issue-sync.github.zh-CN.md +8 -4
  230. package/templates/.agents/rules/lifecycle-orchestration.en.md +5 -2
  231. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +5 -2
  232. package/templates/.agents/rules/milestone-inference.github.en.md +4 -2
  233. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +4 -2
  234. package/templates/.agents/rules/next-step-output.en.md +3 -1
  235. package/templates/.agents/rules/next-step-output.zh-CN.md +3 -1
  236. package/templates/.agents/rules/no-mid-flow-questions.en.md +17 -0
  237. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +17 -0
  238. package/templates/.agents/rules/pr-checks-commands.en.md +3 -3
  239. package/templates/.agents/rules/pr-checks-commands.github.en.md +2 -2
  240. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +3 -3
  241. package/templates/.agents/rules/pr-checks-commands.zh-CN.md +3 -3
  242. package/templates/.agents/rules/pr-sync.en.md +3 -1
  243. package/templates/.agents/rules/pr-sync.github.en.md +11 -1
  244. package/templates/.agents/rules/pr-sync.github.zh-CN.md +11 -1
  245. package/templates/.agents/rules/pr-sync.zh-CN.md +3 -1
  246. package/templates/.agents/rules/release-commands.en.md +1 -1
  247. package/templates/.agents/rules/release-commands.github.en.md +12 -2
  248. package/templates/.agents/rules/release-commands.github.zh-CN.md +12 -2
  249. package/templates/.agents/rules/release-commands.zh-CN.md +1 -1
  250. package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
  251. package/templates/.agents/rules/task-management.en.md +13 -0
  252. package/templates/.agents/rules/task-management.zh-CN.md +13 -0
  253. package/templates/.agents/skills/analyze-task/SKILL.en.md +10 -7
  254. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -7
  255. package/templates/.agents/skills/block-task/SKILL.en.md +7 -5
  256. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +7 -5
  257. package/templates/.agents/skills/cancel-task/SKILL.en.md +6 -4
  258. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +6 -4
  259. package/templates/.agents/skills/close-codescan/SKILL.en.md +3 -1
  260. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -1
  261. package/templates/.agents/skills/close-dependabot/SKILL.en.md +3 -1
  262. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +3 -1
  263. package/templates/.agents/skills/code-task/SKILL.en.md +10 -7
  264. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +12 -9
  265. package/templates/.agents/skills/commit/SKILL.en.md +23 -11
  266. package/templates/.agents/skills/commit/SKILL.zh-CN.md +29 -11
  267. package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
  268. package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
  269. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +49 -0
  270. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +55 -0
  271. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
  272. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
  273. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
  274. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
  275. package/templates/.agents/skills/commit/reference/task-status-update.en.md +4 -4
  276. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +4 -4
  277. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +6 -4
  278. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +6 -4
  279. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +1 -1
  280. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +1 -1
  281. package/templates/.agents/skills/complete-task/SKILL.en.md +22 -7
  282. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +22 -7
  283. package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
  284. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
  285. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +17 -0
  286. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +17 -0
  287. package/templates/.agents/skills/create-pr/SKILL.en.md +7 -5
  288. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -5
  289. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +1 -1
  290. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +1 -1
  291. package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -16
  292. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +17 -16
  293. package/templates/.agents/skills/create-task/SKILL.en.md +5 -3
  294. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -3
  295. package/templates/.agents/skills/create-task/reference/context-capture.en.md +34 -0
  296. package/templates/.agents/skills/create-task/reference/context-capture.zh-CN.md +34 -0
  297. package/templates/.agents/skills/import-issue/SKILL.en.md +5 -3
  298. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -3
  299. package/templates/.agents/skills/plan-task/SKILL.en.md +8 -5
  300. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +8 -5
  301. package/templates/.agents/skills/post-release/SKILL.en.md +2 -0
  302. package/templates/.agents/skills/post-release/SKILL.zh-CN.md +2 -0
  303. package/templates/.agents/skills/release/SKILL.en.md +16 -10
  304. package/templates/.agents/skills/release/SKILL.zh-CN.md +16 -10
  305. package/templates/.agents/skills/restore-task/SKILL.en.md +4 -1
  306. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +4 -1
  307. package/templates/.agents/skills/review-analysis/SKILL.en.md +7 -4
  308. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +7 -4
  309. package/templates/.agents/skills/review-code/SKILL.en.md +11 -8
  310. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +11 -8
  311. package/templates/.agents/skills/review-code/reference/output-templates.en.md +1 -1
  312. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +1 -1
  313. package/templates/.agents/skills/review-plan/SKILL.en.md +7 -4
  314. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +7 -4
  315. package/templates/.agents/skills/review-pr/SKILL.en.md +186 -0
  316. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +186 -0
  317. package/templates/.agents/skills/review-pr/config/verify.en.json +37 -0
  318. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +37 -0
  319. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +65 -0
  320. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +65 -0
  321. package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +18 -0
  322. package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +18 -0
  323. package/templates/.agents/skills/review-pr/reference/report-template.en.md +85 -0
  324. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +85 -0
  325. package/templates/.agents/skills/run-task/SKILL.en.md +8 -8
  326. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +8 -8
  327. package/templates/.agents/skills/run-task/config/verify.en.json +2 -1
  328. package/templates/.agents/skills/run-task/config/verify.zh-CN.json +2 -1
  329. package/templates/.agents/skills/run-task/reference/host-validation.en.md +20 -0
  330. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +20 -0
  331. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +98 -107
  332. package/templates/.agents/skills/watch-pr/SKILL.en.md +13 -13
  333. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +13 -13
  334. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +27 -6
  335. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +26 -5
  336. package/templates/.agents/templates/task.en.md +2 -2
  337. package/templates/.agents/templates/task.zh-CN.md +2 -2
  338. package/templates/.agents/workflows/bug-fix.en.yaml +3 -3
  339. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +3 -3
  340. package/templates/.agents/workflows/code-review.en.yaml +1 -1
  341. package/templates/.agents/workflows/code-review.zh-CN.yaml +1 -1
  342. package/templates/.agents/workflows/feature-development.en.yaml +4 -4
  343. package/templates/.agents/workflows/feature-development.zh-CN.yaml +4 -4
  344. package/templates/.agents/workflows/refactoring.en.yaml +3 -3
  345. package/templates/.agents/workflows/refactoring.zh-CN.yaml +3 -3
  346. package/templates/.claude/commands/post-release.en.md +1 -0
  347. package/templates/.claude/commands/post-release.zh-CN.md +1 -0
  348. package/templates/.claude/commands/review-pr.en.md +8 -0
  349. package/templates/.claude/commands/review-pr.zh-CN.md +8 -0
  350. package/templates/.claude/commands/watch-pr.en.md +1 -1
  351. package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
  352. package/templates/.claude/rules/tool-preferences.en.md +20 -0
  353. package/templates/.claude/rules/tool-preferences.zh-CN.md +20 -0
  354. package/templates/.opencode/commands/post-release.en.md +2 -0
  355. package/templates/.opencode/commands/post-release.zh-CN.md +2 -0
  356. package/templates/.opencode/commands/review-pr.en.md +11 -0
  357. package/templates/.opencode/commands/review-pr.zh-CN.md +11 -0
  358. package/templates/.opencode/commands/watch-pr.en.md +1 -1
  359. package/templates/.opencode/commands/watch-pr.zh-CN.md +1 -1
  360. package/dist/lib/agent-clients/adapters/gemini-cli.js +0 -64
  361. package/lib/agent-clients/adapters/gemini-cli.ts +0 -65
  362. package/templates/.gemini/commands/_project_/analyze-task.en.toml +0 -10
  363. package/templates/.gemini/commands/_project_/analyze-task.zh-CN.toml +0 -10
  364. package/templates/.gemini/commands/_project_/archive-tasks.en.toml +0 -10
  365. package/templates/.gemini/commands/_project_/archive-tasks.zh-CN.toml +0 -10
  366. package/templates/.gemini/commands/_project_/block-task.en.toml +0 -10
  367. package/templates/.gemini/commands/_project_/block-task.zh-CN.toml +0 -10
  368. package/templates/.gemini/commands/_project_/cancel-task.en.toml +0 -10
  369. package/templates/.gemini/commands/_project_/cancel-task.zh-CN.toml +0 -10
  370. package/templates/.gemini/commands/_project_/check-task.en.toml +0 -10
  371. package/templates/.gemini/commands/_project_/check-task.zh-CN.toml +0 -10
  372. package/templates/.gemini/commands/_project_/close-codescan.en.toml +0 -8
  373. package/templates/.gemini/commands/_project_/close-codescan.zh-CN.toml +0 -8
  374. package/templates/.gemini/commands/_project_/close-dependabot.en.toml +0 -8
  375. package/templates/.gemini/commands/_project_/close-dependabot.zh-CN.toml +0 -8
  376. package/templates/.gemini/commands/_project_/code-task.en.toml +0 -10
  377. package/templates/.gemini/commands/_project_/code-task.zh-CN.toml +0 -10
  378. package/templates/.gemini/commands/_project_/commit.en.toml +0 -10
  379. package/templates/.gemini/commands/_project_/commit.zh-CN.toml +0 -10
  380. package/templates/.gemini/commands/_project_/complete-manual-validation.en.toml +0 -10
  381. package/templates/.gemini/commands/_project_/complete-manual-validation.zh-CN.toml +0 -10
  382. package/templates/.gemini/commands/_project_/complete-task.en.toml +0 -10
  383. package/templates/.gemini/commands/_project_/complete-task.zh-CN.toml +0 -10
  384. package/templates/.gemini/commands/_project_/create-pr.en.toml +0 -10
  385. package/templates/.gemini/commands/_project_/create-pr.zh-CN.toml +0 -10
  386. package/templates/.gemini/commands/_project_/create-release-note.en.toml +0 -8
  387. package/templates/.gemini/commands/_project_/create-release-note.zh-CN.toml +0 -8
  388. package/templates/.gemini/commands/_project_/create-task.en.toml +0 -8
  389. package/templates/.gemini/commands/_project_/create-task.zh-CN.toml +0 -8
  390. package/templates/.gemini/commands/_project_/import-codescan.en.toml +0 -8
  391. package/templates/.gemini/commands/_project_/import-codescan.zh-CN.toml +0 -8
  392. package/templates/.gemini/commands/_project_/import-dependabot.en.toml +0 -8
  393. package/templates/.gemini/commands/_project_/import-dependabot.zh-CN.toml +0 -8
  394. package/templates/.gemini/commands/_project_/import-issue.en.toml +0 -8
  395. package/templates/.gemini/commands/_project_/import-issue.zh-CN.toml +0 -8
  396. package/templates/.gemini/commands/_project_/init-labels.en.toml +0 -8
  397. package/templates/.gemini/commands/_project_/init-labels.zh-CN.toml +0 -8
  398. package/templates/.gemini/commands/_project_/init-milestones.en.toml +0 -10
  399. package/templates/.gemini/commands/_project_/init-milestones.zh-CN.toml +0 -10
  400. package/templates/.gemini/commands/_project_/plan-task.en.toml +0 -10
  401. package/templates/.gemini/commands/_project_/plan-task.zh-CN.toml +0 -10
  402. package/templates/.gemini/commands/_project_/post-release.en.toml +0 -6
  403. package/templates/.gemini/commands/_project_/post-release.zh-CN.toml +0 -6
  404. package/templates/.gemini/commands/_project_/refine-title.en.toml +0 -8
  405. package/templates/.gemini/commands/_project_/refine-title.zh-CN.toml +0 -8
  406. package/templates/.gemini/commands/_project_/release.en.toml +0 -8
  407. package/templates/.gemini/commands/_project_/release.zh-CN.toml +0 -8
  408. package/templates/.gemini/commands/_project_/restore-task.en.toml +0 -8
  409. package/templates/.gemini/commands/_project_/restore-task.zh-CN.toml +0 -8
  410. package/templates/.gemini/commands/_project_/review-analysis.en.toml +0 -10
  411. package/templates/.gemini/commands/_project_/review-analysis.zh-CN.toml +0 -10
  412. package/templates/.gemini/commands/_project_/review-code.en.toml +0 -10
  413. package/templates/.gemini/commands/_project_/review-code.zh-CN.toml +0 -10
  414. package/templates/.gemini/commands/_project_/review-plan.en.toml +0 -10
  415. package/templates/.gemini/commands/_project_/review-plan.zh-CN.toml +0 -10
  416. package/templates/.gemini/commands/_project_/run-task.en.toml +0 -8
  417. package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +0 -8
  418. package/templates/.gemini/commands/_project_/test-integration.en.toml +0 -6
  419. package/templates/.gemini/commands/_project_/test-integration.zh-CN.toml +0 -6
  420. package/templates/.gemini/commands/_project_/test.en.toml +0 -6
  421. package/templates/.gemini/commands/_project_/test.zh-CN.toml +0 -6
  422. package/templates/.gemini/commands/_project_/update-agent-infra.en.toml +0 -6
  423. package/templates/.gemini/commands/_project_/update-agent-infra.zh-CN.toml +0 -6
  424. package/templates/.gemini/commands/_project_/upgrade-dependency.en.toml +0 -8
  425. package/templates/.gemini/commands/_project_/upgrade-dependency.zh-CN.toml +0 -8
  426. package/templates/.gemini/commands/_project_/watch-pr.en.toml +0 -10
  427. package/templates/.gemini/commands/_project_/watch-pr.zh-CN.toml +0 -10
  428. package/templates/.gemini/settings.json +0 -3
@@ -4,12 +4,15 @@ import { parseTaskFrontmatter } from '../task/frontmatter.js';
4
4
  import { resolveTaskRef } from '../task/resolve-ref.js';
5
5
  import { extractSection } from '../task/sections.js';
6
6
  import { captureTaskWriteMetadata, writeTask } from '../task/write.js';
7
- import { inspectPlatformChangeRequest, registerPlatformCapabilities } from './adapters.js';
7
+ import { inspectPlatformChangeRequest, inspectPlatformIssueClosingChangeRequests, registerPlatformCapabilities } from './adapters.js';
8
8
  import { parseGitHubRemote, resolvePlatformContext } from './context.js';
9
9
  import { createGitHubClient } from './github-client.js';
10
10
  import { inspectPlatformIssue } from './issues.js';
11
11
  import { planPullRequestMetadata } from './pull-request-metadata.js';
12
12
  import { platformResult } from './types.js';
13
+ import { inspectCompletionArtifacts } from '../task/finalization-artifacts.js';
14
+ import { inspectCreatePrCommitGate } from '../task/commit-finalization.js';
15
+ import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
13
16
  function result(status, taskId, issueNumber, prNumber, overrides = {}) {
14
17
  return {
15
18
  ...platformResult(status),
@@ -18,6 +21,17 @@ function result(status, taskId, issueNumber, prNumber, overrides = {}) {
18
21
  ...overrides
19
22
  };
20
23
  }
24
+ function externalResult(base, overrides = {}) {
25
+ return {
26
+ ...base,
27
+ mode: null,
28
+ authorization: null,
29
+ candidates: [],
30
+ eligible: [],
31
+ selected: null,
32
+ ...overrides
33
+ };
34
+ }
21
35
  function normalizePullRequest(remote, repository) {
22
36
  const number = Number(remote.number);
23
37
  const headRepository = remote.head?.repo?.full_name;
@@ -25,6 +39,12 @@ function normalizePullRequest(remote, repository) {
25
39
  if (!Number.isInteger(number) || number <= 0 || !remote.node_id || !remote.html_url ||
26
40
  !remote.head?.ref || !remote.head.sha || !headRepository || !remote.base?.ref || !baseRepository)
27
41
  return null;
42
+ const mergeabilityDetail = remote.mergeable_state?.trim().toLowerCase() || null;
43
+ const mergeability = remote.mergeable === false
44
+ ? { state: 'conflicting', detail: mergeabilityDetail }
45
+ : remote.mergeable === true && mergeabilityDetail !== 'dirty'
46
+ ? { state: 'mergeable', detail: mergeabilityDetail }
47
+ : { state: 'unknown', detail: mergeabilityDetail };
28
48
  return {
29
49
  repository,
30
50
  number,
@@ -40,9 +60,141 @@ function normalizePullRequest(remote, repository) {
40
60
  mergeCommitSha: remote.merge_commit_sha || null,
41
61
  labels: (remote.labels || []).map((label) => typeof label === 'string' ? label : label.name || '').filter(Boolean).sort(),
42
62
  assignees: (remote.assignees || []).map((assignee) => assignee.login || '').filter(Boolean).sort(),
43
- milestone: remote.milestone?.title || null
63
+ milestone: remote.milestone?.title || null,
64
+ mergeability
44
65
  };
45
66
  }
67
+ function normalizeClosingPullRequest(node, repository) {
68
+ return normalizePullRequest({
69
+ number: node.number,
70
+ node_id: node.id,
71
+ html_url: node.url,
72
+ state: node.state === 'OPEN' ? 'open' : 'closed',
73
+ title: node.title,
74
+ body: node.body,
75
+ draft: node.isDraft,
76
+ head: { ref: node.headRefName, sha: node.headRefOid, repo: node.headRepository ? { full_name: node.headRepository.nameWithOwner } : null },
77
+ base: { ref: node.baseRefName, sha: node.baseRefOid, repo: node.baseRepository ? { full_name: node.baseRepository.nameWithOwner } : null },
78
+ merged_at: node.mergedAt,
79
+ merge_commit_sha: node.mergeCommit?.oid,
80
+ labels: node.labels?.nodes,
81
+ assignees: node.assignees?.nodes,
82
+ milestone: node.milestone
83
+ }, repository);
84
+ }
85
+ function repositoryKey(repository) {
86
+ return repository.trim().toLowerCase();
87
+ }
88
+ function identitySignature(pullRequest) {
89
+ return [
90
+ pullRequest.repository, pullRequest.number, pullRequest.nodeId, pullRequest.url,
91
+ pullRequest.head.repository, pullRequest.head.ref, pullRequest.head.sha,
92
+ pullRequest.base.repository, pullRequest.base.ref, pullRequest.base.sha,
93
+ pullRequest.mergedAt || '', pullRequest.mergeCommitSha || ''
94
+ ].join('|');
95
+ }
96
+ function hasCompleteExternalIdentity(pullRequest) {
97
+ const required = [
98
+ pullRequest.repository, pullRequest.nodeId, pullRequest.url,
99
+ pullRequest.head.repository, pullRequest.head.ref, pullRequest.head.sha,
100
+ pullRequest.base.repository, pullRequest.base.ref, pullRequest.base.sha
101
+ ];
102
+ return Number.isInteger(pullRequest.number) && pullRequest.number > 0 &&
103
+ required.every((value) => Boolean(value?.trim())) &&
104
+ Boolean(pullRequest.mergedAt) === Boolean(pullRequest.mergeCommitSha);
105
+ }
106
+ function selectExternalPullRequest(candidates, repository, existingPrNumber, explicitPrNumber) {
107
+ const invalid = candidates.find((candidate) => !hasCompleteExternalIdentity(candidate));
108
+ if (invalid)
109
+ return {
110
+ status: 'failed', code: 'PR_IDENTITY_INVALID',
111
+ message: `Closing PR #${invalid.number} lacks required identity`, candidates, eligible: []
112
+ };
113
+ const byNumber = new Map();
114
+ for (const candidate of candidates) {
115
+ const signature = identitySignature(candidate);
116
+ const prior = byNumber.get(candidate.number);
117
+ if (prior && prior !== signature)
118
+ return {
119
+ status: 'failed', code: 'PR_IDENTITY_INVALID',
120
+ message: `Closing PR #${candidate.number} has conflicting identities`, candidates, eligible: []
121
+ };
122
+ byNumber.set(candidate.number, signature);
123
+ }
124
+ const unique = candidates.filter((candidate, index) => candidates.findIndex((other) => other.number === candidate.number) === index);
125
+ const wantedRepository = repositoryKey(repository);
126
+ const eligible = unique.filter((candidate) => repositoryKey(candidate.repository) === wantedRepository &&
127
+ repositoryKey(candidate.base.repository) === wantedRepository &&
128
+ Boolean(candidate.mergedAt && candidate.mergeCommitSha));
129
+ if (explicitPrNumber !== null) {
130
+ const selected = unique.find((candidate) => candidate.number === explicitPrNumber);
131
+ if (!selected)
132
+ return { status: 'failed', code: 'PR_NOT_FOUND', message: `PR #${explicitPrNumber} is not a closing PR`, candidates, eligible };
133
+ if (!eligible.some((candidate) => candidate.number === explicitPrNumber)) {
134
+ return { status: 'failed', code: 'PR_IDENTITY_INVALID', message: `PR #${explicitPrNumber} is not an eligible merged PR`, candidates, eligible };
135
+ }
136
+ if (existingPrNumber !== null && existingPrNumber !== explicitPrNumber) {
137
+ return { status: 'failed', code: 'PR_BIND_CONFLICT', message: `Task is already bound to PR #${existingPrNumber}`, candidates, eligible };
138
+ }
139
+ return { status: 'selected', source: 'explicit', selected, candidates, eligible };
140
+ }
141
+ if (existingPrNumber !== null && eligible.length === 1 && existingPrNumber !== eligible[0].number) {
142
+ return { status: 'failed', code: 'PR_BIND_CONFLICT', message: `Task is already bound to PR #${existingPrNumber}`, candidates, eligible };
143
+ }
144
+ if (eligible.length === 0)
145
+ return { status: 'normal', candidates, eligible: [] };
146
+ if (eligible.length > 1)
147
+ return {
148
+ status: 'failed', code: 'PR_IDENTITY_AMBIGUOUS',
149
+ message: 'Multiple eligible merged closing pull requests were found', candidates, eligible
150
+ };
151
+ return { status: 'selected', source: 'unique', selected: eligible[0], candidates, eligible };
152
+ }
153
+ 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}}}}}`;
154
+ function inspectGitHubIssueClosingChangeRequests(client, repository, issueNumber, cwd) {
155
+ const [owner, name] = repository.split('/');
156
+ if (!owner || !name || !Number.isInteger(issueNumber) || issueNumber <= 0)
157
+ return {
158
+ ok: false,
159
+ error: { code: 'PR_IDENTITY_INVALID', message: 'Repository or Issue identity is invalid', retryable: false }
160
+ };
161
+ const candidates = [];
162
+ let cursor = null;
163
+ for (;;) {
164
+ const args = [
165
+ 'api', 'graphql', '-f', `query=${CLOSING_PULL_REQUESTS_QUERY}`,
166
+ '-F', `owner=${owner}`, '-F', `name=${name}`, '-F', `issue=${issueNumber}`
167
+ ];
168
+ if (cursor)
169
+ args.push('-F', `cursor=${cursor}`);
170
+ const response = client.json(args, { cwd });
171
+ if (!response.ok)
172
+ return response;
173
+ const connection = response.value?.data?.repository?.issue?.closedByPullRequestsReferences;
174
+ if (!connection || !Array.isArray(connection.nodes) || !connection.pageInfo)
175
+ return {
176
+ ok: false,
177
+ error: { code: 'PR_IDENTITY_INVALID', message: 'Closing pull request response is incomplete', retryable: false }
178
+ };
179
+ for (const node of connection.nodes) {
180
+ const normalized = normalizeClosingPullRequest(node, repository);
181
+ if (!normalized)
182
+ return {
183
+ ok: false,
184
+ error: { code: 'PR_IDENTITY_INVALID', message: 'Closing pull request identity is incomplete', retryable: false }
185
+ };
186
+ candidates.push(normalized);
187
+ }
188
+ if (!connection.pageInfo.hasNextPage)
189
+ return { ok: true, value: candidates };
190
+ if (!connection.pageInfo.endCursor || connection.pageInfo.endCursor === cursor)
191
+ return {
192
+ ok: false,
193
+ error: { code: 'PR_IDENTITY_INVALID', message: 'Closing pull request pagination cursor is invalid', retryable: false }
194
+ };
195
+ cursor = connection.pageInfo.endCursor;
196
+ }
197
+ }
46
198
  function expectedHead(repository, head) {
47
199
  const colon = head.indexOf(':');
48
200
  if (colon === -1)
@@ -179,6 +331,9 @@ registerPlatformCapabilities('github', {
179
331
  inspectChangeRequest({ client, repository, number, cwd }) {
180
332
  return inspectGitHubPullRequest(client || createGitHubClient(), repository, number, cwd);
181
333
  },
334
+ inspectIssueClosingChangeRequests({ client, repository, issueNumber, cwd }) {
335
+ return inspectGitHubIssueClosingChangeRequests(client || createGitHubClient(), repository, issueNumber, cwd);
336
+ },
182
337
  resolveChangeRequestGitEvidence(context) {
183
338
  return resolveGitHubChangeRequestGitEvidence(context);
184
339
  }
@@ -230,6 +385,43 @@ function inspectPlatformPullRequest(taskRef, options = {}) {
230
385
  resource: { kind: 'pull-request', number: base.prNumber }, pullRequest: fetched.value, error: null
231
386
  });
232
387
  }
388
+ function inspectPlatformPullRequestByNumber(prNumber, options = {}) {
389
+ const client = options.client || createGitHubClient();
390
+ const context = resolvePlatformContext({ cwd: options.cwd || process.cwd(), client });
391
+ const usable = (context.status === 'no-op' || context.status === 'degraded') && context.platform.repository;
392
+ if (!usable) {
393
+ return result(context.status, null, null, null, {
394
+ platform: context.platform, capabilities: context.capabilities, operations: context.operations, error: context.error
395
+ });
396
+ }
397
+ if (!Number.isInteger(prNumber) || prNumber <= 0) {
398
+ return result('failed', null, null, null, {
399
+ platform: context.platform, capabilities: context.capabilities,
400
+ error: { code: 'PR_NUMBER_INVALID', message: 'PR number must be positive', retryable: false }
401
+ });
402
+ }
403
+ const fetched = inspectPlatformChangeRequest(context.platform.type, {
404
+ cwd: options.cwd || process.cwd(),
405
+ repository: context.platform.repository,
406
+ number: prNumber,
407
+ client
408
+ });
409
+ if (!fetched.ok || !fetched.value) {
410
+ const error = fetched.error || {
411
+ code: 'PR_INSPECTION_INVALID',
412
+ message: 'Platform adapter returned no change-request snapshot',
413
+ retryable: false
414
+ };
415
+ return result(error.retryable ? 'blocked' : 'failed', null, null, prNumber, {
416
+ platform: context.platform, capabilities: context.capabilities,
417
+ resource: { kind: 'pull-request', number: prNumber }, error
418
+ });
419
+ }
420
+ return result('no-op', null, null, prNumber, {
421
+ platform: context.platform, capabilities: context.capabilities,
422
+ resource: { kind: 'pull-request', number: prNumber }, pullRequest: fetched.value, error: null
423
+ });
424
+ }
233
425
  function appendActivity(content, line) {
234
426
  const body = extractSection(content, ['活动日志', 'Activity Log']);
235
427
  return `${body.replace(/\s+$/, '')}${body.trim() ? '\n' : ''}${line}`;
@@ -291,7 +483,138 @@ function bindPlatformPullRequest(taskRef, options) {
291
483
  operations: [{ name: 'task:bind-pr', status: options.dryRun ? 'planned' : written.status === 'no-op' ? 'no-op' : 'applied', reasonCode: null }], error: null
292
484
  });
293
485
  }
294
- function createPlatformPullRequest(taskRef, options) {
486
+ function externalEvidenceNote(issueNumber, source, pullRequest) {
487
+ return [
488
+ `authorization=${source}`,
489
+ externalIdentityEvidenceNote(issueNumber, pullRequest)
490
+ ].join('; ');
491
+ }
492
+ function externalIdentityEvidenceNote(issueNumber, pullRequest) {
493
+ return [
494
+ `issue=#${issueNumber}`,
495
+ `pr=#${pullRequest.number}`,
496
+ `url=${pullRequest.url}`,
497
+ `base=${pullRequest.base.repository}:${pullRequest.base.ref}@${pullRequest.base.sha}`,
498
+ `head=${pullRequest.head.repository}:${pullRequest.head.ref}@${pullRequest.head.sha}`,
499
+ `mergedAt=${pullRequest.mergedAt}`,
500
+ `mergeCommitSha=${pullRequest.mergeCommitSha}`
501
+ ].join('; ');
502
+ }
503
+ function resolveExternalPullRequest(taskRef, options) {
504
+ const base = resolvedContext(taskRef, options);
505
+ if (!base.ok)
506
+ return externalResult(base.output);
507
+ const inventory = inspectCompletionArtifacts(base.resolved.taskId, { repoRoot: base.resolved.repoRoot });
508
+ if (inventory.status === 'failed')
509
+ return externalResult(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
510
+ platform: base.context.platform,
511
+ capabilities: base.context.capabilities,
512
+ error: { code: inventory.error?.code || 'ARTIFACT_TOPOLOGY_CONFLICT', message: inventory.error?.message || 'Completion artifact inventory failed', retryable: false }
513
+ }));
514
+ if (inventory.artifacts.length > 0) {
515
+ if (options.pr !== undefined)
516
+ return externalResult(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
517
+ platform: base.context.platform,
518
+ capabilities: base.context.capabilities,
519
+ error: { code: 'EXTERNAL_DELIVERY_NOT_APPLICABLE', message: 'External PR selection is only valid when the completion inventory is empty', retryable: false }
520
+ }), { mode: 'normal' });
521
+ return externalResult(result('no-op', base.resolved.taskId, base.issueNumber, base.prNumber, {
522
+ platform: base.context.platform,
523
+ capabilities: base.context.capabilities,
524
+ operations: [{ name: 'external-pr:inventory', status: 'no-op', reasonCode: 'CANONICAL_ARTIFACTS_PRESENT' }],
525
+ error: null
526
+ }), { mode: 'normal' });
527
+ }
528
+ if (!base.issueNumber)
529
+ return externalResult(result('failed', base.resolved.taskId, null, base.prNumber, {
530
+ platform: base.context.platform,
531
+ capabilities: base.context.capabilities,
532
+ error: { code: 'EXTERNAL_DELIVERY_ISSUE_REQUIRED', message: 'External delivery requires a valid issue_number', retryable: false }
533
+ }));
534
+ const inspected = inspectPlatformIssueClosingChangeRequests(base.context.platform.type, {
535
+ cwd: base.resolved.repoRoot,
536
+ repository: base.context.platform.repository,
537
+ issueNumber: base.issueNumber,
538
+ client: base.client
539
+ });
540
+ if (!inspected.ok || !inspected.value) {
541
+ const error = inspected.error || { code: 'PR_IDENTITY_INVALID', message: 'Closing PR inspection returned no candidates', retryable: false };
542
+ return externalResult(result(error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
543
+ platform: base.context.platform, capabilities: base.context.capabilities, error
544
+ }));
545
+ }
546
+ const selected = selectExternalPullRequest(inspected.value, base.context.platform.repository, base.prNumber, options.pr ?? null);
547
+ if (selected.status === 'normal')
548
+ return externalResult(result('no-op', base.resolved.taskId, base.issueNumber, base.prNumber, {
549
+ platform: base.context.platform,
550
+ capabilities: base.context.capabilities,
551
+ operations: [{ name: 'external-pr:select', status: 'no-op', reasonCode: 'NO_ELIGIBLE_CANDIDATE' }],
552
+ error: null
553
+ }), { mode: 'normal', candidates: selected.candidates, eligible: selected.eligible });
554
+ if (selected.status === 'failed')
555
+ return externalResult(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
556
+ platform: base.context.platform,
557
+ capabilities: base.context.capabilities,
558
+ error: { code: selected.code, message: selected.message, retryable: false }
559
+ }), { candidates: selected.candidates, eligible: selected.eligible });
560
+ const note = externalEvidenceNote(base.issueNumber, selected.source, selected.selected);
561
+ const identityNote = externalIdentityEvidenceNote(base.issueNumber, selected.selected);
562
+ const activityEntry = `**Bind External PR** by ${options.agent} — ${note}`;
563
+ const auditLines = base.content.split(/\r?\n/).filter((line) => line.includes('**Bind External PR**'));
564
+ const alreadyAudited = auditLines.some((line) => line.endsWith(identityNote));
565
+ if (auditLines.length > 0 && !alreadyAudited)
566
+ return externalResult(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
567
+ platform: base.context.platform,
568
+ capabilities: base.context.capabilities,
569
+ pullRequest: selected.selected,
570
+ error: { code: 'PR_BIND_CONFLICT', message: 'Existing external PR audit evidence conflicts with the selected identity', retryable: false }
571
+ }), { candidates: selected.candidates, eligible: selected.eligible, selected: selected.selected });
572
+ if (alreadyAudited && base.prNumber !== selected.selected.number)
573
+ return externalResult(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
574
+ platform: base.context.platform,
575
+ capabilities: base.context.capabilities,
576
+ pullRequest: selected.selected,
577
+ error: { code: 'PR_BIND_CONFLICT', message: 'External PR audit evidence does not match the task binding', retryable: false }
578
+ }), { candidates: selected.candidates, eligible: selected.eligible, selected: selected.selected });
579
+ if (base.prNumber === selected.selected.number && alreadyAudited) {
580
+ return externalResult(result('no-op', base.resolved.taskId, base.issueNumber, base.prNumber, {
581
+ platform: base.context.platform,
582
+ capabilities: base.context.capabilities,
583
+ resource: { kind: 'pull-request', number: selected.selected.number },
584
+ pullRequest: selected.selected,
585
+ operations: [{ name: 'task:bind-external-pr', status: 'no-op', reasonCode: 'EVIDENCE_ALREADY_RECORDED' }],
586
+ error: null
587
+ }), { mode: 'external', authorization: selected.source, candidates: selected.candidates, eligible: selected.eligible, selected: selected.selected });
588
+ }
589
+ const metadata = captureTaskWriteMetadata();
590
+ const write = writeTask({
591
+ taskRef: base.resolved.taskId,
592
+ expectedState: 'active',
593
+ dryRun: options.dryRun,
594
+ mutations: [
595
+ { kind: 'frontmatter', set: { pr_number: selected.selected.number, pr_status: 'created', assigned_to: options.agent } },
596
+ { kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: '活动日志', body: appendActivity(base.content, `- ${metadata.timestamp} — ${activityEntry}`) }
597
+ ]
598
+ }, { repoRoot: base.resolved.repoRoot, metadataProvider: () => metadata });
599
+ if (write.status === 'failed')
600
+ return externalResult(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
601
+ platform: base.context.platform,
602
+ capabilities: base.context.capabilities,
603
+ pullRequest: selected.selected,
604
+ error: { code: write.error.code, message: write.error.message, retryable: false }
605
+ }), { candidates: selected.candidates, eligible: selected.eligible, selected: selected.selected });
606
+ const operationStatus = options.dryRun ? 'planned' : write.status === 'applied' ? 'applied' : 'no-op';
607
+ return externalResult(result(operationStatus, base.resolved.taskId, base.issueNumber, options.dryRun ? base.prNumber : selected.selected.number, {
608
+ changed: !options.dryRun && write.changed,
609
+ platform: base.context.platform,
610
+ capabilities: base.context.capabilities,
611
+ resource: { kind: 'pull-request', number: selected.selected.number },
612
+ pullRequest: selected.selected,
613
+ operations: [{ name: 'task:bind-external-pr', status: operationStatus, reasonCode: null }],
614
+ error: null
615
+ }), { mode: 'external', authorization: selected.source, candidates: selected.candidates, eligible: selected.eligible, selected: selected.selected });
616
+ }
617
+ function createPlatformPullRequestUnlocked(taskRef, options) {
295
618
  const base = resolvedContext(taskRef, options);
296
619
  if (!base.ok)
297
620
  return base.output;
@@ -301,6 +624,17 @@ function createPlatformPullRequest(taskRef, options) {
301
624
  });
302
625
  if (base.prNumber)
303
626
  return inspectPlatformPullRequest(taskRef, options);
627
+ const gate = inspectCreatePrCommitGate(base.resolved.taskDir, base.resolved.repoRoot, base.resolved.taskId);
628
+ if (!gate.allowed)
629
+ return result('blocked', base.resolved.taskId, base.issueNumber, null, {
630
+ platform: base.context.platform,
631
+ capabilities: base.context.capabilities,
632
+ error: {
633
+ code: gate.code,
634
+ message: `${gate.message}; action=${gate.action}`,
635
+ retryable: gate.action === 'rerun-commit' || gate.action === 'rerun-review-code'
636
+ }
637
+ });
304
638
  const located = locatePullRequest(base, options.head, options.base);
305
639
  if (!located.ok && located.error.code === 'PR_IDENTITY_AMBIGUOUS')
306
640
  return result('failed', base.resolved.taskId, base.issueNumber, null, {
@@ -368,6 +702,39 @@ function createPlatformPullRequest(taskRef, options) {
368
702
  operations: [{ name: created ? 'pr:create' : 'pr:reuse', status: created ? 'applied' : 'no-op', reasonCode: null }, { name: 'task:bind-pr', status: 'applied', reasonCode: null }], error: null
369
703
  });
370
704
  }
705
+ function createPlatformPullRequest(taskRef, options) {
706
+ const base = resolvedContext(taskRef, options);
707
+ if (!base.ok)
708
+ return base.output;
709
+ if (!options.base || !options.head || !options.title.trim() || !options.body.trim()) {
710
+ return result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
711
+ error: { code: 'PR_PAYLOAD_INVALID', message: 'base, head, title and body are required', retryable: false }
712
+ });
713
+ }
714
+ if (base.prNumber)
715
+ return inspectPlatformPullRequest(taskRef, options);
716
+ try {
717
+ return withTaskExecutionLock(base.resolved.repoRoot, base.resolved.taskId, 'platform-pr.create', () => createPlatformPullRequestUnlocked(taskRef, options));
718
+ }
719
+ catch (error) {
720
+ if (error instanceof TaskExecutionLockError) {
721
+ return result('blocked', base.resolved.taskId, base.issueNumber, null, {
722
+ platform: base.context.platform,
723
+ capabilities: base.context.capabilities,
724
+ error: { code: error.code, message: error.message, retryable: true }
725
+ });
726
+ }
727
+ return result('failed', base.resolved.taskId, base.issueNumber, null, {
728
+ platform: base.context.platform,
729
+ capabilities: base.context.capabilities,
730
+ error: {
731
+ code: 'PR_CREATE_FAILED',
732
+ message: error instanceof Error ? error.message : String(error),
733
+ retryable: false
734
+ }
735
+ });
736
+ }
737
+ }
371
738
  function syncPlatformPullRequest(taskRef, options) {
372
739
  const base = resolvedContext(taskRef, options);
373
740
  if (!base.ok)
@@ -440,5 +807,5 @@ function syncPlatformPullRequest(taskRef, options) {
440
807
  operations: operations.map((operation) => operation.status === 'planned' ? { ...operation, status: 'applied' } : operation), error: null
441
808
  });
442
809
  }
443
- export { bindPlatformPullRequest, createPlatformPullRequest, inspectGitHubPullRequest, inspectPlatformPullRequest, normalizePullRequest, resolveGitHubChangeRequestGitEvidence, selectPullRequest, syncPlatformPullRequest };
810
+ export { bindPlatformPullRequest, createPlatformPullRequest, inspectGitHubPullRequest, inspectGitHubIssueClosingChangeRequests, inspectPlatformPullRequest, inspectPlatformPullRequestByNumber, normalizePullRequest, resolveGitHubChangeRequestGitEvidence, resolveExternalPullRequest, selectExternalPullRequest, selectPullRequest, syncPlatformPullRequest };
444
811
  //# 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
@@ -55,9 +55,17 @@ function reconcileReleaseMilestones(version, options = {}) {
55
55
  const major = Number(match[1]);
56
56
  const minor = Number(match[2]);
57
57
  const patch = Number(match[3]);
58
- const ensure = [`${major}.${minor}.${patch + 1}`, `${major}.${minor}.x`];
59
- if (patch === 0)
60
- ensure.push(`${major}.${minor + 1}.0`, `${major}.${minor + 1}.x`);
58
+ const nextPatch = `${major}.${minor}.${patch + 1}`;
59
+ const currentLine = `${major}.${minor}.x`;
60
+ const ensure = [
61
+ { title: nextPatch, description: `Issues that we want to release in v${nextPatch}.` },
62
+ { title: currentLine, description: `Issues that we want to resolve in ${major}.${minor} line.` }
63
+ ];
64
+ if (patch === 0) {
65
+ const nextMinor = `${major}.${minor + 1}.0`;
66
+ const nextLine = `${major}.${minor + 1}.x`;
67
+ ensure.push({ title: nextMinor, description: `Issues that we want to release in v${nextMinor}.` }, { title: nextLine, description: `Issues that we want to resolve in ${major}.${minor + 1} line.` });
68
+ }
61
69
  const operations = [];
62
70
  const current = milestones.find((item) => item.title === version);
63
71
  if (current && current.state !== 'closed') {
@@ -66,11 +74,11 @@ function reconcileReleaseMilestones(version, options = {}) {
66
74
  }
67
75
  else
68
76
  operations.push({ name: 'close-milestone', title: version, status: 'no-op' });
69
- for (const title of ensure) {
77
+ for (const { title, description } of ensure) {
70
78
  if (milestones.some((item) => item.title === title))
71
79
  operations.push({ name: 'ensure-milestone', title, status: 'no-op' });
72
80
  else {
73
- const created = client.json(['api', '--method', 'POST', `repos/${context.platform.repository}/milestones`, '-f', `title=${title}`], { cwd: options.cwd });
81
+ const created = client.json(['api', '--method', 'POST', `repos/${context.platform.repository}/milestones`, '-f', `title=${title}`, '-f', `description=${description}`], { cwd: options.cwd });
74
82
  operations.push({ name: 'ensure-milestone', title, status: created.ok ? 'applied' : 'failed' });
75
83
  }
76
84
  }