@fitlab-ai/agent-infra 0.9.0 → 0.9.2

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 (327) hide show
  1. package/README.md +22 -5
  2. package/README.zh-CN.md +21 -5
  3. package/bin/cli.ts +12 -0
  4. package/dist/bin/cli.js +14 -0
  5. package/dist/bin/internal-cli.js +20 -0
  6. package/dist/lib/agent-client.js +163 -0
  7. package/dist/lib/agent-clients/adapter.js +294 -0
  8. package/dist/lib/agent-clients/adapters/claude-code.js +62 -0
  9. package/dist/lib/agent-clients/adapters/codex-sandbox.js +210 -0
  10. package/dist/lib/agent-clients/adapters/codex.js +54 -0
  11. package/dist/lib/agent-clients/adapters/gemini-cli.js +64 -0
  12. package/dist/lib/agent-clients/adapters/index.js +20 -0
  13. package/dist/lib/agent-clients/adapters/opencode.js +64 -0
  14. package/dist/lib/agent-clients/config.js +177 -0
  15. package/dist/lib/agent-clients/custom-tuis.js +99 -0
  16. package/dist/lib/agent-clients/invocation.js +9 -0
  17. package/dist/lib/agent-clients/next-steps.js +38 -0
  18. package/dist/lib/agent-clients/project-assets.js +45 -0
  19. package/dist/lib/agent-clients/reconcile.js +235 -0
  20. package/dist/lib/agent-clients/registry.js +72 -0
  21. package/dist/lib/agent-clients/schema.js +57 -0
  22. package/dist/lib/agent-clients/types.js +29 -0
  23. package/dist/lib/builtin-tuis.js +9 -14
  24. package/dist/lib/defaults.json +85 -73
  25. package/dist/lib/git/review-snapshot.js +25 -10
  26. package/dist/lib/git/reviewed-commit-equivalence.js +110 -0
  27. package/dist/lib/init.js +47 -91
  28. package/dist/lib/internal/agent-client.js +117 -0
  29. package/dist/lib/internal/git-workflow.js +1 -1
  30. package/dist/lib/internal/platform-release-notes.js +53 -0
  31. package/dist/lib/internal/release-workflow.js +175 -17
  32. package/dist/lib/internal/task-orchestration.js +151 -0
  33. package/dist/lib/internal/task-review.js +65 -0
  34. package/dist/lib/merge.js +2 -1
  35. package/dist/lib/platform/adapters.js +38 -1
  36. package/dist/lib/platform/change-request-git-evidence.js +113 -0
  37. package/dist/lib/platform/github-release-notes.js +96 -0
  38. package/dist/lib/platform/issues.js +4 -2
  39. package/dist/lib/platform/merged-pr-equivalence.js +84 -0
  40. package/dist/lib/platform/pr-checks.js +43 -15
  41. package/dist/lib/platform/pull-requests.js +113 -7
  42. package/dist/lib/platform/release-notes.js +143 -0
  43. package/dist/lib/platform/releases.js +1 -1
  44. package/dist/lib/platform/verification-required.js +31 -9
  45. package/dist/lib/prompt.js +15 -3
  46. package/dist/lib/release/channels.js +4 -1
  47. package/dist/lib/release/workflow.js +4 -2
  48. package/dist/lib/remove-dir.js +25 -0
  49. package/dist/lib/run/tui.js +8 -2
  50. package/dist/lib/sandbox/agent-client-reconciler.js +234 -0
  51. package/dist/lib/sandbox/build-proxy.js +63 -0
  52. package/dist/lib/sandbox/clipboard/linux.js +2 -1
  53. package/dist/lib/sandbox/commands/create.js +133 -189
  54. package/dist/lib/sandbox/commands/enter.js +17 -1
  55. package/dist/lib/sandbox/commands/prune.js +2 -2
  56. package/dist/lib/sandbox/commands/rebuild.js +24 -7
  57. package/dist/lib/sandbox/commands/rm.js +3 -2
  58. package/dist/lib/sandbox/config.js +21 -4
  59. package/dist/lib/sandbox/constants.js +3 -0
  60. package/dist/lib/sandbox/dockerfile.js +2 -1
  61. package/dist/lib/sandbox/image-build.js +2 -1
  62. package/dist/lib/sandbox/managed-fs.js +10 -5
  63. package/dist/lib/sandbox/recovery.js +52 -36
  64. package/dist/lib/sandbox/runtimes/ai-tools.dockerfile +24 -3
  65. package/dist/lib/sandbox/runtimes/base.dockerfile +8 -1
  66. package/dist/lib/sandbox/runtimes/java17.dockerfile +8 -1
  67. package/dist/lib/sandbox/runtimes/java21.dockerfile +8 -1
  68. package/dist/lib/sandbox/runtimes/node20.dockerfile +8 -1
  69. package/dist/lib/sandbox/runtimes/node22.dockerfile +8 -1
  70. package/dist/lib/sandbox/runtimes/python3.dockerfile +8 -1
  71. package/dist/lib/sandbox/shell.js +3 -2
  72. package/dist/lib/sandbox/tool-types.js +8 -0
  73. package/dist/lib/sandbox/tools.js +2 -126
  74. package/dist/lib/task/artifact-lifecycle.js +1 -49
  75. package/dist/lib/task/commands/decisions.js +50 -20
  76. package/dist/lib/task/commands/issue-body.js +10 -5
  77. package/dist/lib/task/delegation-receipts.js +107 -0
  78. package/dist/lib/task/events.js +101 -2
  79. package/dist/lib/task/issue-form.js +2 -0
  80. package/dist/lib/task/orchestration.js +420 -0
  81. package/dist/lib/task/review-artifacts.js +143 -59
  82. package/dist/lib/task/review-finalization.js +166 -0
  83. package/dist/lib/task/review-fingerprint.js +13 -0
  84. package/dist/lib/task/verification-checks.js +2 -1
  85. package/dist/lib/task/verification-engine.js +168 -23
  86. package/dist/lib/task/verification-review.js +1 -1
  87. package/dist/lib/task/verification.js +4 -2
  88. package/dist/lib/task/workspace-snapshot.js +34 -0
  89. package/dist/lib/update.js +42 -161
  90. package/lib/agent-client.ts +214 -0
  91. package/lib/agent-clients/adapter.ts +461 -0
  92. package/lib/agent-clients/adapters/claude-code.ts +70 -0
  93. package/lib/agent-clients/adapters/codex-sandbox.ts +245 -0
  94. package/lib/agent-clients/adapters/codex.ts +58 -0
  95. package/lib/agent-clients/adapters/gemini-cli.ts +65 -0
  96. package/lib/agent-clients/adapters/index.ts +27 -0
  97. package/lib/agent-clients/adapters/opencode.ts +66 -0
  98. package/lib/agent-clients/config.ts +278 -0
  99. package/lib/agent-clients/custom-tuis.ts +149 -0
  100. package/lib/agent-clients/invocation.ts +19 -0
  101. package/lib/agent-clients/next-steps.ts +72 -0
  102. package/lib/agent-clients/project-assets.ts +96 -0
  103. package/lib/agent-clients/reconcile.ts +324 -0
  104. package/lib/agent-clients/registry.ts +144 -0
  105. package/lib/agent-clients/schema.ts +62 -0
  106. package/lib/agent-clients/types.ts +78 -0
  107. package/lib/benchmark/schemas/v1.0.0/case-manifest.schema.json +120 -0
  108. package/lib/benchmark/schemas/v1.0.0/common.schema.json +109 -0
  109. package/lib/benchmark/schemas/v1.0.0/grader-result.schema.json +160 -0
  110. package/lib/benchmark/schemas/v1.0.0/run-manifest.schema.json +122 -0
  111. package/lib/benchmark/schemas/v1.0.0/subject.schema.json +227 -0
  112. package/lib/builtin-tuis.ts +16 -15
  113. package/lib/defaults.json +24 -12
  114. package/lib/git/review-snapshot.ts +39 -9
  115. package/lib/git/reviewed-commit-equivalence.ts +135 -0
  116. package/lib/init.ts +53 -121
  117. package/lib/internal/agent-client.ts +131 -0
  118. package/lib/internal/git-workflow.ts +1 -1
  119. package/lib/internal/platform-release-notes.ts +55 -0
  120. package/lib/internal/release-workflow.ts +172 -18
  121. package/lib/internal/task-orchestration.ts +132 -0
  122. package/lib/internal/task-review.ts +65 -0
  123. package/lib/merge.ts +2 -1
  124. package/lib/platform/adapters.ts +142 -3
  125. package/lib/platform/change-request-git-evidence.ts +159 -0
  126. package/lib/platform/github-release-notes.ts +132 -0
  127. package/lib/platform/issues.ts +6 -4
  128. package/lib/platform/merged-pr-equivalence.ts +107 -0
  129. package/lib/platform/pr-checks.ts +57 -28
  130. package/lib/platform/pull-requests.ts +140 -25
  131. package/lib/platform/release-notes.ts +167 -0
  132. package/lib/platform/releases.ts +1 -1
  133. package/lib/platform/verification-required.ts +37 -9
  134. package/lib/prompt.ts +16 -3
  135. package/lib/release/channels.ts +4 -1
  136. package/lib/release/workflow.ts +4 -3
  137. package/lib/remove-dir.ts +28 -0
  138. package/lib/run/tui.ts +9 -1
  139. package/lib/sandbox/agent-client-reconciler.ts +378 -0
  140. package/lib/sandbox/build-proxy.ts +94 -0
  141. package/lib/sandbox/clipboard/linux.ts +2 -1
  142. package/lib/sandbox/commands/create.ts +188 -203
  143. package/lib/sandbox/commands/enter.ts +23 -1
  144. package/lib/sandbox/commands/prune.ts +2 -2
  145. package/lib/sandbox/commands/rebuild.ts +29 -7
  146. package/lib/sandbox/commands/rm.ts +3 -2
  147. package/lib/sandbox/config.ts +31 -4
  148. package/lib/sandbox/constants.ts +6 -0
  149. package/lib/sandbox/dockerfile.ts +2 -1
  150. package/lib/sandbox/image-build.ts +4 -1
  151. package/lib/sandbox/managed-fs.ts +18 -5
  152. package/lib/sandbox/recovery.ts +98 -67
  153. package/lib/sandbox/runtimes/ai-tools.dockerfile +24 -3
  154. package/lib/sandbox/runtimes/base.dockerfile +8 -1
  155. package/lib/sandbox/runtimes/java17.dockerfile +8 -1
  156. package/lib/sandbox/runtimes/java21.dockerfile +8 -1
  157. package/lib/sandbox/runtimes/node20.dockerfile +8 -1
  158. package/lib/sandbox/runtimes/node22.dockerfile +8 -1
  159. package/lib/sandbox/runtimes/python3.dockerfile +8 -1
  160. package/lib/sandbox/shell.ts +4 -2
  161. package/lib/sandbox/tool-types.ts +143 -0
  162. package/lib/sandbox/tools.ts +5 -157
  163. package/lib/task/artifact-lifecycle.ts +1 -38
  164. package/lib/task/commands/decisions.ts +53 -20
  165. package/lib/task/commands/issue-body.ts +10 -5
  166. package/lib/task/delegation-receipts.ts +170 -0
  167. package/lib/task/events.ts +111 -3
  168. package/lib/task/issue-form.ts +4 -0
  169. package/lib/task/orchestration.ts +543 -0
  170. package/lib/task/review-artifacts.ts +201 -69
  171. package/lib/task/review-finalization.ts +275 -0
  172. package/lib/task/review-fingerprint.ts +15 -1
  173. package/lib/task/verification-checks.ts +2 -1
  174. package/lib/task/verification-engine.ts +190 -25
  175. package/lib/task/verification-review.ts +2 -0
  176. package/lib/task/verification.ts +6 -3
  177. package/lib/task/workspace-snapshot.ts +36 -0
  178. package/lib/update.ts +48 -184
  179. package/package.json +2 -2
  180. package/templates/.agents/README.en.md +21 -20
  181. package/templates/.agents/README.zh-CN.md +21 -20
  182. package/templates/.agents/hooks/lifecycle-delegation.js +64 -0
  183. package/templates/.agents/rules/README.en.md +2 -0
  184. package/templates/.agents/rules/README.zh-CN.md +2 -0
  185. package/templates/.agents/rules/commit-and-pr.en.md +1 -0
  186. package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -0
  187. package/templates/.agents/rules/human-decision-context.en.md +14 -12
  188. package/templates/.agents/rules/human-decision-context.zh-CN.md +14 -12
  189. package/templates/.agents/rules/lifecycle-orchestration.en.md +21 -0
  190. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +21 -0
  191. package/templates/.agents/rules/next-step-output.en.md +16 -0
  192. package/templates/.agents/rules/next-step-output.zh-CN.md +16 -0
  193. package/templates/.agents/rules/no-mid-flow-questions.en.md +2 -0
  194. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +2 -0
  195. package/templates/.agents/rules/release-commands.en.md +2 -2
  196. package/templates/.agents/rules/release-commands.github.en.md +15 -39
  197. package/templates/.agents/rules/release-commands.github.zh-CN.md +14 -38
  198. package/templates/.agents/rules/release-commands.zh-CN.md +2 -2
  199. package/templates/.agents/rules/review-handshake.en.md +4 -3
  200. package/templates/.agents/rules/review-handshake.zh-CN.md +4 -3
  201. package/templates/.agents/rules/review-method.en.md +113 -0
  202. package/templates/.agents/rules/review-method.zh-CN.md +98 -0
  203. package/templates/.agents/rules/testing-discipline.en.md +20 -2
  204. package/templates/.agents/rules/testing-discipline.zh-CN.md +20 -2
  205. package/templates/.agents/skills/analyze-task/SKILL.en.md +9 -6
  206. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +9 -6
  207. package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
  208. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +4 -4
  209. package/templates/.agents/skills/cancel-task/SKILL.en.md +4 -4
  210. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +5 -5
  211. package/templates/.agents/skills/check-task/SKILL.en.md +16 -16
  212. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -16
  213. package/templates/.agents/skills/close-codescan/SKILL.en.md +4 -4
  214. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +4 -4
  215. package/templates/.agents/skills/close-dependabot/SKILL.en.md +4 -4
  216. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +4 -4
  217. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  218. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
  219. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +14 -16
  220. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +14 -16
  221. package/templates/.agents/skills/code-task/reference/output-template.en.md +3 -3
  222. package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +3 -3
  223. package/templates/.agents/skills/commit/SKILL.en.md +11 -1
  224. package/templates/.agents/skills/commit/SKILL.zh-CN.md +11 -1
  225. package/templates/.agents/skills/commit/reference/task-status-update.en.md +24 -28
  226. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +24 -28
  227. package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
  228. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
  229. package/templates/.agents/skills/complete-task/config/verify.en.json +0 -1
  230. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +0 -1
  231. package/templates/.agents/skills/create-pr/SKILL.en.md +10 -10
  232. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +10 -10
  233. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +3 -3
  234. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +3 -3
  235. package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -38
  236. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +16 -37
  237. package/templates/.agents/skills/create-task/SKILL.en.md +15 -11
  238. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +15 -11
  239. package/templates/.agents/skills/create-task/reference/context-capture.en.md +29 -0
  240. package/templates/.agents/skills/create-task/reference/context-capture.zh-CN.md +29 -0
  241. package/templates/.agents/skills/import-codescan/SKILL.en.md +5 -5
  242. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +5 -5
  243. package/templates/.agents/skills/import-dependabot/SKILL.en.md +5 -5
  244. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +5 -5
  245. package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
  246. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
  247. package/templates/.agents/skills/init-labels/SKILL.en.md +4 -4
  248. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +4 -4
  249. package/templates/.agents/skills/init-milestones/SKILL.en.md +4 -4
  250. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -4
  251. package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
  252. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
  253. package/templates/.agents/skills/refine-title/SKILL.en.md +1 -1
  254. package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +1 -1
  255. package/templates/.agents/skills/release/SKILL.en.md +1 -1
  256. package/templates/.agents/skills/review-analysis/SKILL.en.md +13 -2
  257. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -3
  258. package/templates/.agents/skills/review-analysis/config/verify.en.json +6 -0
  259. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +6 -0
  260. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +13 -15
  261. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +13 -15
  262. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +51 -5
  263. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +52 -6
  264. package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +25 -22
  265. package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +25 -22
  266. package/templates/.agents/skills/review-code/SKILL.en.md +28 -8
  267. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +28 -8
  268. package/templates/.agents/skills/review-code/config/verify.en.json +7 -0
  269. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +7 -0
  270. package/templates/.agents/skills/review-code/reference/concurrency-risks.en.md +25 -0
  271. package/templates/.agents/skills/review-code/reference/concurrency-risks.zh-CN.md +25 -0
  272. package/templates/.agents/skills/review-code/reference/cross-platform-risks.en.md +27 -0
  273. package/templates/.agents/skills/review-code/reference/cross-platform-risks.zh-CN.md +27 -0
  274. package/templates/.agents/skills/review-code/reference/documentation-antipatterns.en.md +13 -0
  275. package/templates/.agents/skills/review-code/reference/documentation-antipatterns.zh-CN.md +13 -0
  276. package/templates/.agents/skills/review-code/reference/migration-risks.en.md +25 -0
  277. package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +25 -0
  278. package/templates/.agents/skills/review-code/reference/output-templates.en.md +22 -24
  279. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +22 -24
  280. package/templates/.agents/skills/review-code/reference/report-template.en.md +54 -7
  281. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +54 -7
  282. package/templates/.agents/skills/review-code/reference/review-criteria.en.md +29 -24
  283. package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +29 -24
  284. package/templates/.agents/skills/review-code/reference/security-risks.en.md +25 -0
  285. package/templates/.agents/skills/review-code/reference/security-risks.zh-CN.md +25 -0
  286. package/templates/.agents/skills/review-plan/SKILL.en.md +13 -2
  287. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -3
  288. package/templates/.agents/skills/review-plan/config/verify.en.json +6 -0
  289. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +6 -0
  290. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +13 -15
  291. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +13 -15
  292. package/templates/.agents/skills/review-plan/reference/report-template.en.md +61 -5
  293. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +61 -5
  294. package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +21 -24
  295. package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +21 -24
  296. package/templates/.agents/skills/run-task/SKILL.en.md +22 -0
  297. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +22 -0
  298. package/templates/.agents/skills/run-task/config/verify.en.json +9 -0
  299. package/templates/.agents/skills/run-task/config/verify.zh-CN.json +9 -0
  300. package/templates/.agents/skills/test/SKILL.en.md +4 -4
  301. package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -4
  302. package/templates/.agents/skills/test-integration/SKILL.en.md +4 -4
  303. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +4 -4
  304. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -5
  305. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -5
  306. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +647 -203
  307. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +4 -4
  308. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +4 -4
  309. package/templates/.agents/skills/watch-pr/SKILL.en.md +8 -8
  310. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +8 -8
  311. package/templates/.agents/templates/task.en.md +18 -0
  312. package/templates/.agents/templates/task.zh-CN.md +18 -0
  313. package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +6 -0
  314. package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +6 -0
  315. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +6 -0
  316. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +6 -0
  317. package/templates/.claude/commands/run-task.en.md +8 -0
  318. package/templates/.claude/commands/run-task.zh-CN.md +8 -0
  319. package/templates/.claude/settings.json +24 -0
  320. package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +3 -0
  321. package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +3 -0
  322. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +3 -0
  323. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +3 -0
  324. package/templates/.gemini/commands/_project_/run-task.en.toml +8 -0
  325. package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +8 -0
  326. package/templates/.opencode/commands/run-task.en.md +11 -0
  327. package/templates/.opencode/commands/run-task.zh-CN.md +11 -0
