@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,159 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { execFileSync, spawnSync } from "node:child_process";
5
+
6
+ import { resolvePlatformChangeRequestGitEvidence } from "./adapters.ts";
7
+ import { resolveReviewedHeadRelation } from "./merged-pr-equivalence.ts";
8
+ import type { ReviewedHeadRelation } from "./merged-pr-equivalence.ts";
9
+ import type { PullRequestSnapshot } from "./pull-requests.ts";
10
+
11
+ const SHA_PATTERN = /^[0-9a-f]{40}$/i;
12
+ const REVIEWED_HEAD_REF = "refs/agent-infra/reviewed-head";
13
+ const TARGET_HEAD_REF = "refs/agent-infra/target-head";
14
+
15
+ function git(cwd: string, args: string[]): string {
16
+ return execFileSync("git", args, {
17
+ cwd,
18
+ encoding: "utf8",
19
+ maxBuffer: 32 * 1024 * 1024,
20
+ stdio: ["ignore", "pipe", "pipe"]
21
+ }).trim();
22
+ }
23
+
24
+ function validRef(ref: string): boolean {
25
+ return spawnSync("git", ["check-ref-format", ref], {
26
+ encoding: "utf8",
27
+ stdio: ["ignore", "ignore", "ignore"]
28
+ }).status === 0;
29
+ }
30
+
31
+ function blocked(code: string, message: string): ReviewedHeadRelation {
32
+ return { status: "blocked", code, message };
33
+ }
34
+
35
+ function failed(code: string, message: string): ReviewedHeadRelation {
36
+ return { status: "failed", code, message };
37
+ }
38
+
39
+ function validMergeIdentity(lastReviewedCommit: string, pullRequest: PullRequestSnapshot):
40
+ ReviewedHeadRelation | null {
41
+ if (
42
+ pullRequest.state !== "closed" ||
43
+ !pullRequest.mergedAt ||
44
+ !pullRequest.mergeCommitSha ||
45
+ !SHA_PATTERN.test(lastReviewedCommit) ||
46
+ lastReviewedCommit !== pullRequest.head.sha
47
+ ) {
48
+ return failed(
49
+ "PR_MERGE_IDENTITY_INVALID",
50
+ "PR merge identity does not match the reviewed head"
51
+ );
52
+ }
53
+ return null;
54
+ }
55
+
56
+ function resolveMaterializedReviewedHeadRelation(input: {
57
+ cwd: string;
58
+ platformType: string | null;
59
+ lastReviewedCommit: string;
60
+ pullRequest: PullRequestSnapshot;
61
+ }): ReviewedHeadRelation {
62
+ const identityFailure = validMergeIdentity(input.lastReviewedCommit, input.pullRequest);
63
+ if (identityFailure) return identityFailure;
64
+
65
+ const source = resolvePlatformChangeRequestGitEvidence(input.platformType, {
66
+ cwd: input.cwd,
67
+ repository: input.pullRequest.repository,
68
+ number: input.pullRequest.number,
69
+ pullRequest: input.pullRequest
70
+ });
71
+ if (!source.ok || !source.value) {
72
+ return blocked(
73
+ source.error?.code || "PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE",
74
+ source.error?.message || "Unable to resolve authoritative PR Git evidence"
75
+ );
76
+ }
77
+ if (
78
+ !source.value.remoteUrl.trim() ||
79
+ source.value.remoteUrl.trimStart().startsWith("-") ||
80
+ !validRef(source.value.reviewedHeadRef) ||
81
+ !validRef(source.value.targetHeadRef)
82
+ ) {
83
+ return blocked(
84
+ "PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE",
85
+ "Platform returned an invalid PR Git evidence source"
86
+ );
87
+ }
88
+
89
+ let evidenceRoot: string | null = null;
90
+ let phase: "prepare" | "fetch" | "resolve" = "prepare";
91
+ let result: ReviewedHeadRelation = blocked(
92
+ "PR_MERGE_GIT_EVIDENCE_UNAVAILABLE",
93
+ "Unable to prepare isolated PR Git evidence"
94
+ );
95
+ try {
96
+ evidenceRoot = fs.mkdtempSync(path.join(os.tmpdir(), "agent-infra-pr-evidence-"));
97
+ execFileSync("git", ["init", "--bare", "-q", evidenceRoot], {
98
+ cwd: input.cwd,
99
+ encoding: "utf8",
100
+ stdio: ["ignore", "pipe", "pipe"]
101
+ });
102
+
103
+ phase = "fetch";
104
+ git(evidenceRoot, [
105
+ "fetch",
106
+ "--no-tags",
107
+ "--no-write-fetch-head",
108
+ source.value.remoteUrl,
109
+ `+${source.value.reviewedHeadRef}:${REVIEWED_HEAD_REF}`,
110
+ `+${source.value.targetHeadRef}:${TARGET_HEAD_REF}`
111
+ ]);
112
+
113
+ phase = "resolve";
114
+ const fetchedReviewedHead = git(evidenceRoot, ["rev-parse", `${REVIEWED_HEAD_REF}^{commit}`]);
115
+ const comparisonHead = git(evidenceRoot, ["rev-parse", `${TARGET_HEAD_REF}^{commit}`]);
116
+ if (fetchedReviewedHead !== input.pullRequest.head.sha) {
117
+ result = failed(
118
+ "PR_MERGE_FETCHED_HEAD_MISMATCH",
119
+ "Fetched PR head does not match the authoritative platform snapshot"
120
+ );
121
+ } else {
122
+ result = resolveReviewedHeadRelation({
123
+ gitRoot: evidenceRoot,
124
+ comparisonHead,
125
+ lastReviewedCommit: input.lastReviewedCommit,
126
+ pullRequest: input.pullRequest
127
+ });
128
+ }
129
+ } catch {
130
+ result = phase === "fetch"
131
+ ? blocked(
132
+ "PR_MERGE_EVIDENCE_FETCH_FAILED",
133
+ "Unable to fetch PR Git evidence; verify Git credentials and platform refs"
134
+ )
135
+ : phase === "resolve"
136
+ ? blocked(
137
+ "PR_MERGE_OBJECT_MISSING",
138
+ "Fetched PR Git evidence is incomplete"
139
+ )
140
+ : blocked(
141
+ "PR_MERGE_GIT_EVIDENCE_UNAVAILABLE",
142
+ "Unable to prepare isolated PR Git evidence"
143
+ );
144
+ } finally {
145
+ if (evidenceRoot) {
146
+ try {
147
+ fs.rmSync(evidenceRoot, { recursive: true, force: true });
148
+ } catch {
149
+ result = blocked(
150
+ "PR_MERGE_EVIDENCE_CLEANUP_FAILED",
151
+ "Unable to clean up isolated PR Git evidence"
152
+ );
153
+ }
154
+ }
155
+ }
156
+ return result;
157
+ }
158
+
159
+ export { resolveMaterializedReviewedHeadRelation };
@@ -0,0 +1,132 @@
1
+ import type { GitHubClient } from './github-client.ts';
2
+ import { createGitHubClient } from './github-client.ts';
3
+
4
+ type GitHubActorInput = {
5
+ name?: string | null;
6
+ email?: string | null;
7
+ user?: { login?: string | null } | null;
8
+ };
9
+
10
+ type ReleaseNoteActor = {
11
+ name: string;
12
+ email: string | null;
13
+ login: string | null;
14
+ bot: boolean;
15
+ resolution: 'platform-user' | 'platform-noreply' | 'unresolved';
16
+ };
17
+
18
+ type GitHubReleaseNoteOptions = { cwd?: string; client?: GitHubClient };
19
+
20
+ function normalizeGitHubActor(actor: GitHubActorInput): ReleaseNoteActor {
21
+ const name = String(actor.name || '').trim();
22
+ const email = actor.email ? String(actor.email).trim() : null;
23
+ const platformLogin = actor.user?.login?.trim().toLowerCase() || null;
24
+ const noReplyLogin = email
25
+ ? /^(?:\d+\+)?([^@]+)@users\.noreply\.github\.com$/i.exec(email)?.[1]?.toLowerCase() || null
26
+ : null;
27
+ const login = platformLogin || noReplyLogin;
28
+ return {
29
+ name,
30
+ email,
31
+ login,
32
+ bot: Boolean(login?.endsWith('[bot]')),
33
+ resolution: platformLogin ? 'platform-user' : noReplyLogin ? 'platform-noreply' : 'unresolved'
34
+ };
35
+ }
36
+
37
+ function failure(error: { code: string; message: string; retryable: boolean }) {
38
+ return {
39
+ status: error.retryable ? 'blocked' as const : 'failed' as const,
40
+ changed: false,
41
+ operation: null,
42
+ url: null,
43
+ error
44
+ };
45
+ }
46
+
47
+ function publishGitHubReleaseNotes(
48
+ input: { repository: string; tag: string; title: string; notesFile: string; dryRun?: boolean },
49
+ options: GitHubReleaseNoteOptions = {}
50
+ ) {
51
+ const client = options.client ?? createGitHubClient();
52
+ const inspected = client.json<{ url?: string }>(
53
+ ['release', 'view', input.tag, '--repo', input.repository, '--json', 'url'],
54
+ { cwd: options.cwd }
55
+ );
56
+ if (!inspected.ok && inspected.error.code !== 'RESOURCE_NOT_FOUND') return failure(inspected.error);
57
+ const exists = inspected.ok;
58
+ const operation = exists ? 'release:update-notes' as const : 'release:create' as const;
59
+ if (input.dryRun) {
60
+ return {
61
+ status: 'planned' as const,
62
+ changed: false,
63
+ operation,
64
+ url: inspected.ok ? inspected.value.url || null : null,
65
+ error: null
66
+ };
67
+ }
68
+ const args = exists
69
+ ? ['release', 'edit', input.tag, '--repo', input.repository, '--notes-file', input.notesFile]
70
+ : ['release', 'create', input.tag, '--repo', input.repository, '--title', input.title, '--notes-file', input.notesFile];
71
+ const written = client.text(args, { cwd: options.cwd, method: exists ? 'PATCH' : 'POST' });
72
+ if (!written.ok) return failure(written.error);
73
+ return {
74
+ status: 'applied' as const,
75
+ changed: true,
76
+ operation,
77
+ url: written.value || (inspected.ok ? inspected.value.url || null : null),
78
+ error: null
79
+ };
80
+ }
81
+
82
+ function fetchGitHubReleaseNoteData(
83
+ input: { repository: string; commitOids: string[]; branch: string; historyLimit: number; fromTime: string; toTime: string },
84
+ options: GitHubReleaseNoteOptions = {}
85
+ ) {
86
+ const client = options.client ?? createGitHubClient();
87
+ const releases = client.json<Array<{ tagName?: string; isDraft?: boolean; isPrerelease?: boolean }>>(
88
+ ['release', 'list', '--repo', input.repository, '--limit', String(input.historyLimit + 10), '--json', 'tagName,isDraft,isPrerelease'],
89
+ { cwd: options.cwd }
90
+ );
91
+ if (!releases.ok) return failure(releases.error);
92
+ const history = [];
93
+ for (const item of releases.value.filter((entry) => !entry.isDraft && !entry.isPrerelease).slice(0, input.historyLimit)) {
94
+ const viewed = client.json<{ body?: string; url?: string }>(
95
+ ['release', 'view', String(item.tagName), '--repo', input.repository, '--json', 'body,url'],
96
+ { cwd: options.cwd }
97
+ );
98
+ if (!viewed.ok) return failure(viewed.error);
99
+ history.push({ tag: String(item.tagName), body: String(viewed.value.body || ''), url: viewed.value.url || null });
100
+ }
101
+ const prs = client.json<Array<Record<string, unknown>>>(
102
+ ['pr', 'list', '--repo', input.repository, '--state', 'merged', '--base', input.branch, '--limit', '1000', '--json', 'number,title,body,url,mergedAt,labels,author,closingIssuesReferences'],
103
+ { cwd: options.cwd }
104
+ );
105
+ if (!prs.ok) return failure(prs.error);
106
+ const authors = new Map<string, ReleaseNoteActor[]>();
107
+ for (const oid of input.commitOids) {
108
+ 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}}}}}}`;
109
+ const [owner, name] = input.repository.split('/');
110
+ const result = client.json<{
111
+ data?: { repository?: { object?: { authors?: { nodes?: GitHubActorInput[]; pageInfo?: { hasNextPage?: boolean } } } } };
112
+ }>([
113
+ 'api', 'graphql', '-f', `query=${query}`, '-F', `owner=${owner}`, '-F', `name=${name}`, '-F', `oid=${oid}`
114
+ ], { cwd: options.cwd });
115
+ if (!result.ok) return failure(result.error);
116
+ const connection = result.value.data?.repository?.object?.authors;
117
+ if (connection?.pageInfo?.hasNextPage) {
118
+ return failure({ code: 'RELEASE_NOTES_AUTHORS_TRUNCATED', message: `Commit authors exceeded the supported page size for ${oid}`, retryable: false });
119
+ }
120
+ authors.set(oid, (connection?.nodes || []).map(normalizeGitHubActor));
121
+ }
122
+ const fromTime = Date.parse(input.fromTime);
123
+ const toTime = Date.parse(input.toTime);
124
+ const pullRequests = prs.value.filter((item) => {
125
+ const mergedAt = Date.parse(String(item.mergedAt || ''));
126
+ return Number.isFinite(mergedAt) && mergedAt > fromTime && mergedAt <= toTime;
127
+ });
128
+ return { status: 'no-op' as const, changed: false, history, pullRequests, authors, error: null };
129
+ }
130
+
131
+ export { fetchGitHubReleaseNoteData, normalizeGitHubActor, publishGitHubReleaseNotes };
132
+ export type { ReleaseNoteActor };
@@ -6,7 +6,7 @@ import { buildDefaultBody } from '../task/commands/issue-body.ts';
6
6
  import { extractTitle, parseTaskFrontmatter } from '../task/frontmatter.ts';
7
7
  import { requirementFieldLabels, renderTemplateBody } from '../task/issue-form.ts';
8
8
  import { resolveTaskRef } from '../task/resolve-ref.ts';
9
- import { extractSection } from '../task/sections.ts';
9
+ import { extractSection, findSectionHeading } from '../task/sections.ts';
10
10
  import { writeTask } from '../task/write.ts';
11
11
  import { resolvePlatformContext } from './context.ts';
12
12
  import { createGitHubClient } from './github-client.ts';
@@ -308,9 +308,11 @@ function deterministicIssueBody(repoRoot: string, content: string, taskType: str
308
308
  if (selected) {
309
309
  try {
310
310
  return renderTemplateBody(fs.readFileSync(selected, 'utf8'), {
311
- title: taskTitle(content),
312
- description: extractSection(content, ['描述', 'Description']),
313
- requirements: extractSection(content, ['需求', 'Requirements'])
311
+ title: taskTitle(content),
312
+ description: extractSection(content, ['描述', 'Description']),
313
+ requirements: extractSection(content, ['需求', 'Requirements']),
314
+ taskInput: extractSection(content, ['任务输入', 'Task Input']),
315
+ taskInputHeading: findSectionHeading(content, ['任务输入', 'Task Input'])
314
316
  });
315
317
  } catch {
316
318
  // Invalid or unavailable forms fall back to the deterministic default body.
@@ -0,0 +1,107 @@
1
+ import { execFileSync, spawnSync } from "node:child_process";
2
+
3
+ import type { PullRequestSnapshot } from "./pull-requests.ts";
4
+
5
+ const SHA_PATTERN = /^[0-9a-f]{40}$/i;
6
+
7
+ type ReviewedHeadRelation =
8
+ | { status: "strict"; reviewedHead: string }
9
+ | {
10
+ status: "merged-equivalent";
11
+ reviewedHead: string;
12
+ mergeCommit: string;
13
+ comparisonHead: string;
14
+ }
15
+ | { status: "failed" | "blocked"; code: string; message: string };
16
+
17
+ function git(cwd: string, args: string[]): string {
18
+ return execFileSync("git", args, {
19
+ cwd,
20
+ encoding: "utf8",
21
+ maxBuffer: 32 * 1024 * 1024,
22
+ stdio: ["ignore", "pipe", "pipe"]
23
+ });
24
+ }
25
+
26
+ function commitExists(gitRoot: string, sha: string): boolean {
27
+ try {
28
+ git(gitRoot, ["cat-file", "-e", `${sha}^{commit}`]);
29
+ return true;
30
+ } catch {
31
+ return false;
32
+ }
33
+ }
34
+
35
+ function isAncestor(gitRoot: string, ancestor: string, descendant: string):
36
+ | { status: "yes" | "no" }
37
+ | { status: "error" } {
38
+ const result = spawnSync("git", ["merge-base", "--is-ancestor", ancestor, descendant], {
39
+ cwd: gitRoot,
40
+ encoding: "utf8",
41
+ stdio: ["ignore", "pipe", "pipe"]
42
+ });
43
+ if (result.status === 0) return { status: "yes" };
44
+ if (result.status === 1) return { status: "no" };
45
+ return { status: "error" };
46
+ }
47
+
48
+ export function resolveReviewedHeadRelation(input: {
49
+ gitRoot: string;
50
+ comparisonHead: string;
51
+ lastReviewedCommit: string;
52
+ pullRequest: PullRequestSnapshot;
53
+ }): ReviewedHeadRelation {
54
+ const { gitRoot, comparisonHead, lastReviewedCommit, pullRequest } = input;
55
+ if (
56
+ comparisonHead === lastReviewedCommit &&
57
+ lastReviewedCommit === pullRequest.head.sha &&
58
+ !(pullRequest.state === "closed" && pullRequest.mergedAt && pullRequest.mergeCommitSha)
59
+ ) {
60
+ return { status: "strict", reviewedHead: lastReviewedCommit };
61
+ }
62
+ if (pullRequest.state !== "closed" || !pullRequest.mergedAt || !pullRequest.mergeCommitSha ||
63
+ lastReviewedCommit !== pullRequest.head.sha) {
64
+ return { status: "failed", code: "PR_MERGE_IDENTITY_INVALID", message: "PR merge identity does not match the reviewed head" };
65
+ }
66
+ const shas = [pullRequest.base.sha, pullRequest.head.sha, pullRequest.mergeCommitSha, comparisonHead];
67
+ if (shas.some((sha) => !SHA_PATTERN.test(sha) || !commitExists(gitRoot, sha))) {
68
+ return { status: "blocked", code: "PR_MERGE_OBJECT_MISSING", message: "Required PR merge Git objects are unavailable" };
69
+ }
70
+ const ancestry = isAncestor(gitRoot, pullRequest.mergeCommitSha, comparisonHead);
71
+ if (ancestry.status === "no") {
72
+ return {
73
+ status: "failed",
74
+ code: "PR_MERGE_TARGET_MISMATCH",
75
+ message: "PR merge commit is not in the authoritative target history"
76
+ };
77
+ }
78
+ if (ancestry.status === "error") {
79
+ return {
80
+ status: "blocked",
81
+ code: "PR_MERGE_GIT_EVIDENCE_UNAVAILABLE",
82
+ message: "Unable to verify PR merge ancestry"
83
+ };
84
+ }
85
+ try {
86
+ const parents = git(gitRoot, ["rev-list", "--parents", "-n", "1", pullRequest.mergeCommitSha]).trim().split(/\s+/);
87
+ if (parents.length !== 2) {
88
+ return { status: "failed", code: "PR_MERGE_TOPOLOGY_INVALID", message: "PR merge commit is not a single-parent squash commit" };
89
+ }
90
+ const diffArgs = ["diff", "--no-renames", "--binary", "--full-index"];
91
+ const reviewedDiff = git(gitRoot, [...diffArgs, pullRequest.base.sha, pullRequest.head.sha]);
92
+ const mergedDiff = git(gitRoot, [...diffArgs, `${pullRequest.mergeCommitSha}^`, pullRequest.mergeCommitSha]);
93
+ if (reviewedDiff !== mergedDiff) {
94
+ return { status: "failed", code: "PR_MERGE_CONTENT_MISMATCH", message: "Squash merge content differs from the reviewed PR changes" };
95
+ }
96
+ } catch {
97
+ return { status: "blocked", code: "PR_MERGE_GIT_EVIDENCE_UNAVAILABLE", message: "Unable to verify PR merge topology or content" };
98
+ }
99
+ return {
100
+ status: "merged-equivalent",
101
+ reviewedHead: lastReviewedCommit,
102
+ mergeCommit: pullRequest.mergeCommitSha,
103
+ comparisonHead
104
+ };
105
+ }
106
+
107
+ export type { ReviewedHeadRelation };
@@ -2,32 +2,31 @@ import fs from 'node:fs';
2
2
 
3
3
  import { parseTaskFrontmatter } from '../task/frontmatter.ts';
4
4
  import { resolveTaskRef } from '../task/resolve-ref.ts';
5
+ import {
6
+ inspectPlatformRequiredChecks,
7
+ registerPlatformCapabilities
8
+ } from './adapters.ts';
9
+ import type { PlatformCheckSnapshot } from './adapters.ts';
5
10
  import { resolvePlatformContext } from './context.ts';
6
11
  import { createGitHubClient } from './github-client.ts';
7
12
  import type { GitHubClient } from './github-client.ts';
8
13
  import { inspectPlatformPullRequest } from './pull-requests.ts';
9
14
  import { platformResult } from './types.ts';
10
15
  import type { PlatformResult } from './types.ts';
16
+ import type { PullRequestSnapshot } from './pull-requests.ts';
11
17
 
12
18
  type CheckBucket = 'pass' | 'fail' | 'pending' | 'cancel';
13
19
  type CheckState = 'passed' | 'failed' | 'pending' | 'timed-out' | 'cancelled' | 'no-required';
14
- type CheckSnapshot = {
15
- name: string;
16
- bucket: CheckBucket;
17
- workflow?: string | null;
18
- conclusion?: string | null;
19
- detailsUrl?: string | null;
20
- startedAt?: string | null;
21
- completedAt?: string | null;
22
- };
20
+ type CheckSnapshot = PlatformCheckSnapshot;
23
21
  type ChecksSnapshot = { state: Exclude<CheckState, 'timed-out'>; required: CheckSnapshot[] };
24
22
  type RunCandidate = { id: number; name: string; headSha: string; jobId?: number | null };
25
23
  type ChecksResult = PlatformResult & {
26
- pullRequest: { number: number; headSha: string } | null;
24
+ pullRequest: PullRequestSnapshot | null;
27
25
  checks: { state: CheckState; required: CheckSnapshot[] };
28
26
  resolution?: { status: 'resolved' | 'missing' | 'ambiguous'; runId: number | null; jobId: number | null };
29
27
  logs?: { runId: number; jobId?: number; text: string };
30
28
  };
29
+ type InspectionOptions = { cwd?: string; client?: unknown };
31
30
  type SharedOptions = { cwd?: string; client?: GitHubClient };
32
31
 
33
32
  function classifyRequiredChecks(required: CheckSnapshot[]): ChecksSnapshot {
@@ -113,14 +112,27 @@ function normalizeChecks(value: unknown): CheckSnapshot[] {
113
112
  });
114
113
  }
115
114
 
116
- function resolvedTask(taskRef: string, options: SharedOptions) {
115
+ registerPlatformCapabilities('github', {
116
+ inspectRequiredChecks({ client, repository, number, cwd }) {
117
+ const github = (client as GitHubClient | undefined) || createGitHubClient();
118
+ const inspected = github.json<unknown>([
119
+ 'pr', 'checks', String(number), '--repo', repository, '--required',
120
+ '--json', 'name,state,bucket,link,workflow,startedAt,completedAt'
121
+ ], { cwd });
122
+ return inspected.ok
123
+ ? { ok: true, value: normalizeChecks(inspected.value) }
124
+ : { ok: false, error: inspected.error };
125
+ }
126
+ });
127
+
128
+ function resolvedTask(taskRef: string, options: InspectionOptions) {
117
129
  const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
118
130
  if (!resolved.ok) return { ok: false as const, output: checksResult('failed', { error: { code: resolved.code, message: resolved.message, retryable: false } }) };
119
131
  const frontmatter = parseTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8'));
120
132
  const pr = Number(frontmatter.pr_number);
121
133
  const prNumber = Number.isInteger(pr) && pr > 0 ? pr : null;
122
134
  if (!prNumber) return { ok: false as const, output: checksResult('failed', { error: { code: 'PR_NOT_LINKED', message: 'Task has no valid pr_number', retryable: false } }) };
123
- const client = options.client || createGitHubClient();
135
+ const client = (options.client as GitHubClient | undefined) || createGitHubClient();
124
136
  const context = resolvePlatformContext({ cwd: resolved.repoRoot, client });
125
137
  if (!context.platform.repository || !['no-op', 'degraded'].includes(context.status)) return { ok: false as const, output: checksResult(context.status, { platform: context.platform, capabilities: context.capabilities, error: context.error }) };
126
138
  const inspected = inspectPlatformPullRequest(taskRef, { cwd: resolved.repoRoot, client });
@@ -128,29 +140,37 @@ function resolvedTask(taskRef: string, options: SharedOptions) {
128
140
  return { ok: true as const, resolved, prNumber, client, context, pullRequest: inspected.pullRequest };
129
141
  }
130
142
 
131
- function inspectRequiredChecks(taskRef: string, options: SharedOptions = {}): ChecksResult {
143
+ function inspectRequiredChecks(taskRef: string, options: InspectionOptions = {}): ChecksResult {
132
144
  const base = resolvedTask(taskRef, options);
133
145
  if (!base.ok) return base.output;
134
146
  const repository = base.context.platform.repository!;
135
- const inspected = base.client.json<unknown>([
136
- 'pr', 'checks', String(base.prNumber), '--repo', repository, '--required',
137
- '--json', 'name,state,bucket,link,workflow,startedAt,completedAt'
138
- ], { cwd: base.resolved.repoRoot });
139
- if (!inspected.ok) {
140
- return checksResult(inspected.error.retryable ? 'blocked' : 'failed', {
147
+ const inspected = inspectPlatformRequiredChecks(base.context.platform.type, {
148
+ cwd: base.resolved.repoRoot,
149
+ repository,
150
+ number: base.prNumber,
151
+ headSha: base.pullRequest.head.sha,
152
+ client: options.client || base.client
153
+ });
154
+ if (!inspected.ok || !inspected.value) {
155
+ const error = inspected.error || {
156
+ code: 'REQUIRED_CHECKS_INSPECTION_INVALID',
157
+ message: 'Platform adapter returned no required-checks snapshot',
158
+ retryable: false
159
+ };
160
+ return checksResult(error.retryable ? 'blocked' : 'failed', {
141
161
  platform: base.context.platform, capabilities: base.context.capabilities,
142
162
  resource: { kind: 'pull-request', number: base.prNumber },
143
- pullRequest: { number: base.prNumber, headSha: base.pullRequest.head.sha }, error: inspected.error
163
+ pullRequest: base.pullRequest, error
144
164
  });
145
165
  }
146
- const classified = classifyRequiredChecks(normalizeChecks(inspected.value));
166
+ const classified = classifyRequiredChecks(inspected.value);
147
167
  const status = classified.state === 'passed' || classified.state === 'no-required'
148
168
  ? 'no-op'
149
169
  : classified.state === 'failed' || classified.state === 'cancelled' ? 'failed' : 'blocked';
150
170
  return checksResult(status, {
151
171
  platform: base.context.platform, capabilities: base.context.capabilities,
152
172
  resource: { kind: 'pull-request', number: base.prNumber },
153
- pullRequest: { number: base.prNumber, headSha: base.pullRequest.head.sha }, checks: classified,
173
+ pullRequest: base.pullRequest, checks: classified,
154
174
  error: status === 'no-op' ? null : {
155
175
  code: classified.state === 'pending' ? 'REQUIRED_CHECKS_PENDING' : `REQUIRED_CHECKS_${classified.state.toUpperCase()}`,
156
176
  message: `Required checks are ${classified.state}`,
@@ -159,7 +179,7 @@ function inspectRequiredChecks(taskRef: string, options: SharedOptions = {}): Ch
159
179
  });
160
180
  }
161
181
 
162
- async function watchPlatformChecks(taskRef: string, options: SharedOptions & {
182
+ async function watchPlatformChecks(taskRef: string, options: InspectionOptions & {
163
183
  intervalSeconds: number;
164
184
  deadlineSeconds: number;
165
185
  signal?: AbortSignal;
@@ -197,9 +217,9 @@ function resolvePlatformCheckRun(taskRef: string, options: SharedOptions & { che
197
217
  const direct = options.detailsUrl ? parseRunJobIdentity(options.detailsUrl) : null;
198
218
  if (direct) {
199
219
  const run = base.client.json<{ id?: number; head_sha?: string }>(['api', `repos/${base.context.platform.repository}/actions/runs/${direct.runId}`], { cwd: base.resolved.repoRoot });
200
- if (!run.ok) 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 });
220
+ if (!run.ok) return checksResult(run.error.retryable ? 'blocked' : 'failed', { platform: base.context.platform, capabilities: base.context.capabilities, pullRequest: base.pullRequest, error: run.error });
201
221
  if (run.value.head_sha !== base.pullRequest.head.sha) return checksResult('failed', { error: { code: 'CHECK_RUN_HEAD_MISMATCH', message: 'Resolved run does not belong to the PR head SHA', retryable: false } });
202
- 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 });
222
+ 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 });
203
223
  }
204
224
  const listed = base.client.json<{ check_runs?: Array<{ name?: string; details_url?: string }> }>(['api', `repos/${base.context.platform.repository}/commits/${base.pullRequest.head.sha}/check-runs`], { cwd: base.resolved.repoRoot });
205
225
  if (!listed.ok) return checksResult(listed.error.retryable ? 'blocked' : 'failed', { error: listed.error });
@@ -211,30 +231,39 @@ function resolvePlatformCheckRun(taskRef: string, options: SharedOptions & { che
211
231
  const resolution = resolveRunCandidate(candidates, base.pullRequest.head.sha, options.checkName);
212
232
  return checksResult(resolution.status === 'resolved' ? 'no-op' : 'failed', {
213
233
  platform: base.context.platform, capabilities: base.context.capabilities,
214
- resource: { kind: 'pull-request', number: base.prNumber }, pullRequest: { number: base.prNumber, headSha: base.pullRequest.head.sha },
234
+ resource: { kind: 'pull-request', number: base.prNumber }, pullRequest: base.pullRequest,
215
235
  resolution,
216
236
  error: resolution.status === 'resolved' ? null : { code: resolution.status === 'ambiguous' ? 'CHECK_RUN_AMBIGUOUS' : 'CHECK_RUN_NOT_FOUND', message: `Check run is ${resolution.status}`, retryable: false }
217
237
  });
218
238
  }
219
239
 
240
+ function fetchCheckLogText(client: GitHubClient, args: string[], cwd: string) {
241
+ const fetched = client.text(args, { cwd });
242
+ if (fetched.ok || args[0] !== 'api' || !/response contains terminal escape sequences/i.test(fetched.error.message)) {
243
+ return fetched;
244
+ }
245
+ return client.text([...args, '--allow-escape-sequences'], { cwd });
246
+ }
247
+
220
248
  function fetchPlatformCheckLogs(taskRef: string, options: SharedOptions & { run: number; job?: number }): ChecksResult {
221
249
  const base = resolvedTask(taskRef, options);
222
250
  if (!base.ok) return base.output;
223
251
  const args = options.job
224
252
  ? ['api', `repos/${base.context.platform.repository}/actions/jobs/${options.job}/logs`]
225
253
  : ['run', 'view', String(options.run), '--repo', base.context.platform.repository!, '--log-failed'];
226
- const fetched = base.client.text(args, { cwd: base.resolved.repoRoot });
254
+ const fetched = fetchCheckLogText(base.client, args, base.resolved.repoRoot);
227
255
  if (!fetched.ok) return checksResult(fetched.error.retryable ? 'blocked' : 'failed', { platform: base.context.platform, capabilities: base.context.capabilities, error: fetched.error });
228
256
  if (!fetched.value) return checksResult('failed', { error: { code: 'CHECK_LOGS_MISSING', message: 'No failed logs are available', retryable: false } });
229
257
  return checksResult('no-op', {
230
258
  platform: base.context.platform, capabilities: base.context.capabilities,
231
- resource: { kind: 'pull-request', number: base.prNumber }, pullRequest: { number: base.prNumber, headSha: base.pullRequest.head.sha },
259
+ resource: { kind: 'pull-request', number: base.prNumber }, pullRequest: base.pullRequest,
232
260
  logs: { runId: options.run, ...(options.job ? { jobId: options.job } : {}), text: fetched.value }, error: null
233
261
  });
234
262
  }
235
263
 
236
264
  export {
237
265
  classifyRequiredChecks,
266
+ fetchCheckLogText,
238
267
  fetchPlatformCheckLogs,
239
268
  inspectRequiredChecks,
240
269
  parseRunJobIdentity,