@fitlab-ai/agent-infra 0.9.11 → 0.9.13

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 (356) hide show
  1. package/README.md +13 -13
  2. package/README.zh-CN.md +13 -13
  3. package/dist/bin/internal-cli.js +29 -19
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +71 -10
  5. package/dist/lib/agent-clients/next-steps.js +6 -1
  6. package/dist/lib/decide.js +3 -3
  7. package/dist/lib/defaults.json +4 -0
  8. package/dist/lib/git/workflow.js +43 -1
  9. package/dist/lib/init.js +1 -0
  10. package/dist/lib/internal/git-workflow.js +8 -1
  11. package/dist/lib/internal/platform-checks.js +3 -3
  12. package/dist/lib/internal/platform-comment.js +9 -9
  13. package/dist/lib/internal/platform-context.js +2 -2
  14. package/dist/lib/internal/platform-issue.js +8 -9
  15. package/dist/lib/internal/platform-pr-review.js +10 -10
  16. package/dist/lib/internal/platform-pr.js +68 -23
  17. package/dist/lib/internal/platform-release-notes.js +3 -3
  18. package/dist/lib/internal/pr-review-grade.js +11 -9
  19. package/dist/lib/internal/release-workflow.js +4 -4
  20. package/dist/lib/internal/sandbox-control.js +30 -4
  21. package/dist/lib/internal/task-activity.js +3 -3
  22. package/dist/lib/internal/task-artifact.js +76 -24
  23. package/dist/lib/internal/task-create.js +2 -2
  24. package/dist/lib/internal/task-delivery.js +59 -0
  25. package/dist/lib/internal/task-event.js +6 -3
  26. package/dist/lib/internal/task-finalization.js +2 -2
  27. package/dist/lib/internal/task-invalidation.js +60 -0
  28. package/dist/lib/internal/task-ledger.js +9 -6
  29. package/dist/lib/internal/task-lifecycle.js +2 -2
  30. package/dist/lib/internal/task-override.js +4 -4
  31. package/dist/lib/internal/task-review.js +3 -3
  32. package/dist/lib/internal/task-verify.js +6 -6
  33. package/dist/lib/internal/task-warning.js +3 -3
  34. package/dist/lib/platform/adapters.js +1 -3
  35. package/dist/lib/platform/change-request-git-evidence.js +27 -7
  36. package/dist/lib/platform/completion-backfill.js +6 -5
  37. package/dist/lib/platform/context.js +70 -130
  38. package/dist/lib/platform/github-client.js +8 -2
  39. package/dist/lib/platform/github-provider.js +903 -0
  40. package/dist/lib/platform/in-label-sync.js +143 -0
  41. package/dist/lib/platform/issue-comments.js +94 -41
  42. package/dist/lib/platform/issues.js +335 -189
  43. package/dist/lib/platform/metadata-labels.js +6 -2
  44. package/dist/lib/platform/none-provider.js +28 -0
  45. package/dist/lib/platform/pr-change-report.js +551 -0
  46. package/dist/lib/platform/pr-checks.js +124 -106
  47. package/dist/lib/platform/pr-review.js +88 -72
  48. package/dist/lib/platform/pr-summary.js +523 -49
  49. package/dist/lib/platform/provider-bridge.js +45 -0
  50. package/dist/lib/platform/provider-contract.d.ts +487 -0
  51. package/dist/lib/platform/provider-contract.js +95 -0
  52. package/dist/lib/platform/provider-loader.js +251 -0
  53. package/dist/lib/platform/provider-validation.js +504 -0
  54. package/dist/lib/platform/pull-request-metadata.js +8 -6
  55. package/dist/lib/platform/pull-requests.js +1050 -502
  56. package/dist/lib/platform/release-notes.js +78 -55
  57. package/dist/lib/platform/releases.js +79 -50
  58. package/dist/lib/platform/resource-identity.d.ts +26 -0
  59. package/dist/lib/platform/resource-identity.js +92 -0
  60. package/dist/lib/platform/task-identities.js +31 -0
  61. package/dist/lib/platform/verification-required.js +22 -12
  62. package/dist/lib/platform/verification-sync.js +118 -239
  63. package/dist/lib/pr-review/evidence-grading.js +30 -9
  64. package/dist/lib/run/index.js +63 -23
  65. package/dist/lib/sandbox/config.js +5 -0
  66. package/dist/lib/sandbox/control/client.js +96 -15
  67. package/dist/lib/sandbox/control/executor.js +3 -3
  68. package/dist/lib/sandbox/control/lifecycle.js +29 -7
  69. package/dist/lib/sandbox/control/protocol.js +77 -0
  70. package/dist/lib/sandbox/control/server.js +398 -25
  71. package/dist/lib/sandbox/control/state.js +204 -1
  72. package/dist/lib/server/process-state.js +2 -2
  73. package/dist/lib/server/protocol.js +38 -1
  74. package/dist/lib/task/activity-log.js +8 -1
  75. package/dist/lib/task/artifact-lifecycle.js +35 -7
  76. package/dist/lib/task/capabilities.js +239 -0
  77. package/dist/lib/task/commands/status.js +32 -0
  78. package/dist/lib/task/commit-intent.js +100 -0
  79. package/dist/lib/task/commit-operation.js +207 -102
  80. package/dist/lib/task/control-authority.js +8 -5
  81. package/dist/lib/task/create-service.js +8 -8
  82. package/dist/lib/task/create.js +33 -7
  83. package/dist/lib/task/current-contract.js +5 -1
  84. package/dist/lib/task/decision-details.js +3 -48
  85. package/dist/lib/task/delivery-target.js +97 -0
  86. package/dist/lib/task/delivery.js +153 -0
  87. package/dist/lib/task/events.js +297 -17
  88. package/dist/lib/task/finalization.js +37 -13
  89. package/dist/lib/task/human-override.js +61 -36
  90. package/dist/lib/task/invalidation-command.js +86 -0
  91. package/dist/lib/task/invalidation.js +260 -0
  92. package/dist/lib/task/ledger-intents.js +37 -0
  93. package/dist/lib/task/lifecycle.js +106 -9
  94. package/dist/lib/task/local-artifact-finalization.js +374 -0
  95. package/dist/lib/task/orchestration.js +100 -151
  96. package/dist/lib/task/pr-delivery-fact.js +183 -0
  97. package/dist/lib/task/review-finalization.js +4 -8
  98. package/dist/lib/task/review-fingerprint.js +32 -2
  99. package/dist/lib/task/rework-intent.js +138 -0
  100. package/dist/lib/task/skill-scope.js +23 -0
  101. package/dist/lib/task/task-execution-lock.js +27 -17
  102. package/dist/lib/task/verification-engine.js +89 -38
  103. package/dist/lib/task/verification-review.js +1 -1
  104. package/dist/lib/task/verification.js +3 -3
  105. package/dist/lib/task/workflow-warning-intents.js +1 -1
  106. package/dist/lib/task/write.js +11 -0
  107. package/dist/lib/update.js +9 -0
  108. package/dist/lib/version.js +11 -1
  109. package/dist/lifecycle-build-manifest.json +3 -3
  110. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +105 -10
  111. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +12 -1
  112. package/lib/agent-clients/next-steps.ts +6 -1
  113. package/lib/decide.ts +3 -3
  114. package/lib/defaults.json +4 -0
  115. package/lib/git/workflow.ts +38 -1
  116. package/lib/init.ts +4 -1
  117. package/lib/internal/git-workflow.ts +7 -1
  118. package/lib/internal/platform-checks.ts +3 -3
  119. package/lib/internal/platform-comment.ts +8 -8
  120. package/lib/internal/platform-context.ts +2 -2
  121. package/lib/internal/platform-issue.ts +7 -8
  122. package/lib/internal/platform-pr-review.ts +9 -9
  123. package/lib/internal/platform-pr.ts +53 -21
  124. package/lib/internal/platform-release-notes.ts +3 -3
  125. package/lib/internal/pr-review-grade.ts +10 -8
  126. package/lib/internal/release-workflow.ts +4 -4
  127. package/lib/internal/sandbox-control.ts +27 -3
  128. package/lib/internal/task-activity.ts +3 -3
  129. package/lib/internal/task-artifact.ts +65 -22
  130. package/lib/internal/task-create.ts +2 -2
  131. package/lib/internal/task-delivery.ts +53 -0
  132. package/lib/internal/task-event.ts +6 -3
  133. package/lib/internal/task-finalization.ts +3 -3
  134. package/lib/internal/task-invalidation.ts +39 -0
  135. package/lib/internal/task-ledger.ts +11 -8
  136. package/lib/internal/task-lifecycle.ts +2 -2
  137. package/lib/internal/task-override.ts +6 -6
  138. package/lib/internal/task-review.ts +3 -3
  139. package/lib/internal/task-verify.ts +6 -6
  140. package/lib/internal/task-warning.ts +3 -3
  141. package/lib/platform/adapters.ts +3 -2
  142. package/lib/platform/change-request-git-evidence.ts +36 -9
  143. package/lib/platform/completion-backfill.ts +9 -8
  144. package/lib/platform/context.ts +97 -134
  145. package/lib/platform/github-client.ts +10 -3
  146. package/lib/platform/github-provider.ts +910 -0
  147. package/lib/platform/in-label-sync.ts +208 -0
  148. package/lib/platform/issue-comments.ts +97 -47
  149. package/lib/platform/issues.ts +344 -174
  150. package/lib/platform/metadata-labels.ts +5 -2
  151. package/lib/platform/none-provider.ts +35 -0
  152. package/lib/platform/pr-change-report.ts +645 -0
  153. package/lib/platform/pr-checks.ts +129 -107
  154. package/lib/platform/pr-review.ts +88 -85
  155. package/lib/platform/pr-summary.ts +562 -52
  156. package/lib/platform/provider-bridge.ts +82 -0
  157. package/lib/platform/provider-contract.ts +536 -0
  158. package/lib/platform/provider-loader.ts +307 -0
  159. package/lib/platform/provider-validation.ts +529 -0
  160. package/lib/platform/pull-request-metadata.ts +10 -7
  161. package/lib/platform/pull-requests.ts +1126 -505
  162. package/lib/platform/release-notes.ts +76 -59
  163. package/lib/platform/releases.ts +77 -43
  164. package/lib/platform/resource-identity.ts +121 -0
  165. package/lib/platform/task-identities.ts +40 -0
  166. package/lib/platform/types.ts +4 -2
  167. package/lib/platform/verification-required.ts +21 -13
  168. package/lib/platform/verification-sync.ts +118 -293
  169. package/lib/pr-review/evidence-grading.ts +31 -11
  170. package/lib/run/index.ts +57 -22
  171. package/lib/sandbox/config.ts +7 -0
  172. package/lib/sandbox/control/client.ts +105 -15
  173. package/lib/sandbox/control/executor.ts +4 -4
  174. package/lib/sandbox/control/lifecycle.ts +30 -8
  175. package/lib/sandbox/control/protocol.ts +113 -0
  176. package/lib/sandbox/control/server.ts +431 -23
  177. package/lib/sandbox/control/state.ts +251 -1
  178. package/lib/server/process-state.ts +2 -2
  179. package/lib/server/protocol.ts +36 -1
  180. package/lib/task/activity-log.ts +8 -0
  181. package/lib/task/artifact-lifecycle.ts +34 -8
  182. package/lib/task/capabilities.ts +262 -0
  183. package/lib/task/commands/status.ts +38 -0
  184. package/lib/task/commit-intent.ts +137 -0
  185. package/lib/task/commit-operation.ts +186 -88
  186. package/lib/task/control-authority.ts +11 -8
  187. package/lib/task/create-service.ts +8 -8
  188. package/lib/task/create.ts +34 -9
  189. package/lib/task/current-contract.ts +6 -1
  190. package/lib/task/decision-details.ts +8 -64
  191. package/lib/task/delivery-target.ts +130 -0
  192. package/lib/task/delivery.ts +160 -0
  193. package/lib/task/events.ts +296 -16
  194. package/lib/task/finalization.ts +50 -14
  195. package/lib/task/human-override.ts +73 -46
  196. package/lib/task/invalidation-command.ts +86 -0
  197. package/lib/task/invalidation.ts +332 -0
  198. package/lib/task/ledger-intents.ts +35 -1
  199. package/lib/task/lifecycle.ts +93 -4
  200. package/lib/task/local-artifact-finalization.ts +563 -0
  201. package/lib/task/orchestration.ts +110 -146
  202. package/lib/task/pr-delivery-fact.ts +237 -0
  203. package/lib/task/review-finalization.ts +4 -14
  204. package/lib/task/review-fingerprint.ts +33 -2
  205. package/lib/task/rework-intent.ts +165 -0
  206. package/lib/task/skill-scope.ts +24 -0
  207. package/lib/task/task-execution-lock.ts +23 -14
  208. package/lib/task/verification-engine.ts +95 -43
  209. package/lib/task/verification-review.ts +2 -0
  210. package/lib/task/verification.ts +4 -4
  211. package/lib/task/workflow-warning-intents.ts +1 -1
  212. package/lib/task/write.ts +19 -0
  213. package/lib/update.ts +11 -1
  214. package/lib/version.ts +15 -1
  215. package/package.json +7 -1
  216. package/templates/.agents/rules/README.en.md +4 -2
  217. package/templates/.agents/rules/README.zh-CN.md +4 -2
  218. package/templates/.agents/rules/create-issue.en.md +11 -2
  219. package/templates/.agents/rules/create-issue.zh-CN.md +24 -2
  220. package/templates/.agents/rules/issue-pr-commands.en.md +4 -17
  221. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
  222. package/templates/.agents/rules/issue-sync.en.md +28 -5
  223. package/templates/.agents/rules/issue-sync.zh-CN.md +64 -5
  224. package/templates/.agents/rules/label-milestone-setup.en.md +15 -2
  225. package/templates/.agents/rules/label-milestone-setup.zh-CN.md +15 -2
  226. package/templates/.agents/rules/local-artifact-repair.en.md +53 -0
  227. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +53 -0
  228. package/templates/.agents/rules/milestone-inference.en.md +12 -2
  229. package/templates/.agents/rules/milestone-inference.zh-CN.md +25 -3
  230. package/templates/.agents/rules/pr-sync.en.md +28 -13
  231. package/templates/.agents/rules/pr-sync.zh-CN.md +39 -5
  232. package/templates/.agents/rules/release-commands.en.md +42 -3
  233. package/templates/.agents/rules/release-commands.zh-CN.md +42 -3
  234. package/templates/.agents/rules/security-alerts.en.md +17 -2
  235. package/templates/.agents/rules/security-alerts.zh-CN.md +17 -2
  236. package/templates/.agents/rules/sync-content-generation.en.md +8 -0
  237. package/templates/.agents/rules/sync-content-generation.zh-CN.md +8 -0
  238. package/templates/.agents/rules/task-management.en.md +1 -1
  239. package/templates/.agents/rules/task-management.zh-CN.md +1 -1
  240. package/templates/.agents/rules/task-short-id.en.md +1 -4
  241. package/templates/.agents/rules/task-short-id.zh-CN.md +1 -3
  242. package/templates/.agents/scripts/security-alerts.github.sh +151 -0
  243. package/templates/.agents/scripts/security-alerts.sh +31 -0
  244. package/templates/.agents/skills/analyze-task/SKILL.en.md +14 -3
  245. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -3
  246. package/templates/.agents/skills/check-task/SKILL.en.md +16 -37
  247. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -37
  248. package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
  249. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
  250. package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
  251. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
  252. package/templates/.agents/skills/code-task/SKILL.en.md +32 -10
  253. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +35 -13
  254. package/templates/.agents/skills/code-task/config/verify.en.json +1 -0
  255. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -0
  256. package/templates/.agents/skills/commit/SKILL.en.md +3 -3
  257. package/templates/.agents/skills/commit/SKILL.zh-CN.md +3 -3
  258. package/templates/.agents/skills/commit/config/verify.json +1 -1
  259. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +3 -3
  260. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +3 -3
  261. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +2 -0
  262. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +2 -0
  263. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +4 -4
  264. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +4 -4
  265. package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -2
  266. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -2
  267. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +7 -4
  268. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +7 -4
  269. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
  270. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
  271. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +5 -4
  272. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +5 -4
  273. package/templates/.agents/skills/complete-task/SKILL.en.md +31 -13
  274. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +30 -13
  275. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +2 -2
  276. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +2 -2
  277. package/templates/.agents/skills/create-pr/SKILL.en.md +11 -10
  278. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +11 -11
  279. package/templates/.agents/skills/create-pr/config/verify.json +1 -0
  280. package/templates/.agents/skills/create-pr/reference/change-report.en.md +16 -4
  281. package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +16 -4
  282. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +5 -3
  283. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +5 -3
  284. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
  285. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
  286. package/templates/.agents/skills/create-pr/scripts/change-report.mjs +3 -0
  287. package/templates/.agents/skills/create-task/SKILL.en.md +1 -0
  288. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +1 -0
  289. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  290. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  291. package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
  292. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  293. package/templates/.agents/skills/init-labels/SKILL.en.md +9 -10
  294. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +9 -10
  295. package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +57 -1
  296. package/templates/.agents/skills/init-labels/scripts/init-labels.sh +27 -1
  297. package/templates/.agents/skills/init-milestones/SKILL.en.md +3 -5
  298. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +3 -5
  299. package/templates/.agents/skills/plan-task/SKILL.en.md +14 -3
  300. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +11 -3
  301. package/templates/.agents/skills/review-analysis/SKILL.en.md +6 -5
  302. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +6 -5
  303. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +18 -0
  304. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +18 -0
  305. package/templates/.agents/skills/review-code/SKILL.en.md +10 -9
  306. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +10 -9
  307. package/templates/.agents/skills/review-code/reference/output-templates.en.md +19 -1
  308. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +19 -1
  309. package/templates/.agents/skills/review-code/reference/report-template.en.md +4 -2
  310. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +4 -2
  311. package/templates/.agents/skills/review-plan/SKILL.en.md +6 -5
  312. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +6 -5
  313. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +18 -0
  314. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +18 -0
  315. package/templates/.agents/skills/review-pr/SKILL.en.md +1 -0
  316. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +1 -0
  317. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +2 -2
  318. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +2 -2
  319. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +5 -2
  320. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +5 -2
  321. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  322. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  323. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +4 -0
  324. package/templates/.agents/skills/watch-pr/SKILL.en.md +6 -6
  325. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +6 -6
  326. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +6 -0
  327. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +6 -0
  328. package/templates/.agents/templates/task.en.md +23 -2
  329. package/templates/.agents/templates/task.zh-CN.md +23 -2
  330. package/templates/.agents/workflows/bug-fix.en.yaml +9 -8
  331. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +9 -8
  332. package/templates/.agents/workflows/feature-development.en.yaml +9 -8
  333. package/templates/.agents/workflows/feature-development.zh-CN.yaml +9 -8
  334. package/templates/.agents/workflows/refactoring.en.yaml +9 -8
  335. package/templates/.agents/workflows/refactoring.zh-CN.yaml +9 -8
  336. package/templates/.github/workflows/pr-label.yml +7 -26
  337. package/templates/.agents/rules/create-issue.github.en.md +0 -14
  338. package/templates/.agents/rules/create-issue.github.zh-CN.md +0 -27
  339. package/templates/.agents/rules/issue-pr-commands.github.en.md +0 -34
  340. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +0 -49
  341. package/templates/.agents/rules/issue-sync.github.en.md +0 -40
  342. package/templates/.agents/rules/issue-sync.github.zh-CN.md +0 -76
  343. package/templates/.agents/rules/label-milestone-setup.github.en.md +0 -60
  344. package/templates/.agents/rules/label-milestone-setup.github.zh-CN.md +0 -60
  345. package/templates/.agents/rules/milestone-inference.github.en.md +0 -15
  346. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +0 -27
  347. package/templates/.agents/rules/pr-checks-commands.github.en.md +0 -18
  348. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +0 -34
  349. package/templates/.agents/rules/pr-sync.github.en.md +0 -43
  350. package/templates/.agents/rules/pr-sync.github.zh-CN.md +0 -69
  351. package/templates/.agents/rules/release-commands.github.en.md +0 -44
  352. package/templates/.agents/rules/release-commands.github.zh-CN.md +0 -44
  353. package/templates/.agents/rules/security-alerts.github.en.md +0 -43
  354. package/templates/.agents/rules/security-alerts.github.zh-CN.md +0 -43
  355. /package/templates/.agents/rules/{issue-fields.github.en.md → issue-fields.en.md} +0 -0
  356. /package/templates/.agents/rules/{issue-fields.github.zh-CN.md → issue-fields.zh-CN.md} +0 -0
