@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,5 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import crypto from 'node:crypto';
3
4
  import { spawnSync } from 'node:child_process';
4
5
  import semver from 'semver';
5
6
 
@@ -17,12 +18,101 @@ type CommandResult = ReturnType<typeof command>;
17
18
  type CommandRunner = (cwd: string, executable: string, args: string[]) => CommandResult;
18
19
  type DemoResult = {
19
20
  status: 'recorded' | 'skipped' | 'failed';
20
- reasonCode: 'VHS_MISSING' | 'FFMPEG_MISSING' | 'DEMO_COMMAND_FAILED' | 'DEMO_OUTPUT_MISSING' | null;
21
+ reasonCode: 'DEMO_INPUTS_UNCHANGED' | 'GIT_LFS_MISSING' | 'VHS_MISSING' | 'FFMPEG_MISSING'
22
+ | 'DEMO_COMMAND_FAILED' | 'DEMO_OUTPUT_MISSING' | 'DEMO_OUTPUT_INVALID'
23
+ | 'DEMO_OUTPUT_TOO_LARGE' | 'DEMO_DIGEST_FAILED' | null;
21
24
  message: string | null;
22
25
  outputPath: string | null;
23
26
  };
24
27
 
28
+ const DEMO_INPUT_PATHS = [
29
+ 'assets/demo-init.tape',
30
+ 'scripts/demo-regen.sh',
31
+ 'scripts/normalize-gif-duration.py',
32
+ 'bin/cli.ts',
33
+ 'lib/init.ts',
34
+ 'lib/log.ts',
35
+ 'lib/prompt.ts',
36
+ 'lib/paths.ts',
37
+ 'lib/render.ts',
38
+ 'lib/builtin-tuis.ts',
39
+ 'lib/sandbox/engines/',
40
+ 'src/sync-templates.js',
41
+ 'templates/'
42
+ ] as const;
43
+ const DEMO_DIGEST_PATH = 'assets/demo-init.inputs.sha256';
44
+ const DEMO_OUTPUT_PATH = 'assets/demo-init.gif';
45
+ const DEMO_MAX_BYTES = 4 * 1024 * 1024;
46
+
47
+ function demoInputFiles(cwd: string): string[] {
48
+ const files = new Set<string>();
49
+ for (const input of DEMO_INPUT_PATHS) {
50
+ if (input.endsWith('/')) {
51
+ const listed = command(cwd, 'git', ['ls-files', '--', input]);
52
+ if (listed.status !== 0) throw new Error(String(listed.stderr || `Unable to list ${input}`));
53
+ const directoryFiles = String(listed.stdout).split('\n').filter(Boolean);
54
+ if (!directoryFiles.length) throw new Error(`Canonical demo input directory is empty: ${input}`);
55
+ for (const file of directoryFiles) files.add(file.replaceAll('\\', '/'));
56
+ } else {
57
+ files.add(input);
58
+ }
59
+ }
60
+ const sorted = [...files].sort((left, right) => Buffer.from(left).compare(Buffer.from(right)));
61
+ if (!sorted.length) throw new Error('Canonical demo input set is empty');
62
+ for (const file of sorted) {
63
+ const absolute = path.join(cwd, file);
64
+ if (!fs.statSync(absolute).isFile()) throw new Error(`Canonical demo input is not a file: ${file}`);
65
+ }
66
+ return sorted;
67
+ }
68
+
69
+ function computeDemoInputDigest(cwd: string): string {
70
+ const hash = crypto.createHash('sha256');
71
+ for (const file of demoInputFiles(cwd)) {
72
+ const bytes = fs.readFileSync(path.join(cwd, file));
73
+ hash.update(file);
74
+ hash.update('\0');
75
+ hash.update(String(bytes.byteLength));
76
+ hash.update('\0');
77
+ hash.update(bytes);
78
+ hash.update('\0');
79
+ }
80
+ return hash.digest('hex');
81
+ }
82
+
83
+ function validGif(filePath: string): boolean {
84
+ if (!fs.existsSync(filePath)) return false;
85
+ const header = fs.readFileSync(filePath).subarray(0, 6).toString('ascii');
86
+ return header === 'GIF87a' || header === 'GIF89a';
87
+ }
88
+
89
+ function writeDigestAtomically(cwd: string, digest: string): void {
90
+ const target = path.join(cwd, DEMO_DIGEST_PATH);
91
+ const temporary = `${target}.tmp-${process.pid}`;
92
+ fs.writeFileSync(temporary, `${digest}\n`);
93
+ fs.renameSync(temporary, target);
94
+ }
95
+
25
96
  function runOptionalDemo(cwd: string, run: CommandRunner = command): DemoResult {
97
+ let digest: string;
98
+ try {
99
+ digest = computeDemoInputDigest(cwd);
100
+ } catch (error) {
101
+ return { status: 'failed', reasonCode: 'DEMO_DIGEST_FAILED', message: String(error), outputPath: null };
102
+ }
103
+ const digestPath = path.join(cwd, DEMO_DIGEST_PATH);
104
+ const priorDigest = fs.existsSync(digestPath) ? fs.readFileSync(digestPath, 'utf8').trim() : '';
105
+ if (/^[0-9a-f]{64}$/.test(priorDigest) && priorDigest === digest) {
106
+ return { status: 'skipped', reasonCode: 'DEMO_INPUTS_UNCHANGED', message: null, outputPath: null };
107
+ }
108
+ const lfs = run(cwd, 'git', ['lfs', 'version']);
109
+ if (lfs.status !== 0) {
110
+ return { status: 'failed', reasonCode: 'GIT_LFS_MISSING', message: String(lfs.stderr || lfs.stdout), outputPath: null };
111
+ }
112
+ const lfsAttribute = run(cwd, 'git', ['check-attr', 'filter', '--', DEMO_OUTPUT_PATH]);
113
+ if (lfsAttribute.status !== 0 || !String(lfsAttribute.stdout).trim().endsWith(': lfs')) {
114
+ return { status: 'failed', reasonCode: 'GIT_LFS_MISSING', message: `${DEMO_OUTPUT_PATH} is not tracked by Git LFS`, outputPath: null };
115
+ }
26
116
  const vhs = run(cwd, 'vhs', ['--version']);
27
117
  if (vhs.status !== 0) return { status: 'skipped', reasonCode: 'VHS_MISSING', message: null, outputPath: null };
28
118
  const ffmpeg = run(cwd, 'ffmpeg', ['-version']);
@@ -36,7 +126,7 @@ function runOptionalDemo(cwd: string, run: CommandRunner = command): DemoResult
36
126
  outputPath: null
37
127
  };
