@fitlab-ai/agent-infra 0.9.10 → 0.9.12

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