@@ -4,8 +4,7 @@ import { randomUUID } from 'node:crypto';
4
4
 
5
5
  import { parseTypedTaskFrontmatter } from './frontmatter.ts';
6
6
  import { validateCurrentTaskContract } from './current-contract.ts';
7
- import { parseReviewSummary, resolveCanonicalVerdict } from './review-artifacts.ts';
8
- import { inspectArtifactDirectory } from './artifact-lifecycle.ts';
7
+ import { parseReviewSummary } from './review-artifacts.ts';
9
8
  import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.ts';
10
9
  import type { LedgerDocument } from './ledger.ts';
11
10
  import { resolveTaskRef } from './resolve-ref.ts';
@@ -29,7 +28,6 @@ import type {
29
28
  } from './delegation-receipts.ts';
30
29
  import { normalizeAgentClients } from '../agent-clients/config.ts';
31
30
  import { resolveAgentRuntimeStoreRoot } from '../runtime/agent-runtime.ts';
32
- import { inspectPlatformPullRequest } from '../platform/pull-requests.ts';
33
31
  import {
34
32
  getAgentClientCapability,
35
33
  getAgentClientDelegationEvidence,
@@ -52,6 +50,13 @@ import { assertGitRepositoryBinding } from '../git/worktree-identity.ts';
52
50
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
53
51
  import { hasActiveCodexLifecycleEvidence } from '../agent-clients/adapters/codex-lifecycle/store.ts';
54
52
  import type { CodexCapabilityProvenanceDetail } from '../agent-clients/adapters/codex-lifecycle/capability-store.ts';
53
+ import { extractReviewBaseline, extractReviewDiffBase, extractReviewTargetHead, extractReviewedHead } from './review-fingerprint.ts';
54
+ import { resolveDeliveryTarget, resolveDiffBase, resolveTargetHead } from './delivery-target.ts';
55
+ import { buildLifecycleFacts, canStart, recommendNext } from './capabilities.ts';
56
+ import { hasOpenLifecycleExecution } from './activity-log.ts';
57
+ import { invalidationBlocks } from './invalidation.ts';
58
+ import type { InvalidationDocument } from './invalidation.ts';
59
+ import type { LifecycleAction, LifecycleFacts } from './capabilities.ts';
55
60
 
56
61
  type OrchestrationStatus = 'running' | 'paused' | 'completed';
57
62
  type ModelPolicySource = Readonly<{
@@ -84,8 +89,8 @@ type CleanCompletionEvidence = Readonly<{
84
89
  headTree: string;
85
90
  worktreeTree: string;
86
91
  lastReviewedCommit: string;
87
- prNumber: number;
88
- prHead: string;
92
+ prNumber: number | null;
93
+ prHead: string | null;
89
94
  }>;
90
95
  type OrchestrationRun = Readonly<{
91
96
  taskId: string;
@@ -107,7 +112,7 @@ type OrchestrationRun = Readonly<{
107
112
  updatedAt: string;
108
113
  }>;
109
114
  type OrchestrationNext = Readonly<{
110
- action: 'analyze-task' | 'review-analysis' | 'plan-task' | 'review-plan' | 'code-task' | 'review-code' | 'commit';
115
+ action: 'analyze-task' | 'review-analysis' | 'plan-task' | 'review-plan' | 'code-task' | 'review-code';
111
116
  role: DelegationRole;
112
117
  stage: DelegationStage;
113
118
  round: number;
@@ -273,9 +278,8 @@ function isCompletionEvidence(value: unknown): value is CleanCompletionEvidence
273
278
  && exactText(value.headTree)
274
279
  && exactText(value.worktreeTree)
275
280
  && exactText(value.lastReviewedCommit)
276
- && Number.isSafeInteger(value.prNumber)
277
- && (value.prNumber as number) > 0
278
- && exactText(value.prHead);
281
+ && (value.prNumber === null || (Number.isSafeInteger(value.prNumber) && (value.prNumber as number) > 0))
282
+ && (value.prHead === null || exactText(value.prHead));
279
283
  }
280
284
 
281
285
  const ORCHESTRATION_RUN_KEYS = [
@@ -523,78 +527,57 @@ function artifactName(family: string, round: number): string {
523
527
  return round === 1 ? `${family}.md` : `${family}-r${round}.md`;
524
528
  }
525
529
 
526
- function hasReviewAfterArtifact(taskDir: string, family: string, reviewFamily: string): boolean {
527
- const artifact = inspectArtifactDirectory(taskDir, family as 'analysis' | 'plan' | 'code');
528
- const review = inspectArtifactDirectory(taskDir, reviewFamily as 'review-analysis' | 'review-plan' | 'review-code');
529
- return artifact.status === 'ready'
530
- && review.status === 'ready'
531
- && artifact.latest !== null
532
- && review.reviewedInput?.name === artifact.latest.name;
533
- }
534
-
535
- function latestReviewApproved(taskDir: string, family: 'review-analysis' | 'review-plan' | 'review-code'): { approved: boolean; error: string | null } {
536
- const round = highestRound(taskDir, family);
537
- if (round === 0) return { approved: false, error: null };
538
- let content: string;
539
- try { content = fs.readFileSync(path.join(taskDir, artifactName(family, round)), 'utf8'); }
540
- catch (error) { return { approved: false, error: String(error) }; }
541
- const parsed = parseReviewSummary(content);
542
- if (!parsed.ok) return { approved: false, error: `${parsed.code}: ${parsed.message}` };
543
- const verdict = resolveCanonicalVerdict(parsed.summary);
544
- if (!verdict.ok) return { approved: false, error: `${verdict.code}: ${verdict.message}` };
545
- return { approved: verdict.verdict === 'Approved', error: null };
546
- }
547
-
548
- function routeFromFacts(taskDir: string): Omit<OrchestrationNext, 'requestedModel' | 'requestedReasoningEffort'> | { error: string } | null {
549
- const analysisRound = highestRound(taskDir, 'analysis');
550
- const analysisReviewRound = highestRound(taskDir, 'review-analysis');
551
- const planRound = highestRound(taskDir, 'plan');
552
- const planReviewRound = highestRound(taskDir, 'review-plan');
553
- const codeRound = highestRound(taskDir, 'code');
554
- const codeReviewRound = highestRound(taskDir, 'review-code');
555
- if (analysisRound === 0) {
556
- const round = analysisRound + 1;
557
- return { action: 'analyze-task', role: 'executor', stage: 'analysis', round, artifact: artifactName('analysis', round) };
558
- }
559
- if (!hasReviewAfterArtifact(taskDir, 'analysis', 'review-analysis')) {
560
- const round = analysisReviewRound + 1;
561
- return { action: 'review-analysis', role: 'reviewer', stage: 'review-analysis', round, artifact: artifactName('review-analysis', round) };
562
- }
563
- const analysisVerdict = latestReviewApproved(taskDir, 'review-analysis');
564
- if (analysisVerdict.error) return { error: analysisVerdict.error };
565
- if (!analysisVerdict.approved) {
566
- const round = analysisRound + 1;
567
- return { action: 'analyze-task', role: 'executor', stage: 'analysis', round, artifact: artifactName('analysis', round) };
568
- }
569
- if (planRound === 0) {
570
- const round = planRound + 1;
571
- return { action: 'plan-task', role: 'executor', stage: 'plan', round, artifact: artifactName('plan', round) };
530
+ function validateSavedCurrentDiffBase(
531
+ repoRoot: string,
532
+ metadata: ReturnType<typeof parseTypedTaskFrontmatter>,
533
+ taskDir: string,
534
+ reviewedHead: string
535
+ ): Readonly<{ code: string; message: string }> | null {
536
+ const reviewRound = highestRound(taskDir, 'review-code');
537
+ const reviewContent = fs.readFileSync(path.join(taskDir, artifactName('review-code', reviewRound)), 'utf8');
538
+ const savedTargetHead = extractReviewTargetHead(reviewContent);
539
+ const savedDiffBase = extractReviewDiffBase(reviewContent);
540
+ const savedReviewedHead = extractReviewedHead(reviewContent) || extractReviewBaseline(reviewContent);
541
+ if (!savedTargetHead && !savedDiffBase && !savedReviewedHead) return null;
542
+ if (savedReviewedHead && savedReviewedHead !== reviewedHead) {
543
+ return { code: 'ORCHESTRATION_REVIEWED_HEAD_MISMATCH', message: 'review-code saved reviewed head does not match last_reviewed_commit' };
572
544
  }
573
- if (!hasReviewAfterArtifact(taskDir, 'plan', 'review-plan')) {
574
- const round = planReviewRound + 1;
575
- return { action: 'review-plan', role: 'reviewer', stage: 'review-plan', round, artifact: artifactName('review-plan', round) };
545
+ if (!savedTargetHead || !savedDiffBase) {
546
+ return { code: 'ORCHESTRATION_REVIEW_TARGET_INVALID', message: 'review-code must save target head and diff base together' };
576
547
  }
577
- const planVerdict = latestReviewApproved(taskDir, 'review-plan');
578
- if (planVerdict.error) return { error: planVerdict.error };
579
- if (!planVerdict.approved) {
580
- const round = planRound + 1;
581
- return { action: 'plan-task', role: 'executor', stage: 'plan', round, artifact: artifactName('plan', round) };
548
+ const savedBase = resolveDiffBase(repoRoot, reviewedHead, savedTargetHead);
549
+ if (!savedBase.ok) return { code: 'ORCHESTRATION_REVIEW_TARGET_INVALID', message: savedBase.message };
550
+ if (savedBase.diffBase !== savedDiffBase) {
551
+ return { code: 'ORCHESTRATION_REVIEW_DIFF_BASE_MISMATCH', message: 'saved review diff base does not match reviewed head and saved target head' };
582
552
  }
583
- if (codeRound === 0) {
584
- const round = codeRound + 1;
585
- return { action: 'code-task', role: 'executor', stage: 'code', round, artifact: artifactName('code', round) };
586
- }
587
- if (!hasReviewAfterArtifact(taskDir, 'code', 'review-code')) {
588
- const round = codeReviewRound + 1;
589
- return { action: 'review-code', role: 'reviewer', stage: 'review-code', round, artifact: artifactName('review-code', round) };
590
- }
591
- const codeVerdict = latestReviewApproved(taskDir, 'review-code');
592
- if (codeVerdict.error) return { error: codeVerdict.error };
593
- if (!codeVerdict.approved) {
594
- const round = codeRound + 1;
595
- return { action: 'code-task', role: 'executor', stage: 'code', round, artifact: artifactName('code', round) };
553
+ const target = resolveDeliveryTarget(repoRoot, {
554
+ remote: typeof metadata.delivery_remote === 'string' ? metadata.delivery_remote : undefined,
555
+ baseRef: typeof metadata.delivery_base_ref === 'string' ? metadata.delivery_base_ref : undefined
556
+ });
557
+ if (!target.ok) return { code: target.code, message: target.message };
558
+ const currentTarget = resolveTargetHead(repoRoot, target.value);
559
+ if (!currentTarget.ok) return { code: currentTarget.code, message: currentTarget.message };
560
+ const currentBase = resolveDiffBase(repoRoot, reviewedHead, currentTarget.head!);
561
+ if (!currentBase.ok) return { code: currentBase.code, message: currentBase.message };
562
+ if (currentBase.diffBase !== savedDiffBase) {
563
+ return { code: 'ORCHESTRATION_REVIEW_TARGET_CHANGED', message: 'current delivery target changed the reviewed diff base; re-run review-code' };
596
564
  }
597
- return { action: 'commit', role: 'executor', stage: 'commit', round: 1, artifact: 'commit' };
565
+ return null;
566
+ }
567
+
568
+ function routeFromFacts(facts: LifecycleFacts): Omit<OrchestrationNext, 'requestedModel' | 'requestedReasoningEffort'> | { completion: true } | null {
569
+ const recommendation = recommendNext(facts);
570
+ if (!recommendation.action) return { completion: true };
571
+ const action = recommendation.action;
572
+ if (action === 'manual-validation' || action === 'validation-run') return null;
573
+ const family = action === 'analysis' ? 'analysis' : action;
574
+ const round = Math.max(0, ...[...(facts.artifacts[family] ?? []), ...(facts.staleArtifacts?.[family] ?? [])].map((name) => {
575
+ const match = /-r(\d+)\.md$/.exec(name);
576
+ return match ? Number(match[1]) : 1;
577
+ })) + 1;
578
+ const command = action === 'analysis' ? 'analyze-task' : action === 'plan' ? 'plan-task' : action === 'code' ? 'code-task' : action;
579
+ const role = action.startsWith('review-') ? 'reviewer' : 'executor';
580
+ return { action: command, role, stage: action, round, artifact: artifactName(family, round) };
598
581
  }
599
582
 
600
583
  function routeOrchestration(taskRef: string, options: OrchestrationOptions = {}): OrchestrationResult {
@@ -614,11 +597,13 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
614
597
  }
615
598
  let metadata: ReturnType<typeof parseTypedTaskFrontmatter>;
616
599
  let currentLedger: LedgerDocument | null = null;
600
+ let currentInvalidation: InvalidationDocument = { operations: [], targets: [] };
617
601
  if (resolved.state === 'active') {
618
602
  const contract = validateCurrentTaskContract(content);
619
603
  if (!contract.ok) return failed('ORCHESTRATION_CURRENT_CONTRACT_INVALID', contract.message, resolved.taskId);
620
604
  metadata = contract.metadata;
621
605
  currentLedger = contract.ledger;
606
+ currentInvalidation = contract.invalidation;
622
607
  } else {
623
608
  try {
624
609
  metadata = parseTypedTaskFrontmatter(content);
@@ -626,17 +611,25 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
626
611
  return failed('ORCHESTRATION_TASK_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
627
612
  }
628
613
  }
629
- const routed = routeFromFacts(resolved.taskDir);
630
- if (routed && 'error' in routed) return failed('ORCHESTRATION_REVIEW_INVALID', routed.error, resolved.taskId);
614
+ if (invalidationBlocks(currentInvalidation)) {
615
+ return failed('ORCHESTRATION_INVALIDATION_INCOMPLETE', 'artifact invalidation is incomplete; reconcile task.md before routing downstream work', resolved.taskId);
616
+ }
617
+ let run: OrchestrationRun | null;
618
+ try {
619
+ run = readRun(resolved.taskDir);
620
+ } catch (error) {
621
+ return failed('ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
622
+ }
623
+ const facts = buildLifecycleFacts(
624
+ resolved.taskDir,
625
+ content,
626
+ resolved.state,
627
+ hasOpenLifecycleExecution(content) || Boolean(run?.pendingDelegation)
628
+ );
629
+ if (!facts.ok) return failed('ORCHESTRATION_CAPABILITY_FACTS_INVALID', facts.message, resolved.taskId);
630
+ const routed = routeFromFacts(facts.facts);
631
631
  if (!routed) return failed('ORCHESTRATION_ROUTE_UNKNOWN', 'cannot determine a unique lifecycle action', resolved.taskId);
632
- const run = readRun(resolved.taskDir);
633
- const policy = run ? rolePolicy(run, routed.role) : null;
634
- const next: OrchestrationNext = {
635
- ...routed,
636
- requestedModel: policy?.model ?? null,
637
- requestedReasoningEffort: policy?.reasoningEffort ?? null
638
- };
639
- if (next.stage === 'commit') {
632
+ if ('completion' in routed) {
640
633
  const reviewRound = highestRound(resolved.taskDir, 'review-code');
641
634
  const review = parseReviewSummary(fs.readFileSync(
642
635
  path.join(resolved.taskDir, artifactName('review-code', reviewRound)),
@@ -656,29 +649,8 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
656
649
  if (run?.status === 'completed') {
657
650
  return { status: 'completed', changed: false, taskId: resolved.taskId, run, next: null, error: null };
658
651
  }
659
-
660
- let config: unknown;
661
- try {
662
- config = JSON.parse(fs.readFileSync(path.join(resolved.repoRoot, '.agents', '.airc.json'), 'utf8'));
663
- } catch (error) {
664
- return failed('ORCHESTRATION_CONFIG_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
665
- }
666
- if (!config || typeof config !== 'object' || Array.isArray(config)) {
667
- return failed('ORCHESTRATION_CONFIG_INVALID', 'project configuration must be an object', resolved.taskId);
668
- }
669
- const projectConfig = config as Record<string, unknown>;
670
- if (!Object.hasOwn(projectConfig, 'prFlow') || projectConfig.prFlow === 'disabled') {
671
- return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
672
- }
673
- if (projectConfig.prFlow !== 'required') {
674
- return failed('ORCHESTRATION_CONFIG_INVALID', "project configuration 'prFlow' must be 'required' or 'disabled'", resolved.taskId);
675
- }
676
- const prNumber = Number(metadata.pr_number);
677
- if (!Number.isInteger(prNumber) || prNumber <= 0) {
678
- return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
679
- }
680
652
  if (!run) {
681
- return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
653
+ return { status: 'running', changed: false, taskId: resolved.taskId, run, next: null, error: null };
682
654
  }
683
655
  if (run.status !== 'running' || run.pendingDelegation !== null) {
684
656
  return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'clean completion requires an idle running orchestration', resolved.taskId);
@@ -692,35 +664,19 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
692
664
  return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
693
665
  }
694
666
  if (before.headTree !== before.worktreeTree) {
695
- return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
667
+ return { status: 'running', changed: false, taskId: resolved.taskId, run, next: null, error: null };
696
668
  }
697
669
  const lastReviewedCommit = String(metadata.last_reviewed_commit ?? '');
698
670
  if (before.head !== lastReviewedCommit) {
699
671
  return failed('ORCHESTRATION_REVIEWED_HEAD_MISMATCH', 'local HEAD does not match last_reviewed_commit', resolved.taskId);
700
672
  }
701
-
702
- const inspect: NonNullable<OrchestrationOptions['inspectPullRequest']> =
703
- options.inspectPullRequest ?? inspectPlatformPullRequest;
704
- let inspection: ReturnType<NonNullable<OrchestrationOptions['inspectPullRequest']>>;
705
- try {
706
- inspection = inspect(resolved.taskId, { cwd: gitRoot });
707
- } catch (error) {
708
- return failed('ORCHESTRATION_PR_INSPECTION_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
709
- }
710
- if (inspection.status === 'blocked') {
711
- return failed('ORCHESTRATION_PR_INSPECTION_BLOCKED', inspection.error?.message ?? 'pull request inspection is blocked', resolved.taskId);
712
- }
713
- if (inspection.status === 'failed') {
714
- return failed('ORCHESTRATION_PR_INSPECTION_FAILED', inspection.error?.message ?? 'pull request inspection failed', resolved.taskId);
715
- }
716
- const inspectedPrNumber = inspection.task?.prNumber ?? inspection.prNumber;
717
- const prHead = inspection.pullRequest?.head?.sha;
718
- if (typeof prHead !== 'string') {
719
- return failed('ORCHESTRATION_PR_INSPECTION_INVALID', 'pull request inspection returned incomplete identity evidence', resolved.taskId);
720
- }
721
- if (inspectedPrNumber !== prNumber || prHead !== before.head) {
722
- return failed('ORCHESTRATION_PR_HEAD_MISMATCH', 'pull request identity does not match the reviewed local HEAD', resolved.taskId);
723
- }
673
+ const diffBaseError = validateSavedCurrentDiffBase(
674
+ resolved.repoRoot,
675
+ metadata,
676
+ resolved.taskDir,
677
+ lastReviewedCommit
678
+ );
679
+ if (diffBaseError) return failed(diffBaseError.code, diffBaseError.message, resolved.taskId);
724
680
 
725
681
  let after: RepositorySnapshot;
726
682
  try {
@@ -729,7 +685,7 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
729
685
  return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
730
686
  }
731
687
  if (after.headTree !== after.worktreeTree) {
732
- return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
688
+ return { status: 'running', changed: false, taskId: resolved.taskId, run, next: null, error: null };
733
689
  }
734
690
  if (after.head !== before.head || after.headTree !== before.headTree) {
735
691
  return failed('ORCHESTRATION_REPOSITORY_CHANGED', 'repository changed while clean completion evidence was collected', resolved.taskId);
@@ -743,8 +699,8 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
743
699
  headTree: after.headTree,
744
700
  worktreeTree: after.worktreeTree,
745
701
  lastReviewedCommit,
746
- prNumber,
747
- prHead
702
+ prNumber: null,
703
+ prHead: null
748
704
  };
749
705
  const completed = withUpdatedRun(run, {
750
706
  status: 'completed',
@@ -755,6 +711,18 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
755
711
  saveRun(resolved.taskDir, completed);
756
712
  return { status: 'completed', changed: true, taskId: resolved.taskId, run: completed, next: null, error: null };
757
713
  }
714
+ const action = routed.stage as LifecycleAction;
715
+ const capability = canStart(action, facts.facts, {
716
+ initiator: 'orchestrator', requestId: `orchestration:${resolved.taskId}:${routed.stage}:${routed.round}`,
717
+ requestedAction: action, reasonCode: 'user-request', explicitRequest: true
718
+ });
719
+ if (!capability.allowed) return failed('ORCHESTRATION_CAPABILITY_DENIED', `${capability.reasonCode}: ${capability.evidence.join(', ')}`, resolved.taskId);
720
+ const policy = run ? rolePolicy(run, routed.role) : null;
721
+ const next: OrchestrationNext = {
722
+ ...routed,
723
+ requestedModel: policy?.model ?? null,
724
+ requestedReasoningEffort: policy?.reasoningEffort ?? null
725
+ };
758
726
  return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
759
727
  }
760
728
 
@@ -852,10 +820,7 @@ function prepareOrchestrationDelegationUnlocked(
852
820
  }
853
821
  const updated = withUpdatedRun(run, {
854
822
  nextStage: next.stage,
855
- pendingDelegation: receipt,
856
- commitAuthorization: next.stage === 'commit' && run.commitAuthorization.issuedAt === null
857
- ? { issuedAt: (options.now ?? (() => new Date().toISOString()))(), consumedAt: null }
858
- : run.commitAuthorization
823
+ pendingDelegation: receipt
859
824
  });
860
825
  saveRun(resolved.taskDir, updated);
861
826
  return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next, error: null };
@@ -1460,16 +1425,15 @@ function advanceOrchestration(taskRef: string, options: OrchestrationOptions = {
1460
1425
  if (!run?.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1461
1426
  const result = consumeDelegation(run.pendingDelegation, { now: options.now });
1462
1427
  if (!result.ok) return pauseOrchestration(taskRef, result.code, result.message, true, options);
1463
- const completed = result.receipt.stage === 'commit';
1428
+ if (result.receipt.stage === 'commit') {
1429
+ return failed('ORCHESTRATED_COMMIT_REMOVED', 'Orchestrated commit is no longer a lifecycle stage; use local checkpoint delivery from code-task', resolved.taskId);
1430
+ }
1464
1431
  const updated = withUpdatedRun(run, {
1465
- status: completed ? 'completed' : 'running',
1432
+ status: 'running',
1466
1433
  nextStage: null,
1467
1434
  stepCount: run.stepCount + 1,
1468
1435
  pendingDelegation: null,
1469
- receipts: Object.freeze([...run.receipts, result.receipt]),
1470
- commitAuthorization: completed
1471
- ? { ...run.commitAuthorization, consumedAt: (options.now ?? (() => new Date().toISOString()))() }
1472
- : run.commitAuthorization
1436
+ receipts: Object.freeze([...run.receipts, result.receipt])
1473
1437
  });
1474
1438
  saveRun(resolved.taskDir, updated);
1475
1439
  return { status: updated.status, changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
@@ -0,0 +1,237 @@
1
+ import type { ResourceIdentity } from '../platform/resource-identity.ts';
2
+ import { isLegacyCompatibilityEnabled, legacyCompatibilityError, VERSION } from '../version.ts';
3
+ import {
4
+ isResourceIdentity,
5
+ parseResourceIdentity,
6
+ resourceIdentityNumber,
7
+ serializeResourceIdentity
8
+ } from '../platform/resource-identity.ts';
9
+
10
+ const PR_DELIVERY_FACT_KEY = 'pr_delivery_fact';
11
+
12
+ type PrDeliveryIdentity = {
13
+ resource: ResourceIdentity;
14
+ repository: string;
15
+ url: string;
16
+ head: { repository: string; ref: string; sha: string };
17
+ base: { repository: string; ref: string; sha: string };
18
+ };
19
+
20
+ type LegacyPrDeliveryIdentity = {
21
+ repository: string;
22
+ number: number;
23
+ nodeId: string;
24
+ url: string;
25
+ head: { repository: string; ref: string; sha: string };
26
+ base: { repository: string; ref: string; sha: string };
27
+ };
28
+ type CurrentPrDeliveryIdentityInput = {
29
+ resource: ResourceIdentity;
30
+ repository: string;
31
+ url: string;
32
+ head: { repository: string; ref: string; sha: string };
33
+ base: { repository: string; ref: string; sha: string };
34
+ };
35
+ type PrDeliveryIdentityInput = CurrentPrDeliveryIdentityInput;
36
+
37
+ type PrDeliveryBindingSource = 'created' | 'reused' | 'explicit-bind' | 'external-unique' | 'external-explicit';
38
+ type PrDeliveryProvenance = 'create-post' | 'reuse' | 'explicit-bind' | 'external-unique' | 'external-explicit';
39
+
40
+ type PrDeliveryFact =
41
+ | { version: 2; state: 'unbound'; reason: 'initial' }
42
+ | { version: 2; state: 'skipped'; reason: 'explicit'; decidedAt: string }
43
+ | {
44
+ version: 2;
45
+ state: 'bound';
46
+ identity: PrDeliveryIdentity;
47
+ binding: {
48
+ status: 'verified';
49
+ source: PrDeliveryBindingSource;
50
+ verifiedAt: string;
51
+ issueIdentity: ResourceIdentity | null;
52
+ remoteState: 'open' | 'closed';
53
+ mergedAt: string | null;
54
+ mergeCommitSha: string | null;
55
+ };
56
+ provenance: { establishedBy: PrDeliveryProvenance };
57
+ };
58
+
59
+ type CreationOutcome =
60
+ | { kind: 'planned'; action: 'create' | 'reuse'; createdByCurrentOperation: false }
61
+ | { kind: 'not-created'; reason: 'precondition-failed' | 'post-rejected'; createdByCurrentOperation: false }
62
+ | { kind: 'created'; createdByCurrentOperation: true }
63
+ | { kind: 'reused'; createdByCurrentOperation: false }
64
+ | { kind: 'no-op'; createdByCurrentOperation: false }
65
+ | { kind: 'unknown'; errorCode: 'PR_CREATE_OUTCOME_UNKNOWN' };
66
+
67
+ type PrDeliveryFactError = Error & { code: string };
68
+ type PrDeliveryFactReadResult =
69
+ | { status: 'valid'; fact: PrDeliveryFact }
70
+ | { status: 'missing'; fact: null }
71
+ | { status: 'invalid'; fact: null; error: PrDeliveryFactError };
72
+
73
+ function factError(message: string): Error & { code: string } {
74
+ return Object.assign(new Error(`PR_DELIVERY_FACT_INVALID: ${message}`), { code: 'PR_DELIVERY_FACT_INVALID' });
75
+ }
76
+ function asFactError(error: unknown): PrDeliveryFactError {
77
+ if (error instanceof Error && typeof (error as { code?: unknown }).code === 'string') return error as PrDeliveryFactError;
78
+ return factError(error instanceof Error ? error.message : String(error));
79
+ }
80
+ function isRecord(value: unknown): value is Record<string, unknown> { return Boolean(value) && typeof value === 'object' && !Array.isArray(value); }
81
+ function exactKeys(value: Record<string, unknown>, keys: readonly string[], label: string): void {
82
+ const actual = Object.keys(value).sort();
83
+ const expected = [...keys].sort();
84
+ if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) throw factError(`${label} has an invalid field set`);
85
+ }
86
+ function text(value: unknown, label: string, allowEmpty = false): string {
87
+ if (typeof value !== 'string' || (!allowEmpty && !value) || value.trim() !== value) throw factError(`${label} must be a string`);
88
+ return value;
89
+ }
90
+ function nullableText(value: unknown, label: string): string | null { return value === null ? null : text(value, label); }
91
+ function timestamp(value: unknown, label: string): string {
92
+ const result = text(value, label);
93
+ if (Number.isNaN(Date.parse(result))) throw factError(`${label} must be a valid timestamp`);
94
+ return new Date(result).toISOString();
95
+ }
96
+ function positiveNumber(value: unknown, label: string): number {
97
+ if (!Number.isSafeInteger(value) || (value as number) <= 0) throw factError(`${label} must be a positive integer`);
98
+ return value as number;
99
+ }
100
+ function parseRef(value: unknown, label: string): { repository: string; ref: string; sha: string } {
101
+ if (!isRecord(value)) throw factError(`${label} must be an object`);
102
+ exactKeys(value, ['repository', 'ref', 'sha'], label);
103
+ return { repository: text(value.repository, `${label}.repository`), ref: text(value.ref, `${label}.ref`), sha: text(value.sha, `${label}.sha`) };
104
+ }
105
+
106
+ function canonicalIdentity(value: unknown): PrDeliveryIdentity {
107
+ if (!isRecord(value)) throw factError('identity must be an object');
108
+ exactKeys(value, ['resource', 'repository', 'url', 'head', 'base'], 'identity');
109
+ return {
110
+ resource: parseResourceIdentity(value.resource, 'identity.resource'),
111
+ repository: text(value.repository, 'identity.repository'),
112
+ url: text(value.url, 'identity.url'),
113
+ head: parseRef(value.head, 'identity.head'),
114
+ base: parseRef(value.base, 'identity.base')
115
+ };
116
+ }
117
+
118
+ function decodeLegacyIdentity(value: unknown): PrDeliveryIdentity {
119
+ if (!isRecord(value)) throw factError('legacy identity must be an object');
120
+ // TODO(compat): Remove the v1 identity decoder before the first stable v1.0.0 release.
121
+ exactKeys(value, ['repository', 'number', 'nodeId', 'url', 'head', 'base'], 'legacy identity');
122
+ const legacy = {
123
+ number: positiveNumber(value.number, 'legacy identity.number'),
124
+ repository: text(value.repository, 'identity.repository'),
125
+ nodeId: text(value.nodeId, 'legacy identity.nodeId'),
126
+ url: text(value.url, 'identity.url'),
127
+ head: parseRef(value.head, 'identity.head'),
128
+ base: parseRef(value.base, 'identity.base')
129
+ } satisfies LegacyPrDeliveryIdentity;
130
+ return {
131
+ resource: { kind: 'number', value: legacy.number },
132
+ repository: legacy.repository,
133
+ url: legacy.url,
134
+ head: legacy.head,
135
+ base: legacy.base
136
+ };
137
+ }
138
+
139
+ function provenanceForSource(source: PrDeliveryBindingSource): PrDeliveryProvenance { return source === 'created' ? 'create-post' : source === 'reused' ? 'reuse' : source; }
140
+
141
+ function parseFact(value: unknown, runtimeVersion = VERSION): PrDeliveryFact {
142
+ if (!isRecord(value)) throw factError('fact must be an object');
143
+ if ((value.version !== 1 && value.version !== 2) || typeof value.state !== 'string') throw factError('version or state is invalid');
144
+ if (value.version === 1 && !isLegacyCompatibilityEnabled(runtimeVersion)) {
145
+ throw legacyCompatibilityError('v1 PR delivery fact', runtimeVersion);
146
+ }
147
+ if (value.state === 'unbound') {
148
+ exactKeys(value, ['version', 'state', 'reason'], 'unbound fact');
149
+ if (value.reason !== 'initial') throw factError('unbound reason is invalid');
150
+ return { version: 2, state: 'unbound', reason: 'initial' };
151
+ }
152
+ if (value.state === 'skipped') {
153
+ exactKeys(value, ['version', 'state', 'reason', 'decidedAt'], 'skipped fact');
154
+ if (value.reason !== 'explicit') throw factError('skipped reason is invalid');
155
+ return { version: 2, state: 'skipped', reason: 'explicit', decidedAt: timestamp(value.decidedAt, 'skipped.decidedAt') };
156
+ }
157
+ if (value.state !== 'bound') throw factError('state is invalid');
158
+ if (value.version === 1) {
159
+ // TODO(compat): Remove this v1-to-v2 shape conversion before the first stable v1.0.0 release.
160
+ exactKeys(value, ['version', 'state', 'identity', 'binding', 'provenance'], 'legacy bound fact');
161
+ if (!isRecord(value.binding) || !isRecord(value.provenance)) throw factError('legacy binding or provenance is invalid');
162
+ exactKeys(value.binding, ['status', 'source', 'verifiedAt', 'issueNumber', 'remoteState', 'mergedAt', 'mergeCommitSha'], 'legacy binding');
163
+ const issueNumber = value.binding.issueNumber === null ? null : positiveNumber(value.binding.issueNumber, 'legacy binding.issueNumber');
164
+ return parseFact({
165
+ version: 2,
166
+ state: 'bound',
167
+ identity: decodeLegacyIdentity(value.identity),
168
+ binding: {
169
+ status: 'verified', source: value.binding.source, verifiedAt: timestamp(value.binding.verifiedAt, 'binding.verifiedAt'),
170
+ issueIdentity: issueNumber === null ? null : { kind: 'number', value: issueNumber }, remoteState: value.binding.remoteState,
171
+ mergedAt: value.binding.mergedAt === null ? null : timestamp(value.binding.mergedAt, 'binding.mergedAt'), mergeCommitSha: nullableText(value.binding.mergeCommitSha, 'binding.mergeCommitSha')
172
+ },
173
+ provenance: value.provenance
174
+ }, runtimeVersion);
175
+ }
176
+ exactKeys(value, ['version', 'state', 'identity', 'binding', 'provenance'], 'bound fact');
177
+ if (!isRecord(value.binding) || !isRecord(value.provenance)) throw factError('binding or provenance is invalid');
178
+ exactKeys(value.binding, ['status', 'source', 'verifiedAt', 'issueIdentity', 'remoteState', 'mergedAt', 'mergeCommitSha'], 'binding');
179
+ if (value.binding.status !== 'verified') throw factError('binding status is invalid');
180
+ const sources: readonly PrDeliveryBindingSource[] = ['created', 'reused', 'explicit-bind', 'external-unique', 'external-explicit'];
181
+ if (!sources.includes(value.binding.source as PrDeliveryBindingSource)) throw factError('binding source is invalid');
182
+ const issueIdentity = value.binding.issueIdentity === null ? null : parseResourceIdentity(value.binding.issueIdentity, 'binding.issueIdentity');
183
+ if (value.binding.remoteState !== 'open' && value.binding.remoteState !== 'closed') throw factError('binding.remoteState is invalid');
184
+ const mergedAt = value.binding.mergedAt === null ? null : timestamp(value.binding.mergedAt, 'binding.mergedAt');
185
+ const mergeCommitSha = nullableText(value.binding.mergeCommitSha, 'binding.mergeCommitSha');
186
+ if (Boolean(mergedAt) !== Boolean(mergeCommitSha)) throw factError('mergedAt and mergeCommitSha must be paired');
187
+ exactKeys(value.provenance, ['establishedBy'], 'provenance');
188
+ const provenances: readonly PrDeliveryProvenance[] = ['create-post', 'reuse', 'explicit-bind', 'external-unique', 'external-explicit'];
189
+ if (!provenances.includes(value.provenance.establishedBy as PrDeliveryProvenance)) throw factError('provenance.establishedBy is invalid');
190
+ if (value.provenance.establishedBy !== provenanceForSource(value.binding.source as PrDeliveryBindingSource)) throw factError('binding.source and provenance.establishedBy do not match');
191
+ return {
192
+ version: 2, state: 'bound', identity: canonicalIdentity(value.identity),
193
+ binding: { status: 'verified', source: value.binding.source as PrDeliveryBindingSource, verifiedAt: timestamp(value.binding.verifiedAt, 'binding.verifiedAt'), issueIdentity, remoteState: value.binding.remoteState, mergedAt, mergeCommitSha },
194
+ provenance: { establishedBy: value.provenance.establishedBy as PrDeliveryProvenance }
195
+ };
196
+ }
197
+
198
+ function decodePrDeliveryFact(value: unknown, runtimeVersion = VERSION): PrDeliveryFact {
199
+ if (typeof value === 'string') {
200
+ let decoded: unknown;
201
+ try { decoded = JSON.parse(value); }
202
+ catch { throw factError('value is not valid JSON'); }
203
+ return parseFact(decoded, runtimeVersion);
204
+ }
205
+ return parseFact(value, runtimeVersion);
206
+ }
207
+ function encodePrDeliveryFact(fact: PrDeliveryFact): string { return JSON.stringify(parseFact(fact)); }
208
+ function readPrDeliveryFact(metadata: Record<string, unknown>, runtimeVersion = VERSION): PrDeliveryFactReadResult {
209
+ if (!Object.hasOwn(metadata, PR_DELIVERY_FACT_KEY) || metadata[PR_DELIVERY_FACT_KEY] === '') return { status: 'missing', fact: null };
210
+ try { return { status: 'valid', fact: decodePrDeliveryFact(metadata[PR_DELIVERY_FACT_KEY], runtimeVersion) }; }
211
+ catch (error) { return { status: 'invalid', fact: null, error: asFactError(error) }; }
212
+ }
213
+ function buildUnboundFact(): PrDeliveryFact { return { version: 2, state: 'unbound', reason: 'initial' }; }
214
+ function buildSkippedFact(decidedAt: string): PrDeliveryFact { return parseFact({ version: 2, state: 'skipped', reason: 'explicit', decidedAt }); }
215
+
216
+ function buildBoundFact(input: {
217
+ identity: CurrentPrDeliveryIdentityInput;
218
+ source: PrDeliveryBindingSource;
219
+ verifiedAt: string;
220
+ issueIdentity?: ResourceIdentity | null;
221
+ remoteState: 'open' | 'closed';
222
+ mergedAt?: string | null;
223
+ mergeCommitSha?: string | null;
224
+ }): PrDeliveryFact {
225
+ return parseFact({ version: 2, state: 'bound', identity: input.identity, binding: {
226
+ status: 'verified', source: input.source, verifiedAt: input.verifiedAt,
227
+ issueIdentity: input.issueIdentity === undefined ? null : input.issueIdentity,
228
+ remoteState: input.remoteState, mergedAt: input.mergedAt ?? null, mergeCommitSha: input.mergeCommitSha ?? null
229
+ }, provenance: { establishedBy: provenanceForSource(input.source) } });
230
+ }
231
+ function factFrontmatterMutation(fact: PrDeliveryFact): { set: { [PR_DELIVERY_FACT_KEY]: string } } { return { set: { [PR_DELIVERY_FACT_KEY]: encodePrDeliveryFact(fact) } }; }
232
+ function identityFromPullRequest(pullRequest: PrDeliveryIdentityInput): PrDeliveryIdentity {
233
+ return canonicalIdentity(pullRequest);
234
+ }
235
+
236
+ export { PR_DELIVERY_FACT_KEY, buildBoundFact, buildSkippedFact, buildUnboundFact, decodePrDeliveryFact, encodePrDeliveryFact, factFrontmatterMutation, identityFromPullRequest, readPrDeliveryFact, serializeResourceIdentity };
237
+ export type { CreationOutcome, PrDeliveryBindingSource, PrDeliveryFact, PrDeliveryFactError, PrDeliveryFactReadResult, PrDeliveryIdentity, PrDeliveryIdentityInput, PrDeliveryProvenance };