38
128
  }
39
- const outputPath = 'assets/demo-init.gif';
129
+ const outputPath = DEMO_OUTPUT_PATH;
40
130
  if (!fs.existsSync(path.join(cwd, outputPath))) {
41
131
  return {
42
132
  status: 'failed',
@@ -45,14 +135,73 @@ function runOptionalDemo(cwd: string, run: CommandRunner = command): DemoResult
45
135
  outputPath: null
46
136
  };
47
137
  }
138
+ const absoluteOutput = path.join(cwd, outputPath);
139
+ if (!validGif(absoluteOutput)) {
140
+ return { status: 'failed', reasonCode: 'DEMO_OUTPUT_INVALID', message: `${outputPath} is not a GIF`, outputPath: null };
141
+ }
142
+ if (fs.statSync(absoluteOutput).size > DEMO_MAX_BYTES) {
143
+ return { status: 'failed', reasonCode: 'DEMO_OUTPUT_TOO_LARGE', message: `${outputPath} exceeds 4 MiB`, outputPath: null };
144
+ }
145
+ const pointer = run(cwd, 'git', ['lfs', 'pointer', `--file=${outputPath}`]);
146
+ if (pointer.status !== 0 || !String(pointer.stdout).includes(`size ${fs.statSync(absoluteOutput).size}`)) {
147
+ return { status: 'failed', reasonCode: 'DEMO_OUTPUT_INVALID', message: 'Git LFS could not produce a matching pointer', outputPath: null };
148
+ }
149
+ writeDigestAtomically(cwd, digest);
48
150
  return { status: 'recorded', reasonCode: null, message: null, outputPath };
49
151
  }
50
152
 
