@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
  import { commitExplicitPaths, inspectGitWorkflow, pushGitRefs } from '../git/workflow.js';
@@ -9,7 +10,97 @@ import { releaseSnapshot } from '../release/workflow.js';
9
10
  function command(cwd, executable, args) {
10
11
  return spawnSync(executable, args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
11
12
  }
13
+ const DEMO_INPUT_PATHS = [
14
+ 'assets/demo-init.tape',
15
+ 'scripts/demo-regen.sh',
16
+ 'scripts/normalize-gif-duration.py',
17
+ 'bin/cli.ts',
18
+ 'lib/init.ts',
19
+ 'lib/log.ts',
20
+ 'lib/prompt.ts',
21
+ 'lib/paths.ts',
22
+ 'lib/render.ts',
23
+ 'lib/builtin-tuis.ts',
24
+ 'lib/sandbox/engines/',
25
+ 'src/sync-templates.js',
26
+ 'templates/'
27
+ ];
28
+ const DEMO_DIGEST_PATH = 'assets/demo-init.inputs.sha256';
29
+ const DEMO_OUTPUT_PATH = 'assets/demo-init.gif';
30
+ const DEMO_MAX_BYTES = 4 * 1024 * 1024;
31
+ function demoInputFiles(cwd) {
32
+ const files = new Set();
33
+ for (const input of DEMO_INPUT_PATHS) {
34
+ if (input.endsWith('/')) {
35
+ const listed = command(cwd, 'git', ['ls-files', '--', input]);
36
+ if (listed.status !== 0)
37
+ throw new Error(String(listed.stderr || `Unable to list ${input}`));
38
+ const directoryFiles = String(listed.stdout).split('\n').filter(Boolean);
39
+ if (!directoryFiles.length)
40
+ throw new Error(`Canonical demo input directory is empty: ${input}`);
41
+ for (const file of directoryFiles)
42
+ files.add(file.replaceAll('\\', '/'));
43
+ }
44
+ else {
45
+ files.add(input);
46
+ }
47
+ }
48
+ const sorted = [...files].sort((left, right) => Buffer.from(left).compare(Buffer.from(right)));
49
+ if (!sorted.length)
50
+ throw new Error('Canonical demo input set is empty');
51
+ for (const file of sorted) {
52
+ const absolute = path.join(cwd, file);
53
+ if (!fs.statSync(absolute).isFile())
54
+ throw new Error(`Canonical demo input is not a file: ${file}`);
55
+ }
56
+ return sorted;
57
+ }
58
+ function computeDemoInputDigest(cwd) {
59
+ const hash = crypto.createHash('sha256');
60
+ for (const file of demoInputFiles(cwd)) {
61
+ const bytes = fs.readFileSync(path.join(cwd, file));
62
+ hash.update(file);
63
+ hash.update('\0');
64
+ hash.update(String(bytes.byteLength));
65
+ hash.update('\0');
66
+ hash.update(bytes);
67
+ hash.update('\0');
68
+ }
69
+ return hash.digest('hex');
70
+ }
71
+ function validGif(filePath) {
72
+ if (!fs.existsSync(filePath))
73
+ return false;
74
+ const header = fs.readFileSync(filePath).subarray(0, 6).toString('ascii');
75
+ return header === 'GIF87a' || header === 'GIF89a';
76
+ }
77
+ function writeDigestAtomically(cwd, digest) {
78
+ const target = path.join(cwd, DEMO_DIGEST_PATH);
79
+ const temporary = `${target}.tmp-${process.pid}`;
80
+ fs.writeFileSync(temporary, `${digest}\n`);
81
+ fs.renameSync(temporary, target);
82
+ }
12
83
  function runOptionalDemo(cwd, run = command) {
84
+ let digest;
85
+ try {
86
+ digest = computeDemoInputDigest(cwd);
87
+ }
88
+ catch (error) {
89
+ return { status: 'failed', reasonCode: 'DEMO_DIGEST_FAILED', message: String(error), outputPath: null };
90
+ }
91
+ const digestPath = path.join(cwd, DEMO_DIGEST_PATH);
92
+ const priorDigest = fs.existsSync(digestPath) ? fs.readFileSync(digestPath, 'utf8').trim() : '';
93
+ if (/^[0-9a-f]{64}$/.test(priorDigest) && priorDigest === digest) {
94
+ return { status: 'skipped', reasonCode: 'DEMO_INPUTS_UNCHANGED', message: null, outputPath: null };
95
+ }
96
+ const lfs = run(cwd, 'git', ['lfs', 'version']);
97
+ if (lfs.status !== 0) {
98
+ return { status: 'failed', reasonCode: 'GIT_LFS_MISSING', message: String(lfs.stderr || lfs.stdout), outputPath: null };
99
+ }
100
+ const lfsAttribute = run(cwd, 'git', ['check-attr', 'filter', '--', DEMO_OUTPUT_PATH]);
101
+ if (lfsAttribute.status !== 0 || !String(lfsAttribute.stdout).trim().endsWith(': lfs')) {
102
+ return { status: 'failed', reasonCode: 'GIT_LFS_MISSING', message: `${DEMO_OUTPUT_PATH} is not tracked by Git LFS`, outputPath: null };
103
+ }
13
104
  const vhs = run(cwd, 'vhs', ['--version']);
14
105
  if (vhs.status !== 0)
15
106
  return { status: 'skipped', reasonCode: 'VHS_MISSING', message: null, outputPath: null };
@@ -25,7 +116,7 @@ function runOptionalDemo(cwd, run = command) {
25
116
  outputPath: null
26
117
  };
27
118
  }
28
- const outputPath = 'assets/demo-init.gif';
119
+ const outputPath = DEMO_OUTPUT_PATH;
29
120
  if (!fs.existsSync(path.join(cwd, outputPath))) {
30
121
  return {
31
122
  status: 'failed',
@@ -34,12 +125,72 @@ function runOptionalDemo(cwd, run = command) {
34
125
  outputPath: null
35
126
  };
36
127
  }
128
+ const absoluteOutput = path.join(cwd, outputPath);
129
+ if (!validGif(absoluteOutput)) {
130
+ return { status: 'failed', reasonCode: 'DEMO_OUTPUT_INVALID', message: `${outputPath} is not a GIF`, outputPath: null };
131
+ }
132
+ if (fs.statSync(absoluteOutput).size > DEMO_MAX_BYTES) {
133
+ return { status: 'failed', reasonCode: 'DEMO_OUTPUT_TOO_LARGE', message: `${outputPath} exceeds 4 MiB`, outputPath: null };
134
+ }
135
+ const pointer = run(cwd, 'git', ['lfs', 'pointer', `--file=${outputPath}`]);
136
+ if (pointer.status !== 0 || !String(pointer.stdout).includes(`size ${fs.statSync(absoluteOutput).size}`)) {
137
+ return { status: 'failed', reasonCode: 'DEMO_OUTPUT_INVALID', message: 'Git LFS could not produce a matching pointer', outputPath: null };
138
+ }
139
+ writeDigestAtomically(cwd, digest);
37
140
  return { status: 'recorded', reasonCode: null, message: null, outputPath };
38
141
  }
39
- function git(cwd, args) {
40
- const result = command(cwd, 'git', args);
142
+ function git(cwd, args, run = command) {
143
+ const result = run(cwd, 'git', args);
41
144
  return result.status === 0 ? String(result.stdout).trim() : null;
42
145
  }
146
+ function inspectLocalReleaseFacts(cwd, version, run = command) {
147
+ const tag = `v${version}`;
148
+ const head = git(cwd, ['rev-parse', 'HEAD'], run);
149
+ const tagCommit = git(cwd, ['rev-parse', '--verify', `${tag}^{commit}`], run);
150
+ const localTag = Boolean(tagCommit && tagCommit === head);
151
+ const localTagAncestor = Boolean(tagCommit && head && !localTag
152
+ && run(cwd, 'git', ['merge-base', '--is-ancestor', tagCommit, head]).status === 0);
153
+ const localTagConflict = Boolean(tagCommit && !localTag && !localTagAncestor);
154
+ const postSubject = `chore: prepare next dev iteration after v${version}`;
155
+ const postLog = localTag || localTagAncestor
156
+ ? git(cwd, ['log', '--format=%s', `${tag}..HEAD`], run) ?? ''
157
+ : '';
158
+ const postCommit = postLog.split('\n').some((subject) => subject === postSubject);
159
+ return { localTag, localTagAncestor, localTagConflict, postCommit };
160
+ }
161
+ function releaseSmokeStatus(workflows, version, tagCommit) {
162
+ const manualTitle = `Post-Release Smoke v${version}`;
163
+ const targets = workflows.filter((run) => {
164
+ if (String(run.workflowName || run.name) !== 'Post-Release Smoke')
165
+ return false;
166
+ if (String(run.event) === 'workflow_run')
167
+ return Boolean(tagCommit && String(run.headSha) === tagCommit);
168
+ return String(run.event) === 'workflow_dispatch' && String(run.displayTitle) === manualTitle;
169
+ }).sort((left, right) => {
170
+ const leftOrder = [Date.parse(String(left.createdAt || '')) || 0, Number(left.databaseId) || 0, Number(left.attempt) || 0];
171
+ const rightOrder = [Date.parse(String(right.createdAt || '')) || 0, Number(right.databaseId) || 0, Number(right.attempt) || 0];
172
+ for (let index = 0; index < leftOrder.length; index += 1) {
173
+ if (leftOrder[index] !== rightOrder[index])
174
+ return rightOrder[index] - leftOrder[index];
175
+ }
176
+ return 0;
177
+ });
178
+ const latest = targets[0];
179
+ if (!latest)
180
+ return null;
181
+ if (String(latest.status) !== 'completed')
182
+ return 'pending';
183
+ return String(latest.conclusion) === 'success' ? 'success' : 'failed';
184
+ }
185
+ function inspectPostWorktree(cwd) {
186
+ const inspected = inspectGitWorkflow(cwd);
187
+ if (!inspected.snapshot)
188
+ return inspected.error ?? { code: 'GIT_INSPECT_FAILED', message: 'Unable to inspect Git repository' };
189
+ if (inspected.snapshot.worktree.length || inspected.snapshot.staged.length) {
190
+ return { code: 'WORKTREE_DIRTY', message: 'Release post requires a clean worktree' };
191
+ }
192
+ return null;
193
+ }
43
194
  async function inspectFacts(cwd, version) {
44
195
  const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
45
196
  const config = JSON.parse(fs.readFileSync(path.join(cwd, '.agents', '.airc.json'), 'utf8'));
@@ -47,6 +198,7 @@ async function inspectFacts(cwd, version) {
47
198
  const branch = git(cwd, ['branch', '--show-current']) || '';
48
199
  const head = git(cwd, ['rev-parse', 'HEAD']);
49
200
  const localTagTarget = git(cwd, ['rev-parse', '--verify', `${tag}^{commit}`]);
201
+ const local = inspectLocalReleaseFacts(cwd, version);
50
202
  const remoteBranch = branch ? git(cwd, ['ls-remote', '--heads', 'origin', branch]) : null;
51
203
  const remoteTag = git(cwd, ['ls-remote', '--tags', 'origin', `refs/tags/${tag}`]);
52
204
  const platform = inspectPlatformRelease(tag, { cwd });
@@ -54,18 +206,14 @@ async function inspectFacts(cwd, version) {
54
206
  const formulaUrl = `https://raw.githubusercontent.com/${config.org}/homebrew-tap/main/Formula/${config.project}.rb`;
55
207
  const homebrew = await inspectHomebrewChannel(formulaUrl, version);
56
208
  const workflows = platform.workflows;
57
- const smokeRun = workflows.find((run) => String(run.name).toLowerCase().includes('post-release-smoke') && String(run.headBranch) === tag);
58
- const smoke = smokeRun ? String(smokeRun.conclusion) === 'success' ? 'success' : String(smokeRun.status) === 'completed' ? 'failed' : 'pending' : null;
59
- const postMessage = git(cwd, ['log', '-1', '--pretty=%s']) || '';
209
+ const smoke = releaseSmokeStatus(workflows, version, localTagTarget);
60
210
  return {
61
- localTag: Boolean(localTagTarget && localTagTarget === head),
62
- localTagConflict: Boolean(localTagTarget && localTagTarget !== head),
211
+ ...local,
63
212
  remoteBranch: Boolean(remoteBranch && remoteBranch.split(/\s+/)[0] === head), remoteTag: Boolean(remoteTag),
64
213
  githubRelease: platform.platform.type !== 'github'
65
214
  ? true
66
215
  : platform.status === 'blocked' ? null : Boolean(platform.release?.published),
67
- npm: npm.published, homebrew: homebrew.published, smoke,
68
- postCommit: postMessage.includes(`after v${version}`)
216
+ npm: npm.published, homebrew: homebrew.published, smoke
69
217
  };
70
218
  }
71
219
  function changedPaths(cwd) {
@@ -105,12 +253,12 @@ async function releaseWorkflow(args = []) {
105
253
  process.stdout.write(`${JSON.stringify({ status: 'no-op', changed: false, snapshot: before, error: null })}\n`);
106
254
  return;
107
255
  }
256
+ if (before.facts.localTagConflict) {
257
+ 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`);
258
+ process.exitCode = 1;
259
+ return;
260
+ }
108
261
  if (action === 'prepare') {
109
- if (before.facts.localTagConflict) {
110
- 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`);
111
- process.exitCode = 1;
112
- return;
113
- }
114
262
  if (before.phase !== 'unprepared') {
115
263
  const milestones = reconcileReleaseMilestones(version, { cwd });
116
264
  process.stdout.write(`${JSON.stringify({ ...milestones, snapshot: before })}\n`);
@@ -164,7 +312,7 @@ async function releaseWorkflow(args = []) {
164
312
  return;
165
313
  }
166
314
  if (action === 'publish') {
167
- if (!['prepared', 'partially-published'].includes(before.phase)) {
315
+ if (!before.facts.localTag || !['prepared', 'partially-published'].includes(before.phase)) {
168
316
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, snapshot: before, error: { code: 'RELEASE_PHASE_INVALID', message: `Cannot publish from ${before.phase}` } })}\n`);
169
317
  process.exitCode = 1;
170
318
  return;
@@ -176,12 +324,22 @@ async function releaseWorkflow(args = []) {
176
324
  process.exitCode = result.status === 'failed' ? 1 : result.status === 'degraded' ? 2 : 0;
177
325
  return;
178
326
  }
327
+ if (before.phase === 'complete') {
328
+ process.stdout.write(`${JSON.stringify({ status: 'no-op', changed: false, snapshot: before, error: null })}\n`);
329
+ return;
330
+ }
179
331
  const channelsComplete = before.facts.githubRelease === true && before.facts.npm === true && before.facts.homebrew === true;
180
332
  if (!['published', 'post-pending'].includes(before.phase) || !channelsComplete || before.facts.smoke !== 'success') {
181
333
  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`);
182
334
  process.exitCode = before.facts.smoke === 'failed' ? 1 : 2;
183
335
  return;
184
336
  }
337
+ const worktreeError = inspectPostWorktree(cwd);
338
+ if (worktreeError) {
339
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, snapshot: before, error: worktreeError })}\n`);
340
+ process.exitCode = 1;
341
+ return;
342
+ }
185
343
  const built = command(cwd, 'npm', ['run', 'build']);
186
344
  if (built.status !== 0) {
187
345
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: true, snapshot: before, error: { code: 'RELEASE_POST_COMMAND_FAILED', message: String(built.stderr || built.stdout) } })}\n`);
@@ -213,5 +371,5 @@ async function releaseWorkflow(args = []) {
213
371
  process.stdout.write(`${JSON.stringify({ ...pushed, demo, snapshot: releaseSnapshot(version, await inspectFacts(cwd, version)) })}\n`);
214
372
  process.exitCode = pushed.status === 'failed' ? 1 : pushed.status === 'degraded' ? 2 : 0;
215
373
  }
