@fitlab-ai/agent-infra 0.9.14 → 0.10.0

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 (354) hide show
  1. package/README.md +10 -0
  2. package/README.zh-CN.md +10 -0
  3. package/bin/internal-cli.sh +45 -0
  4. package/dist/bin/internal-cli.js +93 -30
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +9 -4
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
  8. package/dist/lib/defaults.json +103 -103
  9. package/dist/lib/fs/durable-write.js +39 -0
  10. package/dist/lib/fs/file-lock.js +111 -0
  11. package/dist/lib/host-control/audit.js +19 -0
  12. package/dist/lib/host-control/client.js +142 -0
  13. package/dist/lib/host-control/command.js +49 -0
  14. package/dist/lib/host-control/path.js +160 -0
  15. package/dist/lib/host-control/secure-fs.js +140 -0
  16. package/dist/lib/host-control/server.js +159 -0
  17. package/dist/lib/host-control/service.js +112 -0
  18. package/dist/lib/init.js +11 -2
  19. package/dist/lib/internal/cli-route-inventory.js +4 -2
  20. package/dist/lib/internal/codex-lifecycle.js +6 -6
  21. package/dist/lib/internal/demo-promotion.js +322 -0
  22. package/dist/lib/internal/demo-transcript.js +454 -0
  23. package/dist/lib/internal/host-control.js +28 -0
  24. package/dist/lib/internal/manual-validation.js +331 -0
  25. package/dist/lib/internal/platform-pr.js +18 -4
  26. package/dist/lib/internal/release-workflow.js +63 -84
  27. package/dist/lib/internal/sandbox-control.js +149 -2
  28. package/dist/lib/internal/task-artifact.js +16 -263
  29. package/dist/lib/internal/task-create.js +6 -5
  30. package/dist/lib/internal/task-event.js +37 -37
  31. package/dist/lib/internal/task-lifecycle.js +2 -2
  32. package/dist/lib/internal/task-operation-registry.js +101 -4
  33. package/dist/lib/internal/task-review.js +11 -56
  34. package/dist/lib/internal/task-validate.js +30 -10
  35. package/dist/lib/owned-path.js +33 -0
  36. package/dist/lib/platform/check-status.js +11 -0
  37. package/dist/lib/platform/comment-safety.js +4 -1
  38. package/dist/lib/platform/completion-backfill.js +1 -1
  39. package/dist/lib/platform/github-data.js +241 -0
  40. package/dist/lib/platform/github-provider.js +15 -20
  41. package/dist/lib/platform/issue-comments.js +18 -12
  42. package/dist/lib/platform/issues.js +2 -180
  43. package/dist/lib/platform/pr-change-report.js +5 -4
  44. package/dist/lib/platform/pr-checks.js +12 -65
  45. package/dist/lib/platform/pr-summary.js +117 -33
  46. package/dist/lib/platform/pull-requests.js +5 -108
  47. package/dist/lib/platform/snapshots.js +2 -0
  48. package/dist/lib/platform/task-identities.js +2 -9
  49. package/dist/lib/platform/verification-sync.js +132 -143
  50. package/dist/lib/pr-review/evidence-grading.js +4 -8
  51. package/dist/lib/release/workflow.js +1 -1
  52. package/dist/lib/sandbox/commands/create.js +7 -2
  53. package/dist/lib/sandbox/commands/rm.js +19 -1511
  54. package/dist/lib/sandbox/control/audit.js +203 -0
  55. package/dist/lib/sandbox/control/client.js +93 -54
  56. package/dist/lib/sandbox/control/executor.js +129 -65
  57. package/dist/lib/sandbox/control/finalization-response.js +27 -0
  58. package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
  59. package/dist/lib/sandbox/control/lifecycle.js +36 -2
  60. package/dist/lib/sandbox/control/native-file-lock.js +6 -98
  61. package/dist/lib/sandbox/control/protocol.js +55 -4
  62. package/dist/lib/sandbox/control/server.js +518 -127
  63. package/dist/lib/sandbox/control/state.js +64 -0
  64. package/dist/lib/sandbox/control/task-workflow.js +74 -0
  65. package/dist/lib/sandbox/control/workflow-executor.js +89 -0
  66. package/dist/lib/sandbox/index.js +1 -2
  67. package/dist/lib/sandbox/recovery.js +28 -4
  68. package/dist/lib/sandbox/removal.js +822 -0
  69. package/dist/lib/sandbox/task-cleanup.js +217 -0
  70. package/dist/lib/sandbox/workspace-identity.js +17 -1
  71. package/dist/lib/sandbox/workspace-view.js +13 -14
  72. package/dist/lib/task/activity-intent.js +2 -1
  73. package/dist/lib/task/activity-log.js +30 -24
  74. package/dist/lib/task/artifact-command.js +79 -0
  75. package/dist/lib/task/artifact-lifecycle.js +30 -28
  76. package/dist/lib/task/artifact-name.js +36 -0
  77. package/dist/lib/task/artifact-operations.js +29 -289
  78. package/dist/lib/task/artifact-receipts.js +3 -15
  79. package/dist/lib/task/artifact-recovery.js +430 -0
  80. package/dist/lib/task/artifact-repair-intent.js +134 -29
  81. package/dist/lib/task/artifact-schema.js +6 -14
  82. package/dist/lib/task/capabilities.js +12 -19
  83. package/dist/lib/task/codex-orchestration.js +71 -25
  84. package/dist/lib/task/commit-identity.js +28 -0
  85. package/dist/lib/task/commit-operation.js +33 -20
  86. package/dist/lib/task/control-authority.js +403 -6
  87. package/dist/lib/task/control-recovery.js +140 -0
  88. package/dist/lib/task/create-service.js +1 -8
  89. package/dist/lib/task/create.js +62 -9
  90. package/dist/lib/task/decision-details.js +2 -46
  91. package/dist/lib/task/delegation-receipts.js +64 -3
  92. package/dist/lib/task/events.js +275 -22
  93. package/dist/lib/task/finalization-state.js +9 -0
  94. package/dist/lib/task/human-override.js +8 -14
  95. package/dist/lib/task/implementation-inputs.js +3 -3
  96. package/dist/lib/task/intermediate-cleanup.js +441 -0
  97. package/dist/lib/task/invalidation.js +18 -65
  98. package/dist/lib/task/ledger-intents.js +7 -22
  99. package/dist/lib/task/ledger.js +1 -3
  100. package/dist/lib/task/lifecycle-recovery.js +549 -0
  101. package/dist/lib/task/lifecycle.js +31 -4
  102. package/dist/lib/task/local-artifact-finalization.js +197 -197
  103. package/dist/lib/task/manual-validation-completion.js +26 -0
  104. package/dist/lib/task/manual-validation-evidence.js +130 -0
  105. package/dist/lib/task/manual-validation-receipt.js +105 -0
  106. package/dist/lib/task/manual-validation-shared.js +40 -0
  107. package/dist/lib/task/manual-validation-transaction.js +243 -0
  108. package/dist/lib/task/markdown.js +47 -0
  109. package/dist/lib/task/orchestration.js +66 -19
  110. package/dist/lib/task/pr-delivery-fact.js +12 -56
  111. package/dist/lib/task/qualification-audit.js +20 -34
  112. package/dist/lib/task/recovery-warning.js +19 -0
  113. package/dist/lib/task/review-artifacts.js +2 -18
  114. package/dist/lib/task/review-command.js +26 -0
  115. package/dist/lib/task/review-finalization.js +175 -216
  116. package/dist/lib/task/review-fingerprint.js +2 -2
  117. package/dist/lib/task/sections.js +34 -56
  118. package/dist/lib/task/short-id.js +15 -7
  119. package/dist/lib/task/task-execution-lock.js +1 -1
  120. package/dist/lib/task/verification-engine.js +119 -337
  121. package/dist/lib/task/verification.js +1 -1
  122. package/dist/lib/task/workflow-command.js +16 -0
  123. package/dist/lib/task/workflow-warning-intents.js +0 -16
  124. package/dist/lib/task/workflow-warnings.js +28 -2
  125. package/dist/lib/update.js +2 -0
  126. package/dist/lib/version.js +1 -11
  127. package/dist/lifecycle-build-manifest.json +3 -3
  128. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
  129. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +10 -4
  130. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
  131. package/lib/fs/durable-write.ts +30 -0
  132. package/lib/fs/file-lock.ts +113 -0
  133. package/lib/host-control/audit.ts +22 -0
  134. package/lib/host-control/client.ts +172 -0
  135. package/lib/host-control/command.ts +60 -0
  136. package/lib/host-control/path.ts +167 -0
  137. package/lib/host-control/secure-fs.ts +135 -0
  138. package/lib/host-control/server.ts +180 -0
  139. package/lib/host-control/service.ts +133 -0
  140. package/lib/init.ts +15 -2
  141. package/lib/internal/cli-route-inventory.ts +4 -2
  142. package/lib/internal/codex-lifecycle.ts +6 -6
  143. package/lib/internal/demo-promotion.ts +354 -0
  144. package/lib/internal/demo-transcript.ts +419 -0
  145. package/lib/internal/host-control.ts +28 -0
  146. package/lib/internal/manual-validation.ts +315 -0
  147. package/lib/internal/platform-pr.ts +15 -4
  148. package/lib/internal/release-workflow.ts +74 -82
  149. package/lib/internal/sandbox-control.ts +162 -1
  150. package/lib/internal/task-artifact.ts +14 -217
  151. package/lib/internal/task-create.ts +5 -5
  152. package/lib/internal/task-event.ts +20 -11
  153. package/lib/internal/task-lifecycle.ts +4 -3
  154. package/lib/internal/task-operation-registry.ts +111 -4
  155. package/lib/internal/task-review.ts +9 -54
  156. package/lib/internal/task-validate.ts +26 -10
  157. package/lib/owned-path.ts +26 -0
  158. package/lib/platform/check-status.ts +9 -0
  159. package/lib/platform/comment-safety.ts +5 -0
  160. package/lib/platform/completion-backfill.ts +1 -1
  161. package/lib/platform/github-data.ts +322 -0
  162. package/lib/platform/github-provider.ts +15 -20
  163. package/lib/platform/issue-comments.ts +21 -14
  164. package/lib/platform/issues.ts +5 -246
  165. package/lib/platform/pr-change-report.ts +7 -5
  166. package/lib/platform/pr-checks.ts +13 -77
  167. package/lib/platform/pr-summary.ts +123 -35
  168. package/lib/platform/pull-requests.ts +6 -173
  169. package/lib/platform/snapshots.ts +53 -0
  170. package/lib/platform/task-identities.ts +3 -14
  171. package/lib/platform/verification-sync.ts +150 -156
  172. package/lib/pr-review/evidence-grading.ts +4 -9
  173. package/lib/release/workflow.ts +3 -3
  174. package/lib/sandbox/commands/create.ts +7 -6
  175. package/lib/sandbox/commands/rm.ts +21 -1895
  176. package/lib/sandbox/control/audit.ts +253 -0
  177. package/lib/sandbox/control/client.ts +109 -66
  178. package/lib/sandbox/control/executor.ts +134 -74
  179. package/lib/sandbox/control/finalization-response.ts +29 -0
  180. package/lib/sandbox/control/identity-sentinel.ts +137 -0
  181. package/lib/sandbox/control/lifecycle.ts +45 -3
  182. package/lib/sandbox/control/native-file-lock.ts +6 -107
  183. package/lib/sandbox/control/protocol.ts +80 -6
  184. package/lib/sandbox/control/server.ts +567 -132
  185. package/lib/sandbox/control/state.ts +86 -0
  186. package/lib/sandbox/control/task-workflow.ts +95 -0
  187. package/lib/sandbox/control/workflow-executor.ts +92 -0
  188. package/lib/sandbox/index.ts +1 -2
  189. package/lib/sandbox/recovery.ts +28 -5
  190. package/lib/sandbox/removal.ts +1142 -0
  191. package/lib/sandbox/task-cleanup.ts +209 -0
  192. package/lib/sandbox/workspace-identity.ts +19 -3
  193. package/lib/sandbox/workspace-view.ts +17 -15
  194. package/lib/task/activity-intent.ts +2 -2
  195. package/lib/task/activity-log.ts +27 -19
  196. package/lib/task/artifact-command.ts +85 -0
  197. package/lib/task/artifact-lifecycle.ts +31 -26
  198. package/lib/task/artifact-name.ts +55 -0
  199. package/lib/task/artifact-operations.ts +34 -310
  200. package/lib/task/artifact-receipts.ts +3 -14
  201. package/lib/task/artifact-recovery.ts +519 -0
  202. package/lib/task/artifact-repair-intent.ts +162 -33
  203. package/lib/task/artifact-schema.ts +5 -34
  204. package/lib/task/capabilities.ts +13 -22
  205. package/lib/task/codex-orchestration.ts +85 -24
  206. package/lib/task/commit-identity.ts +31 -0
  207. package/lib/task/commit-operation.ts +37 -20
  208. package/lib/task/control-authority.ts +540 -10
  209. package/lib/task/control-recovery.ts +186 -0
  210. package/lib/task/create-service.ts +1 -10
  211. package/lib/task/create.ts +63 -8
  212. package/lib/task/decision-details.ts +3 -71
  213. package/lib/task/delegation-receipts.ts +73 -2
  214. package/lib/task/events.ts +309 -24
  215. package/lib/task/finalization-state.ts +11 -0
  216. package/lib/task/human-override.ts +8 -16
  217. package/lib/task/implementation-inputs.ts +3 -3
  218. package/lib/task/intermediate-cleanup.ts +493 -0
  219. package/lib/task/invalidation.ts +18 -62
  220. package/lib/task/ledger-intents.ts +7 -24
  221. package/lib/task/ledger.ts +1 -3
  222. package/lib/task/lifecycle-recovery.ts +691 -0
  223. package/lib/task/lifecycle.ts +30 -5
  224. package/lib/task/local-artifact-finalization.ts +244 -234
  225. package/lib/task/manual-validation-completion.ts +40 -0
  226. package/lib/task/manual-validation-evidence.ts +209 -0
  227. package/lib/task/manual-validation-receipt.ts +156 -0
  228. package/lib/task/manual-validation-shared.ts +50 -0
  229. package/lib/task/manual-validation-transaction.ts +304 -0
  230. package/lib/task/markdown.ts +70 -0
  231. package/lib/task/orchestration.ts +84 -19
  232. package/lib/task/pr-delivery-fact.ts +15 -62
  233. package/lib/task/qualification-audit.ts +22 -33
  234. package/lib/task/recovery-warning.ts +21 -0
  235. package/lib/task/review-artifacts.ts +1 -20
  236. package/lib/task/review-command.ts +24 -0
  237. package/lib/task/review-finalization.ts +183 -292
  238. package/lib/task/review-fingerprint.ts +2 -2
  239. package/lib/task/sections.ts +33 -54
  240. package/lib/task/short-id.ts +27 -5
  241. package/lib/task/task-execution-lock.ts +1 -1
  242. package/lib/task/verification-engine.ts +113 -383
  243. package/lib/task/verification-types.ts +15 -0
  244. package/lib/task/verification.ts +1 -1
  245. package/lib/task/workflow-command.ts +17 -0
  246. package/lib/task/workflow-warning-intents.ts +0 -13
  247. package/lib/task/workflow-warnings.ts +21 -2
  248. package/lib/update.ts +6 -0
  249. package/lib/version.ts +1 -15
  250. package/package.json +6 -6
  251. package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
  252. package/templates/.agents/rules/create-issue.en.md +1 -1
  253. package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
  254. package/templates/.agents/rules/issue-sync.en.md +4 -2
  255. package/templates/.agents/rules/issue-sync.zh-CN.md +4 -3
  256. package/templates/.agents/rules/local-artifact-repair.en.md +46 -39
  257. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +43 -36
  258. package/templates/.agents/rules/testing-discipline.en.md +2 -0
  259. package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
  260. package/templates/.agents/scripts/package.json +3 -0
  261. package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
  262. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
  263. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
  264. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
  265. package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
  266. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
  267. package/templates/.agents/skills/block-task/config/verify.json +1 -1
  268. package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
  269. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
  270. package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
  271. package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
  272. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +7 -7
  273. package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
  274. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
  275. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
  276. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
  277. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +33 -21
  278. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +33 -21
  279. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
  280. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
  281. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  282. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  283. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
  284. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
  285. package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
  286. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
  287. package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
  288. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
  289. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
  290. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
  291. package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
  292. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
  293. package/templates/.agents/skills/create-pr/config/verify.json +1 -1
  294. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
  295. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
  296. package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
  297. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
  298. package/templates/.agents/skills/create-task/config/verify.json +1 -1
  299. package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
  300. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
  301. package/templates/.agents/skills/import-issue/config/verify.json +2 -2
  302. package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
  303. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
  304. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
  305. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
  306. package/templates/.agents/skills/release/SKILL.en.md +7 -3
  307. package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
  308. package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
  309. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
  310. package/templates/.agents/skills/restore-task/config/verify.json +1 -1
  311. package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
  312. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
  313. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
  314. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
  315. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
  316. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
  317. package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
  318. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
  319. package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
  320. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
  321. package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
  322. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
  323. package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
  324. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
  325. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
  326. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
  327. package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
  328. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
  329. package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
  330. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
  331. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
  332. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
  333. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
  334. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
  335. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
  336. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
  337. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
  338. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
  339. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
  340. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
  341. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  342. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  343. package/templates/.agents/skills/test/SKILL.en.md +17 -6
  344. package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
  345. package/templates/.agents/templates/task.en.md +1 -1
  346. package/templates/.agents/templates/task.zh-CN.md +1 -1
  347. package/templates/.claude/commands/import-issue.en.md +1 -1
  348. package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
  349. package/templates/.claude/commands/restore-task.en.md +1 -1
  350. package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
  351. package/templates/.claude/commands/run-manual-validation.en.md +1 -1
  352. package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
  353. package/dist/lib/platform/adapters.js +0 -56
  354. package/lib/platform/adapters.ts +0 -197