51
- function git(cwd: string, args: string[]): string | null {
52
- const result = command(cwd, 'git', args);
153
+ function git(cwd: string, args: string[], run: CommandRunner = command): string | null {
154
+ const result = run(cwd, 'git', args);
53
155
  return result.status === 0 ? String(result.stdout).trim() : null;
54
156
  }
55
157
 
158
+ function inspectLocalReleaseFacts(cwd: string, version: string, run: CommandRunner = command) {
159
+ const tag = `v${version}`;
160
+ const head = git(cwd, ['rev-parse', 'HEAD'], run);
161
+ const tagCommit = git(cwd, ['rev-parse', '--verify', `${tag}^{commit}`], run);
162
+ const localTag = Boolean(tagCommit && tagCommit === head);
163
+ const localTagAncestor = Boolean(
164
+ tagCommit && head && !localTag
165
+ && run(cwd, 'git', ['merge-base', '--is-ancestor', tagCommit, head]).status === 0
166
+ );
167
+ const localTagConflict = Boolean(tagCommit && !localTag && !localTagAncestor);
168
+ const postSubject = `chore: prepare next dev iteration after v${version}`;
169
+ const postLog = localTag || localTagAncestor
170
+ ? git(cwd, ['log', '--format=%s', `${tag}..HEAD`], run) ?? ''
171
+ : '';
172
+ const postCommit = postLog.split('\n').some((subject) => subject === postSubject);
173
+ return { localTag, localTagAncestor, localTagConflict, postCommit };
174
+ }
175
+
176
+ function releaseSmokeStatus(workflows: Array<Record<string, unknown>>, version: string, tagCommit: string | null): ReleaseFacts['smoke'] {
177
+ const manualTitle = `Post-Release Smoke v${version}`;
178
+ const targets = workflows.filter((run) => {
179
+ if (String(run.workflowName || run.name) !== 'Post-Release Smoke') return false;
180
+ if (String(run.event) === 'workflow_run') return Boolean(tagCommit && String(run.headSha) === tagCommit);
181
+ return String(run.event) === 'workflow_dispatch' && String(run.displayTitle) === manualTitle;
182
+ }).sort((left, right) => {
183
+ const leftOrder = [Date.parse(String(left.createdAt || '')) || 0, Number(left.databaseId) || 0, Number(left.attempt) || 0];
184
+ const rightOrder = [Date.parse(String(right.createdAt || '')) || 0, Number(right.databaseId) || 0, Number(right.attempt) || 0];
185
+ for (let index = 0; index < leftOrder.length; index += 1) {
186
+ if (leftOrder[index] !== rightOrder[index]) return rightOrder[index]! - leftOrder[index]!;
187
+ }
188
+ return 0;
189
+ });
190
+ const latest = targets[0];
191
+ if (!latest) return null;
192
+ if (String(latest.status) !== 'completed') return 'pending';
193
+ return String(latest.conclusion) === 'success' ? 'success' : 'failed';
194
+ }
195
+
196
+ function inspectPostWorktree(cwd: string) {
197
+ const inspected = inspectGitWorkflow(cwd);
198
+ if (!inspected.snapshot) return inspected.error ?? { code: 'GIT_INSPECT_FAILED', message: 'Unable to inspect Git repository' };
199
+ if (inspected.snapshot.worktree.length || inspected.snapshot.staged.length) {
200
+ return { code: 'WORKTREE_DIRTY', message: 'Release post requires a clean worktree' };
201
+ }
202
+ return null;
203
+ }
204
+
56
205
  async function inspectFacts(cwd: string, version: string): Promise<ReleaseFacts> {
57
206
  const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8')) as { name: string };
58
207
  const config = JSON.parse(fs.readFileSync(path.join(cwd, '.agents', '.airc.json'), 'utf8')) as { project: string; org: string };
@@ -60,6 +209,7 @@ async function inspectFacts(cwd: string, version: string): Promise<ReleaseFacts>
60
209
  const branch = git(cwd, ['branch', '--show-current']) || '';
61
210
  const head = git(cwd, ['rev-parse', 'HEAD']);
62
211
  const localTagTarget = git(cwd, ['rev-parse', '--verify', `${tag}^{commit}`]);
212
+ const local = inspectLocalReleaseFacts(cwd, version);
63
213
  const remoteBranch = branch ? git(cwd, ['ls-remote', '--heads', 'origin', branch]) : null;
64
214
  const remoteTag = git(cwd, ['ls-remote', '--tags', 'origin', `refs/tags/${tag}`]);
65
215
  const platform = inspectPlatformRelease(tag, { cwd });
@@ -67,18 +217,14 @@ async function inspectFacts(cwd: string, version: string): Promise<ReleaseFacts>
67
217
  const formulaUrl = `https://raw.githubusercontent.com/${config.org}/homebrew-tap/main/Formula/${config.project}.rb`;
68
218
  const homebrew = await inspectHomebrewChannel(formulaUrl, version);
69
219
  const workflows = platform.workflows as Array<Record<string, unknown>>;
70
- const smokeRun = workflows.find((run) => String(run.name).toLowerCase().includes('post-release-smoke') && String(run.headBranch) === tag);
71
- const smoke = smokeRun ? String(smokeRun.conclusion) === 'success' ? 'success' : String(smokeRun.status) === 'completed' ? 'failed' : 'pending' : null;
72
- const postMessage = git(cwd, ['log', '-1', '--pretty=%s']) || '';
220
+ const smoke = releaseSmokeStatus(workflows, version, localTagTarget);
73
221
  return {
74
- localTag: Boolean(localTagTarget && localTagTarget === head),
75
- localTagConflict: Boolean(localTagTarget && localTagTarget !== head),
222
+ ...local,
76
223
  remoteBranch: Boolean(remoteBranch && remoteBranch.split(/\s+/)[0] === head), remoteTag: Boolean(remoteTag),
77
224
  githubRelease: platform.platform.type !== 'github'
78
225
  ? true
79
226
  : platform.status === 'blocked' ? null : Boolean(platform.release?.published),
80
- npm: npm.published, homebrew: homebrew.published, smoke,
81
- postCommit: postMessage.includes(`after v${version}`)
227
+ npm: npm.published, homebrew: homebrew.published, smoke
82
228
  };
83
229
  }
84
230
 
@@ -116,12 +262,11 @@ async function releaseWorkflow(args: string[] = []): Promise<void> {
116
262
  }
117
263
  const before = releaseSnapshot(version, await inspectFacts(cwd, version));
118
264
  if (action === 'inspect') { process.stdout.write(`${JSON.stringify({ status: 'no-op', changed: false, snapshot: before, error: null })}\n`); return; }
265
+ if (before.facts.localTagConflict) {
266
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, snapshot: before, error: { code: 'GIT_TAG_CONFLICT', message: `Tag v${version} is not reachable from HEAD` } })}\n`);
267
+ process.exitCode = 1; return;
268
+ }
119
269
  if (action === 'prepare') {
120
- if (before.facts.localTagConflict) {
121
- process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, snapshot: before, error: { code: 'GIT_TAG_CONFLICT', message: `Tag v${version} does not point to HEAD` } })}\n`);
122
- process.exitCode = 1;
123
- return;
124
- }
125
270
  if (before.phase !== 'unprepared') {
126
271
  const milestones = reconcileReleaseMilestones(version, { cwd });
127
272
  process.stdout.write(`${JSON.stringify({ ...milestones, snapshot: before })}\n`);
@@ -152,16 +297,25 @@ async function releaseWorkflow(args: string[] = []): Promise<void> {
152
297
  process.exitCode = failed ? 1 : blocked ? 2 : 0; return;
153
298
  }
154
299
  if (action === 'publish') {
155
- if (!['prepared', 'partially-published'].includes(before.phase)) { process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, snapshot: before, error: { code: 'RELEASE_PHASE_INVALID', message: `Cannot publish from ${before.phase}` } })}\n`); process.exitCode = 1; return; }
300
+ if (!before.facts.localTag || !['prepared', 'partially-published'].includes(before.phase)) { process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, snapshot: before, error: { code: 'RELEASE_PHASE_INVALID', message: `Cannot publish from ${before.phase}` } })}\n`); process.exitCode = 1; return; }
156
301
  const branch = git(cwd, ['branch', '--show-current']) || '';
