@fitlab-ai/agent-infra 0.9.2 → 0.9.3

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 (318) hide show
  1. package/README.md +10 -9
  2. package/README.zh-CN.md +10 -9
  3. package/dist/bin/internal-cli.js +15 -0
  4. package/dist/lib/agent-clients/adapter.js +45 -2
  5. package/dist/lib/agent-clients/adapters/antigravity-cli.js +64 -0
  6. package/dist/lib/agent-clients/adapters/claude-code.js +10 -1
  7. package/dist/lib/agent-clients/adapters/codex.js +9 -0
  8. package/dist/lib/agent-clients/adapters/index.js +2 -2
  9. package/dist/lib/agent-clients/adapters/opencode.js +9 -0
  10. package/dist/lib/agent-clients/config.js +42 -4
  11. package/dist/lib/agent-clients/next-steps.js +11 -2
  12. package/dist/lib/agent-clients/reconcile.js +2 -1
  13. package/dist/lib/agent-clients/registry.js +7 -1
  14. package/dist/lib/agent-clients/schema.js +18 -0
  15. package/dist/lib/agent-clients/tokens.js +49 -0
  16. package/dist/lib/agent-clients/types.js +1 -1
  17. package/dist/lib/defaults.json +1 -1
  18. package/dist/lib/git/workflow.js +54 -1
  19. package/dist/lib/internal/agent-client.js +61 -4
  20. package/dist/lib/internal/git-workflow.js +6 -1
  21. package/dist/lib/internal/platform-checks.js +3 -3
  22. package/dist/lib/internal/platform-comment.js +29 -2
  23. package/dist/lib/internal/platform-issue.js +7 -0
  24. package/dist/lib/internal/platform-pr-review.js +132 -0
  25. package/dist/lib/internal/platform-pr.js +20 -2
  26. package/dist/lib/internal/platform-release-notes.js +52 -16
  27. package/dist/lib/internal/pr-review-grade.js +140 -0
  28. package/dist/lib/internal/release-workflow.js +13 -6
  29. package/dist/lib/internal/task-activity.js +73 -0
  30. package/dist/lib/internal/task-event.js +17 -1
  31. package/dist/lib/internal/task-lifecycle.js +7 -0
  32. package/dist/lib/internal/task-orchestration.js +51 -6
  33. package/dist/lib/internal/task-review.js +8 -3
  34. package/dist/lib/platform/adapters.js +8 -1
  35. package/dist/lib/platform/completion-backfill.js +122 -0
  36. package/dist/lib/platform/issue-comments.js +3 -2
  37. package/dist/lib/platform/merged-pr-equivalence.js +115 -5
  38. package/dist/lib/platform/pr-checks.js +64 -41
  39. package/dist/lib/platform/pr-review.js +166 -0
  40. package/dist/lib/platform/pull-requests.js +324 -3
  41. package/dist/lib/platform/release-notes.js +85 -3
  42. package/dist/lib/pr-review/evidence-grading.js +229 -0
  43. package/dist/lib/run/tui.js +3 -5
  44. package/dist/lib/sandbox/commands/create.js +0 -20
  45. package/dist/lib/sandbox/commands/show.js +1 -1
  46. package/dist/lib/task/activity-intent.js +101 -0
  47. package/dist/lib/task/artifact-lifecycle.js +2 -1
  48. package/dist/lib/task/commands/log.js +15 -14
  49. package/dist/lib/task/delegation-receipts.js +23 -2
  50. package/dist/lib/task/events.js +67 -28
  51. package/dist/lib/task/finalization-artifacts.js +35 -0
  52. package/dist/lib/task/lifecycle-execution.js +46 -0
  53. package/dist/lib/task/orchestration.js +408 -44
  54. package/dist/lib/task/review-finalization.js +29 -9
  55. package/dist/lib/task/task-execution-lock.js +231 -0
  56. package/dist/lib/task/verification-checks.js +1 -1
  57. package/dist/lib/task/verification-engine.js +218 -2
  58. package/dist/lib/task/verification.js +4 -3
  59. package/dist/lib/task/workspace-snapshot.js +17 -5
  60. package/lib/agent-clients/adapter.ts +75 -2
  61. package/lib/agent-clients/adapters/antigravity-cli.ts +65 -0
  62. package/lib/agent-clients/adapters/claude-code.ts +10 -1
  63. package/lib/agent-clients/adapters/codex.ts +9 -0
  64. package/lib/agent-clients/adapters/index.ts +2 -2
  65. package/lib/agent-clients/adapters/opencode.ts +9 -0
  66. package/lib/agent-clients/config.ts +49 -4
  67. package/lib/agent-clients/next-steps.ts +15 -2
  68. package/lib/agent-clients/reconcile.ts +2 -1
  69. package/lib/agent-clients/registry.ts +10 -0
  70. package/lib/agent-clients/schema.ts +18 -0
  71. package/lib/agent-clients/tokens.ts +53 -0
  72. package/lib/agent-clients/types.ts +15 -1
  73. package/lib/defaults.json +1 -1
  74. package/lib/git/workflow.ts +66 -2
  75. package/lib/internal/agent-client.ts +59 -4
  76. package/lib/internal/git-workflow.ts +5 -1
  77. package/lib/internal/platform-checks.ts +4 -4
  78. package/lib/internal/platform-comment.ts +17 -2
  79. package/lib/internal/platform-issue.ts +4 -0
  80. package/lib/internal/platform-pr-review.ts +104 -0
  81. package/lib/internal/platform-pr.ts +14 -1
  82. package/lib/internal/platform-release-notes.ts +59 -16
  83. package/lib/internal/pr-review-grade.ts +130 -0
  84. package/lib/internal/release-workflow.ts +13 -6
  85. package/lib/internal/task-activity.ts +55 -0
  86. package/lib/internal/task-event.ts +9 -1
  87. package/lib/internal/task-lifecycle.ts +4 -0
  88. package/lib/internal/task-orchestration.ts +45 -6
  89. package/lib/internal/task-review.ts +8 -3
  90. package/lib/platform/adapters.ts +29 -2
  91. package/lib/platform/completion-backfill.ts +134 -0
  92. package/lib/platform/issue-comments.ts +3 -2
  93. package/lib/platform/merged-pr-equivalence.ts +127 -5
  94. package/lib/platform/pr-checks.ts +72 -45
  95. package/lib/platform/pr-review.ts +201 -0
  96. package/lib/platform/pull-requests.ts +395 -2
  97. package/lib/platform/release-notes.ts +100 -4
  98. package/lib/pr-review/evidence-grading.ts +318 -0
  99. package/lib/run/tui.ts +3 -4
  100. package/lib/sandbox/commands/create.ts +0 -20
  101. package/lib/sandbox/commands/show.ts +1 -1
  102. package/lib/server/server.schema.json +2 -2
  103. package/lib/task/activity-intent.ts +128 -0
  104. package/lib/task/artifact-lifecycle.ts +2 -1
  105. package/lib/task/commands/log.ts +15 -14
  106. package/lib/task/delegation-receipts.ts +32 -3
  107. package/lib/task/events.ts +75 -30
  108. package/lib/task/finalization-artifacts.ts +58 -0
  109. package/lib/task/lifecycle-execution.ts +106 -0
  110. package/lib/task/orchestration.ts +543 -64
  111. package/lib/task/review-finalization.ts +40 -9
  112. package/lib/task/task-execution-lock.ts +296 -0
  113. package/lib/task/verification-checks.ts +1 -1
  114. package/lib/task/verification-engine.ts +249 -2
  115. package/lib/task/verification.ts +5 -3
  116. package/lib/task/workspace-snapshot.ts +26 -5
  117. package/package.json +3 -3
  118. package/templates/.agents/QUICKSTART.en.md +25 -8
  119. package/templates/.agents/QUICKSTART.zh-CN.md +25 -8
  120. package/templates/.agents/README.en.md +10 -7
  121. package/templates/.agents/README.zh-CN.md +10 -7
  122. package/templates/.agents/hooks/lifecycle-delegation.js +15 -4
  123. package/templates/.agents/rules/create-issue.github.en.md +4 -2
  124. package/templates/.agents/rules/create-issue.github.zh-CN.md +4 -2
  125. package/templates/.agents/rules/issue-fields.github.en.md +3 -1
  126. package/templates/.agents/rules/issue-fields.github.zh-CN.md +3 -1
  127. package/templates/.agents/rules/issue-pr-commands.en.md +5 -3
  128. package/templates/.agents/rules/issue-pr-commands.github.en.md +5 -3
  129. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +5 -3
  130. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +5 -3
  131. package/templates/.agents/rules/issue-sync.github.en.md +7 -3
  132. package/templates/.agents/rules/issue-sync.github.zh-CN.md +8 -4
  133. package/templates/.agents/rules/lifecycle-orchestration.en.md +5 -2
  134. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +5 -2
  135. package/templates/.agents/rules/milestone-inference.github.en.md +4 -2
  136. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +4 -2
  137. package/templates/.agents/rules/next-step-output.en.md +3 -1
  138. package/templates/.agents/rules/next-step-output.zh-CN.md +3 -1
  139. package/templates/.agents/rules/no-mid-flow-questions.en.md +17 -0
  140. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +17 -0
  141. package/templates/.agents/rules/pr-checks-commands.en.md +3 -3
  142. package/templates/.agents/rules/pr-checks-commands.github.en.md +2 -2
  143. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +2 -2
  144. package/templates/.agents/rules/pr-checks-commands.zh-CN.md +3 -3
  145. package/templates/.agents/rules/pr-sync.en.md +3 -1
  146. package/templates/.agents/rules/pr-sync.github.en.md +11 -1
  147. package/templates/.agents/rules/pr-sync.github.zh-CN.md +11 -1
  148. package/templates/.agents/rules/pr-sync.zh-CN.md +3 -1
  149. package/templates/.agents/rules/release-commands.en.md +1 -1
  150. package/templates/.agents/rules/release-commands.github.en.md +12 -2
  151. package/templates/.agents/rules/release-commands.github.zh-CN.md +12 -2
  152. package/templates/.agents/rules/release-commands.zh-CN.md +1 -1
  153. package/templates/.agents/rules/task-management.en.md +11 -0
  154. package/templates/.agents/rules/task-management.zh-CN.md +11 -0
  155. package/templates/.agents/skills/analyze-task/SKILL.en.md +10 -7
  156. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -7
  157. package/templates/.agents/skills/block-task/SKILL.en.md +7 -5
  158. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +7 -5
  159. package/templates/.agents/skills/cancel-task/SKILL.en.md +6 -4
  160. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +6 -4
  161. package/templates/.agents/skills/close-codescan/SKILL.en.md +3 -1
  162. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -1
  163. package/templates/.agents/skills/close-dependabot/SKILL.en.md +3 -1
  164. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +3 -1
  165. package/templates/.agents/skills/code-task/SKILL.en.md +9 -6
  166. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +11 -8
  167. package/templates/.agents/skills/commit/SKILL.en.md +3 -1
  168. package/templates/.agents/skills/commit/SKILL.zh-CN.md +3 -1
  169. package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
  170. package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
  171. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
  172. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
  173. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
  174. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
  175. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +6 -4
  176. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +6 -4
  177. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +1 -1
  178. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +1 -1
  179. package/templates/.agents/skills/complete-task/SKILL.en.md +21 -6
  180. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +21 -6
  181. package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
  182. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
  183. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +17 -0
  184. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +17 -0
  185. package/templates/.agents/skills/create-pr/SKILL.en.md +3 -1
  186. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +3 -1
  187. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +1 -1
  188. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +1 -1
  189. package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -16
  190. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +17 -16
  191. package/templates/.agents/skills/create-task/SKILL.en.md +5 -3
  192. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -3
  193. package/templates/.agents/skills/import-issue/SKILL.en.md +5 -3
  194. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -3
  195. package/templates/.agents/skills/plan-task/SKILL.en.md +8 -5
  196. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +8 -5
  197. package/templates/.agents/skills/post-release/SKILL.en.md +2 -0
  198. package/templates/.agents/skills/post-release/SKILL.zh-CN.md +2 -0
  199. package/templates/.agents/skills/release/SKILL.en.md +16 -10
  200. package/templates/.agents/skills/release/SKILL.zh-CN.md +16 -10
  201. package/templates/.agents/skills/restore-task/SKILL.en.md +4 -1
  202. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +4 -1
  203. package/templates/.agents/skills/review-analysis/SKILL.en.md +7 -4
  204. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +7 -4
  205. package/templates/.agents/skills/review-code/SKILL.en.md +9 -6
  206. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +9 -6
  207. package/templates/.agents/skills/review-plan/SKILL.en.md +7 -4
  208. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +7 -4
  209. package/templates/.agents/skills/review-pr/SKILL.en.md +158 -0
  210. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +158 -0
  211. package/templates/.agents/skills/review-pr/config/verify.en.json +37 -0
  212. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +37 -0
  213. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +65 -0
  214. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +65 -0
  215. package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +18 -0
  216. package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +18 -0
  217. package/templates/.agents/skills/review-pr/reference/report-template.en.md +85 -0
  218. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +85 -0
  219. package/templates/.agents/skills/run-task/SKILL.en.md +8 -8
  220. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +8 -8
  221. package/templates/.agents/skills/run-task/config/verify.en.json +2 -1
  222. package/templates/.agents/skills/run-task/config/verify.zh-CN.json +2 -1
  223. package/templates/.agents/skills/run-task/reference/host-validation.en.md +20 -0
  224. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +20 -0
  225. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +35 -61
  226. package/templates/.agents/skills/watch-pr/SKILL.en.md +13 -13
  227. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +13 -13
  228. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +27 -6
  229. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +26 -5
  230. package/templates/.agents/templates/task.en.md +1 -1
  231. package/templates/.agents/templates/task.zh-CN.md +1 -1
  232. package/templates/.agents/workflows/bug-fix.en.yaml +3 -3
  233. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +3 -3
  234. package/templates/.agents/workflows/code-review.en.yaml +1 -1
  235. package/templates/.agents/workflows/code-review.zh-CN.yaml +1 -1
  236. package/templates/.agents/workflows/feature-development.en.yaml +4 -4
  237. package/templates/.agents/workflows/feature-development.zh-CN.yaml +4 -4
  238. package/templates/.agents/workflows/refactoring.en.yaml +3 -3
  239. package/templates/.agents/workflows/refactoring.zh-CN.yaml +3 -3
  240. package/templates/.claude/commands/post-release.en.md +1 -0
  241. package/templates/.claude/commands/post-release.zh-CN.md +1 -0
  242. package/templates/.claude/commands/review-pr.en.md +8 -0
  243. package/templates/.claude/commands/review-pr.zh-CN.md +8 -0
  244. package/templates/.gemini/commands/_project_/review-pr.en.toml +10 -0
  245. package/templates/.gemini/commands/_project_/review-pr.zh-CN.toml +10 -0
  246. package/templates/.opencode/commands/post-release.en.md +2 -0
  247. package/templates/.opencode/commands/post-release.zh-CN.md +2 -0
  248. package/templates/.opencode/commands/review-pr.en.md +11 -0
  249. package/templates/.opencode/commands/review-pr.zh-CN.md +11 -0
  250. package/dist/lib/agent-clients/adapters/gemini-cli.js +0 -64
  251. package/lib/agent-clients/adapters/gemini-cli.ts +0 -65
  252. package/templates/.gemini/commands/_project_/analyze-task.en.toml +0 -10
  253. package/templates/.gemini/commands/_project_/analyze-task.zh-CN.toml +0 -10
  254. package/templates/.gemini/commands/_project_/archive-tasks.en.toml +0 -10
  255. package/templates/.gemini/commands/_project_/archive-tasks.zh-CN.toml +0 -10
  256. package/templates/.gemini/commands/_project_/block-task.en.toml +0 -10
  257. package/templates/.gemini/commands/_project_/block-task.zh-CN.toml +0 -10
  258. package/templates/.gemini/commands/_project_/cancel-task.en.toml +0 -10
  259. package/templates/.gemini/commands/_project_/cancel-task.zh-CN.toml +0 -10
  260. package/templates/.gemini/commands/_project_/check-task.en.toml +0 -10
  261. package/templates/.gemini/commands/_project_/check-task.zh-CN.toml +0 -10
  262. package/templates/.gemini/commands/_project_/close-codescan.en.toml +0 -8
  263. package/templates/.gemini/commands/_project_/close-codescan.zh-CN.toml +0 -8
  264. package/templates/.gemini/commands/_project_/close-dependabot.en.toml +0 -8
  265. package/templates/.gemini/commands/_project_/close-dependabot.zh-CN.toml +0 -8
  266. package/templates/.gemini/commands/_project_/code-task.en.toml +0 -10
  267. package/templates/.gemini/commands/_project_/code-task.zh-CN.toml +0 -10
  268. package/templates/.gemini/commands/_project_/commit.en.toml +0 -10
  269. package/templates/.gemini/commands/_project_/commit.zh-CN.toml +0 -10
  270. package/templates/.gemini/commands/_project_/complete-manual-validation.en.toml +0 -10
  271. package/templates/.gemini/commands/_project_/complete-manual-validation.zh-CN.toml +0 -10
  272. package/templates/.gemini/commands/_project_/complete-task.en.toml +0 -10
  273. package/templates/.gemini/commands/_project_/complete-task.zh-CN.toml +0 -10
  274. package/templates/.gemini/commands/_project_/create-pr.en.toml +0 -10
  275. package/templates/.gemini/commands/_project_/create-pr.zh-CN.toml +0 -10
  276. package/templates/.gemini/commands/_project_/create-release-note.en.toml +0 -8
  277. package/templates/.gemini/commands/_project_/create-release-note.zh-CN.toml +0 -8
  278. package/templates/.gemini/commands/_project_/create-task.en.toml +0 -8
  279. package/templates/.gemini/commands/_project_/create-task.zh-CN.toml +0 -8
  280. package/templates/.gemini/commands/_project_/import-codescan.en.toml +0 -8
  281. package/templates/.gemini/commands/_project_/import-codescan.zh-CN.toml +0 -8
  282. package/templates/.gemini/commands/_project_/import-dependabot.en.toml +0 -8
  283. package/templates/.gemini/commands/_project_/import-dependabot.zh-CN.toml +0 -8
  284. package/templates/.gemini/commands/_project_/import-issue.en.toml +0 -8
  285. package/templates/.gemini/commands/_project_/import-issue.zh-CN.toml +0 -8
  286. package/templates/.gemini/commands/_project_/init-labels.en.toml +0 -8
  287. package/templates/.gemini/commands/_project_/init-labels.zh-CN.toml +0 -8
  288. package/templates/.gemini/commands/_project_/init-milestones.en.toml +0 -10
  289. package/templates/.gemini/commands/_project_/init-milestones.zh-CN.toml +0 -10
  290. package/templates/.gemini/commands/_project_/plan-task.en.toml +0 -10
  291. package/templates/.gemini/commands/_project_/plan-task.zh-CN.toml +0 -10
  292. package/templates/.gemini/commands/_project_/post-release.en.toml +0 -6
  293. package/templates/.gemini/commands/_project_/post-release.zh-CN.toml +0 -6
  294. package/templates/.gemini/commands/_project_/refine-title.en.toml +0 -8
  295. package/templates/.gemini/commands/_project_/refine-title.zh-CN.toml +0 -8
  296. package/templates/.gemini/commands/_project_/release.en.toml +0 -8
  297. package/templates/.gemini/commands/_project_/release.zh-CN.toml +0 -8
  298. package/templates/.gemini/commands/_project_/restore-task.en.toml +0 -8
  299. package/templates/.gemini/commands/_project_/restore-task.zh-CN.toml +0 -8
  300. package/templates/.gemini/commands/_project_/review-analysis.en.toml +0 -10
  301. package/templates/.gemini/commands/_project_/review-analysis.zh-CN.toml +0 -10
  302. package/templates/.gemini/commands/_project_/review-code.en.toml +0 -10
  303. package/templates/.gemini/commands/_project_/review-code.zh-CN.toml +0 -10
  304. package/templates/.gemini/commands/_project_/review-plan.en.toml +0 -10
  305. package/templates/.gemini/commands/_project_/review-plan.zh-CN.toml +0 -10
  306. package/templates/.gemini/commands/_project_/run-task.en.toml +0 -8
  307. package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +0 -8
  308. package/templates/.gemini/commands/_project_/test-integration.en.toml +0 -6
  309. package/templates/.gemini/commands/_project_/test-integration.zh-CN.toml +0 -6
  310. package/templates/.gemini/commands/_project_/test.en.toml +0 -6
  311. package/templates/.gemini/commands/_project_/test.zh-CN.toml +0 -6
  312. package/templates/.gemini/commands/_project_/update-agent-infra.en.toml +0 -6
  313. package/templates/.gemini/commands/_project_/update-agent-infra.zh-CN.toml +0 -6
  314. package/templates/.gemini/commands/_project_/upgrade-dependency.en.toml +0 -8
  315. package/templates/.gemini/commands/_project_/upgrade-dependency.zh-CN.toml +0 -8
  316. package/templates/.gemini/commands/_project_/watch-pr.en.toml +0 -10
  317. package/templates/.gemini/commands/_project_/watch-pr.zh-CN.toml +0 -10
  318. package/templates/.gemini/settings.json +0 -3