@@ -0,0 +1,315 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ import { locateActivityLog, pairEntries } from '../task/activity-log.ts';
5
+ import { resolveTaskRef } from '../task/resolve-ref.ts';
6
+ import { applyTaskEvent } from '../task/events.ts';
7
+ import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
8
+ import { normalizeAgentToken } from '../agent-clients/tokens.ts';
9
+ import { createManualValidationReceipt, manualValidationFinalSummaryDigest, manualValidationFinalSummaryProjectionMatches, readManualValidationReceipt, writeManualValidationReceiptAtomic } from '../task/manual-validation-receipt.ts';
10
+ import { archiveManualValidationGeneration, createManualValidationTransaction, readManualValidationTransaction, retryManualValidationTransaction, summaryPreimageDigest, transitionManualValidationTransaction, validateManualValidationGenerationArchive, writeManualValidationTransactionAtomic } from '../task/manual-validation-transaction.ts';
11
+ import type { ManualValidationTransaction } from '../task/manual-validation-transaction.ts';
12
+ import type { ManualValidationReceipt } from '../task/manual-validation-receipt.ts';
13
+ import { sha256File } from '../task/artifact-receipts.ts';
14
+ import { summaryCommentState, syncPullRequestSummary } from '../platform/pr-summary.ts';
15
+ import type { PlatformClient } from '../platform/context.ts';
16
+ import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
17
+
18
+ const USAGE = `Usage: agent-infra-internal manual-validation transaction <task-ref> --prepare --artifact <artifact> --summary-file <path> --agent <agent> [--cwd <path>]
19
+ agent-infra-internal manual-validation transaction <task-ref> --artifact <artifact> --summary-file <path> --change-report-file <path> --agent <agent> [--result pr_created|pr_reused|no_op] [--cwd <path>]`;
20
+
21
+ type OutputFormat = 'json' | 'text';
22
+ type ManualValidationCoordinatorOptions = {
23
+ client?: PlatformClient;
24
+ archiveGeneration?: typeof archiveManualValidationGeneration;
25
+ };
26
+ type ManualValidationResult = {
27
+ status: 'applied' | 'failed';
28
+ changed: false;
29
+ error: { code: string; message: string } | null;
30
+ transaction?: ManualValidationTransaction;
31
+ receipt?: ManualValidationReceipt;
32
+ prepared?: boolean;
33
+ idempotent?: boolean;
34
+ [key: string]: unknown;
35
+ };
36
+ function result(status: 'applied' | 'failed', error: { code: string; message: string } | null = null, extra: Record<string, unknown> = {}): ManualValidationResult {
37
+ return { status, changed: false, error, ...extra };
38
+ }
39
+
40
+ function printFailure(format: OutputFormat, error: { code: string; message: string }): void {
41
+ if (format === 'json') process.stdout.write(`${JSON.stringify(result('failed', error))}\n`);
42
+ else process.stderr.write(`${error.code}: ${error.message}\n`);
43
+ process.exitCode = 1;
44
+ }
45
+
46
+ const MANUAL_VALIDATION_STATUS_SECTION = /^###\s+(?:⚠️\s+(?:需人工校验|Manual Validation Required)|✅\s+(?:人工验证已通过|无需人工校验|Manual Validation Passed|No Manual Validation Required)|⏳\s+(?:人工验证待收尾|Manual Validation Pending))\s*$[\s\S]*?(?=^#{1,3}\s|(?![\s\S]))/gmu;
47
+
48
+ function manualSummaryBody(body: string, phase: 'pending' | 'final', transactionId: string, receiptDigest = '', evidenceDigest = '', prHeadSha = ''): string {
49
+ const chinese = /###\s+(?:⚠️\s+需人工校验|✅\s+(?:人工验证已通过|无需人工校验)|⏳\s+人工验证待收尾)\s*$/mu.test(body);
50
+ const section = phase === 'pending'
51
+ ? chinese
52
+ ? `### ⏳ 人工验证待收尾\n\n人工验证证据已暂存;事务 ${transactionId} 正在等待最终完成。`
53
+ : `### ⏳ Manual Validation Pending\n\nManual validation evidence is staged; transaction ${transactionId} is awaiting final promotion.`
54
+ : chinese
55
+ ? `### ✅ 人工验证已通过\n\n人工验证已通过;transaction=${transactionId}; receipt=${receiptDigest}; evidence=${evidenceDigest}; head=${prHeadSha}.`
56
+ : `### ✅ Manual Validation Passed\n\nManual validation passed; transaction=${transactionId}; receipt=${receiptDigest}; evidence=${evidenceDigest}; head=${prHeadSha}.`;
57
+ let inserted = false;
58
+ const updated = body.replace(MANUAL_VALIDATION_STATUS_SECTION, () => {
59
+ if (inserted) return '';
60
+ inserted = true;
61
+ return `${section}\n\n`;
62
+ });
63
+ return inserted ? `${updated.replace(/\s+$/u, '')}\n` : `${body.replace(/\s+$/u, '')}\n\n${section}\n`;
64
+ }
65
+
66
+ function openManualValidationStarted(taskMdPath: string): { present: boolean; transactionId: string | null } {
67
+ let content: string;
68
+ try { content = fs.readFileSync(taskMdPath, 'utf8'); }
69
+ catch { return { present: false, transactionId: null }; }
70
+ const section = locateActivityLog(content);
71
+ if (!section) return { present: false, transactionId: null };
72
+ const row = pairEntries(section.entries)
73
+ .filter((item) => item.step === 'Complete Manual Validation' && item.started && !item.done)
74
+ .at(-1);
75
+ if (!row) return { present: false, transactionId: null };
76
+ return {
77
+ present: true,
78
+ transactionId: /(?:^|;\s*)transaction=([A-Za-z0-9][A-Za-z0-9._-]{0,127})(?:;|$)/u.exec(row.note)?.[1] ?? null
79
+ };
80
+ }
81
+
82
+ async function executeManualValidationTransaction(taskRef: string, values: Record<string, string>, cwd: string, options: ManualValidationCoordinatorOptions = {}) {
83
+ const prepareOnly = values.prepare === 'true';
84
+ const required = prepareOnly
85
+ ? ['artifact', 'summaryFile', 'agent'] as const
86
+ : ['artifact', 'summaryFile', 'changeReportFile', 'agent'] as const;
87
+ const missing = required.find((key) => !values[key]);
88
+ if (missing) return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_ARGS_INVALID', message: `transaction requires --${missing.replace(/[A-Z]/gu, (letter) => `-${letter.toLowerCase()}`)}` });
89
+ const agent = normalizeAgentToken(values.agent!);
90
+ if (!agent) return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_ARGS_INVALID', message: 'transaction agent is invalid' });
91
+ const primaryResult = values.result ?? 'no_op';
92
+ if (!['pr_created', 'pr_reused', 'no_op'].includes(primaryResult)) return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_ARGS_INVALID', message: '--result must be pr_created, pr_reused, or no_op' });
93
+ const resolved = resolveTaskRef(taskRef, { repoRoot: cwd });
94
+ if (!resolved.ok) return result('failed', { code: resolved.code, message: resolved.message });
95
+ try {
96
+ return await withTaskExecutionLock(
97
+ resolved.repoRoot,
98
+ resolved.taskId,
99
+ `manual-validation:${taskRef}`,
100
+ () => executeManualValidationTransactionLocked(taskRef, values, cwd, resolved, agent, primaryResult as 'pr_created' | 'pr_reused' | 'no_op', prepareOnly, options)
101
+ );
102
+ } catch (error) {
103
+ if (error instanceof TaskExecutionLockError) return result('failed', { code: error.code, message: error.message });
104
+ throw error;
105
+ }
106
+ }
107
+
108
+ async function executeManualValidationTransactionLocked(
109
+ taskRef: string,
110
+ values: Record<string, string>,
111
+ cwd: string,
112
+ resolved: Extract<ReturnType<typeof resolveTaskRef>, { ok: true }>,
113
+ agent: string,
114
+ primaryResult: 'pr_created' | 'pr_reused' | 'no_op',
115
+ prepareOnly: boolean,
116
+ options: ManualValidationCoordinatorOptions
117
+ ) {
118
+ const artifactPath = path.resolve(resolved.taskDir, values.artifact!);
119
+ if (!prepareOnly && !fs.existsSync(artifactPath)) return result('failed', { code: 'MANUAL_VALIDATION_ARTIFACT_MISSING', message: `manual-validation artifact is missing: ${values.artifact}` });
120
+ const currentState = await summaryCommentState(taskRef, { cwd, client: options.client });
121
+ if (!currentState.pullRequest) return result('failed', { code: 'MANUAL_VALIDATION_PR_REQUIRED', message: 'canonical pull-request head is unavailable' });
122
+ const pullRequest = currentState.pullRequest;
123
+ const preimageBody = currentState.comment?.body ?? '';
124
+ const preimage = { commentId: currentState.comment?.id ?? null, body: preimageBody, digest: summaryPreimageDigest(preimageBody) };
125
+ const evidenceDigest = sha256File(path.resolve(cwd, values.summaryFile!));
126
+ const archiveGeneration = options.archiveGeneration ?? archiveManualValidationGeneration;
127
+ let transactionResult = readManualValidationTransaction(resolved.taskDir, { taskId: resolved.taskId, prNumber: pullRequest.number, prHeadSha: pullRequest.head.sha, evidenceDigest, artifact: values.artifact! });
128
+ let previousTransaction: ManualValidationTransaction | null = null;
129
+ if (!transactionResult.ok && transactionResult.error.code === 'MANUAL_VALIDATION_TRANSACTION_IDENTITY_MISMATCH') {
130
+ const existing = readManualValidationTransaction(resolved.taskDir);
131
+ if (!existing.ok) return result('failed', existing.error);
132
+ if (!['aborted', 'recovery-required', 'committed'].includes(existing.value.phase)) return result('failed', transactionResult.error);
133
+ try { validateManualValidationGenerationArchive(resolved.taskDir, existing.value, existing.value.phase === 'committed'); }
134
+ catch (error) { return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: error instanceof Error ? error.message : String(error) }); }
135
+ previousTransaction = existing.value;
136
+ transactionResult = { ok: false, error: { code: 'MANUAL_VALIDATION_TRANSACTION_MISSING', message: 'previous failed transaction was archived for a new pull-request head' } };
137
+ }
138
+ if (!transactionResult.ok && transactionResult.error.code !== 'MANUAL_VALIDATION_TRANSACTION_MISSING') return result('failed', transactionResult.error);
139
+ if (transactionResult.ok && transactionResult.value.phase === 'committed') return result('applied', null, { transaction: transactionResult.value, receipt: transactionResult.value.committedReceipt, idempotent: true });
140
+ const openStarted = transactionResult.ok ? { present: false, transactionId: null } : openManualValidationStarted(resolved.taskMdPath);
141
+ if (openStarted.present && !openStarted.transactionId) {
142
+ return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'open manual-validation started event has no recoverable transactionId' });
143
+ }
144
+ if (previousTransaction && openStarted.transactionId === previousTransaction.transactionId) {
145
+ return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'previous manual-validation generation is still open; retry the existing transaction before starting a new generation' });
146
+ }
147
+ const transactionId = transactionResult.ok
148
+ ? transactionResult.value.transactionId
149
+ : (openStarted.transactionId ?? values.transactionId ?? `mv-${Date.now()}`);
150
+ const finalBodyWithoutReceipt = fs.readFileSync(path.resolve(cwd, values.summaryFile!), 'utf8');
151
+ const pendingBody = manualSummaryBody(finalBodyWithoutReceipt, 'pending', transactionId);
152
+ const createTransaction = (): ManualValidationTransaction => createManualValidationTransaction({
153
+ transactionId,
154
+ taskId: resolved.taskId,
155
+ prNumber: pullRequest.number,
156
+ prHeadSha: pullRequest.head.sha,
157
+ evidenceDigest,
158
+ summaryPreimage: preimage,
159
+ pendingSummaryDigest: summaryPreimageDigest(pendingBody),
160
+ finalSummaryDigest: manualValidationFinalSummaryDigest(manualSummaryBody(finalBodyWithoutReceipt, 'final', transactionId, '<receipt>', evidenceDigest, pullRequest.head.sha)),
161
+ artifact: values.artifact!,
162
+ attempt: 1,
163
+ createdAt: new Date().toISOString(),
164
+ updatedAt: new Date().toISOString()
165
+ });
166
+ let transaction: ManualValidationTransaction;
167
+ const idempotent = transactionResult.ok;
168
+ let startedEventApplied = false;
169
+ if (!transactionResult.ok) {
170
+ transaction = createTransaction();
171
+ const started = applyTaskEvent({ taskRef, event: 'manual-validation.started', agent, initiator: 'model', requestId: transactionId, reasonCode: 'user-request', transactionId }, { lockAlreadyHeld: true, repoRoot: cwd });
172
+ if (started.status === 'failed') return result('failed', started.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_FAILED', message: 'manual-validation started event failed' });
173
+ startedEventApplied = true;
174
+ if (previousTransaction) {
175
+ try { archiveGeneration(resolved.taskDir, previousTransaction, previousTransaction.phase === 'committed'); }
176
+ catch (error) { return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: error instanceof Error ? error.message : String(error) }); }
177
+ }
178
+ writeManualValidationTransactionAtomic(resolved.taskDir, transaction);
179
+ } else {
180
+ transaction = transactionResult.value;
181
+ if (['aborted', 'recovery-required'].includes(transaction.phase)) {
182
+ const retried = retryManualValidationTransaction(transaction);
183
+ if (!retried.ok) return result('failed', retried.error);
184
+ transaction = retried.value;
185
+ writeManualValidationTransactionAtomic(resolved.taskDir, transaction);
186
+ }
187
+ }
188
+ if (transaction.phase === 'prepared' && !startedEventApplied) {
189
+ const started = applyTaskEvent({ taskRef, event: 'manual-validation.started', agent, initiator: 'model', requestId: transaction.transactionId, reasonCode: 'user-request', transactionId: transaction.transactionId }, { lockAlreadyHeld: true, repoRoot: cwd });
190
+ if (started.status === 'failed') return result('failed', started.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_FAILED', message: 'manual-validation started event failed' });
191
+ }
192
+ if (prepareOnly) return result('applied', null, { transaction, prepared: true, idempotent });
193
+ const transactionFailure = (failure: { code: string; message: string }): never => {
194
+ throw new Error(`${failure.code}: ${failure.message}`);
195
+ };
196
+ const transitionAndPersist = (
197
+ phase: Parameters<typeof transitionManualValidationTransaction>[1],
198
+ patch: Parameters<typeof transitionManualValidationTransaction>[2] = {}
199
+ ): void => {
200
+ const next = transitionManualValidationTransaction(transaction, phase, patch);
201
+ if (!next.ok) throw new Error(`${next.error.code}: ${next.error.message}`);
202
+ transaction = next.value;
203
+ writeManualValidationTransactionAtomic(resolved.taskDir, transaction);
204
+ };
205
+ try {
206
+ if (transaction.phase === 'prepared') {
207
+ const staged = await syncPullRequestSummary(taskRef, {
208
+ cwd, client: options.client, agent, body: pendingBody, changeReportFile: path.resolve(cwd, values.changeReportFile!), primaryResult: primaryResult as 'pr_created' | 'pr_reused' | 'no_op', strict: true,
209
+ manualValidation: { phase: 'pending' }, lockAlreadyHeld: true
210
+ });
211
+ if (!['applied', 'no-op'].includes(staged.status)) transactionFailure(staged.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_FAILED', message: 'pending summary staging failed' });
212
+ transitionAndPersist('summary-staged');
213
+ }
214
+ let receiptResult = readManualValidationReceipt(resolved.taskDir, {
215
+ transactionId: transaction.transactionId,
216
+ taskId: resolved.taskId,
217
+ prNumber: pullRequest.number,
218
+ prHeadSha: pullRequest.head.sha,
219
+ evidenceDigest: transaction.evidenceDigest,
220
+ artifact: values.artifact!
221
+ });
222
+ if (!receiptResult.ok && receiptResult.error.code !== 'MANUAL_VALIDATION_RECEIPT_MISSING') transactionFailure(receiptResult.error);
223
+ let receipt: ManualValidationReceipt;
224
+ if (receiptResult.ok) {
225
+ receipt = receiptResult.value;
226
+ } else {
227
+ receipt = createManualValidationReceipt({
228
+ transactionId: transaction.transactionId,
229
+ taskId: resolved.taskId,
230
+ prNumber: pullRequest.number,
231
+ prHeadSha: pullRequest.head.sha,
232
+ evidenceDigest: transaction.evidenceDigest,
233
+ artifact: values.artifact!,
234
+ artifactSha256: sha256File(artifactPath),
235
+ pendingSummaryDigest: transaction.pendingSummaryDigest,
236
+ finalSummaryDigest: transaction.finalSummaryDigest,
237
+ committedAt: new Date().toISOString()
238
+ });
239
+ writeManualValidationReceiptAtomic(resolved.taskDir, receipt);
240
+ }
241
+ if (transaction.phase === 'summary-staged') {
242
+ transitionAndPersist('receipt-committed', { committedReceipt: receipt.receiptDigest });
243
+ }
244
+ if (transaction.phase === 'receipt-committed' && !transaction.eventAppended) {
245
+ const completed = applyTaskEvent({ taskRef, event: 'manual-validation.completed', agent, initiator: 'model', requestId: transaction.transactionId, reasonCode: 'user-request', artifact: values.artifact, summaryResult: 'human-confirmed validation and committed receipt', transactionId: transaction.transactionId, receiptDigest: receipt.receiptDigest, prHeadSha: receipt.prHeadSha }, { lockAlreadyHeld: true, repoRoot: cwd });
246
+ if (completed.status === 'failed') transactionFailure(completed.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_FAILED', message: 'manual-validation completion event failed' });
247
+ transitionAndPersist('receipt-committed', { eventAppended: true });
248
+ }
249
+ if (transaction.phase === 'receipt-committed') {
250
+ transitionAndPersist('final-promotion-in-progress');
251
+ }
252
+ const promoted = await syncPullRequestSummary(taskRef, {
253
+ cwd, client: options.client, agent, body: manualSummaryBody(finalBodyWithoutReceipt, 'final', transaction.transactionId, receipt.receiptDigest, receipt.evidenceDigest, receipt.prHeadSha), changeReportFile: path.resolve(cwd, values.changeReportFile!), primaryResult, strict: true,
254
+ manualValidation: { phase: 'final', transactionId: transaction.transactionId, receiptDigest: receipt.receiptDigest, prHeadSha: receipt.prHeadSha, authority: 'coordinator' }, lockAlreadyHeld: true
255
+ });
256
+ if (!['applied', 'no-op'].includes(promoted.status)) transactionFailure(promoted.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'final summary promotion failed' });
257
+ const postWrite = await summaryCommentState(taskRef, { cwd, client: options.client });
258
+ if (!/^###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)\s*$/mu.test(postWrite.comment?.body ?? '') || postWrite.pullRequest?.head.sha !== receipt.prHeadSha || !manualValidationFinalSummaryProjectionMatches(postWrite.comment?.body ?? '', receipt)) transactionFailure({ code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'final summary post-write verification failed' });
259
+ transitionAndPersist('committed', { postWriteVerified: true });
260
+ return result('applied', null, { transaction, receipt, idempotent: false });
261
+ } catch (error) {
262
+ const message = error instanceof Error ? error.message : String(error);
263
+ if (transaction.phase === 'final-promotion-in-progress') {
264
+ try {
265
+ await syncPullRequestSummary(taskRef, {
266
+ cwd, client: options.client, agent, body: pendingBody, changeReportFile: path.resolve(cwd, values.changeReportFile!), primaryResult: primaryResult as 'pr_created' | 'pr_reused' | 'no_op', strict: true,
267
+ manualValidation: { phase: 'pending' }, lockAlreadyHeld: true
268
+ });
269
+ } catch {
270
+ // Preserve recovery-required when pending compensation cannot be confirmed.
271
+ }
272
+ }
273
+ const failedTransaction = transitionManualValidationTransaction(transaction, 'recovery-required', { error: message.slice(0, 400) });
274
+ if (failedTransaction.ok) writeManualValidationTransactionAtomic(resolved.taskDir, failedTransaction.value);
275
+ return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message });
276
+ }
277
+ }
278
+
279
+ function parse(args: string[], start: number): { values: Record<string, string>; error?: string } {
280
+ const values: Record<string, string> = {};
281
+ for (let index = start; index < args.length; index += 1) {
282
+ const flag = args[index]!;
283
+ if (flag === '--prepare') {
284
+ if (values.prepare) return { values, error: `duplicate option '${flag}'` };
285
+ values.prepare = 'true';
286
+ continue;
287
+ }
288
+ if (!['--format', '--cwd', '--artifact', '--summary-file', '--change-report-file', '--agent', '--result'].includes(flag)) return { values, error: `unknown option '${flag}'` };
289
+ const value = args[++index];
290
+ if (!value || value.startsWith('--')) return { values, error: `option '${flag}' requires a value` };
291
+ const key = flag.slice(2).replace(/-([a-z])/gu, (_, letter: string) => letter.toUpperCase());
292
+ if (values[key]) return { values, error: `duplicate option '${flag}'` };
293
+ values[key] = value;
294
+ }
295
+ return { values };
296
+ }
297
+
298
+ async function manualValidation(args: string[] = []): Promise<void> {
299
+ if (!ensureInternalHandlerRoute('manual-validation', args)) return;
300
+ if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(`${USAGE}\n`); return; }
301
+ const operation = args[0];
302
+ const isTransaction = operation === 'transaction' && internalHandlerRoute('manual-validation', 'transaction', operation);
303
+ if (!isTransaction) { printFailure('json', { code: 'MANUAL_VALIDATION_ARGS_INVALID', message: 'transaction operation is required' }); return; }
304
+ const taskRef = args[1];
305
+ if (!taskRef || taskRef.startsWith('--')) { printFailure('json', { code: 'MANUAL_VALIDATION_ARGS_INVALID', message: 'task ref is required' }); return; }
306
+ const parsed = parse(args, 2);
307
+ if (parsed.error) { printFailure('json', { code: 'MANUAL_VALIDATION_ARGS_INVALID', message: parsed.error }); return; }
308
+ const values = parsed.values;
309
+ const format = values.format === 'text' ? 'text' : 'json';
310
+ const output = await executeManualValidationTransaction(taskRef, values, path.resolve(values.cwd ?? process.cwd()));
311
+ process.stdout.write(format === 'json' ? `${JSON.stringify(output)}\n` : `${output.status === 'applied' ? 'Manual validation transaction committed.' : `${output.error?.code}: ${output.error?.message}`}\n`);
312
+ if (output.status === 'failed') process.exitCode = 1;
313
+ }
314
+
315
+ export { executeManualValidationTransaction, manualValidation };
@@ -25,11 +25,11 @@ const USAGE = `Usage: agent-infra-internal platform-pr inspect <task-ref> [--cwd
25
25
  agent-infra-internal platform-pr sync-in-labels --pr <N> [--dry-run] [--cwd <path>]
26
26
  agent-infra-internal platform-pr summary-context <task-ref> [--cwd <path>]
27
27
  agent-infra-internal platform-pr change-report <task-ref> --agent <agent> --mechanical-file <path> --precheck-file <path> [--dry-run] [--cwd <path>]
28
- agent-infra-internal platform-pr summary-sync <task-ref> --agent <agent> --body-file <path|-> --change-report-file <path> --result <pr_created|pr_reused|no_op> [--strict] [--dry-run] [--cwd <path>]
28
+ agent-infra-internal platform-pr summary-sync <task-ref> --agent <agent> --body-file <path|-> --change-report-file <path> --result <pr_created|pr_reused|no_op> [--manual-phase pending|final ...] [--strict] [--dry-run] [--cwd <path>]
29
29
  `;