216
- export { inspectFacts, releaseWorkflow, runOptionalDemo };
374
+ export { computeDemoInputDigest, inspectFacts, inspectLocalReleaseFacts, inspectPostWorktree, releaseSmokeStatus, releaseWorkflow, runOptionalDemo };
217
375
  //# sourceMappingURL=release-workflow.js.map
@@ -0,0 +1,151 @@
1
+ import { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, advanceOrchestration, beginOrResumeOrchestration, completeCommitOrchestrationStage, pauseOrchestration, prepareOrchestrationDelegation, routeOrchestration, sealMatchingOrchestrationDelegation, sealOrchestrationDelegation, statusOrchestration } from '../task/orchestration.js';
2
+ 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';
3
+ function usageFailure(message) {
4
+ process.stdout.write(`${JSON.stringify({
5
+ status: 'failed', changed: false,
6
+ error: { code: 'ORCHESTRATION_PAYLOAD_INVALID', message }
7
+ })}\n`);
8
+ process.stderr.write(USAGE);
9
+ process.exitCode = 2;
10
+ }
11
+ function taskOrchestration(args = []) {
12
+ if (args[0] === '--help' || args[0] === '-h') {
13
+ process.stdout.write(USAGE);
14
+ return;
15
+ }
16
+ if (args.length < 2) {
17
+ usageFailure('task ref and intent are required');
18
+ return;
19
+ }
20
+ const [taskRef, intent] = args;
21
+ if (!['begin-or-resume', 'route', 'prepare', 'hook-start', 'stage-completed', 'hook-stop', 'advance', 'pause', 'status'].includes(intent)) {
22
+ usageFailure(`unknown intent '${intent}'`);
23
+ return;
24
+ }
25
+ const values = {};
26
+ const seen = new Set();
27
+ for (let index = 2; index < args.length; index += 1) {
28
+ const flag = args[index];
29
+ if (![
30
+ '--max-steps', '--client', '--requested-model', '--parent-id', '--before-fingerprint',
31
+ '--native-agent', '--child-id', '--spawn-mode', '--actual-model', '--fallback-reason',
32
+ '--exit-code', '--after-fingerprint', '--changed-paths', '--code', '--message', '--recoverable', '--agent'
33
+ ].includes(flag)) {
34
+ usageFailure(`unknown option '${flag}'`);
35
+ return;
36
+ }
37
+ if (seen.has(flag)) {
38
+ usageFailure(`duplicate option '${flag}'`);
39
+ return;
40
+ }
41
+ seen.add(flag);
42
+ const value = args[++index];
43
+ if (!value || value.startsWith('--')) {
44
+ usageFailure(`option '${flag}' requires a value`);
45
+ return;
46
+ }
47
+ values[flag] = value;
48
+ }
49
+ const requireValues = (flags) => flags.find((flag) => values[flag] === undefined);
50
+ let result;
51
+ if (intent === 'begin-or-resume') {
52
+ const maxSteps = values['--max-steps'] === undefined ? undefined : Number(values['--max-steps']);
53
+ if (maxSteps !== undefined && (!Number.isInteger(maxSteps) || maxSteps < 1)) {
54
+ usageFailure('--max-steps must be a positive integer');
55
+ return;
56
+ }
57
+ result = beginOrResumeOrchestration(taskRef, { maxSteps });
58
+ }
59
+ else if (intent === 'route') {
60
+ result = routeOrchestration(taskRef);
61
+ }
62
+ else if (intent === 'status') {
63
+ result = statusOrchestration(taskRef);
64
+ }
65
+ else if (intent === 'prepare') {
66
+ const missing = requireValues(['--client']);
67
+ if (missing) {
68
+ usageFailure(`intent 'prepare' requires '${missing}'`);
69
+ return;
70
+ }
71
+ result = prepareOrchestrationDelegation(taskRef, {
72
+ client: values['--client'],
73
+ requestedModel: values['--requested-model']
74
+ });
75
+ }
76
+ else if (intent === 'hook-start') {
77
+ const missing = requireValues([
78
+ ...(taskRef === 'auto' ? ['--client'] : []),
79
+ '--native-agent', '--child-id', '--parent-id', '--spawn-mode'
80
+ ]);
81
+ if (missing) {
82
+ usageFailure(`intent 'hook-start' requires '${missing}'`);
83
+ return;
84
+ }
85
+ const event = {
86
+ nativeAgent: values['--native-agent'], childId: values['--child-id'],
87
+ parentId: values['--parent-id'], spawnMode: values['--spawn-mode'],
88
+ actualModel: values['--actual-model'], modelFallbackReason: values['--fallback-reason']
89
+ };
90
+ result = taskRef === 'auto'
91
+ ? activateMatchingOrchestrationDelegation(values['--client'], event)
92
+ : activateOrchestrationDelegation(taskRef, event);
93
+ }
94
+ else if (intent === 'hook-stop') {
95
+ if (taskRef === 'auto') {
96
+ const missing = requireValues(['--client', '--native-agent', '--child-id']);
97
+ if (missing) {
98
+ usageFailure(`intent 'hook-stop' requires '${missing}'`);
99
+ return;
100
+ }
101
+ result = sealMatchingOrchestrationDelegation(values['--client'], {
102
+ nativeAgent: values['--native-agent'], childId: values['--child-id']
103
+ });
104
+ }
105
+ else {
106
+ const missing = requireValues(['--child-id', '--exit-code', '--after-fingerprint']);
107
+ if (missing) {
108
+ usageFailure(`intent 'hook-stop' requires '${missing}'`);
109
+ return;
110
+ }
111
+ const exitCode = Number(values['--exit-code']);
112
+ if (!Number.isInteger(exitCode)) {
113
+ usageFailure('--exit-code must be an integer');
114
+ return;
115
+ }
116
+ result = sealOrchestrationDelegation(taskRef, {
117
+ childId: values['--child-id'], exitCode,
118
+ afterFingerprint: values['--after-fingerprint'],
119
+ changedPaths: values['--changed-paths'] ? values['--changed-paths'].split(',').filter(Boolean) : []
120
+ });
121
+ }
122
+ }
123
+ else if (intent === 'stage-completed') {
124
+ const missing = requireValues(['--agent']);
125
+ if (missing) {
126
+ usageFailure(`intent 'stage-completed' requires '${missing}'`);
127
+ return;
128
+ }
129
+ result = completeCommitOrchestrationStage(taskRef, values['--agent']);
130
+ }
131
+ else if (intent === 'advance') {
132
+ result = advanceOrchestration(taskRef);
133
+ }
134
+ else {
135
+ const missing = requireValues(['--code', '--message', '--recoverable']);
136
+ if (missing) {
137
+ usageFailure(`intent 'pause' requires '${missing}'`);
138
+ return;
139
+ }
140
+ if (!['true', 'false'].includes(values['--recoverable'])) {
141
+ usageFailure('--recoverable must be true or false');
142
+ return;
143
+ }
144
+ result = pauseOrchestration(taskRef, values['--code'], values['--message'], values['--recoverable'] === 'true');
145
+ }
146
+ process.stdout.write(`${JSON.stringify(result)}\n`);
147
+ if (result.status === 'failed')
148
+ process.exitCode = 1;
149
+ }
150
+ export { taskOrchestration };
151
+ //# sourceMappingURL=task-orchestration.js.map
@@ -0,0 +1,65 @@
1
+ import { finalizeReviewSummary } from '../task/review-finalization.js';
2
+ const USAGE = 'Usage: agent-infra-internal task-review <task-ref> finalize-summary --stage <analysis|plan|code> --artifact <review-*.md> [--dry-run]\n';
3
+ function failUsage(message) {
4
+ process.stdout.write(`${JSON.stringify({
5
+ status: 'failed',
6
+ changed: false,
7
+ intent: 'finalize-summary',
8
+ error: { code: 'REVIEW_PAYLOAD_INVALID', message }
9
+ })}\n`);
10
+ process.stderr.write(USAGE);
11
+ process.exitCode = 2;
12
+ }
13
+ function taskReview(args = []) {
14
+ if (args[0] === '--help' || args[0] === '-h') {
15
+ process.stdout.write(USAGE);
16
+ return;
17
+ }
18
+ if (args.length < 2) {
19
+ failUsage('task ref and intent are required');
20
+ return;
21
+ }
22
+ if (args[1] !== 'finalize-summary') {
23
+ failUsage(`unknown intent '${args[1]}'`);
24
+ return;
25
+ }
26
+ let stage = '';
27
+ let artifact = '';
28
+ let dryRun = false;
29
+ const seen = new Set();
30
+ for (let index = 2; index < args.length; index += 1) {
31
+ const flag = args[index];
32
+ if (!['--stage', '--artifact', '--dry-run'].includes(flag)) {
33
+ failUsage(`unknown option '${flag}'`);
34
+ return;
35
+ }
36
+ if (seen.has(flag)) {
37
+ failUsage(`duplicate option '${flag}'`);
38
+ return;
39
+ }
40
+ seen.add(flag);
41
+ if (flag === '--dry-run') {
42
+ dryRun = true;
43
+ continue;
44
+ }
45
+ const value = args[++index];
46
+ if (!value || value.startsWith('--')) {
47
+ failUsage(`option '${flag}' requires a value`);
48
+ return;
49
+ }
50
+ if (flag === '--stage')
51
+ stage = value;
52
+ else
53
+ artifact = value;
54
+ }
55
+ if (!stage || !artifact) {
56
+ failUsage("options '--stage' and '--artifact' are required");
57
+ return;
58
+ }
59
+ const result = finalizeReviewSummary({ taskRef: args[0], stage, artifact, dryRun });
60
+ process.stdout.write(`${JSON.stringify(result)}\n`);
61
+ if (result.status === 'failed')
62
+ process.exitCode = 1;
63
+ }
64
+ export { taskReview };
65
+ //# sourceMappingURL=task-review.js.map
package/dist/lib/merge.js 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.js';
4
+ import { removeDirRecursive } from './remove-dir.js';
4
5
  const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