@@ -16,24 +16,75 @@ import {
16
16
  sealDelegation
17
17
  } from './delegation-receipts.ts';
18
18
  import type { DelegationReceipt, DelegationRole, DelegationStage } from './delegation-receipts.ts';
19
- import { getAgentClientCapability } from '../agent-clients/registry.ts';
20
- import type { AgentClientId } from '../agent-clients/types.ts';
21
- import { captureWorkspaceSnapshot, diffWorkspaceSnapshots } from './workspace-snapshot.ts';
19
+ import { normalizeAgentClients } from '../agent-clients/config.ts';
20
+ import { inspectPlatformPullRequest } from '../platform/pull-requests.ts';
21
+ import {
22
+ getAgentClientCapability,
23
+ getAgentClientDelegationEvidence,
24
+ getAgentClientModelSelection
25
+ } from '../agent-clients/registry.ts';
26
+ import { isAgentClientId } from '../agent-clients/types.ts';
27
+ import type {
28
+ AgentClientId,
29
+ OrchestrationModelPolicy,
30
+ OrchestrationRolePolicy
31
+ } from '../agent-clients/types.ts';
32
+ import {
33
+ captureRepositorySnapshot,
34
+ captureWorkspaceSnapshot,
35
+ diffWorkspaceSnapshots
36
+ } from './workspace-snapshot.ts';
37
+ import type { RepositorySnapshot } from './workspace-snapshot.ts';
38
+ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
22
39
 