30
30
 
31
31
  const BOOLEAN_FLAGS = new Set(['--draft', '--dry-run', '--metadata', '--closing-issue', '--strict']);
32
- const VALUE_FLAGS = new Set(['--cwd', '--agent', '--base', '--head', '--title-file', '--body-file', '--change-report-file', '--mechanical-file', '--precheck-file', '--pr', '--result']);
32
+ const VALUE_FLAGS = new Set(['--cwd', '--agent', '--base', '--head', '--title-file', '--body-file', '--change-report-file', '--mechanical-file', '--precheck-file', '--pr', '--result', '--manual-phase', '--transaction-id', '--receipt-digest', '--pr-head-sha']);
33
33
 
34
34
  function key(flag: string): string {
35
35
  return flag.slice(2).replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase());
@@ -111,7 +111,7 @@ async function platformPr(args: string[] = []): Promise<void> {
111
111
  'sync-in-labels': ['cwd', 'pr', 'dryRun'],
112
112
  'summary-context': ['cwd'],
113
113
  'change-report': ['cwd', 'agent', 'mechanicalFile', 'precheckFile', 'dryRun'],
114
- 'summary-sync': ['cwd', 'agent', 'bodyFile', 'changeReportFile', 'result', 'dryRun', 'strict']
114
+ 'summary-sync': ['cwd', 'agent', 'bodyFile', 'changeReportFile', 'result', 'manualPhase', 'transactionId', 'receiptDigest', 'prHeadSha', 'dryRun', 'strict']
115
115
  };
