@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
@@ -1,10 +1,16 @@
1
1
  import fs from 'node:fs';
2
+ import { execFileSync } from 'node:child_process';
2
3
 
3
4
  import { parseTaskFrontmatter } from '../task/frontmatter.ts';
4
5
  import { resolveTaskRef } from '../task/resolve-ref.ts';
5
6
  import { extractSection } from '../task/sections.ts';
6
7
  import { captureTaskWriteMetadata, writeTask } from '../task/write.ts';
7
- import { resolvePlatformContext } from './context.ts';
8
+ import {
9
+ inspectPlatformChangeRequest,
10
+ registerPlatformCapabilities
11
+ } from './adapters.ts';
12
+ import type { PlatformChangeRequestSnapshot } from './adapters.ts';
13
+ import { parseGitHubRemote, resolvePlatformContext } from './context.ts';
8
14
  import { createGitHubClient } from './github-client.ts';
9
15
  import type { GitHubClient } from './github-client.ts';
10
16
  import { inspectPlatformIssue } from './issues.ts';
@@ -12,26 +18,13 @@ import { planPullRequestMetadata } from './pull-request-metadata.ts';
12
18
  import { platformResult } from './types.ts';
13
19
  import type { PlatformOperation, PlatformResult } from './types.ts';
14
20
 
15
- type PullRequestSnapshot = {
16
- repository: string;
17
- number: number;
18
- nodeId: string;
19
- url: string;
20
- state: 'open' | 'closed';
21
- title: string;
22
- body: string;
23
- draft: boolean;
24
- head: { repository: string; ref: string; sha: string };
25
- base: { repository: string; ref: string };
26
- labels: string[];
27
- assignees: string[];
28
- milestone: string | null;
29
- };
21
+ type PullRequestSnapshot = PlatformChangeRequestSnapshot;
30
22
 
31
23
  type PullRequestResult = PlatformResult & {
32
24
  task: { id: string | null; issueNumber: number | null; prNumber: number | null };
33
25
  pullRequest: PullRequestSnapshot | null;
34
26
  };
27
+ type InspectionOptions = { cwd?: string; client?: unknown };
35
28
  type SharedOptions = { cwd?: string; client?: GitHubClient };