23
40
  type OrchestrationStatus = 'running' | 'paused' | 'completed';
41
+ type LegacyOrchestrationModelPolicy = Readonly<{
42
+ executor: string;
43
+ reviewer: string;
44
+ }>;
45
+ type ModelPolicySource = Readonly<{
46
+ kind: 'explicit' | 'project-config';
47
+ client: AgentClientId;
48
+ resolvedAt: string;
49
+ }>;
50
+ type OrchestrationRecovery = Readonly<{
51
+ code: 'MODEL_POLICY_SUPPLEMENTED';
52
+ recoveredAt: string;
53
+ previousSchemaVersion: 1;
54
+ previousStatus: OrchestrationStatus;
55
+ previousPause: Readonly<{ code: string; message: string; recoverable: boolean }> | null;
56
+ policySource: ModelPolicySource;
57
+ receiptCount: 0;
58
+ pendingDelegation: false;
59
+ resultingStatus: OrchestrationStatus;
60
+ }>;
61
+ type CleanCompletionEvidence = Readonly<{
62
+ kind: 'reviewed-head-clean';
63
+ observedAt: string;
64
+ head: string;
65
+ headTree: string;
66
+ worktreeTree: string;
67
+ lastReviewedCommit: string;
68
+ prNumber: number;
69
+ prHead: string;
70
+ }>;
24
71
  type OrchestrationRun = Readonly<{
25
- schemaVersion: 1;
72
+ schemaVersion: 1 | 2;
26
73
  taskId: string;
27
74
  runId: string;
28
75
  status: OrchestrationStatus;
29
76
  nextStage: DelegationStage | null;
30
77
  stepCount: number;
31
78
  maxSteps: number;
79
+ modelPolicy?: OrchestrationModelPolicy | LegacyOrchestrationModelPolicy;
80
+ modelPolicySource?: ModelPolicySource;
81
+ recoveryHistory?: readonly OrchestrationRecovery[];
32
82
  baseline: string;
33
83
  pendingDelegation: DelegationReceipt | null;
34
84
  receipts: readonly DelegationReceipt[];
35
85
  pause: Readonly<{ code: string; message: string; recoverable: boolean }> | null;
36
86
  commitAuthorization: Readonly<{ issuedAt: string | null; consumedAt: string | null }>;
87
+ completionEvidence?: CleanCompletionEvidence | null;
37
88
  createdAt: string;
38
89
  updatedAt: string;
39
90
  }>;