157
302
  const refs = [...(!before.facts.remoteBranch ? [branch] : []), ...(!before.facts.remoteTag ? [`refs/tags/v${version}`] : [])];
158
303
  const result = pushGitRefs({ cwd, remote: 'origin', refs });
159
304
  process.stdout.write(`${JSON.stringify({ ...result, snapshot: releaseSnapshot(version, await inspectFacts(cwd, version)) })}\n`); process.exitCode = result.status === 'failed' ? 1 : result.status === 'degraded' ? 2 : 0; return;
160
305
  }
306
+ if (before.phase === 'complete') {
307
+ process.stdout.write(`${JSON.stringify({ status: 'no-op', changed: false, snapshot: before, error: null })}\n`);
308
+ return;
309
+ }
161
310
  const channelsComplete = before.facts.githubRelease === true && before.facts.npm === true && before.facts.homebrew === true;
162
311
  if (!['published', 'post-pending'].includes(before.phase) || !channelsComplete || before.facts.smoke !== 'success') {
163
312
  process.stdout.write(`${JSON.stringify({ status: before.facts.smoke === 'failed' ? 'failed' : 'blocked', changed: false, snapshot: before, error: { code: 'RELEASE_CHANNELS_PENDING', message: 'Release channels or smoke workflow are not complete' } })}\n`); process.exitCode = before.facts.smoke === 'failed' ? 1 : 2; return;
164
313
  }
314
+ const worktreeError = inspectPostWorktree(cwd);
315
+ if (worktreeError) {
316
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, snapshot: before, error: worktreeError })}\n`);
317
+ process.exitCode = 1; return;
318
+ }
165
319
  const built = command(cwd, 'npm', ['run', 'build']);
166
320
  if (built.status !== 0) {
167
321
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: true, snapshot: before, error: { code: 'RELEASE_POST_COMMAND_FAILED', message: String(built.stderr || built.stdout) } })}\n`);
@@ -182,5 +336,5 @@ async function releaseWorkflow(args: string[] = []): Promise<void> {
182
336
  process.stdout.write(`${JSON.stringify({ ...pushed, demo, snapshot: releaseSnapshot(version, await inspectFacts(cwd, version)) })}\n`); process.exitCode = pushed.status === 'failed' ? 1 : pushed.status === 'degraded' ? 2 : 0;
183
337
  }