5
6
  const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
6
7
  const TITLE_RE = /^# (.+)$/m;
@@ -527,7 +528,7 @@ function mergeMutableSections({ sourceWorkspace, localWorkspace, backupRoot, rep
527
528
  if (comparison > 0) {
528
529
  backupTaskDir(backupRoot, localMatch.section, localMatch.taskDir, localMatch.taskId);
529
530
  report.backupCount += 1;
530
- fs.rmSync(localMatch.taskDir, { recursive: true, force: true });
531
+ removeDirRecursive(localMatch.taskDir);
531
532
  const destinationDir = copyTaskToSection(sourceTask, localWorkspace);
532
533
  localIndex.set(sourceTask.taskId, {
533
534
  taskId: sourceTask.taskId,
@@ -7,8 +7,45 @@ function registerPlatformAdapter(adapter) {
7
7
  function getPlatformAdapter(type) {
8
8
  return type ? adapters.get(type) ?? null : null;
9
9
  }
10
+ function registerPlatformCapabilities(type, capabilities) {
11
+ const adapter = adapters.get(type);
12
+ if (!adapter)
13
+ throw new Error(`Platform adapter '${type}' must be registered before its capabilities`);
14
+ adapters.set(type, { ...adapter, ...capabilities });
15
+ }
16
+ function hasPlatformCapability(type, capability) {
17
+ const adapter = getPlatformAdapter(type);
18
+ if (capability === 'change-request')
19
+ return typeof adapter?.inspectChangeRequest === 'function';
20
+ if (capability === 'required-checks')
21
+ return typeof adapter?.inspectRequiredChecks === 'function';
22
+ return typeof adapter?.resolveChangeRequestGitEvidence === 'function';
23
+ }
24
+ function unsupported(type, capability) {
25
+ return {
26
+ ok: false,
27
+ error: {
28
+ code: 'PLATFORM_CAPABILITY_UNSUPPORTED',
29
+ message: `Platform '${type || 'none'}' does not provide ${capability}`,
30
+ retryable: false
31
+ }
32
+ };
33
+ }
34
+ function inspectPlatformChangeRequest(type, context) {
35
+ const adapter = getPlatformAdapter(type);
36
+ return adapter?.inspectChangeRequest?.(context) ?? unsupported(type, 'change-request inspection');
37
+ }
38
+ function inspectPlatformRequiredChecks(type, context) {
39
+ const adapter = getPlatformAdapter(type);
40
+ return adapter?.inspectRequiredChecks?.(context) ?? unsupported(type, 'required-checks inspection');
41
+ }
42
+ function resolvePlatformChangeRequestGitEvidence(type, context) {
43
+ const adapter = getPlatformAdapter(type);
44
+ return adapter?.resolveChangeRequestGitEvidence?.(context) ??
45
+ unsupported(type, 'change-request Git evidence');
46
+ }
10
47
  function listPlatformAdapters() {
11
48
  return [...adapters.keys()].sort();
12
49
  }
13
- export { getPlatformAdapter, listPlatformAdapters, registerPlatformAdapter };
50
+ export { getPlatformAdapter, hasPlatformCapability, inspectPlatformChangeRequest, inspectPlatformRequiredChecks, listPlatformAdapters, registerPlatformAdapter, registerPlatformCapabilities, resolvePlatformChangeRequestGitEvidence };
14
51
  //# sourceMappingURL=adapters.js.map
@@ -0,0 +1,113 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { execFileSync, spawnSync } from "node:child_process";
5
+ import { resolvePlatformChangeRequestGitEvidence } from "./adapters.js";
6
+ import { resolveReviewedHeadRelation } from "./merged-pr-equivalence.js";
7
+ const SHA_PATTERN = /^[0-9a-f]{40}$/i;
8
+ const REVIEWED_HEAD_REF = "refs/agent-infra/reviewed-head";
9
+ const TARGET_HEAD_REF = "refs/agent-infra/target-head";
10
+ function git(cwd, args) {
11
+ return execFileSync("git", args, {
12
+ cwd,
13
+ encoding: "utf8",
14
+ maxBuffer: 32 * 1024 * 1024,
15
+ stdio: ["ignore", "pipe", "pipe"]
16
+ }).trim();
17
+ }
18
+ function validRef(ref) {
19
+ return spawnSync("git", ["check-ref-format", ref], {
20
+ encoding: "utf8",
21
+ stdio: ["ignore", "ignore", "ignore"]
22
+ }).status === 0;
23
+ }
24
+ function blocked(code, message) {
25
+ return { status: "blocked", code, message };
26
+ }
27
+ function failed(code, message) {
28
+ return { status: "failed", code, message };
29
+ }
30
+ function validMergeIdentity(lastReviewedCommit, pullRequest) {
31
+ if (pullRequest.state !== "closed" ||
32
+ !pullRequest.mergedAt ||
33
+ !pullRequest.mergeCommitSha ||
34
+ !SHA_PATTERN.test(lastReviewedCommit) ||
35
+ lastReviewedCommit !== pullRequest.head.sha) {
36
+ return failed("PR_MERGE_IDENTITY_INVALID", "PR merge identity does not match the reviewed head");
37
+ }
38
+ return null;
39
+ }
40
+ function resolveMaterializedReviewedHeadRelation(input) {
41
+ const identityFailure = validMergeIdentity(input.lastReviewedCommit, input.pullRequest);
42
+ if (identityFailure)
43
+ return identityFailure;
44
+ const source = resolvePlatformChangeRequestGitEvidence(input.platformType, {
45
+ cwd: input.cwd,
46
+ repository: input.pullRequest.repository,
47
+ number: input.pullRequest.number,
48
+ pullRequest: input.pullRequest
49
+ });
50
+ if (!source.ok || !source.value) {
51
+ return blocked(source.error?.code || "PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE", source.error?.message || "Unable to resolve authoritative PR Git evidence");
52
+ }
53
+ if (!source.value.remoteUrl.trim() ||
54
+ source.value.remoteUrl.trimStart().startsWith("-") ||
55
+ !validRef(source.value.reviewedHeadRef) ||
56
+ !validRef(source.value.targetHeadRef)) {
57
+ return blocked("PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE", "Platform returned an invalid PR Git evidence source");
58
+ }
59
+ let evidenceRoot = null;
60
+ let phase = "prepare";
61
+ let result = blocked("PR_MERGE_GIT_EVIDENCE_UNAVAILABLE", "Unable to prepare isolated PR Git evidence");
62
+ try {
63
+ evidenceRoot = fs.mkdtempSync(path.join(os.tmpdir(), "agent-infra-pr-evidence-"));
64
+ execFileSync("git", ["init", "--bare", "-q", evidenceRoot], {
65
+ cwd: input.cwd,
66
+ encoding: "utf8",
67
+ stdio: ["ignore", "pipe", "pipe"]
68
+ });
69
+ phase = "fetch";
70
+ git(evidenceRoot, [
71
+ "fetch",
72
+ "--no-tags",
73
+ "--no-write-fetch-head",
74
+ source.value.remoteUrl,
75
+ `+${source.value.reviewedHeadRef}:${REVIEWED_HEAD_REF}`,
76
+ `+${source.value.targetHeadRef}:${TARGET_HEAD_REF}`
77
+ ]);
78
+ phase = "resolve";
79
+ const fetchedReviewedHead = git(evidenceRoot, ["rev-parse", `${REVIEWED_HEAD_REF}^{commit}`]);
80
+ const comparisonHead = git(evidenceRoot, ["rev-parse", `${TARGET_HEAD_REF}^{commit}`]);
81
+ if (fetchedReviewedHead !== input.pullRequest.head.sha) {
82
+ result = failed("PR_MERGE_FETCHED_HEAD_MISMATCH", "Fetched PR head does not match the authoritative platform snapshot");
83
+ }
84
+ else {
85
+ result = resolveReviewedHeadRelation({
86
+ gitRoot: evidenceRoot,
87
+ comparisonHead,
88
+ lastReviewedCommit: input.lastReviewedCommit,
89
+ pullRequest: input.pullRequest
90
+ });
91
+ }
92
+ }
93
+ catch {
94
+ result = phase === "fetch"
95
+ ? blocked("PR_MERGE_EVIDENCE_FETCH_FAILED", "Unable to fetch PR Git evidence; verify Git credentials and platform refs")
96
+ : phase === "resolve"
97
+ ? blocked("PR_MERGE_OBJECT_MISSING", "Fetched PR Git evidence is incomplete")
98
+ : blocked("PR_MERGE_GIT_EVIDENCE_UNAVAILABLE", "Unable to prepare isolated PR Git evidence");
99
+ }
100
+ finally {
101
+ if (evidenceRoot) {
102
+ try {
103
+ fs.rmSync(evidenceRoot, { recursive: true, force: true });
104
+ }
105
+ catch {
106
+ result = blocked("PR_MERGE_EVIDENCE_CLEANUP_FAILED", "Unable to clean up isolated PR Git evidence");
107
+ }
108
+ }
109
+ }
110
+ return result;
111
+ }
112
+ export { resolveMaterializedReviewedHeadRelation };
113
+ //# sourceMappingURL=change-request-git-evidence.js.map