116
116
  const unexpected = Object.keys(values).find((name) => !allowed[operation]!.includes(name));
117
117
  if (unexpected) { fail(`${operation} does not accept --${unexpected}`); return; }
@@ -160,7 +160,18 @@ async function platformPr(args: string[] = []): Promise<void> {
160
160
  if (typeof values.changeReportFile !== 'string') { fail('summary-sync requires --change-report-file'); return; }
161
161
  if (!primaryResult) { fail('summary-sync requires --result pr_created, pr_reused, or no_op'); return; }
162
162
  try {
163
- finish(await syncPullRequestSummary(taskRef, { cwd, agent: values.agent, body: readFile(values.bodyFile, cwd), changeReportFile: values.changeReportFile, primaryResult: primaryResult as 'pr_created' | 'pr_reused' | 'no_op', dryRun: values.dryRun === true, strict: values.strict === true }));
163
+ const manualPhase = values.manualPhase;
164
+ if (manualPhase !== undefined && manualPhase !== 'pending' && manualPhase !== 'final') { fail('--manual-phase must be pending or final'); return; }
165
+ finish(await syncPullRequestSummary(taskRef, {
166
+ cwd, agent: values.agent, body: readFile(values.bodyFile, cwd), changeReportFile: values.changeReportFile,
167
+ primaryResult: primaryResult as 'pr_created' | 'pr_reused' | 'no_op', dryRun: values.dryRun === true, strict: values.strict === true,
168
+ ...(manualPhase ? { manualValidation: {
169
+ phase: manualPhase,
170
+ ...(typeof values.transactionId === 'string' ? { transactionId: values.transactionId } : {}),
171
+ ...(typeof values.receiptDigest === 'string' ? { receiptDigest: values.receiptDigest } : {}),
172
+ ...(typeof values.prHeadSha === 'string' ? { prHeadSha: values.prHeadSha } : {})
173
+ } } : {})
174
+ }));
164
175
  } catch (error) {
165
176
  fail(`unable to read body file: ${error instanceof Error ? error.message : String(error)}`);
166
177
  }
@@ -1,6 +1,5 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import crypto from 'node:crypto';
4
3
  import { spawnSync } from 'node:child_process';
5
4
  import semver from 'semver';
6
5
 
@@ -9,115 +8,95 @@ import { inspectPlatformRelease, reconcileReleaseMilestones } from '../platform/
9
8
  import { inspectHomebrewChannel, inspectNpmChannel } from '../release/channels.ts';
10
9
  import { releaseSnapshot } from '../release/workflow.ts';
11
10
  import type { PostReleaseFacts, ReleaseFacts } from '../release/workflow.ts';
11
+ import { collectDemoTranscript } from './demo-transcript.ts';
12
+ import { createDemoPromotion, promoteDemoAssets, recoverDemoPromotion } from './demo-promotion.ts';
12
13
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
13
14
 
14
- function command(cwd: string, executable: string, args: string[]) {
15
- return spawnSync(executable, args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
15
+ type CommandOptions = { env?: NodeJS.ProcessEnv };
16
+
17
+ function command(cwd: string, executable: string, args: string[], options: CommandOptions = {}) {
18
+ return spawnSync(executable, args, {
19
+ cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, ...options.env }
20
+ });
16
21
  }
17
22
 
18
23
  type CommandResult = ReturnType<typeof command>;
19
- type CommandRunner = (cwd: string, executable: string, args: string[]) => CommandResult;
24
+ type CommandRunner = (cwd: string, executable: string, args: string[], options?: CommandOptions) => CommandResult;
20
25
  type DemoResult = {
21
26
  status: 'recorded' | 'skipped' | 'failed';
22
- reasonCode: 'DEMO_INPUTS_UNCHANGED' | 'GIT_LFS_MISSING' | 'VHS_MISSING' | 'FFMPEG_MISSING'
27
+ reasonCode: 'DEMO_TRANSCRIPT_UNCHANGED' | 'DEMO_TRANSCRIPT_UNAVAILABLE' | 'DEMO_TRANSCRIPT_FAILED'
28
+ | 'DEMO_PROMOTION_FAILED' | 'DEMO_PROMOTION_RECOVERY_REQUIRED' | 'DEMO_PROMOTION_CLEANUP_FAILED'
29
+ | 'GIT_LFS_MISSING' | 'VHS_MISSING' | 'FFMPEG_MISSING'
23
30
  | 'DEMO_COMMAND_FAILED' | 'DEMO_OUTPUT_MISSING' | 'DEMO_OUTPUT_INVALID'
24
- | 'DEMO_OUTPUT_TOO_LARGE' | 'DEMO_DIGEST_FAILED' | null;
31
+ | 'DEMO_OUTPUT_TOO_LARGE' | null;
25
32
  message: string | null;
26
33
  outputPath: string | null;
27
34
  };
28
35
 
29
- // The tape is the canonical init interaction contract. Do not hash all of
30
- // lib/init.ts: non-visual config serialization changes must not re-record it.
31
- const DEMO_INPUT_PATHS = [
32
- 'assets/demo-init.tape',
33
- 'scripts/demo-regen.sh',
34
- 'scripts/normalize-gif-duration.py',
35
- 'bin/cli.ts',
36
- 'lib/log.ts',
37
- 'lib/prompt.ts',
38
- 'lib/paths.ts',
39
- 'lib/render.ts',
40
- 'lib/sandbox/engines/'
41
- ] as const;
42
- const DEMO_DIGEST_PATH = 'assets/demo-init.inputs.sha256';
36
+ const DEMO_DIGEST_PATH = 'assets/demo-init.transcript.sha256';
43
37
  const DEMO_OUTPUT_PATH = 'assets/demo-init.gif';
44
38
  const DEMO_MAX_BYTES = 4 * 1024 * 1024;
45
39
 
46
- function demoInputFiles(cwd: string): string[] {
47
- const files = new Set<string>();
48
- for (const input of DEMO_INPUT_PATHS) {
49
- if (input.endsWith('/')) {
50
- const listed = command(cwd, 'git', ['ls-files', '--', input]);
51
- if (listed.status !== 0) throw new Error(String(listed.stderr || `Unable to list ${input}`));
52
- const directoryFiles = String(listed.stdout).split('\n').filter(Boolean);
53
- if (!directoryFiles.length) throw new Error(`Canonical demo input directory is empty: ${input}`);
54
- for (const file of directoryFiles) files.add(file.replaceAll('\\', '/'));
55
- } else {
56
- files.add(input);
57
- }
58
- }
59
- const sorted = [...files].sort((left, right) => Buffer.from(left).compare(Buffer.from(right)));
60
- if (!sorted.length) throw new Error('Canonical demo input set is empty');
61
- for (const file of sorted) {
62
- const absolute = path.join(cwd, file);
63
- if (!fs.statSync(absolute).isFile()) throw new Error(`Canonical demo input is not a file: ${file}`);
64
- }
65
- return sorted;
66
- }
67
-
68
- function computeDemoInputDigest(cwd: string): string {
69
- const hash = crypto.createHash('sha256');
70
- for (const file of demoInputFiles(cwd)) {
71
- const bytes = fs.readFileSync(path.join(cwd, file));
72
- hash.update(file);
73
- hash.update('\0');
74
- hash.update(String(bytes.byteLength));
75
- hash.update('\0');
76
- hash.update(bytes);
77
- hash.update('\0');
78
- }
79
- return hash.digest('hex');
80
- }
81
-
82
40
  function validGif(filePath: string): boolean {
83
41
  if (!fs.existsSync(filePath)) return false;
84
42
  const header = fs.readFileSync(filePath).subarray(0, 6).toString('ascii');
85
43
  return header === 'GIF87a' || header === 'GIF89a';
86
44
  }
87
45
 
88
- function writeDigestAtomically(cwd: string, digest: string): void {
89
- const target = path.join(cwd, DEMO_DIGEST_PATH);
90
- const temporary = `${target}.tmp-${process.pid}`;
91
- fs.writeFileSync(temporary, `${digest}\n`);
92
- fs.renameSync(temporary, target);
93
- }
94
-
95
- function runOptionalDemo(cwd: string, run: CommandRunner = command): DemoResult {
96
- let digest: string;
97
- try {
98
- digest = computeDemoInputDigest(cwd);
99
- } catch (error) {
100
- return { status: 'failed', reasonCode: 'DEMO_DIGEST_FAILED', message: String(error), outputPath: null };
46
+ async function runOptionalDemo(
47
+ cwd: string,
48
+ run: CommandRunner = command,
49
+ collect = collectDemoTranscript
50
+ ): Promise<DemoResult> {
51
+ const recovery = recoverDemoPromotion(cwd);
52
+ if (recovery?.status === 'failed') {
53
+ return { status: 'failed', reasonCode: recovery.code === 'DEMO_PROMOTION_CLEANUP_FAILED' ? recovery.code : 'DEMO_PROMOTION_RECOVERY_REQUIRED', message: recovery.message, outputPath: null };
54
+ }
55
+ const transcript = await collect(cwd);
56
+ if (transcript.status === 'failed') {
57
+ return { status: 'failed', reasonCode: transcript.reasonCode, message: transcript.message, outputPath: null };
101
58
  }
102
59
  const digestPath = path.join(cwd, DEMO_DIGEST_PATH);
103
60
  const priorDigest = fs.existsSync(digestPath) ? fs.readFileSync(digestPath, 'utf8').trim() : '';
104
- if (/^[0-9a-f]{64}$/.test(priorDigest) && priorDigest === digest) {
105
- return { status: 'skipped', reasonCode: 'DEMO_INPUTS_UNCHANGED', message: null, outputPath: null };
61
+ if (/^[0-9a-f]{64}$/.test(priorDigest) && priorDigest === transcript.sha256) {
62
+ return { status: 'skipped', reasonCode: 'DEMO_TRANSCRIPT_UNCHANGED', message: null, outputPath: null };
106
63
  }
64
+ const promotion = createDemoPromotion(cwd, transcript.transcript);
65
+ const discardPromotion = () => fs.rmSync(promotion.directory, { recursive: true, force: true });
107
66
  const lfs = run(cwd, 'git', ['lfs', 'version']);
108
67
  if (lfs.status !== 0) {
68
+ discardPromotion();
109
69
  return { status: 'failed', reasonCode: 'GIT_LFS_MISSING', message: String(lfs.stderr || lfs.stdout), outputPath: null };
110
70
  }
111
71
  const lfsAttribute = run(cwd, 'git', ['check-attr', 'filter', '--', DEMO_OUTPUT_PATH]);
112
72
  if (lfsAttribute.status !== 0 || !String(lfsAttribute.stdout).trim().endsWith(': lfs')) {
73
+ discardPromotion();
113
74
  return { status: 'failed', reasonCode: 'GIT_LFS_MISSING', message: `${DEMO_OUTPUT_PATH} is not tracked by Git LFS`, outputPath: null };
114
75
  }
115
76
  const vhs = run(cwd, 'vhs', ['--version']);
116
- if (vhs.status !== 0) return { status: 'skipped', reasonCode: 'VHS_MISSING', message: null, outputPath: null };
77
+ if (vhs.status !== 0) {
78
+ discardPromotion();
79
+ return {
80
+ status: 'failed',
81
+ reasonCode: 'VHS_MISSING',
82
+ message: String(vhs.stderr || vhs.stdout) || 'VHS is required to record the changed demo transcript',
83
+ outputPath: null
84
+ };
85
+ }
117
86
  const ffmpeg = run(cwd, 'ffmpeg', ['-version']);
118
- if (ffmpeg.status !== 0) return { status: 'skipped', reasonCode: 'FFMPEG_MISSING', message: null, outputPath: null };
119
- const demo = run(cwd, 'npm', ['run', 'demo:regen']);
87
+ if (ffmpeg.status !== 0) {
88
+ discardPromotion();
89
+ return {
90
+ status: 'failed',
91
+ reasonCode: 'FFMPEG_MISSING',
92
+ message: String(ffmpeg.stderr || ffmpeg.stdout) || 'ffmpeg is required to record the changed demo transcript',
93
+ outputPath: null
94
+ };
95
+ }
96
+ const stagedOutputPath = path.relative(cwd, promotion.staging.gif).replaceAll(path.sep, '/');
97
+ const demo = run(cwd, 'npm', ['run', 'demo:regen'], { env: { DEMO_OUTPUT_PATH: stagedOutputPath } });
120
98
  if (demo.status !== 0) {
99
+ discardPromotion();
121
100
  return {
122
101
  status: 'failed',
123
102
  reasonCode: 'DEMO_COMMAND_FAILED',
@@ -126,7 +105,8 @@ function runOptionalDemo(cwd: string, run: CommandRunner = command): DemoResult
126
105
  };
127
106
  }
128
107
  const outputPath = DEMO_OUTPUT_PATH;
129
- if (!fs.existsSync(path.join(cwd, outputPath))) {
108
+ if (!fs.existsSync(promotion.staging.gif)) {
109
+ discardPromotion();
130
110
  return {
131
111
  status: 'failed',
132
112
  reasonCode: 'DEMO_OUTPUT_MISSING',
@@ -134,18 +114,24 @@ function runOptionalDemo(cwd: string, run: CommandRunner = command): DemoResult
134
114
  outputPath: null
135
115
  };
136
116
  }
137
- const absoluteOutput = path.join(cwd, outputPath);
117
+ const absoluteOutput = promotion.staging.gif;
138
118
  if (!validGif(absoluteOutput)) {
119
+ discardPromotion();
139
120
  return { status: 'failed', reasonCode: 'DEMO_OUTPUT_INVALID', message: `${outputPath} is not a GIF`, outputPath: null };
140
121
  }
141
122
  if (fs.statSync(absoluteOutput).size > DEMO_MAX_BYTES) {
123
+ discardPromotion();
142
124
  return { status: 'failed', reasonCode: 'DEMO_OUTPUT_TOO_LARGE', message: `${outputPath} exceeds 4 MiB`, outputPath: null };
143
125
  }
144
- const pointer = run(cwd, 'git', ['lfs', 'pointer', `--file=${outputPath}`]);
126
+ const pointer = run(cwd, 'git', ['lfs', 'pointer', `--file=${stagedOutputPath}`]);
145
127
  if (pointer.status !== 0 || !String(pointer.stdout).includes(`size ${fs.statSync(absoluteOutput).size}`)) {
128
+ discardPromotion();
146
129
  return { status: 'failed', reasonCode: 'DEMO_OUTPUT_INVALID', message: 'Git LFS could not produce a matching pointer', outputPath: null };
147
130
  }
148
- writeDigestAtomically(cwd, digest);
131
+ const promoted = promoteDemoAssets(cwd, promotion.staging, `${process.pid}-${Date.now()}`);
132
+ if (promoted.status === 'failed') {
133
+ return { status: 'failed', reasonCode: promoted.code ?? 'DEMO_PROMOTION_FAILED', message: promoted.message, outputPath: null };
134
+ }
149
135
  return { status: 'recorded', reasonCode: null, message: null, outputPath };
150
136
  }
151
137
 
@@ -207,7 +193,7 @@ function inspectPostReleaseFacts(
207
193
  remoteHead,
208
194
  newVersion,
209
195
  changedPaths,
210
- demoInputSha256: digest && /^[0-9a-f]{64}$/.test(digest) ? digest : null,
196
+ demoTranscriptSha256: digest && /^[0-9a-f]{64}$/.test(digest) ? digest : null,
211
197
  worktree: snapshot?.worktree ?? [],
212
198
  staged: snapshot?.staged ?? []
213
199
  };
@@ -333,7 +319,8 @@ async function releaseWorkflow(args: string[] = []): Promise<void> {
333
319
  if (internalHandlerRoute('release-workflow', 'prepare', action || '')) {
334
320
  if (before.phase !== 'unprepared') {
335
321
  const milestones = await reconcileReleaseMilestones(version, { cwd });
336
- process.stdout.write(`${JSON.stringify({ ...milestones, snapshot: before })}\n`);
322
+ const snapshot = releaseSnapshot(version, await inspectFacts(cwd, version));
323
+ process.stdout.write(`${JSON.stringify({ ...milestones, snapshot })}\n`);
337
324
  process.exitCode = milestones.status === 'failed' ? 1 : milestones.status === 'blocked' ? 2 : 0;
338
325
  return;
339
326
  }
@@ -420,6 +407,11 @@ async function releaseWorkflow(args: string[] = []): Promise<void> {
420
407
  if (!['published', 'post-pending'].includes(before.phase) || !channelsComplete || before.facts.smoke !== 'success') {
421
408
  process.stdout.write(`${JSON.stringify({ status: before.facts.smoke === 'failed' ? 'failed' : 'blocked', changed: false, snapshot: before, error: { code: 'RELEASE_CHANNELS_PENDING', message: 'Release channels or smoke workflow are not complete' } })}\n`); process.exitCode = before.facts.smoke === 'failed' ? 1 : 2; return;
422
409
  }
410
+ const recovery = recoverDemoPromotion(cwd);
411
+ if (recovery?.status === 'failed') {
412
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: true, snapshot: before, error: { code: recovery.code, message: recovery.message } })}\n`);
413
+ process.exitCode = 1; return;
414
+ }
423
415
  const worktreeError = inspectPostWorktree(cwd);
424
416
  if (worktreeError) {
425
417
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, snapshot: before, error: worktreeError })}\n`);
@@ -430,7 +422,7 @@ async function releaseWorkflow(args: string[] = []): Promise<void> {
430
422
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: true, snapshot: before, error: { code: 'RELEASE_POST_COMMAND_FAILED', message: String(built.stderr || built.stdout) } })}\n`);
431
423
  process.exitCode = 1; return;
432
424
  }
433
- const demo = runOptionalDemo(cwd);
425
+ const demo = await runOptionalDemo(cwd);
434
426
  if (demo.status === 'failed') {
435
427
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: true, snapshot: before, demo, error: { code: 'RELEASE_POST_DEMO_FAILED', message: demo.message } })}\n`);
436
428
  process.exitCode = 1; return;
@@ -449,5 +441,5 @@ async function releaseWorkflow(args: string[] = []): Promise<void> {
449
441
  process.stdout.write(`${JSON.stringify({ status: 'applied', changed: true, demo, snapshot, error: null })}\n`);
450
442
  }
451
443
 
452
- export { changedPaths, computeDemoInputDigest, inspectFacts, inspectLocalReleaseFacts, inspectPostReleaseFacts, inspectPostWorktree, releaseSmokeStatus, releaseWorkflow, runOptionalDemo };
444
+ export { changedPaths, inspectFacts, inspectLocalReleaseFacts, inspectPostReleaseFacts, inspectPostWorktree, releaseSmokeStatus, releaseWorkflow, runOptionalDemo };
453
445
  export type { CommandRunner, DemoResult };