184
338
 
185
- export { inspectFacts, releaseWorkflow, runOptionalDemo };
339
+ export { computeDemoInputDigest, inspectFacts, inspectLocalReleaseFacts, inspectPostWorktree, releaseSmokeStatus, releaseWorkflow, runOptionalDemo };
186
340
  export type { CommandRunner, DemoResult };
@@ -0,0 +1,132 @@
1
+ import {
2
+ activateMatchingOrchestrationDelegation,
3
+ activateOrchestrationDelegation,
4
+ advanceOrchestration,
5
+ beginOrResumeOrchestration,
6
+ completeCommitOrchestrationStage,
7
+ pauseOrchestration,
8
+ prepareOrchestrationDelegation,
9
+ routeOrchestration,
10
+ sealMatchingOrchestrationDelegation,
11
+ sealOrchestrationDelegation,
12
+ statusOrchestration
13
+ } from '../task/orchestration.ts';
14
+ import type { AgentClientId } from '../agent-clients/types.ts';
15
+
16
+ const USAGE = 'Usage: agent-infra-internal task-orchestration <task-ref|auto> <begin-or-resume|route|prepare|hook-start|stage-completed|hook-stop|advance|pause|status> [options]\n';
17
+
18
+ function usageFailure(message: string): void {
19
+ process.stdout.write(`${JSON.stringify({
20
+ status: 'failed', changed: false,
21
+ error: { code: 'ORCHESTRATION_PAYLOAD_INVALID', message }
22
+ })}\n`);
23
+ process.stderr.write(USAGE);
24
+ process.exitCode = 2;
25
+ }
26
+
27
+ function taskOrchestration(args: string[] = []): void {
28
+ if (args[0] === '--help' || args[0] === '-h') {
29
+ process.stdout.write(USAGE);
30
+ return;
31
+ }
32
+ if (args.length < 2) {
33
+ usageFailure('task ref and intent are required');
34
+ return;
35
+ }
36
+ const [taskRef, intent] = args;
37
+ if (!['begin-or-resume', 'route', 'prepare', 'hook-start', 'stage-completed', 'hook-stop', 'advance', 'pause', 'status'].includes(intent!)) {
38
+ usageFailure(`unknown intent '${intent}'`);
39
+ return;
40
+ }
41
+ const values: Record<string, string> = {};
42
+ const seen = new Set<string>();
43
+ for (let index = 2; index < args.length; index += 1) {
44
+ const flag = args[index]!;
45
+ if (![
46
+ '--max-steps', '--client', '--requested-model', '--parent-id', '--before-fingerprint',
47
+ '--native-agent', '--child-id', '--spawn-mode', '--actual-model', '--fallback-reason',
48
+ '--exit-code', '--after-fingerprint', '--changed-paths', '--code', '--message', '--recoverable', '--agent'
49
+ ].includes(flag)) {
50
+ usageFailure(`unknown option '${flag}'`);
51
+ return;
52
+ }
53
+ if (seen.has(flag)) {
54
+ usageFailure(`duplicate option '${flag}'`);
55
+ return;
56
+ }
57
+ seen.add(flag);
58
+ const value = args[++index];
59
+ if (!value || value.startsWith('--')) {
60
+ usageFailure(`option '${flag}' requires a value`);
61
+ return;
62
+ }
63
+ values[flag] = value;
64
+ }
65
+ const requireValues = (flags: string[]) => flags.find((flag) => values[flag] === undefined);
66
+ let result;
67
+ if (intent === 'begin-or-resume') {
68
+ const maxSteps = values['--max-steps'] === undefined ? undefined : Number(values['--max-steps']);
69
+ if (maxSteps !== undefined && (!Number.isInteger(maxSteps) || maxSteps < 1)) {
70
+ usageFailure('--max-steps must be a positive integer'); return;
71
+ }
72
+ result = beginOrResumeOrchestration(taskRef!, { maxSteps });
73
+ } else if (intent === 'route') {
74
+ result = routeOrchestration(taskRef!);
75
+ } else if (intent === 'status') {
76
+ result = statusOrchestration(taskRef!);
77
+ } else if (intent === 'prepare') {
78
+ const missing = requireValues(['--client']);
79
+ if (missing) { usageFailure(`intent 'prepare' requires '${missing}'`); return; }
80
+ result = prepareOrchestrationDelegation(taskRef!, {
81
+ client: values['--client'] as AgentClientId,
82
+ requestedModel: values['--requested-model']
83
+ });
84
+ } else if (intent === 'hook-start') {
85
+ const missing = requireValues([
86
+ ...(taskRef === 'auto' ? ['--client'] : []),
87
+ '--native-agent', '--child-id', '--parent-id', '--spawn-mode'
88
+ ]);
89
+ if (missing) { usageFailure(`intent 'hook-start' requires '${missing}'`); return; }
90
+ const event = {
91
+ nativeAgent: values['--native-agent']!, childId: values['--child-id']!,
92
+ parentId: values['--parent-id']!, spawnMode: values['--spawn-mode']!,
93
+ actualModel: values['--actual-model'], modelFallbackReason: values['--fallback-reason']
94
+ };
95
+ result = taskRef === 'auto'
96
+ ? activateMatchingOrchestrationDelegation(values['--client'] as AgentClientId, event)
97
+ : activateOrchestrationDelegation(taskRef!, event);
98
+ } else if (intent === 'hook-stop') {
99
+ if (taskRef === 'auto') {
100
+ const missing = requireValues(['--client', '--native-agent', '--child-id']);
101
+ if (missing) { usageFailure(`intent 'hook-stop' requires '${missing}'`); return; }
102
+ result = sealMatchingOrchestrationDelegation(values['--client'] as AgentClientId, {
103
+ nativeAgent: values['--native-agent']!, childId: values['--child-id']!
104
+ });
105
+ } else {
106
+ const missing = requireValues(['--child-id', '--exit-code', '--after-fingerprint']);
107
+ if (missing) { usageFailure(`intent 'hook-stop' requires '${missing}'`); return; }
108
+ const exitCode = Number(values['--exit-code']);
109
+ if (!Number.isInteger(exitCode)) { usageFailure('--exit-code must be an integer'); return; }
110
+ result = sealOrchestrationDelegation(taskRef!, {
111
+ childId: values['--child-id']!, exitCode,
112
+ afterFingerprint: values['--after-fingerprint']!,
113
+ changedPaths: values['--changed-paths'] ? values['--changed-paths']!.split(',').filter(Boolean) : []
114
+ });
115
+ }
116
+ } else if (intent === 'stage-completed') {
117
+ const missing = requireValues(['--agent']);
118
+ if (missing) { usageFailure(`intent 'stage-completed' requires '${missing}'`); return; }
119
+ result = completeCommitOrchestrationStage(taskRef!, values['--agent']!);
120
+ } else if (intent === 'advance') {
121
+ result = advanceOrchestration(taskRef!);
122
+ } else {
123
+ const missing = requireValues(['--code', '--message', '--recoverable']);
124
+ if (missing) { usageFailure(`intent 'pause' requires '${missing}'`); return; }
125
+ if (!['true', 'false'].includes(values['--recoverable']!)) { usageFailure('--recoverable must be true or false'); return; }
126
+ result = pauseOrchestration(taskRef!, values['--code']!, values['--message']!, values['--recoverable'] === 'true');
127
+ }
128
+ process.stdout.write(`${JSON.stringify(result)}\n`);
129
+ if (result.status === 'failed') process.exitCode = 1;
130
+ }
131
+
132
+ export { taskOrchestration };
@@ -0,0 +1,65 @@
1
+ import { finalizeReviewSummary } from '../task/review-finalization.ts';
2
+
3
+ const USAGE = 'Usage: agent-infra-internal task-review <task-ref> finalize-summary --stage <analysis|plan|code> --artifact <review-*.md> [--dry-run]\n';
4
+
5
+ function failUsage(message: string): void {
6
+ process.stdout.write(`${JSON.stringify({
7
+ status: 'failed',
8
+ changed: false,
9
+ intent: 'finalize-summary',
10
+ error: { code: 'REVIEW_PAYLOAD_INVALID', message }
11
+ })}\n`);
12
+ process.stderr.write(USAGE);
13
+ process.exitCode = 2;
14
+ }
15
+
16
+ function taskReview(args: string[] = []): void {
17
+ if (args[0] === '--help' || args[0] === '-h') {
18
+ process.stdout.write(USAGE);
19
+ return;
20
+ }
21
+ if (args.length < 2) {
22
+ failUsage('task ref and intent are required');
23
+ return;
24
+ }
25
+ if (args[1] !== 'finalize-summary') {
26
+ failUsage(`unknown intent '${args[1]}'`);
27
+ return;
28
+ }
29
+ let stage = '';
30
+ let artifact = '';
31
+ let dryRun = false;
32
+ const seen = new Set<string>();
33
+ for (let index = 2; index < args.length; index += 1) {
34
+ const flag = args[index]!;
35
+ if (!['--stage', '--artifact', '--dry-run'].includes(flag)) {
36
+ failUsage(`unknown option '${flag}'`);
37
+ return;
38
+ }
39
+ if (seen.has(flag)) {
40
+ failUsage(`duplicate option '${flag}'`);
41
+ return;
42
+ }
43
+ seen.add(flag);
44
+ if (flag === '--dry-run') {
45
+ dryRun = true;
46
+ continue;
47
+ }
48
+ const value = args[++index];
49
+ if (!value || value.startsWith('--')) {
50
+ failUsage(`option '${flag}' requires a value`);
51
+ return;
52
+ }
53
+ if (flag === '--stage') stage = value;
54
+ else artifact = value;
55
+ }
56
+ if (!stage || !artifact) {
57
+ failUsage("options '--stage' and '--artifact' are required");
58
+ return;
59
+ }
60
+ const result = finalizeReviewSummary({ taskRef: args[0]!, stage, artifact, dryRun });
61
+ process.stdout.write(`${JSON.stringify(result)}\n`);
62
+ if (result.status === 'failed') process.exitCode = 1;
63
+ }
64
+
65
+ export { taskReview };
package/lib/merge.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { info, ok } from './log.ts';
4
+ import { removeDirRecursive } from './remove-dir.ts';
4
5
 