36
29
  type CreateOptions = SharedOptions & {
37
30
  agent: string;
@@ -55,7 +48,9 @@ type RemotePullRequest = {
55
48
  draft?: boolean;
56
49
  pull_request?: unknown;
57
50
  head?: { ref?: string; sha?: string; repo?: { full_name?: string } | null };
58
- base?: { ref?: string; repo?: { full_name?: string } | null };
51
+ base?: { ref?: string; sha?: string; repo?: { full_name?: string } | null };
52
+ merged_at?: string | null;
53
+ merge_commit_sha?: string | null;
59
54
  labels?: Array<string | { name?: string }>;
60
55
  assignees?: Array<{ login?: string }>;
61
56
  milestone?: { title?: string } | null;
@@ -92,7 +87,9 @@ function normalizePullRequest(remote: RemotePullRequest, repository: string): Pu
92
87
  body: remote.body || '',
93
88
  draft: Boolean(remote.draft),
94
89
  head: { repository: headRepository, ref: remote.head.ref, sha: remote.head.sha },
95
- base: { repository: baseRepository, ref: remote.base.ref },
90
+ base: { repository: baseRepository, ref: remote.base.ref, sha: remote.base.sha || '' },
91
+ mergedAt: remote.merged_at || null,
92
+ mergeCommitSha: remote.merge_commit_sha || null,
96
93
  labels: (remote.labels || []).map((label) => typeof label === 'string' ? label : label.name || '').filter(Boolean).sort(),
97
94
  assignees: (remote.assignees || []).map((assignee) => assignee.login || '').filter(Boolean).sort(),
98
95
  milestone: remote.milestone?.title || null
@@ -120,7 +117,7 @@ function selectPullRequest(remotes: RemotePullRequest[], repository: string, hea
120
117
  return { status: matches.length === 0 ? 'missing' : 'ambiguous', pullRequest: null };
121
118
  }
122
119
 
123
- function resolvedContext(taskRef: string, options: SharedOptions) {
120
+ function resolvedContext(taskRef: string, options: InspectionOptions) {
124
121
  const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
125
122
  if (!resolved.ok) return { ok: false as const, output: result('failed', resolved.taskId, null, null, {
126
123
  error: { code: resolved.code, message: resolved.message, retryable: false }
@@ -131,7 +128,7 @@ function resolvedContext(taskRef: string, options: SharedOptions) {
131
128
  const pr = Number(frontmatter.pr_number);
132
129
  const issueNumber = Number.isInteger(issue) && issue > 0 ? issue : null;
133
130
  const prNumber = Number.isInteger(pr) && pr > 0 ? pr : null;
134
- const client = options.client || createGitHubClient();
131
+ const client = (options.client as GitHubClient | undefined) || createGitHubClient();
135
132
  const context = resolvePlatformContext({ cwd: resolved.repoRoot, client });
136
133
  const usable = (context.status === 'no-op' || context.status === 'degraded') && context.platform.repository;
137
134
  if (!usable) return { ok: false as const, output: result(context.status, resolved.taskId, issueNumber, prNumber, {
@@ -149,6 +146,111 @@ function inspectGitHubPullRequest(client: GitHubClient, repository: string, numb
149
146
  : { ok: false as const, error: { code: 'PR_IDENTITY_INVALID', message: 'Remote resource is not a valid pull request', retryable: false } };
150
147
  }
151
148
 
149
+ function configuredGitRemotes(cwd: string): Array<{ name: string; url: string; repository: string }> {
150
+ try {
151
+ return execFileSync('git', ['remote'], {
152
+ cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe']
153
+ }).split(/\r?\n/).filter(Boolean).flatMap((name) => {
154
+ try {
155
+ const url = execFileSync('git', ['config', '--get', `remote.${name}.url`], {
156
+ cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe']
157
+ }).trim();
158
+ const repository = parseGitHubRemote(url);
159
+ return repository ? [{ name, url, repository }] : [];
160
+ } catch {
161
+ return [];
162
+ }
163
+ });
164
+ } catch {
165
+ return [];
166
+ }
167
+ }
168
+
169
+ function rewriteGitHubRemote(remote: string, repository: string): string | null {
170
+ const match = remote.trim().match(
171
+ /^(https?:\/\/github\.com\/|ssh:\/\/(?:git@)?github\.com\/|git@github\.com:)[^/\s]+\/[^/\s]+(?:\.git)?$/i
172
+ );
173
+ return match ? `${match[1]}${repository}.git` : null;
174
+ }
175
+
176
+ function resolveGitHubChangeRequestGitEvidence({
177
+ cwd,
178
+ repository,
179
+ pullRequest
180
+ }: {
181
+ cwd: string;
182
+ repository: string;
183
+ pullRequest: PullRequestSnapshot;
184
+ }) {
185
+ if (
186
+ pullRequest.repository !== repository ||
187
+ pullRequest.base.repository !== repository ||
188
+ pullRequest.number <= 0
189
+ ) {
190
+ return {
191
+ ok: false as const,
192
+ error: {
193
+ code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
194
+ message: 'Pull request repository identity is inconsistent',
195
+ retryable: false
196
+ }
197
+ };
198
+ }
199
+ const reviewedHeadRef = `refs/pull/${pullRequest.number}/head`;
200
+ const targetHeadRef = `refs/heads/${pullRequest.base.ref}`;
201
+ const refValid = (ref: string) => {
202
+ try {
203
+ execFileSync('git', ['check-ref-format', ref], {
204
+ cwd, encoding: 'utf8', stdio: ['ignore', 'ignore', 'ignore']
205
+ });
206
+ return true;
207
+ } catch {
208
+ return false;
209
+ }
210
+ };
211
+ if (!refValid(reviewedHeadRef) || !refValid(targetHeadRef)) {
212
+ return {
213
+ ok: false as const,
214
+ error: {
215
+ code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
216
+ message: 'Pull request Git refs are invalid',
217
+ retryable: false
218
+ }
219
+ };
220
+ }
221
+
222
+ const remotes = configuredGitRemotes(cwd);
223
+ const exact = remotes
224
+ .filter((remote) => remote.repository === repository)
225
+ .sort((left, right) => Number(right.name === 'origin') - Number(left.name === 'origin') ||
226
+ left.name.localeCompare(right.name))[0];
227
+ const origin = remotes.find((remote) => remote.name === 'origin');
228
+ const remoteUrl = exact?.url || (origin ? rewriteGitHubRemote(origin.url, repository) : null);
229
+ if (!remoteUrl) {
230
+ return {
231
+ ok: false as const,
232
+ error: {
233
+ code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
234
+ message: `No GitHub remote can provide evidence for ${repository}`,
235
+ retryable: false
236
+ }
237
+ };
238
+ }
239
+ return {
240
+ ok: true as const,
241
+ value: { remoteUrl, reviewedHeadRef, targetHeadRef }
242
+ };
243
+ }
244
+
245
+ registerPlatformCapabilities('github', {
246
+ inspectChangeRequest({ client, repository, number, cwd }) {
247
+ return inspectGitHubPullRequest((client as GitHubClient | undefined) || createGitHubClient(), repository, number, cwd);
248
+ },
249
+ resolveChangeRequestGitEvidence(context) {
250
+ return resolveGitHubChangeRequestGitEvidence(context);
251
+ }
252
+ });
253
+
152
254
  function locatePullRequest(base: ReturnType<typeof resolvedContext> & { ok: true }, head: string, target: string) {
153
255
  const repository = base.context.platform.repository!;
154
256
  const listed = base.client.json<RemotePullRequest[]>([
@@ -165,18 +267,30 @@ function locatePullRequest(base: ReturnType<typeof resolvedContext> & { ok: true
165
267
  } };
166
268
  }
167
269
 
168
- function inspectPlatformPullRequest(taskRef: string, options: SharedOptions = {}): PullRequestResult {
270
+ function inspectPlatformPullRequest(taskRef: string, options: InspectionOptions = {}): PullRequestResult {
169
271
  const base = resolvedContext(taskRef, options);
170
272
  if (!base.ok) return base.output;
171
273
  if (!base.prNumber) return result('no-op', base.resolved.taskId, base.issueNumber, null, {
172
274
  platform: base.context.platform, capabilities: base.context.capabilities,
173
275
  error: { code: 'PR_NOT_LINKED', message: 'Task has no valid pr_number', retryable: false }
174
276
  });
175
- const fetched = inspectGitHubPullRequest(base.client, base.context.platform.repository!, base.prNumber, base.resolved.repoRoot);
176
- if (!fetched.ok) return result(fetched.error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
177
- platform: base.context.platform, capabilities: base.context.capabilities,
178
- resource: { kind: 'pull-request', number: base.prNumber }, error: fetched.error
277
+ const fetched = inspectPlatformChangeRequest(base.context.platform.type, {
278
+ cwd: base.resolved.repoRoot,
279
+ repository: base.context.platform.repository!,
280
+ number: base.prNumber,
281
+ client: options.client || base.client
179
282
  });
283
+ if (!fetched.ok || !fetched.value) {
284
+ const error = fetched.error || {
285
+ code: 'PR_INSPECTION_INVALID',
286
+ message: 'Platform adapter returned no change-request snapshot',
287
+ retryable: false
288
+ };
289
+ return result(error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
290
+ platform: base.context.platform, capabilities: base.context.capabilities,
291
+ resource: { kind: 'pull-request', number: base.prNumber }, error
292
+ });
293
+ }
180
294
  return result('no-op', base.resolved.taskId, base.issueNumber, base.prNumber, {
181
295
  platform: base.context.platform, capabilities: base.context.capabilities,
182
296
  resource: { kind: 'pull-request', number: base.prNumber }, pullRequest: fetched.value, error: null
@@ -374,6 +488,7 @@ export {
374
488
  inspectGitHubPullRequest,
375
489
  inspectPlatformPullRequest,
376
490
  normalizePullRequest,
491
+ resolveGitHubChangeRequestGitEvidence,
377
492
  selectPullRequest,
378
493
  syncPlatformPullRequest
379
494
  };
@@ -0,0 +1,167 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { execFileSync } from 'node:child_process';
4
+
5
+ import { resolvePlatformContext } from './context.ts';
6
+ import {
7
+ fetchGitHubReleaseNoteData,
8
+ publishGitHubReleaseNotes
9
+ } from './github-release-notes.ts';
10
+ import type { ReleaseNoteActor } from './github-release-notes.ts';
11
+ import type { GitHubClient } from './github-client.ts';
12
+
13
+ type ReleaseNoteOptions = {
14
+ cwd?: string;
15
+ platformType?: string;
16
+ client?: GitHubClient;
17
+ };
18
+
19
+ type ReleaseNoteCommit = { oid: string; title: string; authors: ReleaseNoteActor[] };
20
+
21
+ const unsupportedError = {
22
+ code: 'PLATFORM_RELEASE_NOTES_UNSUPPORTED',
23
+ message: 'Release notes are not supported for the configured platform',
24
+ retryable: false
25
+ };
26
+
27
+ function baseResult(status: 'no-op' | 'failed' | 'blocked') {
28
+ return {
29
+ status,
30
+ changed: false,
31
+ range: null,
32
+ history: [],
33
+ pullRequests: [],
34
+ commits: [] as ReleaseNoteCommit[],
35
+ error: status === 'no-op' ? unsupportedError : null
36
+ };
37
+ }
38
+
39
+ function git(cwd: string, args: string[]): string {
40
+ return execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
41
+ }
42
+
43
+ function configuredPlatform(cwd: string): string | null {
44
+ try {
45
+ const value = JSON.parse(fs.readFileSync(path.join(cwd, '.agents', '.airc.json'), 'utf8'));
46
+ return typeof value?.platform?.type === 'string' ? value.platform.type : null;
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ function releaseNoteContext(
53
+ input: { fromTag: string; toTag: string; branch: string; historyLimit?: number },
54
+ options: ReleaseNoteOptions = {}
55
+ ) {
56
+ const historyLimit = input.historyLimit ?? 3;
57
+ if (!input.fromTag || !input.toTag || !input.branch || !Number.isInteger(historyLimit) || historyLimit < 1 || historyLimit > 20) {
58
+ 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 } };
59
+ }
60
+ const cwd = path.resolve(options.cwd || process.cwd());
61
+ const platformType = options.platformType ?? configuredPlatform(cwd);
62
+ if (platformType !== 'github') return baseResult('no-op');
63
+ let fromOid: string;
64
+ let toOid: string;
65
+ let fromTime: string;
66
+ let toTime: string;
67
+ let commitFacts: Array<{ oid: string; title: string }>;
68
+ try {
69
+ fromOid = git(cwd, ['rev-parse', '--verify', `${input.fromTag}^{commit}`]);
70
+ toOid = git(cwd, ['rev-parse', '--verify', `${input.toTag}^{commit}`]);
71
+ git(cwd, ['merge-base', '--is-ancestor', fromOid, toOid]);
72
+ fromTime = git(cwd, ['show', '-s', '--format=%cI', fromOid]);
73
+ toTime = git(cwd, ['show', '-s', '--format=%cI', toOid]);
74
+ const raw = git(cwd, ['log', '--reverse', '--no-merges', '--format=%H%x00%s', `${fromOid}..${toOid}`]);
75
+ commitFacts = raw ? raw.split('\n').map((line) => {
76
+ const [oid = '', title = ''] = line.split('\0');
77
+ return { oid, title };
78
+ }) : [];
79
+ } catch {
80
+ return { ...baseResult('failed'), error: { code: 'RELEASE_NOTES_RANGE_INVALID', message: 'The release tag range is missing or is not ancestral', retryable: false } };
81
+ }
82
+ const context = resolvePlatformContext({ cwd, platformType, client: options.client });
83
+ if (!context.platform.repository) {
84
+ return {
85
+ ...baseResult(context.status === 'blocked' ? 'blocked' : 'failed'),
86
+ error: context.error || { code: 'PLATFORM_CONTEXT_UNAVAILABLE', message: 'Platform repository is unavailable', retryable: context.status === 'blocked' }
87
+ };
88
+ }
89
+ const platform = fetchGitHubReleaseNoteData(
90
+ {
91
+ repository: context.platform.repository,
92
+ commitOids: commitFacts.map((item) => item.oid),
93
+ branch: input.branch,
94
+ historyLimit,
95
+ fromTime,
96
+ toTime
97
+ },
98
+ { cwd, client: options.client }
99
+ );
100
+ if (platform.status === 'blocked' || platform.status === 'failed' || !('authors' in platform)) {
101
+ return { ...baseResult(platform.status), error: platform.error };
102
+ }
103
+ const commits = commitFacts.map((commit) => {
104
+ const seen = new Set<string>();
105
+ const authors = (platform.authors.get(commit.oid) || []).filter((actor) => {
106
+ const key = actor.login || actor.email?.toLowerCase() || actor.name.toLowerCase();
107
+ if (seen.has(key)) return false;
108
+ seen.add(key);
109
+ return true;
110
+ });
111
+ return { ...commit, authors };
112
+ });
113
+ const pullRequests = platform.pullRequests
114
+ .map((item) => ({
115
+ number: Number(item.number),
116
+ title: String(item.title || ''),
117
+ body: String(item.body || ''),
118
+ url: String(item.url || ''),
119
+ mergedAt: String(item.mergedAt || ''),
120
+ labels: Array.isArray(item.labels) ? item.labels.map((label) => String((label as { name?: string }).name || label)) : [],
121
+ author: item.author && typeof item.author === 'object'
122
+ ? { login: String((item.author as { login?: string }).login || '').toLowerCase(), bot: String((item.author as { login?: string }).login || '').endsWith('[bot]') }
123
+ : null,
124
+ closingIssues: Array.isArray(item.closingIssuesReferences)
125
+ ? item.closingIssuesReferences.map((issue) => {
126
+ const value = issue as Record<string, unknown>;
127
+ return {
128
+ number: Number(value.number),
129
+ title: String(value.title || ''),
130
+ url: String(value.url || ''),
131
+ labels: Array.isArray(value.labels) ? value.labels.map((label) => String((label as { name?: string }).name || label)) : [],
132
+ author: value.author && typeof value.author === 'object'
133
+ ? { login: String((value.author as { login?: string }).login || '').toLowerCase(), bot: String((value.author as { login?: string }).login || '').endsWith('[bot]') }
134
+ : null
135
+ };
136
+ }) : []
137
+ }))
138
+ .sort((left, right) => left.number - right.number);
139
+ return {
140
+ status: 'no-op' as const,
141
+ changed: false,
142
+ range: { fromTag: input.fromTag, toTag: input.toTag, fromOid, toOid },
143
+ history: platform.history,
144
+ pullRequests,
145
+ commits,
146
+ error: null
147
+ };
148
+ }
149
+
150
+ function publishReleaseNotes(
151
+ input: { tag: string; title: string; notesFile: string; dryRun?: boolean },
152
+ options: ReleaseNoteOptions = {}
153
+ ) {
154
+ if (!input.tag || !input.title || !input.notesFile) {
155
+ return { status: 'failed' as const, changed: false, operation: null, url: null, error: { code: 'RELEASE_NOTES_INPUT_INVALID', message: 'tag, title, and notes-file are required', retryable: false } };
156
+ }
157
+ const cwd = path.resolve(options.cwd || process.cwd());
158
+ const platformType = options.platformType ?? configuredPlatform(cwd);
159
+ if (platformType !== 'github') return { status: 'no-op' as const, changed: false, operation: null, url: null, error: unsupportedError };
160
+ const context = resolvePlatformContext({ cwd, platformType, client: options.client });
161
+ if (!context.platform.repository) {
162
+ return { status: context.status === 'blocked' ? 'blocked' as const : 'failed' as const, changed: false, operation: null, url: null, error: context.error };
163
+ }
164
+ return publishGitHubReleaseNotes({ ...input, repository: context.platform.repository }, { cwd, client: options.client });
165
+ }
166
+
167
+ export { publishReleaseNotes, releaseNoteContext };
@@ -21,7 +21,7 @@ function inspectPlatformRelease(tag: string, options: ReleaseOptions = {}) {
21
21
  return { ...platformResult(result.error.retryable ? 'blocked' : 'failed'), platform: context.platform, release: null, workflows: [], error: result.error };
22
22
  }
23
23
  const release: ReleaseSnapshot = { 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 };
24
- const runs = client.json<unknown[]>(['run', 'list', '--repo', context.platform.repository, '--limit', '100', '--json', 'name,headBranch,status,conclusion,url'], { cwd: options.cwd });
24
+ const runs = client.json<unknown[]>(['run', 'list', '--repo', context.platform.repository, '--limit', '100', '--json', 'name,workflowName,displayTitle,event,headBranch,headSha,status,conclusion,createdAt,databaseId,attempt,url'], { cwd: options.cwd });
25
25
  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 };
26
26
  }
27
27
 
@@ -2,8 +2,10 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { execFileSync } from "node:child_process";
4
4
 
5
+ import { hasPlatformCapability } from "./adapters.ts";
5
6
  import { inspectRequiredChecks } from "./pr-checks.ts";
6
7
  import { resolvePlatformContext } from "./context.ts";
8
+ import { resolveReviewedHeadRelation } from "./merged-pr-equivalence.ts";
7
9
 
8
10
  const CHECK_TYPE = "required-checks";
9
11
  const SHA_PATTERN = /^[0-9a-f]{7,40}$/i;
@@ -50,14 +52,30 @@ export function evaluateRequiredChecks(context: any, shared: any): any {
50
52
  return shared.failResult(CHECK_TYPE, inspection.error?.message || "Required checks are unavailable", "check_failed");
51
53
  }
52
54
 
53
- const prHead = inspection.pullRequest?.headSha;
54
- if (!prHead || localHead !== reviewedHead || localHead !== prHead) {
55
- return shared.failResult(CHECK_TYPE, "Local HEAD, last reviewed commit, and PR head must match", "check_failed");
55
+ const prHead = inspection.pullRequest?.head?.sha || inspection.pullRequest?.headSha;
56
+ const relation = context.relation || (
57
+ context.gitRoot && inspection.pullRequest?.head
58
+ ? resolveReviewedHeadRelation({
59
+ gitRoot: context.gitRoot,
60
+ comparisonHead: localHead,
61
+ lastReviewedCommit: reviewedHead,
62
+ pullRequest: inspection.pullRequest
63
+ })
64
+ : localHead === reviewedHead && localHead === prHead
65
+ ? { status: "strict" }
66
+ : { status: "failed" }
67
+ );
68
+ if (relation.status !== "strict" && relation.status !== "merged-equivalent") {
69
+ const message = relation.message || "Local HEAD, last reviewed commit, and PR head must match";
70
+ return relation.status === "blocked"
71
+ ? shared.blockedResult(CHECK_TYPE, message, "dependency_error")
72
+ : shared.failResult(CHECK_TYPE, message, "check_failed");
56
73
  }
57
74
 
58
75
  const state = inspection.checks?.state;
59
76
  if (state === "passed" || state === "no-required") {
60
- return shared.passResult(CHECK_TYPE, `Required checks are ${state} for PR head ${prHead}`);
77
+ const suffix = relation.status === "merged-equivalent" ? " via verified squash merge equivalence" : "";
78
+ return shared.passResult(CHECK_TYPE, `Required checks are ${state} for PR head ${prHead}${suffix}`);
61
79
  }
62
80
  if (state === "pending") {
63
81
  return shared.blockedResult(CHECK_TYPE, inspection.error?.message || "Required checks are pending", "check_pending");
@@ -68,16 +86,26 @@ export function evaluateRequiredChecks(context: any, shared: any): any {
68
86
  export function check({ taskDir }: any, shared: any): any {
69
87
  const task = shared.loadTask(taskDir);
70
88
  if (!task.ok) return shared.failResult(CHECK_TYPE, task.message);
71
- const platform = resolvePlatformContext({ cwd: shared.repoRoot });
72
- if (platform.platform.type !== "github") {
73
- return shared.passResult(CHECK_TYPE, "Skipped: this code platform does not provide required checks");
74
- }
75
89
  const prFlow = readPrFlow(shared.repoRoot);
76
90
  if (prFlow === "disabled" || task.metadata.pr_status === "skipped" || !validPrNumber(task.metadata.pr_number)) {
77
91
  return evaluateRequiredChecks({ metadata: task.metadata, localHead: null, inspection: null, prFlow }, shared);
78
92
  }
93
+ const platform = resolvePlatformContext({ cwd: shared.repoRoot });
94
+ if (!hasPlatformCapability(platform.platform.type, "required-checks")) {
95
+ return shared.blockedResult(
96
+ CHECK_TYPE,
97
+ `Platform '${platform.platform.type || "none"}' does not provide required-checks inspection`,
98
+ "dependency_error"
99
+ );
100
+ }
79
101
  const localHead = readHead(shared.repoRoot);
80
102
  if (!localHead) return evaluateRequiredChecks({ metadata: task.metadata, localHead, inspection: null, prFlow }, shared);
81
103
  const inspection = inspectRequiredChecks(task.metadata.id, { cwd: shared.repoRoot });
82
- return evaluateRequiredChecks({ metadata: task.metadata, localHead, inspection, prFlow }, shared);
104
+ return evaluateRequiredChecks({
105
+ metadata: task.metadata,
106
+ localHead,
107
+ inspection,
108
+ prFlow,
109
+ gitRoot: shared.repoRoot
110
+ }, shared);
83
111
  }
package/lib/prompt.ts CHANGED
@@ -88,20 +88,33 @@ async function select(question: string, choices: string[], defaultValue?: string
88
88
 
89
89
  async function multiSelect(
90
90
  question: string,
91
- choices: { id: string; label: string }[]
91
+ choices: { id: string; label: string }[],
92
+ defaultIds: readonly string[] = choices.map((choice) => choice.id)
92
93
  ): Promise<string[]> {
94
+ const choiceIds = new Set(choices.map((choice) => choice.id));
95
+ if (defaultIds.some((id) => !choiceIds.has(id))) {
96
+ throw new Error('Default selection contains an unknown id');
97
+ }
98
+ const normalizedDefaults = choices
99
+ .map((choice) => choice.id)
100
+ .filter((id) => defaultIds.includes(id));
93
101
  process.stdout.write(` ${question}:\n`);
94
102
  const idWidth = Math.max(...choices.map((c) => c.id.length));
95
103
  choices.forEach((c, i) => {
96
104
  process.stdout.write(` ${i + 1}) ${c.id.padEnd(idWidth)} (${c.label})\n`);
97
105
  });
98
- ask('Enter comma-separated numbers or ids to keep, or "none" to select nothing [default: all]: ');
106
+ const defaultLabel = normalizedDefaults.length === choices.length
107
+ ? 'all'
108
+ : normalizedDefaults.length === 0
109
+ ? 'none'
110
+ : normalizedDefaults.join(',');
111
+ ask(`Enter comma-separated numbers or ids to keep, or "none" to select nothing [default: ${defaultLabel}]: `);
99
112
 
100
113
  setupInterface();
101
114
 
102
115
  const line = await nextLine();
103
116
  // Strictly distinguish bare Enter (null/empty string) from whitespace input.
104
- if (line === null || line === '') return choices.map((c) => c.id);
117
+ if (line === null || line === '') return normalizedDefaults;
105
118
  // Explicit empty selection: "none" means deliberately zero built-in choices.
106
119
  if (line.trim().toLowerCase() === 'none') return [];
107
120
 
@@ -20,7 +20,10 @@ async function inspectHomebrewChannel(url: string, version: string, fetcher: Fet
20
20
  if (!response.ok) return { status: 'blocked' as const, published: null, version, error: { code: 'HOMEBREW_UNAVAILABLE', message: `formula endpoint returned ${response.status}` } };
21
21
  const formula = await response.text();
22
22
  const escaped = version.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
23
- return { status: 'no-op' as const, published: new RegExp(`(?:version\\s+["']${escaped}["']|/v?${escaped}\\.)`).test(formula), version, error: null };
23
+ const formulaUrl = /^\s*url\s+["']([^"']+)["']\s*$/m.exec(formula)?.[1] ?? '';
24
+ const npmTarball = new RegExp(`^https://registry\\.npmjs\\.org/.+/-/[^/]+-${escaped}\\.tgz$`).test(formulaUrl);
25
+ const bottle = /^\s*bottle do\s*$/m.test(formula);
26
+ return { status: 'no-op' as const, published: npmTarball && bottle, version, error: null };
24
27
  } catch (error) {
25
28
  return { status: 'blocked' as const, published: null, version, error: { code: 'HOMEBREW_UNAVAILABLE', message: error instanceof Error ? error.message : String(error) } };
26
29
  }
@@ -1,8 +1,8 @@
1
1
  type ReleasePhase = 'unprepared' | 'prepared' | 'partially-published' | 'published' | 'post-pending' | 'complete';
2
- type ReleaseFacts = { localTag: boolean; localTagConflict?: boolean; remoteBranch: boolean; remoteTag: boolean; githubRelease: boolean | null; npm: boolean | null; homebrew: boolean | null; smoke: 'success' | 'pending' | 'failed' | null; postCommit: boolean };
2
+ type ReleaseFacts = { localTag: boolean; localTagAncestor?: boolean; localTagConflict?: boolean; remoteBranch: boolean; remoteTag: boolean; githubRelease: boolean | null; npm: boolean | null; homebrew: boolean | null; smoke: 'success' | 'pending' | 'failed' | null; postCommit: boolean };
3
3
 
4
4
  function deriveReleasePhase(facts: ReleaseFacts): ReleasePhase {
5
- if (!facts.localTag) return 'unprepared';
5
+ if (!facts.localTag && !facts.localTagAncestor) return 'unprepared';
6
6
  if (!facts.remoteBranch && !facts.remoteTag) return 'prepared';
7
7
  if (!facts.remoteBranch || !facts.remoteTag) return 'partially-published';
8
8
  if (facts.githubRelease !== true || facts.npm !== true || facts.homebrew !== true) return 'published';
@@ -17,8 +17,9 @@ function releaseSnapshot(version: string, facts: ReleaseFacts) {
17
17
  async function runReleaseAction(input: { version: string; action: 'inspect' | 'prepare' | 'publish' | 'post'; inspect: () => Promise<ReleaseFacts>; prepare?: () => Promise<void>; publish?: () => Promise<void>; post?: () => Promise<void> }) {
18
18
  const before = releaseSnapshot(input.version, await input.inspect());
19
19
  if (input.action === 'inspect') return { status: 'no-op' as const, changed: false, before, snapshot: before, error: null };
20
+ if (before.facts.localTagConflict) return { status: 'failed' as const, changed: false, before, snapshot: before, error: { code: 'GIT_TAG_CONFLICT', message: `Tag ${before.tag} is not reachable from HEAD` } };
20
21
  const allowed = input.action === 'prepare' ? before.phase === 'unprepared'
21
- : input.action === 'publish' ? ['prepared', 'partially-published'].includes(before.phase)
22
+ : input.action === 'publish' ? before.facts.localTag && ['prepared', 'partially-published'].includes(before.phase)
22
23
  : ['published', 'post-pending'].includes(before.phase);
23
24
  if (!allowed) return { status: before.phase === 'complete' ? 'no-op' as const : 'failed' as const, changed: false, before, snapshot: before, error: before.phase === 'complete' ? null : { code: 'RELEASE_PHASE_INVALID', message: `Cannot ${input.action} from ${before.phase}` } };
24
25
  await input[input.action]?.();
@@ -0,0 +1,28 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ export function removeDirRecursive(dir: string): void {
5
+ if (!fs.existsSync(dir)) {
6
+ return;
7
+ }
8
+
9
+ const pending = [dir];
10
+ const directories: string[] = [];
11
+ while (pending.length > 0) {
12
+ const current = pending.pop()!;
13
+ const stat = fs.lstatSync(current);
14
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
15
+ fs.unlinkSync(current);
16
+ continue;
17
+ }
18
+
19
+ directories.push(current);
20
+ for (const entry of fs.readdirSync(current)) {
21
+ pending.push(path.join(current, entry));
22
+ }
23
+ }
24
+
25
+ for (const directory of directories.reverse()) {
26
+ fs.rmdirSync(directory);
27
+ }
28
+ }
package/lib/run/tui.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { renderAgentClientInvocation } from '../agent-clients/invocation.ts';
2
+ import { getAgentClientAdapter } from '../agent-clients/registry.ts';
3
+
1
4
  export type TuiName = 'claude' | 'codex' | 'gemini' | 'opencode';
2
5
 
3
6
  const TUI_NAMES = new Set(['claude', 'codex', 'gemini', 'opencode']);
@@ -27,7 +30,12 @@ export function selectTui(
27
30
 
28
31
  export function renderPrompt(params: { tui: TuiName; skill: string; args: string[] }): string {
29
32
  const suffix = [params.skill, ...params.args].join(' ').trim();
30
- if (params.tui === 'codex') return `$${suffix}`;
33
+ if (params.tui === 'codex') {
34
+ return renderAgentClientInvocation(getAgentClientAdapter('codex').invocation, {
35
+ skillName: params.skill,
36
+ args: params.args
37
+ });
38
+ }
31
39
  if (params.tui === 'gemini') return `/agent-infra:${suffix}`;
32
40
  return `/${suffix}`;
33
41
  }