@@ -43,6 +94,8 @@ type OrchestrationNext = Readonly<{
43
94
  stage: DelegationStage;
44
95
  round: number;
45
96
  artifact: string;
97
+ requestedModel: string | null;
98
+ requestedReasoningEffort: string | null;
46
99
  }>;
47
100
  type OrchestrationResult = Readonly<{
48
101
  status: OrchestrationStatus | 'failed';
@@ -50,17 +103,56 @@ type OrchestrationResult = Readonly<{
50
103
  taskId: string | null;
51
104
  run: OrchestrationRun | null;
52
105
  next: OrchestrationNext | null;
53
- error: Readonly<{ code: string; message: string }> | null;
106
+ error: Readonly<{
107
+ code: string;
108
+ message: string;
109
+ client?: AgentClientId;
110
+ missingFields?: readonly string[];
111
+ modelSelectionContext?: ReturnType<typeof getAgentClientModelSelection>;
112
+ }> | null;
113
+ }>;
114
+ type OrchestrationStageIdentity = Readonly<{
115
+ stage: DelegationStage;
116
+ round: number;
117
+ artifact: string;
118
+ role: DelegationRole;
119
+ }>;
120
+ type OrchestrationStageCompletion = Readonly<{
121
+ taskId: string;
122
+ taskDir: string;
123
+ updatedRun: OrchestrationRun;
124
+ }>;
125
+ type OrchestrationCompletionPlanResult = Readonly<{
126
+ result: OrchestrationResult;
127
+ plan: OrchestrationStageCompletion | null;
54
128
  }>;
55
129
  type OrchestrationOptions = {
56
130
  repoRoot?: string;
57
131
  id?: () => string;
58
132
  now?: () => string;
59
133
  maxSteps?: number;
60
- captureWorkspace?: (repoRoot: string) => string;
134
+ client?: AgentClientId;
135
+ modelPolicy?: OrchestrationModelPolicy;
136
+ captureWorkspace?: (repoRoot: string, taskId: string | null) => string;
137
+ captureRepository?: (repoRoot: string) => RepositorySnapshot;
138
+ inspectPullRequest?: (taskId: string, options: { cwd: string }) => Readonly<{
139
+ status: string;
140
+ task?: Readonly<{ prNumber?: number | null }>;
141
+ prNumber?: number | null;
142
+ pullRequest?: Readonly<{ head?: Readonly<{ sha?: string }> }> | null;
143
+ error?: Readonly<{ message?: string }> | null;
144
+ }>;
61
145
  diffWorkspace?: (repoRoot: string, before: string, after: string) => string[];
146
+ supportsLifecycleDelegation?: (client: AgentClientId) => boolean;
62
147
  };
63
148
 
149
+ function supportsLifecycleDelegation(client: AgentClientId): boolean {
150
+ return getAgentClientCapability(client, 'subagents').level !== 'unsupported'
151
+ && getAgentClientCapability(client, 'orchestration').level !== 'unsupported'
152
+ && getAgentClientDelegationEvidence(client).actualModel !== 'unavailable'
153
+ && getAgentClientDelegationEvidence(client).actualReasoningEffort !== 'unavailable';
154
+ }
155
+
64
156
  function orchestrationPath(taskDir: string): string {
65
157
  return path.join(taskDir, 'orchestration.json');
66
158
  }
@@ -68,7 +160,21 @@ function orchestrationPath(taskDir: string): string {
68
160
  function readRun(taskDir: string): OrchestrationRun | null {
69
161
  const file = orchestrationPath(taskDir);
70
162
  if (!fs.existsSync(file)) return null;
71
- return JSON.parse(fs.readFileSync(file, 'utf8')) as OrchestrationRun;
163
+ const run = JSON.parse(fs.readFileSync(file, 'utf8')) as OrchestrationRun;
164
+ if (run.schemaVersion !== 2 || !isV2Policy(run.modelPolicy)) return run;
165
+ return {
166
+ ...run,
167
+ modelPolicy: {
168
+ executor: {
169
+ model: run.modelPolicy.executor.model,
170
+ reasoningEffort: run.modelPolicy.executor.reasoningEffort
171
+ },
172
+ reviewer: {
173
+ model: run.modelPolicy.reviewer.model,
174
+ reasoningEffort: run.modelPolicy.reviewer.reasoningEffort
175
+ }
176
+ }
177
+ };
72
178
  }
73
179
 
74
180
  function atomicWrite(file: string, value: unknown): void {
@@ -85,36 +191,224 @@ function withUpdatedRun(run: OrchestrationRun, updates: Partial<OrchestrationRun
85
191
  return Object.freeze({ ...run, ...updates, updatedAt: new Date().toISOString() });
86
192
  }
87
193
 
88
- function failed(code: string, message: string, taskId: string | null = null): OrchestrationResult {
89
- return { status: 'failed', changed: false, taskId, run: null, next: null, error: { code, message } };
194
+ function failed(
195
+ code: string,
196
+ message: string,
197
+ taskId: string | null = null,
198
+ details: Omit<NonNullable<OrchestrationResult['error']>, 'code' | 'message'> = {}
199
+ ): OrchestrationResult {
200
+ return { status: 'failed', changed: false, taskId, run: null, next: null, error: { code, message, ...details } };
201
+ }
202
+
203
+ function validModel(value: unknown): value is string {
204
+ return typeof value === 'string' && value.length > 0 && value.trim() === value;
205
+ }
206
+
207
+ function validateModelPolicy(policy: OrchestrationModelPolicy | undefined): Readonly<{ code: string; message: string }> | null {
208
+ if (
209
+ !policy
210
+ || !validModel(policy.executor?.model)
211
+ || !validModel(policy.executor?.reasoningEffort)
212
+ || !validModel(policy.reviewer?.model)
213
+ || !validModel(policy.reviewer?.reasoningEffort)
214
+ ) {
215
+ return { code: 'ORCHESTRATION_MODEL_POLICY_REQUIRED', message: 'executor and reviewer model and reasoning effort are required' };
216
+ }
217
+ return null;
218
+ }
219
+
220
+ function sameModelPolicy(left: OrchestrationModelPolicy, right: OrchestrationModelPolicy): boolean {
221
+ return left.executor.model === right.executor.model
222
+ && left.executor.reasoningEffort === right.executor.reasoningEffort
223
+ && left.reviewer.model === right.reviewer.model
224
+ && left.reviewer.reasoningEffort === right.reviewer.reasoningEffort;
225
+ }
226
+
227
+ function isV2Policy(policy: OrchestrationRun['modelPolicy']): policy is OrchestrationModelPolicy {
228
+ return typeof policy?.executor === 'object' && typeof policy?.reviewer === 'object';
229
+ }
230
+
231
+ function rolePolicy(run: OrchestrationRun, role: DelegationRole): OrchestrationRolePolicy | null {
232
+ return isV2Policy(run.modelPolicy) ? run.modelPolicy[role] : null;
233
+ }
234
+
235
+ function resolveProjectPolicy(repoRoot: string, client: AgentClientId): OrchestrationModelPolicy | undefined {
236
+ const configPath = path.join(repoRoot, '.agents', '.airc.json');
237
+ if (!fs.existsSync(configPath)) return undefined;
238
+ const raw = JSON.parse(fs.readFileSync(configPath, 'utf8')) as Record<string, unknown>;
239
+ return normalizeAgentClients(raw).state[client].orchestration;
90
240
  }
91
241
 
92
242
  function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptions = {}): OrchestrationResult {
93
243
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
94
244
  if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
95
- const existing = readRun(resolved.taskDir);
245
+ if (!isAgentClientId(options.client)) {
246
+ return failed('ORCHESTRATION_PAYLOAD_INVALID', 'begin-or-resume requires a known client', resolved.taskId);
247
+ }
248
+ let existing: OrchestrationRun | null;
249
+ try {
250
+ existing = readRun(resolved.taskDir);
251
+ } catch (error) {
252
+ return failed('ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
253
+ }
96
254
  if (existing) {
97
- if (existing.status === 'paused' && existing.pause?.recoverable && existing.pendingDelegation === null) {
98
- const resumed = withUpdatedRun(existing, { status: 'running', pause: null });
99
- saveRun(resolved.taskDir, resumed);
100
- return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
255
+ if (
256
+ ![1, 2].includes(existing.schemaVersion)
257
+ || !Array.isArray(existing.receipts)
258
+ || !Object.prototype.hasOwnProperty.call(existing, 'pendingDelegation')
259
+ ) {
260
+ return failed('ORCHESTRATION_STATE_INVALID', 'orchestration state has an invalid schema', resolved.taskId);
261
+ }
262
+ if (existing.status === 'completed' && existing.schemaVersion === 1) {
263
+ return { status: 'completed', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
264
+ }
265
+ if (existing.schemaVersion === 2) {
266
+ const policyError = validateModelPolicy(isV2Policy(existing.modelPolicy) ? existing.modelPolicy : undefined);
267
+ if (
268
+ policyError
269
+ || !existing.modelPolicySource
270
+ || !isAgentClientId(existing.modelPolicySource.client)
271
+ || !Array.isArray(existing.recoveryHistory)
272
+ ) {
273
+ return failed('ORCHESTRATION_STATE_INVALID', 'schemaVersion 2 orchestration state is incomplete', resolved.taskId);
274
+ }
275
+ if (existing.status === 'completed') {
276
+ return { status: 'completed', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
277
+ }
278
+ if (existing.modelPolicySource.client !== options.client) {
279
+ return failed('ORCHESTRATION_CLIENT_MISMATCH', 'provided client does not match the persisted run policy source', resolved.taskId);
280
+ }
281
+ if (options.modelPolicy) {
282
+ const suppliedError = validateModelPolicy(options.modelPolicy);
283
+ if (suppliedError) return failed(suppliedError.code, suppliedError.message, resolved.taskId);
284
+ if (!sameModelPolicy(existing.modelPolicy as OrchestrationModelPolicy, options.modelPolicy)) {
285
+ return failed('ORCHESTRATION_MODEL_POLICY_MISMATCH', 'provided model policy does not match the persisted run policy', resolved.taskId);
286
+ }
287
+ }
288
+ if (existing.status === 'paused' && existing.pause?.recoverable && existing.pendingDelegation === null) {
289
+ const resumed = withUpdatedRun(existing, { status: 'running', pause: null });
290
+ saveRun(resolved.taskDir, resumed);
291
+ return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
292
+ }
293
+ return { status: existing.status, changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
294
+ }
295
+
296
+ let policy: OrchestrationModelPolicy | undefined = options.modelPolicy;
297
+ let sourceKind: ModelPolicySource['kind'] = 'explicit';
298
+ if (!policy) {
299
+ try {
300
+ policy = resolveProjectPolicy(resolved.repoRoot, options.client);
301
+ sourceKind = 'project-config';
302
+ } catch (error) {
303
+ return failed('ORCHESTRATION_CONFIG_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
304
+ }
305
+ }
306
+ const policyError = validateModelPolicy(policy);
307
+ if (policyError) {
308
+ return failed(policyError.code, policyError.message, resolved.taskId, {
309
+ client: options.client,
310
+ missingFields: ['executor.model', 'executor.reasoningEffort', 'reviewer.model', 'reviewer.reasoningEffort'],
311
+ modelSelectionContext: getAgentClientModelSelection(options.client)
312
+ });
313
+ }
314
+ if (existing.pendingDelegation !== null) {
315
+ const alreadyPaused = existing.status === 'paused' && existing.pause?.code === 'ORCHESTRATION_DELEGATION_BUSY';
316
+ if (alreadyPaused) return { status: 'paused', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
317
+ const paused = withUpdatedRun(existing, {
318
+ status: 'paused',
319
+ pause: { code: 'ORCHESTRATION_DELEGATION_BUSY', message: 'legacy run has a pending delegation', recoverable: false }
320
+ });
321
+ saveRun(resolved.taskDir, paused);
322
+ return { status: 'paused', changed: true, taskId: resolved.taskId, run: paused, next: null, error: null };
323
+ }
324
+ if (existing.receipts.length > 0) {
325
+ const alreadyPaused = existing.status === 'paused' && existing.pause?.code === 'ORCHESTRATION_HISTORICAL_EFFORT_UNVERIFIED';
326
+ if (alreadyPaused) return { status: 'paused', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
327
+ const paused = withUpdatedRun(existing, {
328
+ status: 'paused',
329
+ pause: {
330
+ code: 'ORCHESTRATION_HISTORICAL_EFFORT_UNVERIFIED',
331
+ message: 'legacy receipts do not contain actual reasoning-effort evidence',
332
+ recoverable: false
333
+ }
334
+ });
335
+ saveRun(resolved.taskDir, paused);
336
+ return { status: 'paused', changed: true, taskId: resolved.taskId, run: paused, next: null, error: null };
337
+ }
338
+ if (
339
+ existing.modelPolicy
340
+ && typeof existing.modelPolicy.executor === 'string'
341
+ && (
342
+ existing.modelPolicy.executor !== policy!.executor.model
343
+ || existing.modelPolicy.reviewer !== policy!.reviewer.model
344
+ )
345
+ ) {
346
+ return failed('ORCHESTRATION_MODEL_POLICY_MISMATCH', 'provided model policy does not match the persisted run policy', resolved.taskId);
347
+ }
348
+ const now = (options.now ?? (() => new Date().toISOString()))();
349
+ const source: ModelPolicySource = { kind: sourceKind, client: options.client, resolvedAt: now };
350
+ const clearsModelPause = existing.pause?.code === 'ORCHESTRATION_MODEL_EVIDENCE_MISSING';
351
+ const resultingStatus = clearsModelPause ? 'running' : existing.status;
352
+ const recovery: OrchestrationRecovery = {
353
+ code: 'MODEL_POLICY_SUPPLEMENTED',
354
+ recoveredAt: now,
355
+ previousSchemaVersion: 1,
356
+ previousStatus: existing.status,
357
+ previousPause: existing.pause,
358
+ policySource: source,
359
+ receiptCount: 0,
360
+ pendingDelegation: false,
361
+ resultingStatus
362
+ };
363
+ const recovered: OrchestrationRun = Object.freeze({
364
+ ...existing,
365
+ schemaVersion: 2,
366
+ status: resultingStatus,
367
+ modelPolicy: policy!,
368
+ modelPolicySource: source,
369
+ recoveryHistory: Object.freeze([recovery]),
370
+ pause: clearsModelPause ? null : existing.pause,
371
+ updatedAt: now
372
+ });
373
+ saveRun(resolved.taskDir, recovered);
374
+ return { status: resultingStatus, changed: true, taskId: resolved.taskId, run: recovered, next: null, error: null };
375
+ }
376
+
377
+ let policy = options.modelPolicy;
378
+ let sourceKind: ModelPolicySource['kind'] = 'explicit';
379
+ if (!policy) {
380
+ try {
381
+ policy = resolveProjectPolicy(resolved.repoRoot, options.client);
382
+ sourceKind = 'project-config';
383
+ } catch (error) {
384
+ return failed('ORCHESTRATION_CONFIG_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
101
385
  }
102
- return { status: existing.status, changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
103
386
  }
387
+ const policyError = validateModelPolicy(policy);
388
+ if (policyError) return failed(policyError.code, policyError.message, resolved.taskId, {
389
+ client: options.client,
390
+ missingFields: ['executor.model', 'executor.reasoningEffort', 'reviewer.model', 'reviewer.reasoningEffort'],
391
+ modelSelectionContext: getAgentClientModelSelection(options.client)
392
+ });
104
393
  const now = (options.now ?? (() => new Date().toISOString()))();
394
+ const source: ModelPolicySource = { kind: sourceKind, client: options.client, resolvedAt: now };
105
395
  const run: OrchestrationRun = {
106
- schemaVersion: 1,
396
+ schemaVersion: 2,
107
397
  taskId: resolved.taskId,
108
398
  runId: (options.id ?? randomUUID)(),
109
399
  status: 'running',
110
400
  nextStage: null,
111
401
  stepCount: 0,
112
402
  maxSteps: options.maxSteps ?? 24,
403
+ modelPolicy: policy!,
404
+ modelPolicySource: source,
405
+ recoveryHistory: [],
113
406
  baseline: '',
114
407
  pendingDelegation: null,
115
408
  receipts: [],
116
409
  pause: null,
117
410
  commitAuthorization: { issuedAt: null, consumedAt: null },
411
+ completionEvidence: null,
118
412
  createdAt: now,
119
413
  updatedAt: now
120
414
  };
@@ -150,7 +444,7 @@ function latestReviewApproved(taskDir: string, family: 'review-analysis' | 'revi
150
444
  return parsed.ok && parsed.summary.verdict === 'Approved' && parsed.summary.counts !== null;
151
445
  }
152
446
 
153
- function routeFromFacts(taskDir: string): OrchestrationNext | null {
447
+ function routeFromFacts(taskDir: string): Omit<OrchestrationNext, 'requestedModel' | 'requestedReasoningEffort'> | null {
154
448
  const analysisRound = highestRound(taskDir, 'analysis');
155
449
  const analysisReviewRound = highestRound(taskDir, 'review-analysis');
156
450
  const planRound = highestRound(taskDir, 'plan');
@@ -205,13 +499,21 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
205
499
  } catch (error) {
206
500
  return failed('ORCHESTRATION_TASK_READ_FAILED', String(error), resolved.taskId);
207
501
  }
502
+ let metadata: ReturnType<typeof parseTypedTaskFrontmatter>;
208
503
  try {
209
- parseTypedTaskFrontmatter(content);
504
+ metadata = parseTypedTaskFrontmatter(content);
210
505
  } catch (error) {
211
506
  return failed('ORCHESTRATION_TASK_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
212
507
  }
213
- const next = routeFromFacts(resolved.taskDir);
214
- if (!next) return failed('ORCHESTRATION_ROUTE_UNKNOWN', 'cannot determine a unique lifecycle action', resolved.taskId);
508
+ const routed = routeFromFacts(resolved.taskDir);
509
+ if (!routed) return failed('ORCHESTRATION_ROUTE_UNKNOWN', 'cannot determine a unique lifecycle action', resolved.taskId);
510
+ const run = readRun(resolved.taskDir);
511
+ const policy = run ? rolePolicy(run, routed.role) : null;
512
+ const next: OrchestrationNext = {
513
+ ...routed,
514
+ requestedModel: policy?.model ?? null,
515
+ requestedReasoningEffort: policy?.reasoningEffort ?? null
516
+ };
215
517
  if (next.stage === 'commit') {
216
518
  const reviewRound = highestRound(resolved.taskDir, 'review-code');
217
519
  const review = parseReviewSummary(fs.readFileSync(
@@ -221,17 +523,115 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
221
523
  if (!review.ok || review.summary.manualValidation === null) {
222
524
  return failed('ORCHESTRATION_REVIEW_INVALID', 'latest code review has no numeric manual-validation count', resolved.taskId);
223
525
  }
224
- if (review.summary.manualValidation > 0) {
225
- return failed('ORCHESTRATION_MANUAL_VALIDATION_PENDING', 'manual validation must be completed before commit', resolved.taskId);
226
- }
227
526
  const rows = parseLedger(content);
228
527
  const ledgerError = validateLedgerRows(rows);
229
528
  if (ledgerError) return failed('ORCHESTRATION_LEDGER_INVALID', ledgerError.message, resolved.taskId);
230
529
  if (!summarizeLedgerStage(rows, 'code').canAdvance) {
231
530
  return failed('ORCHESTRATION_LEDGER_BLOCKED', 'code review ledger has unresolved findings or human decisions', resolved.taskId);
232
531
  }
532
+ if (run?.status === 'completed') {
533
+ return { status: 'completed', changed: false, taskId: resolved.taskId, run, next: null, error: null };
534
+ }
535
+
536
+ let config: unknown;
537
+ try {
538
+ config = JSON.parse(fs.readFileSync(path.join(resolved.repoRoot, '.agents', '.airc.json'), 'utf8'));
539
+ } catch (error) {
540
+ return failed('ORCHESTRATION_CONFIG_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
541
+ }
542
+ if (!config || typeof config !== 'object' || Array.isArray(config)) {
543
+ return failed('ORCHESTRATION_CONFIG_INVALID', 'project configuration must be an object', resolved.taskId);
544
+ }
545
+ const projectConfig = config as Record<string, unknown>;
546
+ if (!Object.hasOwn(projectConfig, 'prFlow') || projectConfig.prFlow === 'disabled') {
547
+ return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
548
+ }
549
+ if (projectConfig.prFlow !== 'required') {
550
+ return failed('ORCHESTRATION_CONFIG_INVALID', "project configuration 'prFlow' must be 'required' or 'disabled'", resolved.taskId);
551
+ }
552
+ const prNumber = Number(metadata.pr_number);
553
+ if (!Number.isInteger(prNumber) || prNumber <= 0) {
554
+ return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
555
+ }
556
+ if (!run) {
557
+ return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
558
+ }
559
+ if (run.status !== 'running' || run.pendingDelegation !== null) {
560
+ return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'clean completion requires an idle running orchestration', resolved.taskId);
561
+ }
562
+
563
+ const capture = options.captureRepository ?? captureRepositorySnapshot;
564
+ let before: RepositorySnapshot;
565
+ try {
566
+ before = capture(resolved.repoRoot);
567
+ } catch (error) {
568
+ return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
569
+ }
570
+ if (before.headTree !== before.worktreeTree) {
571
+ return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
572
+ }
573
+ const lastReviewedCommit = String(metadata.last_reviewed_commit ?? '');
574
+ if (before.head !== lastReviewedCommit) {
575
+ return failed('ORCHESTRATION_REVIEWED_HEAD_MISMATCH', 'local HEAD does not match last_reviewed_commit', resolved.taskId);
576
+ }
577
+
578
+ const inspect: NonNullable<OrchestrationOptions['inspectPullRequest']> =
579
+ options.inspectPullRequest ?? inspectPlatformPullRequest;
580
+ let inspection: ReturnType<NonNullable<OrchestrationOptions['inspectPullRequest']>>;
581
+ try {
582
+ inspection = inspect(resolved.taskId, { cwd: resolved.repoRoot });
583
+ } catch (error) {
584
+ return failed('ORCHESTRATION_PR_INSPECTION_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
585
+ }
586
+ if (inspection.status === 'blocked') {
587
+ return failed('ORCHESTRATION_PR_INSPECTION_BLOCKED', inspection.error?.message ?? 'pull request inspection is blocked', resolved.taskId);
588
+ }
589
+ if (inspection.status === 'failed') {
590
+ return failed('ORCHESTRATION_PR_INSPECTION_FAILED', inspection.error?.message ?? 'pull request inspection failed', resolved.taskId);
591
+ }
592
+ const inspectedPrNumber = inspection.task?.prNumber ?? inspection.prNumber;
593
+ const prHead = inspection.pullRequest?.head?.sha;
594
+ if (typeof prHead !== 'string') {
595
+ return failed('ORCHESTRATION_PR_INSPECTION_INVALID', 'pull request inspection returned incomplete identity evidence', resolved.taskId);
596
+ }
597
+ if (inspectedPrNumber !== prNumber || prHead !== before.head) {
598
+ return failed('ORCHESTRATION_PR_HEAD_MISMATCH', 'pull request identity does not match the reviewed local HEAD', resolved.taskId);
599
+ }
600
+
601
+ let after: RepositorySnapshot;
602
+ try {
603
+ after = capture(resolved.repoRoot);
604
+ } catch (error) {
605
+ return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
606
+ }
607
+ if (after.headTree !== after.worktreeTree) {
608
+ return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
609
+ }
610
+ if (after.head !== before.head || after.headTree !== before.headTree) {
611
+ return failed('ORCHESTRATION_REPOSITORY_CHANGED', 'repository changed while clean completion evidence was collected', resolved.taskId);
612
+ }
613
+
614
+ const observedAt = (options.now ?? (() => new Date().toISOString()))();
615
+ const completionEvidence: CleanCompletionEvidence = {
616
+ kind: 'reviewed-head-clean',
617
+ observedAt,
618
+ head: after.head,
619
+ headTree: after.headTree,
620
+ worktreeTree: after.worktreeTree,
621
+ lastReviewedCommit,
622
+ prNumber,
623
+ prHead
624
+ };
625
+ const completed = withUpdatedRun(run, {
626
+ status: 'completed',
627
+ nextStage: null,
628
+ pause: null,
629
+ completionEvidence
630
+ });
631
+ saveRun(resolved.taskDir, completed);
632
+ return { status: 'completed', changed: true, taskId: resolved.taskId, run: completed, next: null, error: null };
233
633
  }
234
- return { status: 'running', changed: false, taskId: resolved.taskId, run: readRun(resolved.taskDir), next, error: null };
634
+ return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
235
635
  }
236
636
 
237
637
  function statusOrchestration(taskRef: string, options: OrchestrationOptions = {}): OrchestrationResult {
@@ -242,21 +642,19 @@ function statusOrchestration(taskRef: string, options: OrchestrationOptions = {}
242
642
  return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
243
643
  }
244
644
 
245
- function prepareOrchestrationDelegation(
645
+ function prepareOrchestrationDelegationUnlocked(
246
646
  taskRef: string,
247
- input: Readonly<{ client: AgentClientId; requestedModel?: string }>,
647
+ input: Readonly<{ client: AgentClientId; requestedModel?: string; requestedReasoningEffort?: string }>,
248
648
  options: OrchestrationOptions = {}
249
649
  ): OrchestrationResult {
250
650
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
251
651
  if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
252
- if (
253
- getAgentClientCapability(input.client, 'subagents').level === 'unsupported'
254
- || getAgentClientCapability(input.client, 'orchestration').level === 'unsupported'
255
- ) {
652
+ if (!(options.supportsLifecycleDelegation ?? supportsLifecycleDelegation)(input.client)) {
256
653
  return failed('ORCHESTRATION_CLIENT_UNSUPPORTED', `client '${input.client}' does not support lifecycle orchestration`, resolved.taskId);
257
654
  }
258
655
  const run = readRun(resolved.taskDir);
259
656
  if (!run || run.status !== 'running') return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'a running orchestration is required', resolved.taskId);
657
+ if (!isV2Policy(run.modelPolicy)) return failed('ORCHESTRATION_MODEL_EVIDENCE_MISSING', 'running orchestration has no persisted model policy', resolved.taskId);
260
658
  if (run.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_BUSY', 'the run already has a pending delegation', resolved.taskId);
261
659
  const repositoryPending = (['claude-code', 'codex'] as AgentClientId[])
262
660
  .flatMap((client) => matchingDelegations(client, () => true, options));
@@ -267,9 +665,22 @@ function prepareOrchestrationDelegation(
267
665
  const routed = routeOrchestration(taskRef, options);
268
666
  if (!routed.next) return routed;
269
667
  const next = routed.next;
668
+ if (!validModel(input.requestedModel)) {
669
+ return failed('ORCHESTRATION_REQUESTED_MODEL_REQUIRED', 'prepare requires the exact requested model identity', resolved.taskId);
670
+ }
671
+ if (!validModel(input.requestedReasoningEffort)) {
672
+ return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_REQUIRED', 'prepare requires the exact requested reasoning effort', resolved.taskId);
673
+ }
674
+ const expectedPolicy = run.modelPolicy[next.role];
675
+ if (input.requestedModel !== expectedPolicy.model) {
676
+ return failed('ORCHESTRATION_REQUESTED_MODEL_MISMATCH', `requested model does not match the persisted ${next.role} model`, resolved.taskId);
677
+ }
678
+ if (input.requestedReasoningEffort !== expectedPolicy.reasoningEffort) {
679
+ return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_MISMATCH', `requested reasoning effort does not match the persisted ${next.role} policy`, resolved.taskId);
680
+ }
270
681
  let beforeFingerprint: string;
271
682
  try {
272
- beforeFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)(resolved.repoRoot);
683
+ beforeFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)(resolved.repoRoot, resolved.taskId);
273
684
  } catch (error) {
274
685
  return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
275
686
  }
@@ -281,7 +692,9 @@ function prepareOrchestrationDelegation(
281
692
  round: next.round,
282
693
  artifact: next.artifact,
283
694
  client: input.client,
284
- requestedModel: input.requestedModel ?? null,
695
+ requestedModel: input.requestedModel,
696
+ requestedReasoningEffort: input.requestedReasoningEffort,
697
+ workspaceSnapshotScope: 'task',
285
698
  beforeFingerprint
286
699
  }, { id: options.id, now: options.now });
287
700
  const updated = withUpdatedRun(run, {
@@ -295,6 +708,26 @@ function prepareOrchestrationDelegation(
295
708
  return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next, error: null };
296
709
  }
297
710
 
711
+ function prepareOrchestrationDelegation(
712
+ taskRef: string,
713
+ input: Readonly<{ client: AgentClientId; requestedModel?: string; requestedReasoningEffort?: string }>,
714
+ options: OrchestrationOptions = {}
715
+ ): OrchestrationResult {
716
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
717
+ if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
718
+ try {
719
+ return withTaskExecutionLock(
720
+ resolved.repoRoot,
721
+ resolved.taskId,
722
+ 'task-orchestration.prepare',
723
+ () => prepareOrchestrationDelegationUnlocked(taskRef, input, options)
724
+ );
725
+ } catch (error) {
726
+ if (error instanceof TaskExecutionLockError) return failed(error.code, error.message, resolved.taskId);
727
+ throw error;
728
+ }
729
+ }
730
+
298
731
  function matchingDelegations(
299
732
  client: AgentClientId,
300
733
  predicate: (receipt: DelegationReceipt) => boolean,
@@ -364,7 +797,8 @@ function sealMatchingOrchestrationDelegation(
364
797
  }
365
798
  const repoRoot = options.repoRoot ?? process.cwd();
366
799
  try {
367
- const afterFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)(repoRoot);
800
+ const snapshotTaskId = receipt.workspaceSnapshotScope === 'task' ? receipt.taskId : null;
801
+ const afterFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)(repoRoot, snapshotTaskId);
368
802
  const changedPaths = (options.diffWorkspace ?? diffWorkspaceSnapshots)(repoRoot, receipt.beforeFingerprint, afterFingerprint);
369
803
  return sealOrchestrationDelegation(matched.taskId, {
370
804
  childId: event.childId,
@@ -419,36 +853,20 @@ function completeOrchestrationStage(
419
853
  return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
420
854
  }
421
855
 
422
- function completeCommitOrchestrationStage(
856
+ function inspectOrchestrationStage(
423
857
  taskRef: string,
424
- agent: string,
858
+ event: OrchestrationStageIdentity,
425
859
  options: OrchestrationOptions = {}
426
860
  ): OrchestrationResult {
427
861
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
428
862
  if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
429
- const run = readRun(resolved.taskDir);
430
- if (!run) return { status: 'running', changed: false, taskId: resolved.taskId, run: null, next: null, error: null };
431
- const receipt = run.pendingDelegation;
432
- if (!receipt || receipt.stage !== 'commit') {
433
- return failed('ORCHESTRATION_DELEGATION_MISSING', 'active run has no pending commit delegation', resolved.taskId);
863
+ let run: OrchestrationRun | null;
864
+ try {
865
+ run = readRun(resolved.taskDir);
866
+ } catch (error) {
867
+ return failed('ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
434
868
  }
435
- return completeOrchestrationStage(taskRef, {
436
- stage: receipt.stage,
437
- round: receipt.round,
438
- artifact: receipt.artifact,
439
- agent
440
- }, options);
441
- }
442
-
443
- function validateOrchestrationStage(
444
- taskRef: string,
445
- event: Readonly<{ stage: DelegationStage; round: number; artifact: string; role: DelegationRole }>,
446
- options: OrchestrationOptions = {}
447
- ): OrchestrationResult {
448
- const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
449
- if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
450
- const run = readRun(resolved.taskDir);
451
- if (!run) return { status: 'running', changed: false, taskId: resolved.taskId, run: null, next: null, error: null };
869
+ if (!run) return failed('ORCHESTRATION_RUN_MISSING', 'no orchestration run exists', resolved.taskId);
452
870
  const receipt = run.pendingDelegation;
453
871
  if (
454
872
  !receipt
@@ -458,17 +876,67 @@ function validateOrchestrationStage(
458
876
  || receipt.artifact !== event.artifact
459
877
  || receipt.role !== event.role
460
878
  ) {
461
- return pauseOrchestration(
462
- taskRef,
879
+ return failed(
463
880
  'ORCHESTRATION_PROVENANCE_MISMATCH',
464
881
  'active run does not have one matching activated delegation',
465
- true,
466
- options
882
+ resolved.taskId
467
883
  );
468
884
  }
469
885
  return { status: 'running', changed: false, taskId: resolved.taskId, run, next: null, error: null };
470
886
  }
471
887
 
888
+ function planOrchestrationStageCompletion(
889
+ taskRef: string,
890
+ event: Readonly<{ stage: DelegationStage; round: number; artifact: string; role: DelegationRole; agent: string }>,
891
+ options: OrchestrationOptions = {}
892
+ ): OrchestrationCompletionPlanResult {
893
+ const inspected = inspectOrchestrationStage(taskRef, event, options);
894
+ if (inspected.status === 'failed' || !inspected.run?.pendingDelegation) {
895
+ return { result: inspected, plan: null };
896
+ }
897
+ const receipt = inspected.run.pendingDelegation;
898
+ const completed = completeDelegationStage(receipt, event);
899
+ if (!completed.ok) {
900
+ return { result: failed(completed.code, completed.message, inspected.taskId), plan: null };
901
+ }
902
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
903
+ if (!resolved.ok) return { result: failed(resolved.code, resolved.message, resolved.taskId), plan: null };
904
+ const updatedRun = withUpdatedRun(inspected.run, { pendingDelegation: completed.receipt });
905
+ return {
906
+ result: { status: 'running', changed: true, taskId: resolved.taskId, run: updatedRun, next: null, error: null },
907
+ plan: {
908
+ taskId: resolved.taskId,
909
+ taskDir: resolved.taskDir,
910
+ updatedRun
911
+ }
912
+ };
913
+ }
914
+
915
+ function commitOrchestrationStageCompletion(plan: OrchestrationStageCompletion): void {
916
+ saveRun(plan.taskDir, plan.updatedRun);
917
+ }
918
+
919
+ function completeCommitOrchestrationStage(
920
+ taskRef: string,
921
+ agent: string,
922
+ options: OrchestrationOptions = {}
923
+ ): OrchestrationResult {
924
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
925
+ if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
926
+ const run = readRun(resolved.taskDir);
927
+ if (!run) return { status: 'running', changed: false, taskId: resolved.taskId, run: null, next: null, error: null };
928
+ const receipt = run.pendingDelegation;
929
+ if (!receipt || receipt.stage !== 'commit') {
930
+ return failed('ORCHESTRATION_DELEGATION_MISSING', 'active run has no pending commit delegation', resolved.taskId);
931
+ }
932
+ return completeOrchestrationStage(taskRef, {
933
+ stage: receipt.stage,
934
+ round: receipt.round,
935
+ artifact: receipt.artifact,
936
+ agent
937
+ }, options);
938
+ }
939
+
472
940
  function sealOrchestrationDelegation(
473
941
  taskRef: string,
474
942
  event: Parameters<typeof sealDelegation>[1],
@@ -528,16 +996,27 @@ export {
528
996
  activateOrchestrationDelegation,
529
997
  advanceOrchestration,
530
998
  beginOrResumeOrchestration,
999
+ commitOrchestrationStageCompletion,
531
1000
  completeCommitOrchestrationStage,
532
1001
  completeOrchestrationStage,
1002
+ inspectOrchestrationStage,
533
1003
  orchestrationPath,
534
1004
  pauseOrchestration,
1005
+ planOrchestrationStageCompletion,
535
1006
  prepareOrchestrationDelegation,
536
1007
  readRun,
537
1008
  routeOrchestration,
538
1009
  sealMatchingOrchestrationDelegation,
539
1010
  sealOrchestrationDelegation,
540
- statusOrchestration,
541
- validateOrchestrationStage
1011
+ statusOrchestration
1012
+ };
1013
+ export type {
1014
+ OrchestrationCompletionPlanResult,
1015
+ OrchestrationModelPolicy,
1016
+ OrchestrationNext,
1017
+ OrchestrationResult,
1018
+ OrchestrationRun,
1019
+ OrchestrationStageCompletion,
1020
+ OrchestrationStageIdentity,
1021
+ OrchestrationStatus
542
1022
  };
543
- export type { OrchestrationNext, OrchestrationResult, OrchestrationRun, OrchestrationStatus };