5
6
  const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
6
7
  const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
@@ -690,7 +691,7 @@ function mergeMutableSections({ sourceWorkspace, localWorkspace, backupRoot, rep
690
691
  if (comparison > 0) {
691
692
  backupTaskDir(backupRoot, localMatch.section, localMatch.taskDir, localMatch.taskId);
692
693
  report.backupCount += 1;
693
- fs.rmSync(localMatch.taskDir, { recursive: true, force: true });
694
+ removeDirRecursive(localMatch.taskDir);
694
695
 
695
696
  const destinationDir = copyTaskToSection(sourceTask, localWorkspace);
696
697
  localIndex.set(sourceTask.taskId, {
@@ -1,4 +1,4 @@
1
- import type { PlatformResult } from './types.ts';
1
+ import type { PlatformError, PlatformResult } from './types.ts';
2
2
 
3
3
  type PlatformAdapterContext = {
4
4
  cwd: string;
@@ -6,9 +6,71 @@ type PlatformAdapterContext = {
6
6
  client?: unknown;
7
7
  };
8
8
 
9
+ type PlatformChangeRequestSnapshot = {
10
+ repository: string;
11
+ number: number;
12
+ nodeId: string;
13
+ url: string;
14
+ state: 'open' | 'closed';
15
+ title: string;
16
+ body: string;
17
+ draft: boolean;
18
+ head: { repository: string; ref: string; sha: string };
19
+ base: { repository: string; ref: string; sha: string };
20
+ mergedAt: string | null;
21
+ mergeCommitSha: string | null;
22
+ labels: string[];
23
+ assignees: string[];
24
+ milestone: string | null;
25
+ };
26
+
27
+ type PlatformCheckSnapshot = {
28
+ name: string;
29
+ bucket: 'pass' | 'fail' | 'pending' | 'cancel';
30
+ workflow?: string | null;
31
+ conclusion?: string | null;
32
+ detailsUrl?: string | null;
33
+ startedAt?: string | null;
34
+ completedAt?: string | null;
35
+ };
36
+
37
+ type PlatformInspectionResult<T> = {
38
+ ok: boolean;
39
+ value?: T;
40
+ error?: PlatformError;
41
+ };
42
+
43
+ type ChangeRequestInspectionContext = PlatformAdapterContext & {
44
+ repository: string;
45
+ number: number;
46
+ };
47
+
48
+ type RequiredChecksInspectionContext = ChangeRequestInspectionContext & {
49
+ headSha: string;
50
+ };
51
+
52
+ type ChangeRequestGitEvidenceContext = ChangeRequestInspectionContext & {
53
+ pullRequest: PlatformChangeRequestSnapshot;
54
+ };
55
+
56
+ type PlatformChangeRequestGitEvidenceSpec = {
57
+ remoteUrl: string;
58
+ reviewedHeadRef: string;
59
+ targetHeadRef: string;
60
+ };
61
+
9
62
  type PlatformAdapter = {
10
63
  type: string;
11
64
  resolveContext(context: PlatformAdapterContext): PlatformResult;
65
+ inspectChangeRequest?(
66
+ context: ChangeRequestInspectionContext
67
+ ): PlatformInspectionResult<PlatformChangeRequestSnapshot>;
68
+ inspectRequiredChecks?(
69
+ context: RequiredChecksInspectionContext
70
+ ): PlatformInspectionResult<PlatformCheckSnapshot[]>;
71
+ resolveChangeRequestGitEvidence?(
72
+ context: ChangeRequestGitEvidenceContext
73
+ ): PlatformInspectionResult<PlatformChangeRequestGitEvidenceSpec>;
12
74
  };
13
75
 
14
76
  const adapters = new Map<string, PlatformAdapter>();
@@ -22,9 +84,86 @@ function getPlatformAdapter(type: string | null): PlatformAdapter | null {
22
84
  return type ? adapters.get(type) ?? null : null;
23
85
  }
24
86
 
87
+ function registerPlatformCapabilities(
88
+ type: string,
89
+ capabilities: Pick<
90
+ PlatformAdapter,
91
+ 'inspectChangeRequest' | 'inspectRequiredChecks' | 'resolveChangeRequestGitEvidence'
92
+ >
93
+ ): void {
94
+ const adapter = adapters.get(type);
95
+ if (!adapter) throw new Error(`Platform adapter '${type}' must be registered before its capabilities`);
96
+ adapters.set(type, { ...adapter, ...capabilities });
97
+ }
98
+
99
+ function hasPlatformCapability(
100
+ type: string | null,
101
+ capability: 'change-request' | 'required-checks' | 'change-request-git-evidence'
102
+ ): boolean {
103
+ const adapter = getPlatformAdapter(type);
104
+ if (capability === 'change-request') return typeof adapter?.inspectChangeRequest === 'function';
105
+ if (capability === 'required-checks') return typeof adapter?.inspectRequiredChecks === 'function';
106
+ return typeof adapter?.resolveChangeRequestGitEvidence === 'function';
107
+ }
108
+
109
+ function unsupported<T>(type: string | null, capability: string): PlatformInspectionResult<T> {
110
+ return {
111
+ ok: false,
112
+ error: {
113
+ code: 'PLATFORM_CAPABILITY_UNSUPPORTED',
114
+ message: `Platform '${type || 'none'}' does not provide ${capability}`,
115
+ retryable: false
116
+ }
117
+ };
118
+ }
119
+
120
+ function inspectPlatformChangeRequest(
121
+ type: string | null,
122
+ context: ChangeRequestInspectionContext
123
+ ): PlatformInspectionResult<PlatformChangeRequestSnapshot> {
124
+ const adapter = getPlatformAdapter(type);
125
+ return adapter?.inspectChangeRequest?.(context) ?? unsupported(type, 'change-request inspection');
126
+ }
127
+
128
+ function inspectPlatformRequiredChecks(
129
+ type: string | null,
130
+ context: RequiredChecksInspectionContext
131
+ ): PlatformInspectionResult<PlatformCheckSnapshot[]> {
132
+ const adapter = getPlatformAdapter(type);
133
+ return adapter?.inspectRequiredChecks?.(context) ?? unsupported(type, 'required-checks inspection');
134
+ }
135
+
136
+ function resolvePlatformChangeRequestGitEvidence(
137
+ type: string | null,
138
+ context: ChangeRequestGitEvidenceContext
139
+ ): PlatformInspectionResult<PlatformChangeRequestGitEvidenceSpec> {
140
+ const adapter = getPlatformAdapter(type);
141
+ return adapter?.resolveChangeRequestGitEvidence?.(context) ??
142
+ unsupported(type, 'change-request Git evidence');
143
+ }
144
+
25
145
  function listPlatformAdapters(): string[] {
26
146
  return [...adapters.keys()].sort();
27
147
  }
28
148
 
29
- export { getPlatformAdapter, listPlatformAdapters, registerPlatformAdapter };
30
- export type { PlatformAdapter, PlatformAdapterContext };
149
+ export {
150
+ getPlatformAdapter,
151
+ hasPlatformCapability,
152
+ inspectPlatformChangeRequest,
153
+ inspectPlatformRequiredChecks,
154
+ listPlatformAdapters,
155
+ registerPlatformAdapter,
156
+ registerPlatformCapabilities,
157
+ resolvePlatformChangeRequestGitEvidence
158
+ };
159
+ export type {
160
+ ChangeRequestGitEvidenceContext,
161
+ ChangeRequestInspectionContext,
162
+ PlatformAdapter,
163
+ PlatformAdapterContext,
164
+ PlatformChangeRequestGitEvidenceSpec,
165
+ PlatformChangeRequestSnapshot,
166
+ PlatformCheckSnapshot,
167
+ PlatformInspectionResult,
168
+ RequiredChecksInspectionContext
169
+ };