@@ -0,0 +1,96 @@
1
+ import { createGitHubClient } from './github-client.js';
2
+ function normalizeGitHubActor(actor) {
3
+ const name = String(actor.name || '').trim();
4
+ const email = actor.email ? String(actor.email).trim() : null;
5
+ const platformLogin = actor.user?.login?.trim().toLowerCase() || null;
6
+ const noReplyLogin = email
7
+ ? /^(?:\d+\+)?([^@]+)@users\.noreply\.github\.com$/i.exec(email)?.[1]?.toLowerCase() || null
8
+ : null;
9
+ const login = platformLogin || noReplyLogin;
10
+ return {
11
+ name,
12
+ email,
13
+ login,
14
+ bot: Boolean(login?.endsWith('[bot]')),
15
+ resolution: platformLogin ? 'platform-user' : noReplyLogin ? 'platform-noreply' : 'unresolved'
16
+ };
17
+ }
18
+ function failure(error) {
19
+ return {
20
+ status: error.retryable ? 'blocked' : 'failed',
21
+ changed: false,
22
+ operation: null,
23
+ url: null,
24
+ error
25
+ };
26
+ }
27
+ function publishGitHubReleaseNotes(input, options = {}) {
28
+ const client = options.client ?? createGitHubClient();
29
+ const inspected = client.json(['release', 'view', input.tag, '--repo', input.repository, '--json', 'url'], { cwd: options.cwd });
30
+ if (!inspected.ok && inspected.error.code !== 'RESOURCE_NOT_FOUND')
31
+ return failure(inspected.error);
32
+ const exists = inspected.ok;
33
+ const operation = exists ? 'release:update-notes' : 'release:create';
34
+ if (input.dryRun) {
35
+ return {
36
+ status: 'planned',
37
+ changed: false,
38
+ operation,
39
+ url: inspected.ok ? inspected.value.url || null : null,
40
+ error: null
41
+ };
42
+ }
43
+ const args = exists
44
+ ? ['release', 'edit', input.tag, '--repo', input.repository, '--notes-file', input.notesFile]
45
+ : ['release', 'create', input.tag, '--repo', input.repository, '--title', input.title, '--notes-file', input.notesFile];
46
+ const written = client.text(args, { cwd: options.cwd, method: exists ? 'PATCH' : 'POST' });
47
+ if (!written.ok)
48
+ return failure(written.error);
49
+ return {
50
+ status: 'applied',
51
+ changed: true,
52
+ operation,
53
+ url: written.value || (inspected.ok ? inspected.value.url || null : null),
54
+ error: null
55
+ };
56
+ }
57
+ function fetchGitHubReleaseNoteData(input, options = {}) {
58
+ const client = options.client ?? createGitHubClient();
59
+ const releases = client.json(['release', 'list', '--repo', input.repository, '--limit', String(input.historyLimit + 10), '--json', 'tagName,isDraft,isPrerelease'], { cwd: options.cwd });
60
+ if (!releases.ok)
61
+ return failure(releases.error);
62
+ const history = [];
63
+ for (const item of releases.value.filter((entry) => !entry.isDraft && !entry.isPrerelease).slice(0, input.historyLimit)) {
64
+ const viewed = client.json(['release', 'view', String(item.tagName), '--repo', input.repository, '--json', 'body,url'], { cwd: options.cwd });
65
+ if (!viewed.ok)
66
+ return failure(viewed.error);
67
+ history.push({ tag: String(item.tagName), body: String(viewed.value.body || ''), url: viewed.value.url || null });
68
+ }
69
+ const prs = client.json(['pr', 'list', '--repo', input.repository, '--state', 'merged', '--base', input.branch, '--limit', '1000', '--json', 'number,title,body,url,mergedAt,labels,author,closingIssuesReferences'], { cwd: options.cwd });
70
+ if (!prs.ok)
71
+ return failure(prs.error);
72
+ const authors = new Map();
73
+ for (const oid of input.commitOids) {
74
+ const query = `query($owner:String!,$name:String!,$oid:GitObjectID!){repository(owner:$owner,name:$name){object(oid:$oid){... on Commit{authors(first:100){nodes{name email user{login}} pageInfo{hasNextPage}}}}}}`;
75
+ const [owner, name] = input.repository.split('/');
76
+ const result = client.json([
77
+ 'api', 'graphql', '-f', `query=${query}`, '-F', `owner=${owner}`, '-F', `name=${name}`, '-F', `oid=${oid}`
78
+ ], { cwd: options.cwd });
79
+ if (!result.ok)
80
+ return failure(result.error);
81
+ const connection = result.value.data?.repository?.object?.authors;
82
+ if (connection?.pageInfo?.hasNextPage) {
83
+ return failure({ code: 'RELEASE_NOTES_AUTHORS_TRUNCATED', message: `Commit authors exceeded the supported page size for ${oid}`, retryable: false });
84
+ }
85
+ authors.set(oid, (connection?.nodes || []).map(normalizeGitHubActor));
86
+ }
87
+ const fromTime = Date.parse(input.fromTime);
88
+ const toTime = Date.parse(input.toTime);
89
+ const pullRequests = prs.value.filter((item) => {
90
+ const mergedAt = Date.parse(String(item.mergedAt || ''));
91
+ return Number.isFinite(mergedAt) && mergedAt > fromTime && mergedAt <= toTime;
92
+ });
93
+ return { status: 'no-op', changed: false, history, pullRequests, authors, error: null };
94
+ }
95
+ export { fetchGitHubReleaseNoteData, normalizeGitHubActor, publishGitHubReleaseNotes };
96
+ //# sourceMappingURL=github-release-notes.js.map
@@ -5,7 +5,7 @@ import { buildDefaultBody } from '../task/commands/issue-body.js';
5
5
  import { extractTitle, parseTaskFrontmatter } from '../task/frontmatter.js';
6
6
  import { requirementFieldLabels, renderTemplateBody } from '../task/issue-form.js';
7
7
  import { resolveTaskRef } from '../task/resolve-ref.js';
8
- import { extractSection } from '../task/sections.js';
8
+ import { extractSection, findSectionHeading } from '../task/sections.js';
9
9
  import { writeTask } from '../task/write.js';
10
10
  import { resolvePlatformContext } from './context.js';
11
11
  import { createGitHubClient } from './github-client.js';
@@ -228,7 +228,9 @@ function deterministicIssueBody(repoRoot, content, taskType) {
228
228
  return renderTemplateBody(fs.readFileSync(selected, 'utf8'), {
229
229
  title: taskTitle(content),
230
230
  description: extractSection(content, ['描述', 'Description']),
231
- requirements: extractSection(content, ['需求', 'Requirements'])
231
+ requirements: extractSection(content, ['需求', 'Requirements']),
232
+ taskInput: extractSection(content, ['任务输入', 'Task Input']),
233
+ taskInputHeading: findSectionHeading(content, ['任务输入', 'Task Input'])
232
234
  });
233
235
  }
234
236
  catch {
@@ -0,0 +1,84 @@
1
+ import { execFileSync, spawnSync } from "node:child_process";
2
+ const SHA_PATTERN = /^[0-9a-f]{40}$/i;
3
+ function git(cwd, args) {
4
+ return execFileSync("git", args, {
5
+ cwd,
6
+ encoding: "utf8",
7
+ maxBuffer: 32 * 1024 * 1024,
8
+ stdio: ["ignore", "pipe", "pipe"]
9
+ });
10
+ }
11
+ function commitExists(gitRoot, sha) {
12
+ try {
13
+ git(gitRoot, ["cat-file", "-e", `${sha}^{commit}`]);
14
+ return true;
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ }
20
+ function isAncestor(gitRoot, ancestor, descendant) {
21
+ const result = spawnSync("git", ["merge-base", "--is-ancestor", ancestor, descendant], {
22
+ cwd: gitRoot,
23
+ encoding: "utf8",
24
+ stdio: ["ignore", "pipe", "pipe"]
25
+ });
26
+ if (result.status === 0)
27
+ return { status: "yes" };
28
+ if (result.status === 1)
29
+ return { status: "no" };
30
+ return { status: "error" };
31
+ }
32
+ export function resolveReviewedHeadRelation(input) {
33
+ const { gitRoot, comparisonHead, lastReviewedCommit, pullRequest } = input;
34
+ if (comparisonHead === lastReviewedCommit &&
35
+ lastReviewedCommit === pullRequest.head.sha &&
36
+ !(pullRequest.state === "closed" && pullRequest.mergedAt && pullRequest.mergeCommitSha)) {
37
+ return { status: "strict", reviewedHead: lastReviewedCommit };
38
+ }
39
+ if (pullRequest.state !== "closed" || !pullRequest.mergedAt || !pullRequest.mergeCommitSha ||
40
+ lastReviewedCommit !== pullRequest.head.sha) {
41
+ return { status: "failed", code: "PR_MERGE_IDENTITY_INVALID", message: "PR merge identity does not match the reviewed head" };
42
+ }
43
+ const shas = [pullRequest.base.sha, pullRequest.head.sha, pullRequest.mergeCommitSha, comparisonHead];
44
+ if (shas.some((sha) => !SHA_PATTERN.test(sha) || !commitExists(gitRoot, sha))) {
45
+ return { status: "blocked", code: "PR_MERGE_OBJECT_MISSING", message: "Required PR merge Git objects are unavailable" };
46
+ }
47
+ const ancestry = isAncestor(gitRoot, pullRequest.mergeCommitSha, comparisonHead);
48
+ if (ancestry.status === "no") {
49
+ return {
50
+ status: "failed",
51
+ code: "PR_MERGE_TARGET_MISMATCH",
52
+ message: "PR merge commit is not in the authoritative target history"
53
+ };
54
+ }
55
+ if (ancestry.status === "error") {
56
+ return {
57
+ status: "blocked",
58
+ code: "PR_MERGE_GIT_EVIDENCE_UNAVAILABLE",
59
+ message: "Unable to verify PR merge ancestry"
60
+ };
61
+ }
62
+ try {
63
+ const parents = git(gitRoot, ["rev-list", "--parents", "-n", "1", pullRequest.mergeCommitSha]).trim().split(/\s+/);
64
+ if (parents.length !== 2) {
65
+ return { status: "failed", code: "PR_MERGE_TOPOLOGY_INVALID", message: "PR merge commit is not a single-parent squash commit" };
66
+ }
67
+ const diffArgs = ["diff", "--no-renames", "--binary", "--full-index"];
68
+ const reviewedDiff = git(gitRoot, [...diffArgs, pullRequest.base.sha, pullRequest.head.sha]);
69
+ const mergedDiff = git(gitRoot, [...diffArgs, `${pullRequest.mergeCommitSha}^`, pullRequest.mergeCommitSha]);
70
+ if (reviewedDiff !== mergedDiff) {
71
+ return { status: "failed", code: "PR_MERGE_CONTENT_MISMATCH", message: "Squash merge content differs from the reviewed PR changes" };
72
+ }
73
+ }
74
+ catch {
75
+ return { status: "blocked", code: "PR_MERGE_GIT_EVIDENCE_UNAVAILABLE", message: "Unable to verify PR merge topology or content" };
76
+ }
77
+ return {
78
+ status: "merged-equivalent",
79
+ reviewedHead: lastReviewedCommit,
80
+ mergeCommit: pullRequest.mergeCommitSha,
81
+ comparisonHead
82
+ };
83
+ }
84
+ //# sourceMappingURL=merged-pr-equivalence.js.map
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import { parseTaskFrontmatter } from '../task/frontmatter.js';
3
3
  import { resolveTaskRef } from '../task/resolve-ref.js';
4
+ import { inspectPlatformRequiredChecks, registerPlatformCapabilities } from './adapters.js';
4
5
  import { resolvePlatformContext } from './context.js';
5
6
  import { createGitHubClient } from './github-client.js';
6
7
  import { inspectPlatformPullRequest } from './pull-requests.js';
@@ -87,6 +88,18 @@ function normalizeChecks(value) {
87
88
  }] : [];
88
89
  });
89
90
  }
91
+ registerPlatformCapabilities('github', {
92
+ inspectRequiredChecks({ client, repository, number, cwd }) {
93
+ const github = client || createGitHubClient();
94
+ const inspected = github.json([
95
+ 'pr', 'checks', String(number), '--repo', repository, '--required',
96
+ '--json', 'name,state,bucket,link,workflow,startedAt,completedAt'
97
+ ], { cwd });
98
+ return inspected.ok
99
+ ? { ok: true, value: normalizeChecks(inspected.value) }
100
+ : { ok: false, error: inspected.error };
101
+ }
102
+ });
90
103
  function resolvedTask(taskRef, options) {
91
104
  const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
92
105
  if (!resolved.ok)
@@ -110,25 +123,33 @@ function inspectRequiredChecks(taskRef, options = {}) {
110
123
  if (!base.ok)
111
124
  return base.output;
112
125
  const repository = base.context.platform.repository;
113
- const inspected = base.client.json([
114
- 'pr', 'checks', String(base.prNumber), '--repo', repository, '--required',
115
- '--json', 'name,state,bucket,link,workflow,startedAt,completedAt'
116
- ], { cwd: base.resolved.repoRoot });
117
- if (!inspected.ok) {
118
- return checksResult(inspected.error.retryable ? 'blocked' : 'failed', {
126
+ const inspected = inspectPlatformRequiredChecks(base.context.platform.type, {
127
+ cwd: base.resolved.repoRoot,
128
+ repository,
129
+ number: base.prNumber,
130
+ headSha: base.pullRequest.head.sha,
131
+ client: options.client || base.client
132
+ });
133
+ if (!inspected.ok || !inspected.value) {
134
+ const error = inspected.error || {
135
+ code: 'REQUIRED_CHECKS_INSPECTION_INVALID',
136
+ message: 'Platform adapter returned no required-checks snapshot',
137
+ retryable: false
138
+ };
139
+ return checksResult(error.retryable ? 'blocked' : 'failed', {
119
140
  platform: base.context.platform, capabilities: base.context.capabilities,
120
141
  resource: { kind: 'pull-request', number: base.prNumber },
121
- pullRequest: { number: base.prNumber, headSha: base.pullRequest.head.sha }, error: inspected.error
142
+ pullRequest: base.pullRequest, error
122
143
  });
123
144
  }
124
- const classified = classifyRequiredChecks(normalizeChecks(inspected.value));
145
+ const classified = classifyRequiredChecks(inspected.value);
125
146
  const status = classified.state === 'passed' || classified.state === 'no-required'
126
147
  ? 'no-op'
127
148
  : classified.state === 'failed' || classified.state === 'cancelled' ? 'failed' : 'blocked';
128
149
  return checksResult(status, {
129
150
  platform: base.context.platform, capabilities: base.context.capabilities,
130
151
  resource: { kind: 'pull-request', number: base.prNumber },
131
- pullRequest: { number: base.prNumber, headSha: base.pullRequest.head.sha }, checks: classified,
152
+ pullRequest: base.pullRequest, checks: classified,
132
153
  error: status === 'no-op' ? null : {
133
154
  code: classified.state === 'pending' ? 'REQUIRED_CHECKS_PENDING' : `REQUIRED_CHECKS_${classified.state.toUpperCase()}`,
134
155
  message: `Required checks are ${classified.state}`,
@@ -172,10 +193,10 @@ function resolvePlatformCheckRun(taskRef, options) {
172
193
  if (direct) {
173
194
  const run = base.client.json(['api', `repos/${base.context.platform.repository}/actions/runs/${direct.runId}`], { cwd: base.resolved.repoRoot });
174
195
  if (!run.ok)
175
- return checksResult(run.error.retryable ? 'blocked' : 'failed', { platform: base.context.platform, capabilities: base.context.capabilities, pullRequest: { number: base.prNumber, headSha: base.pullRequest.head.sha }, error: run.error });
196
+ return checksResult(run.error.retryable ? 'blocked' : 'failed', { platform: base.context.platform, capabilities: base.context.capabilities, pullRequest: base.pullRequest, error: run.error });
176
197
  if (run.value.head_sha !== base.pullRequest.head.sha)
177
198
  return checksResult('failed', { error: { code: 'CHECK_RUN_HEAD_MISMATCH', message: 'Resolved run does not belong to the PR head SHA', retryable: false } });
178
- return checksResult('no-op', { platform: base.context.platform, capabilities: base.context.capabilities, resource: { kind: 'pull-request', number: base.prNumber }, pullRequest: { number: base.prNumber, headSha: base.pullRequest.head.sha }, resolution: { status: 'resolved', ...direct }, error: null });
199
+ return checksResult('no-op', { platform: base.context.platform, capabilities: base.context.capabilities, resource: { kind: 'pull-request', number: base.prNumber }, pullRequest: base.pullRequest, resolution: { status: 'resolved', ...direct }, error: null });
179
200
  }
180
201
  const listed = base.client.json(['api', `repos/${base.context.platform.repository}/commits/${base.pullRequest.head.sha}/check-runs`], { cwd: base.resolved.repoRoot });
181
202
  if (!listed.ok)
@@ -189,11 +210,18 @@ function resolvePlatformCheckRun(taskRef, options) {
189
210
  const resolution = resolveRunCandidate(candidates, base.pullRequest.head.sha, options.checkName);
190
211
  return checksResult(resolution.status === 'resolved' ? 'no-op' : 'failed', {
191
212
  platform: base.context.platform, capabilities: base.context.capabilities,
192
- resource: { kind: 'pull-request', number: base.prNumber }, pullRequest: { number: base.prNumber, headSha: base.pullRequest.head.sha },
213
+ resource: { kind: 'pull-request', number: base.prNumber }, pullRequest: base.pullRequest,
193
214
  resolution,
194
215
  error: resolution.status === 'resolved' ? null : { code: resolution.status === 'ambiguous' ? 'CHECK_RUN_AMBIGUOUS' : 'CHECK_RUN_NOT_FOUND', message: `Check run is ${resolution.status}`, retryable: false }
195
216
  });
196
217
  }
218
+ function fetchCheckLogText(client, args, cwd) {
219
+ const fetched = client.text(args, { cwd });
220
+ if (fetched.ok || args[0] !== 'api' || !/response contains terminal escape sequences/i.test(fetched.error.message)) {
221
+ return fetched;
222
+ }
223
+ return client.text([...args, '--allow-escape-sequences'], { cwd });
224
+ }
197
225
  function fetchPlatformCheckLogs(taskRef, options) {
198
226
  const base = resolvedTask(taskRef, options);
199
227
  if (!base.ok)
@@ -201,16 +229,16 @@ function fetchPlatformCheckLogs(taskRef, options) {
201
229
  const args = options.job
202
230
  ? ['api', `repos/${base.context.platform.repository}/actions/jobs/${options.job}/logs`]
203
231
  : ['run', 'view', String(options.run), '--repo', base.context.platform.repository, '--log-failed'];
204
- const fetched = base.client.text(args, { cwd: base.resolved.repoRoot });
232
+ const fetched = fetchCheckLogText(base.client, args, base.resolved.repoRoot);
205
233
  if (!fetched.ok)
206
234
  return checksResult(fetched.error.retryable ? 'blocked' : 'failed', { platform: base.context.platform, capabilities: base.context.capabilities, error: fetched.error });
207
235
  if (!fetched.value)
208
236
  return checksResult('failed', { error: { code: 'CHECK_LOGS_MISSING', message: 'No failed logs are available', retryable: false } });
209
237
  return checksResult('no-op', {
210
238
  platform: base.context.platform, capabilities: base.context.capabilities,
211
- resource: { kind: 'pull-request', number: base.prNumber }, pullRequest: { number: base.prNumber, headSha: base.pullRequest.head.sha },
239
+ resource: { kind: 'pull-request', number: base.prNumber }, pullRequest: base.pullRequest,
212
240
  logs: { runId: options.run, ...(options.job ? { jobId: options.job } : {}), text: fetched.value }, error: null
213
241
  });
214
242
  }
215
- export { classifyRequiredChecks, fetchPlatformCheckLogs, inspectRequiredChecks, parseRunJobIdentity, resolvePlatformCheckRun, resolveRunCandidate, watchPlatformChecks, watchRequiredChecks };
243
+ export { classifyRequiredChecks, fetchCheckLogText, fetchPlatformCheckLogs, inspectRequiredChecks, parseRunJobIdentity, resolvePlatformCheckRun, resolveRunCandidate, watchPlatformChecks, watchRequiredChecks };
216
244
  //# sourceMappingURL=pr-checks.js.map
@@ -1,9 +1,11 @@
1
1
  import fs from 'node:fs';
2
+ import { execFileSync } from 'node:child_process';
2
3
  import { parseTaskFrontmatter } from '../task/frontmatter.js';
3
4
  import { resolveTaskRef } from '../task/resolve-ref.js';
4
5
  import { extractSection } from '../task/sections.js';
5
6
  import { captureTaskWriteMetadata, writeTask } from '../task/write.js';
6
- import { resolvePlatformContext } from './context.js';
7
+ import { inspectPlatformChangeRequest, registerPlatformCapabilities } from './adapters.js';
8
+ import { parseGitHubRemote, resolvePlatformContext } from './context.js';
7
9
  import { createGitHubClient } from './github-client.js';
8
10
  import { inspectPlatformIssue } from './issues.js';
9
11
  import { planPullRequestMetadata } from './pull-request-metadata.js';
@@ -33,7 +35,9 @@ function normalizePullRequest(remote, repository) {
33
35
  body: remote.body || '',
34
36
  draft: Boolean(remote.draft),
35
37
  head: { repository: headRepository, ref: remote.head.ref, sha: remote.head.sha },
36
- base: { repository: baseRepository, ref: remote.base.ref },
38
+ base: { repository: baseRepository, ref: remote.base.ref, sha: remote.base.sha || '' },
39
+ mergedAt: remote.merged_at || null,
40
+ mergeCommitSha: remote.merge_commit_sha || null,
37
41
  labels: (remote.labels || []).map((label) => typeof label === 'string' ? label : label.name || '').filter(Boolean).sort(),
38
42
  assignees: (remote.assignees || []).map((assignee) => assignee.login || '').filter(Boolean).sort(),
39
43
  milestone: remote.milestone?.title || null
@@ -88,6 +92,97 @@ function inspectGitHubPullRequest(client, repository, number, cwd) {
88
92
  ? { ok: true, value: pullRequest }
89
93
  : { ok: false, error: { code: 'PR_IDENTITY_INVALID', message: 'Remote resource is not a valid pull request', retryable: false } };
90
94
  }
95
+ function configuredGitRemotes(cwd) {
96
+ try {
97
+ return execFileSync('git', ['remote'], {
98
+ cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe']
99
+ }).split(/\r?\n/).filter(Boolean).flatMap((name) => {
100
+ try {
101
+ const url = execFileSync('git', ['config', '--get', `remote.${name}.url`], {
102
+ cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe']
103
+ }).trim();
104
+ const repository = parseGitHubRemote(url);
105
+ return repository ? [{ name, url, repository }] : [];
106
+ }
107
+ catch {
108
+ return [];
109
+ }
110
+ });
111
+ }
112
+ catch {
113
+ return [];
114
+ }
115
+ }
116
+ function rewriteGitHubRemote(remote, repository) {
117
+ const match = remote.trim().match(/^(https?:\/\/github\.com\/|ssh:\/\/(?:git@)?github\.com\/|git@github\.com:)[^/\s]+\/[^/\s]+(?:\.git)?$/i);
118
+ return match ? `${match[1]}${repository}.git` : null;
119
+ }
120
+ function resolveGitHubChangeRequestGitEvidence({ cwd, repository, pullRequest }) {
121
+ if (pullRequest.repository !== repository ||
122
+ pullRequest.base.repository !== repository ||
123
+ pullRequest.number <= 0) {
124
+ return {
125
+ ok: false,
126
+ error: {
127
+ code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
128
+ message: 'Pull request repository identity is inconsistent',
129
+ retryable: false
130
+ }
131
+ };
132
+ }
133
+ const reviewedHeadRef = `refs/pull/${pullRequest.number}/head`;
134
+ const targetHeadRef = `refs/heads/${pullRequest.base.ref}`;
135
+ const refValid = (ref) => {
136
+ try {
137
+ execFileSync('git', ['check-ref-format', ref], {
138
+ cwd, encoding: 'utf8', stdio: ['ignore', 'ignore', 'ignore']
139
+ });
140
+ return true;
141
+ }
142
+ catch {
143
+ return false;
144
+ }
145
+ };
146
+ if (!refValid(reviewedHeadRef) || !refValid(targetHeadRef)) {
147
+ return {
148
+ ok: false,
149
+ error: {
150
+ code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
151
+ message: 'Pull request Git refs are invalid',
152
+ retryable: false
153
+ }
154
+ };
155
+ }
156
+ const remotes = configuredGitRemotes(cwd);
157
+ const exact = remotes
158
+ .filter((remote) => remote.repository === repository)
159
+ .sort((left, right) => Number(right.name === 'origin') - Number(left.name === 'origin') ||
160
+ left.name.localeCompare(right.name))[0];
161
+ const origin = remotes.find((remote) => remote.name === 'origin');
162
+ const remoteUrl = exact?.url || (origin ? rewriteGitHubRemote(origin.url, repository) : null);
163
+ if (!remoteUrl) {
164
+ return {
165
+ ok: false,
166
+ error: {
167
+ code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
168
+ message: `No GitHub remote can provide evidence for ${repository}`,
169
+ retryable: false
170
+ }
171
+ };
172
+ }
173
+ return {
174
+ ok: true,
175
+ value: { remoteUrl, reviewedHeadRef, targetHeadRef }
176
+ };
177
+ }
178
+ registerPlatformCapabilities('github', {
179
+ inspectChangeRequest({ client, repository, number, cwd }) {
180
+ return inspectGitHubPullRequest(client || createGitHubClient(), repository, number, cwd);
181
+ },
182
+ resolveChangeRequestGitEvidence(context) {
183
+ return resolveGitHubChangeRequestGitEvidence(context);
184
+ }
185
+ });
91
186
  function locatePullRequest(base, head, target) {
92
187
  const repository = base.context.platform.repository;
93
188
  const listed = base.client.json([
@@ -113,12 +208,23 @@ function inspectPlatformPullRequest(taskRef, options = {}) {
113
208
  platform: base.context.platform, capabilities: base.context.capabilities,
114
209
  error: { code: 'PR_NOT_LINKED', message: 'Task has no valid pr_number', retryable: false }
115
210
  });
116
- const fetched = inspectGitHubPullRequest(base.client, base.context.platform.repository, base.prNumber, base.resolved.repoRoot);
117
- if (!fetched.ok)
118
- return result(fetched.error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
211
+ const fetched = inspectPlatformChangeRequest(base.context.platform.type, {
212
+ cwd: base.resolved.repoRoot,
213
+ repository: base.context.platform.repository,
214
+ number: base.prNumber,
215
+ client: options.client || base.client
216
+ });
217
+ if (!fetched.ok || !fetched.value) {
218
+ const error = fetched.error || {
219
+ code: 'PR_INSPECTION_INVALID',
220
+ message: 'Platform adapter returned no change-request snapshot',
221
+ retryable: false
222
+ };
223
+ return result(error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
119
224
  platform: base.context.platform, capabilities: base.context.capabilities,
120
- resource: { kind: 'pull-request', number: base.prNumber }, error: fetched.error
225
+ resource: { kind: 'pull-request', number: base.prNumber }, error
121
226
  });
227
+ }
122
228
  return result('no-op', base.resolved.taskId, base.issueNumber, base.prNumber, {
123
229
  platform: base.context.platform, capabilities: base.context.capabilities,
124
230
  resource: { kind: 'pull-request', number: base.prNumber }, pullRequest: fetched.value, error: null
@@ -334,5 +440,5 @@ function syncPlatformPullRequest(taskRef, options) {
334
440
  operations: operations.map((operation) => operation.status === 'planned' ? { ...operation, status: 'applied' } : operation), error: null
335
441
  });
336
442
  }
337
- export { bindPlatformPullRequest, createPlatformPullRequest, inspectGitHubPullRequest, inspectPlatformPullRequest, normalizePullRequest, selectPullRequest, syncPlatformPullRequest };
443
+ export { bindPlatformPullRequest, createPlatformPullRequest, inspectGitHubPullRequest, inspectPlatformPullRequest, normalizePullRequest, resolveGitHubChangeRequestGitEvidence, selectPullRequest, syncPlatformPullRequest };
338
444
  //# sourceMappingURL=pull-requests.js.map
@@ -0,0 +1,143 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { execFileSync } from 'node:child_process';
4
+ import { resolvePlatformContext } from './context.js';
5
+ import { fetchGitHubReleaseNoteData, publishGitHubReleaseNotes } from './github-release-notes.js';
6
+ const unsupportedError = {
7
+ code: 'PLATFORM_RELEASE_NOTES_UNSUPPORTED',
8
+ message: 'Release notes are not supported for the configured platform',
9
+ retryable: false
10
+ };
11
+ function baseResult(status) {
12
+ return {
13
+ status,
14
+ changed: false,
15
+ range: null,
16
+ history: [],
17
+ pullRequests: [],
18
+ commits: [],
19
+ error: status === 'no-op' ? unsupportedError : null
20
+ };
21
+ }
22
+ function git(cwd, args) {
23
+ return execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
24
+ }
25
+ function configuredPlatform(cwd) {
26
+ try {
27
+ const value = JSON.parse(fs.readFileSync(path.join(cwd, '.agents', '.airc.json'), 'utf8'));
28
+ return typeof value?.platform?.type === 'string' ? value.platform.type : null;
29
+ }
30
+ catch {
31
+ return null;
32
+ }
33
+ }
34
+ function releaseNoteContext(input, options = {}) {
35
+ const historyLimit = input.historyLimit ?? 3;
36
+ if (!input.fromTag || !input.toTag || !input.branch || !Number.isInteger(historyLimit) || historyLimit < 1 || historyLimit > 20) {
37
+ return { ...baseResult('failed'), error: { code: 'RELEASE_NOTES_INPUT_INVALID', message: 'from-tag, to-tag, branch, and a history limit from 1 to 20 are required', retryable: false } };
38
+ }
39
+ const cwd = path.resolve(options.cwd || process.cwd());
40
+ const platformType = options.platformType ?? configuredPlatform(cwd);
41
+ if (platformType !== 'github')
42
+ return baseResult('no-op');
43
+ let fromOid;
44
+ let toOid;
45
+ let fromTime;
46
+ let toTime;
47
+ let commitFacts;
48
+ try {
49
+ fromOid = git(cwd, ['rev-parse', '--verify', `${input.fromTag}^{commit}`]);
50
+ toOid = git(cwd, ['rev-parse', '--verify', `${input.toTag}^{commit}`]);
51
+ git(cwd, ['merge-base', '--is-ancestor', fromOid, toOid]);
52
+ fromTime = git(cwd, ['show', '-s', '--format=%cI', fromOid]);
53
+ toTime = git(cwd, ['show', '-s', '--format=%cI', toOid]);
54
+ const raw = git(cwd, ['log', '--reverse', '--no-merges', '--format=%H%x00%s', `${fromOid}..${toOid}`]);
55
+ commitFacts = raw ? raw.split('\n').map((line) => {
56
+ const [oid = '', title = ''] = line.split('\0');
57
+ return { oid, title };
58
+ }) : [];
59
+ }
60
+ catch {
61
+ return { ...baseResult('failed'), error: { code: 'RELEASE_NOTES_RANGE_INVALID', message: 'The release tag range is missing or is not ancestral', retryable: false } };
62
+ }
63
+ const context = resolvePlatformContext({ cwd, platformType, client: options.client });
64
+ if (!context.platform.repository) {
65
+ return {
66
+ ...baseResult(context.status === 'blocked' ? 'blocked' : 'failed'),
67
+ error: context.error || { code: 'PLATFORM_CONTEXT_UNAVAILABLE', message: 'Platform repository is unavailable', retryable: context.status === 'blocked' }
68
+ };
69
+ }
70
+ const platform = fetchGitHubReleaseNoteData({
71
+ repository: context.platform.repository,
72
+ commitOids: commitFacts.map((item) => item.oid),
73
+ branch: input.branch,
74
+ historyLimit,
75
+ fromTime,
76
+ toTime
77
+ }, { cwd, client: options.client });
78
+ if (platform.status === 'blocked' || platform.status === 'failed' || !('authors' in platform)) {
79
+ return { ...baseResult(platform.status), error: platform.error };
80
+ }
81
+ const commits = commitFacts.map((commit) => {
82
+ const seen = new Set();
83
+ const authors = (platform.authors.get(commit.oid) || []).filter((actor) => {
84
+ const key = actor.login || actor.email?.toLowerCase() || actor.name.toLowerCase();
85
+ if (seen.has(key))
86
+ return false;
87
+ seen.add(key);
88
+ return true;
89
+ });
90
+ return { ...commit, authors };
91
+ });
92
+ const pullRequests = platform.pullRequests
93
+ .map((item) => ({
94
+ number: Number(item.number),
95
+ title: String(item.title || ''),
96
+ body: String(item.body || ''),
97
+ url: String(item.url || ''),
98
+ mergedAt: String(item.mergedAt || ''),
99
+ labels: Array.isArray(item.labels) ? item.labels.map((label) => String(label.name || label)) : [],
100
+ author: item.author && typeof item.author === 'object'
101
+ ? { login: String(item.author.login || '').toLowerCase(), bot: String(item.author.login || '').endsWith('[bot]') }
102
+ : null,
103
+ closingIssues: Array.isArray(item.closingIssuesReferences)
104
+ ? item.closingIssuesReferences.map((issue) => {
105
+ const value = issue;
106
+ return {
107
+ number: Number(value.number),
108
+ title: String(value.title || ''),
109
+ url: String(value.url || ''),
110
+ labels: Array.isArray(value.labels) ? value.labels.map((label) => String(label.name || label)) : [],
111
+ author: value.author && typeof value.author === 'object'
112
+ ? { login: String(value.author.login || '').toLowerCase(), bot: String(value.author.login || '').endsWith('[bot]') }
113
+ : null
114
+ };
115
+ }) : []
116
+ }))
117
+ .sort((left, right) => left.number - right.number);
118
+ return {
119
+ status: 'no-op',
120
+ changed: false,
121
+ range: { fromTag: input.fromTag, toTag: input.toTag, fromOid, toOid },
122
+ history: platform.history,
123
+ pullRequests,
124
+ commits,
125
+ error: null
126
+ };
127
+ }
128
+ 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 } };
131
+ }
132
+ const cwd = path.resolve(options.cwd || process.cwd());
133
+ const platformType = options.platformType ?? configuredPlatform(cwd);
134
+ if (platformType !== 'github')
135
+ return { status: 'no-op', changed: false, operation: null, url: null, error: unsupportedError };
136
+ const context = resolvePlatformContext({ cwd, platformType, client: options.client });
137
+ if (!context.platform.repository) {
138
+ return { status: context.status === 'blocked' ? 'blocked' : 'failed', changed: false, operation: null, url: null, error: context.error };
139
+ }
140
+ return publishGitHubReleaseNotes({ ...input, repository: context.platform.repository }, { cwd, client: options.client });
141
+ }
142
+ export { publishReleaseNotes, releaseNoteContext };
143
+ //# sourceMappingURL=release-notes.js.map
@@ -18,7 +18,7 @@ function inspectPlatformRelease(tag, options = {}) {
18
18
  return { ...platformResult(result.error.retryable ? 'blocked' : 'failed'), platform: context.platform, release: null, workflows: [], error: result.error };
19
19
  }
20
20
  const release = { tag: String(result.value.tagName || tag), published: !Boolean(result.value.isDraft), draft: Boolean(result.value.isDraft), url: result.value.url ? String(result.value.url) : null };
21
- const runs = client.json(['run', 'list', '--repo', context.platform.repository, '--limit', '100', '--json', 'name,headBranch,status,conclusion,url'], { cwd: options.cwd });
21
+ const runs = client.json(['run', 'list', '--repo', context.platform.repository, '--limit', '100', '--json', 'name,workflowName,displayTitle,event,headBranch,headSha,status,conclusion,createdAt,databaseId,attempt,url'], { cwd: options.cwd });
22
22
  return { ...platformResult(runs.ok ? 'no-op' : runs.error.retryable ? 'blocked' : 'degraded'), platform: context.platform, release, workflows: runs.ok ? runs.value : [], error: runs.ok ? null : runs.error };
23
23
  }
24
24
  function upsertPlatformRelease(input, options = {}) {