@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,331 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { locateActivityLog, pairEntries } from '../task/activity-log.js';
4
+ import { resolveTaskRef } from '../task/resolve-ref.js';
5
+ import { applyTaskEvent } from '../task/events.js';
6
+ import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
7
+ import { normalizeAgentToken } from '../agent-clients/tokens.js';
8
+ import { createManualValidationReceipt, manualValidationFinalSummaryDigest, manualValidationFinalSummaryProjectionMatches, readManualValidationReceipt, writeManualValidationReceiptAtomic } from '../task/manual-validation-receipt.js';
9
+ import { archiveManualValidationGeneration, createManualValidationTransaction, readManualValidationTransaction, retryManualValidationTransaction, summaryPreimageDigest, transitionManualValidationTransaction, validateManualValidationGenerationArchive, writeManualValidationTransactionAtomic } from '../task/manual-validation-transaction.js';
10
+ import { sha256File } from '../task/artifact-receipts.js';
11
+ import { summaryCommentState, syncPullRequestSummary } from '../platform/pr-summary.js';
12
+ import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
13
+ const USAGE = `Usage: agent-infra-internal manual-validation transaction <task-ref> --prepare --artifact <artifact> --summary-file <path> --agent <agent> [--cwd <path>]
14
+ 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>]`;
15
+ function result(status, error = null, extra = {}) {
16
+ return { status, changed: false, error, ...extra };
17
+ }
18
+ function printFailure(format, error) {
19
+ if (format === 'json')
20
+ process.stdout.write(`${JSON.stringify(result('failed', error))}\n`);
21
+ else
22
+ process.stderr.write(`${error.code}: ${error.message}\n`);
23
+ process.exitCode = 1;
24
+ }
25
+ 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;
26
+ function manualSummaryBody(body, phase, transactionId, receiptDigest = '', evidenceDigest = '', prHeadSha = '') {
27
+ const chinese = /###\s+(?:⚠️\s+需人工校验|✅\s+(?:人工验证已通过|无需人工校验)|⏳\s+人工验证待收尾)\s*$/mu.test(body);
28
+ const section = phase === 'pending'
29
+ ? chinese
30
+ ? `### ⏳ 人工验证待收尾\n\n人工验证证据已暂存;事务 ${transactionId} 正在等待最终完成。`
31
+ : `### ⏳ Manual Validation Pending\n\nManual validation evidence is staged; transaction ${transactionId} is awaiting final promotion.`
32
+ : chinese
33
+ ? `### ✅ 人工验证已通过\n\n人工验证已通过;transaction=${transactionId}; receipt=${receiptDigest}; evidence=${evidenceDigest}; head=${prHeadSha}.`
34
+ : `### ✅ Manual Validation Passed\n\nManual validation passed; transaction=${transactionId}; receipt=${receiptDigest}; evidence=${evidenceDigest}; head=${prHeadSha}.`;
35
+ let inserted = false;
36
+ const updated = body.replace(MANUAL_VALIDATION_STATUS_SECTION, () => {
37
+ if (inserted)
38
+ return '';
39
+ inserted = true;
40
+ return `${section}\n\n`;
41
+ });
42
+ return inserted ? `${updated.replace(/\s+$/u, '')}\n` : `${body.replace(/\s+$/u, '')}\n\n${section}\n`;
43
+ }
44
+ function openManualValidationStarted(taskMdPath) {
45
+ let content;
46
+ try {
47
+ content = fs.readFileSync(taskMdPath, 'utf8');
48
+ }
49
+ catch {
50
+ return { present: false, transactionId: null };
51
+ }
52
+ const section = locateActivityLog(content);
53
+ if (!section)
54
+ return { present: false, transactionId: null };
55
+ const row = pairEntries(section.entries)
56
+ .filter((item) => item.step === 'Complete Manual Validation' && item.started && !item.done)
57
+ .at(-1);
58
+ if (!row)
59
+ return { present: false, transactionId: null };
60
+ return {
61
+ present: true,
62
+ transactionId: /(?:^|;\s*)transaction=([A-Za-z0-9][A-Za-z0-9._-]{0,127})(?:;|$)/u.exec(row.note)?.[1] ?? null
63
+ };
64
+ }
65
+ async function executeManualValidationTransaction(taskRef, values, cwd, options = {}) {
66
+ const prepareOnly = values.prepare === 'true';
67
+ const required = prepareOnly
68
+ ? ['artifact', 'summaryFile', 'agent']
69
+ : ['artifact', 'summaryFile', 'changeReportFile', 'agent'];
70
+ const missing = required.find((key) => !values[key]);
71
+ if (missing)
72
+ return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_ARGS_INVALID', message: `transaction requires --${missing.replace(/[A-Z]/gu, (letter) => `-${letter.toLowerCase()}`)}` });
73
+ const agent = normalizeAgentToken(values.agent);
74
+ if (!agent)
75
+ return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_ARGS_INVALID', message: 'transaction agent is invalid' });
76
+ const primaryResult = values.result ?? 'no_op';
77
+ if (!['pr_created', 'pr_reused', 'no_op'].includes(primaryResult))
78
+ return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_ARGS_INVALID', message: '--result must be pr_created, pr_reused, or no_op' });
79
+ const resolved = resolveTaskRef(taskRef, { repoRoot: cwd });
80
+ if (!resolved.ok)
81
+ return result('failed', { code: resolved.code, message: resolved.message });
82
+ try {
83
+ return await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `manual-validation:${taskRef}`, () => executeManualValidationTransactionLocked(taskRef, values, cwd, resolved, agent, primaryResult, prepareOnly, options));
84
+ }
85
+ catch (error) {
86
+ if (error instanceof TaskExecutionLockError)
87
+ return result('failed', { code: error.code, message: error.message });
88
+ throw error;
89
+ }
90
+ }
91
+ async function executeManualValidationTransactionLocked(taskRef, values, cwd, resolved, agent, primaryResult, prepareOnly, options) {
92
+ const artifactPath = path.resolve(resolved.taskDir, values.artifact);
93
+ if (!prepareOnly && !fs.existsSync(artifactPath))
94
+ return result('failed', { code: 'MANUAL_VALIDATION_ARTIFACT_MISSING', message: `manual-validation artifact is missing: ${values.artifact}` });
95
+ const currentState = await summaryCommentState(taskRef, { cwd, client: options.client });
96
+ if (!currentState.pullRequest)
97
+ return result('failed', { code: 'MANUAL_VALIDATION_PR_REQUIRED', message: 'canonical pull-request head is unavailable' });
98
+ const pullRequest = currentState.pullRequest;
99
+ const preimageBody = currentState.comment?.body ?? '';
100
+ const preimage = { commentId: currentState.comment?.id ?? null, body: preimageBody, digest: summaryPreimageDigest(preimageBody) };
101
+ const evidenceDigest = sha256File(path.resolve(cwd, values.summaryFile));
102
+ const archiveGeneration = options.archiveGeneration ?? archiveManualValidationGeneration;
103
+ let transactionResult = readManualValidationTransaction(resolved.taskDir, { taskId: resolved.taskId, prNumber: pullRequest.number, prHeadSha: pullRequest.head.sha, evidenceDigest, artifact: values.artifact });
104
+ let previousTransaction = null;
105
+ if (!transactionResult.ok && transactionResult.error.code === 'MANUAL_VALIDATION_TRANSACTION_IDENTITY_MISMATCH') {
106
+ const existing = readManualValidationTransaction(resolved.taskDir);
107
+ if (!existing.ok)
108
+ return result('failed', existing.error);
109
+ if (!['aborted', 'recovery-required', 'committed'].includes(existing.value.phase))
110
+ return result('failed', transactionResult.error);
111
+ try {
112
+ validateManualValidationGenerationArchive(resolved.taskDir, existing.value, existing.value.phase === 'committed');
113
+ }
114
+ catch (error) {
115
+ return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: error instanceof Error ? error.message : String(error) });
116
+ }
117
+ previousTransaction = existing.value;
118
+ transactionResult = { ok: false, error: { code: 'MANUAL_VALIDATION_TRANSACTION_MISSING', message: 'previous failed transaction was archived for a new pull-request head' } };
119
+ }
120
+ if (!transactionResult.ok && transactionResult.error.code !== 'MANUAL_VALIDATION_TRANSACTION_MISSING')
121
+ return result('failed', transactionResult.error);
122
+ if (transactionResult.ok && transactionResult.value.phase === 'committed')
123
+ return result('applied', null, { transaction: transactionResult.value, receipt: transactionResult.value.committedReceipt, idempotent: true });
124
+ const openStarted = transactionResult.ok ? { present: false, transactionId: null } : openManualValidationStarted(resolved.taskMdPath);
125
+ if (openStarted.present && !openStarted.transactionId) {
126
+ return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'open manual-validation started event has no recoverable transactionId' });
127
+ }
128
+ if (previousTransaction && openStarted.transactionId === previousTransaction.transactionId) {
129
+ 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' });
130
+ }
131
+ const transactionId = transactionResult.ok
132
+ ? transactionResult.value.transactionId
133
+ : (openStarted.transactionId ?? values.transactionId ?? `mv-${Date.now()}`);
134
+ const finalBodyWithoutReceipt = fs.readFileSync(path.resolve(cwd, values.summaryFile), 'utf8');
135
+ const pendingBody = manualSummaryBody(finalBodyWithoutReceipt, 'pending', transactionId);
136
+ const createTransaction = () => createManualValidationTransaction({
137
+ transactionId,
138
+ taskId: resolved.taskId,
139
+ prNumber: pullRequest.number,
140
+ prHeadSha: pullRequest.head.sha,
141
+ evidenceDigest,
142
+ summaryPreimage: preimage,
143
+ pendingSummaryDigest: summaryPreimageDigest(pendingBody),
144
+ finalSummaryDigest: manualValidationFinalSummaryDigest(manualSummaryBody(finalBodyWithoutReceipt, 'final', transactionId, '<receipt>', evidenceDigest, pullRequest.head.sha)),
145
+ artifact: values.artifact,
146
+ attempt: 1,
147
+ createdAt: new Date().toISOString(),
148
+ updatedAt: new Date().toISOString()
149
+ });
150
+ let transaction;
151
+ const idempotent = transactionResult.ok;
152
+ let startedEventApplied = false;
153
+ if (!transactionResult.ok) {
154
+ transaction = createTransaction();
155
+ const started = applyTaskEvent({ taskRef, event: 'manual-validation.started', agent, initiator: 'model', requestId: transactionId, reasonCode: 'user-request', transactionId }, { lockAlreadyHeld: true, repoRoot: cwd });
156
+ if (started.status === 'failed')
157
+ return result('failed', started.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_FAILED', message: 'manual-validation started event failed' });
158
+ startedEventApplied = true;
159
+ if (previousTransaction) {
160
+ try {
161
+ archiveGeneration(resolved.taskDir, previousTransaction, previousTransaction.phase === 'committed');
162
+ }
163
+ catch (error) {
164
+ return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: error instanceof Error ? error.message : String(error) });
165
+ }
166
+ }
167
+ writeManualValidationTransactionAtomic(resolved.taskDir, transaction);
168
+ }
169
+ else {
170
+ transaction = transactionResult.value;
171
+ if (['aborted', 'recovery-required'].includes(transaction.phase)) {
172
+ const retried = retryManualValidationTransaction(transaction);
173
+ if (!retried.ok)
174
+ return result('failed', retried.error);
175
+ transaction = retried.value;
176
+ writeManualValidationTransactionAtomic(resolved.taskDir, transaction);
177
+ }
178
+ }
179
+ if (transaction.phase === 'prepared' && !startedEventApplied) {
180
+ const started = applyTaskEvent({ taskRef, event: 'manual-validation.started', agent, initiator: 'model', requestId: transaction.transactionId, reasonCode: 'user-request', transactionId: transaction.transactionId }, { lockAlreadyHeld: true, repoRoot: cwd });
181
+ if (started.status === 'failed')
182
+ return result('failed', started.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_FAILED', message: 'manual-validation started event failed' });
183
+ }
184
+ if (prepareOnly)
185
+ return result('applied', null, { transaction, prepared: true, idempotent });
186
+ const transactionFailure = (failure) => {
187
+ throw new Error(`${failure.code}: ${failure.message}`);
188
+ };
189
+ const transitionAndPersist = (phase, patch = {}) => {
190
+ const next = transitionManualValidationTransaction(transaction, phase, patch);
191
+ if (!next.ok)
192
+ throw new Error(`${next.error.code}: ${next.error.message}`);
193
+ transaction = next.value;
194
+ writeManualValidationTransactionAtomic(resolved.taskDir, transaction);
195
+ };
196
+ try {
197
+ if (transaction.phase === 'prepared') {
198
+ const staged = await syncPullRequestSummary(taskRef, {
199
+ cwd, client: options.client, agent, body: pendingBody, changeReportFile: path.resolve(cwd, values.changeReportFile), primaryResult: primaryResult, strict: true,
200
+ manualValidation: { phase: 'pending' }, lockAlreadyHeld: true
201
+ });
202
+ if (!['applied', 'no-op'].includes(staged.status))
203
+ transactionFailure(staged.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_FAILED', message: 'pending summary staging failed' });
204
+ transitionAndPersist('summary-staged');
205
+ }
206
+ let receiptResult = readManualValidationReceipt(resolved.taskDir, {
207
+ transactionId: transaction.transactionId,
208
+ taskId: resolved.taskId,
209
+ prNumber: pullRequest.number,
210
+ prHeadSha: pullRequest.head.sha,
211
+ evidenceDigest: transaction.evidenceDigest,
212
+ artifact: values.artifact
213
+ });
214
+ if (!receiptResult.ok && receiptResult.error.code !== 'MANUAL_VALIDATION_RECEIPT_MISSING')
215
+ transactionFailure(receiptResult.error);
216
+ let receipt;
217
+ if (receiptResult.ok) {
218
+ receipt = receiptResult.value;
219
+ }
220
+ else {
221
+ receipt = createManualValidationReceipt({
222
+ transactionId: transaction.transactionId,
223
+ taskId: resolved.taskId,
224
+ prNumber: pullRequest.number,
225
+ prHeadSha: pullRequest.head.sha,
226
+ evidenceDigest: transaction.evidenceDigest,
227
+ artifact: values.artifact,
228
+ artifactSha256: sha256File(artifactPath),
229
+ pendingSummaryDigest: transaction.pendingSummaryDigest,
230
+ finalSummaryDigest: transaction.finalSummaryDigest,
231
+ committedAt: new Date().toISOString()
232
+ });
233
+ writeManualValidationReceiptAtomic(resolved.taskDir, receipt);
234
+ }
235
+ if (transaction.phase === 'summary-staged') {
236
+ transitionAndPersist('receipt-committed', { committedReceipt: receipt.receiptDigest });
237
+ }
238
+ if (transaction.phase === 'receipt-committed' && !transaction.eventAppended) {
239
+ 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 });
240
+ if (completed.status === 'failed')
241
+ transactionFailure(completed.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_FAILED', message: 'manual-validation completion event failed' });
242
+ transitionAndPersist('receipt-committed', { eventAppended: true });
243
+ }
244
+ if (transaction.phase === 'receipt-committed') {
245
+ transitionAndPersist('final-promotion-in-progress');
246
+ }
247
+ const promoted = await syncPullRequestSummary(taskRef, {
248
+ 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,
249
+ manualValidation: { phase: 'final', transactionId: transaction.transactionId, receiptDigest: receipt.receiptDigest, prHeadSha: receipt.prHeadSha, authority: 'coordinator' }, lockAlreadyHeld: true
250
+ });
251
+ if (!['applied', 'no-op'].includes(promoted.status))
252
+ transactionFailure(promoted.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'final summary promotion failed' });
253
+ const postWrite = await summaryCommentState(taskRef, { cwd, client: options.client });
254
+ if (!/^###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)\s*$/mu.test(postWrite.comment?.body ?? '') || postWrite.pullRequest?.head.sha !== receipt.prHeadSha || !manualValidationFinalSummaryProjectionMatches(postWrite.comment?.body ?? '', receipt))
255
+ transactionFailure({ code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'final summary post-write verification failed' });
256
+ transitionAndPersist('committed', { postWriteVerified: true });
257
+ return result('applied', null, { transaction, receipt, idempotent: false });
258
+ }
259
+ catch (error) {
260
+ const message = error instanceof Error ? error.message : String(error);
261
+ if (transaction.phase === 'final-promotion-in-progress') {
262
+ try {
263
+ await syncPullRequestSummary(taskRef, {
264
+ cwd, client: options.client, agent, body: pendingBody, changeReportFile: path.resolve(cwd, values.changeReportFile), primaryResult: primaryResult, strict: true,
265
+ manualValidation: { phase: 'pending' }, lockAlreadyHeld: true
266
+ });
267
+ }
268
+ catch {
269
+ // Preserve recovery-required when pending compensation cannot be confirmed.
270
+ }
271
+ }
272
+ const failedTransaction = transitionManualValidationTransaction(transaction, 'recovery-required', { error: message.slice(0, 400) });
273
+ if (failedTransaction.ok)
274
+ writeManualValidationTransactionAtomic(resolved.taskDir, failedTransaction.value);
275
+ return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message });
276
+ }
277
+ }
278
+ function parse(args, start) {
279
+ const values = {};
280
+ for (let index = start; index < args.length; index += 1) {
281
+ const flag = args[index];
282
+ if (flag === '--prepare') {
283
+ if (values.prepare)
284
+ 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))
289
+ return { values, error: `unknown option '${flag}'` };
290
+ const value = args[++index];
291
+ if (!value || value.startsWith('--'))
292
+ return { values, error: `option '${flag}' requires a value` };
293
+ const key = flag.slice(2).replace(/-([a-z])/gu, (_, letter) => letter.toUpperCase());
294
+ if (values[key])
295
+ return { values, error: `duplicate option '${flag}'` };
296
+ values[key] = value;
297
+ }
298
+ return { values };
299
+ }
300
+ async function manualValidation(args = []) {
301
+ if (!ensureInternalHandlerRoute('manual-validation', args))
302
+ return;
303
+ if (args[0] === '--help' || args[0] === '-h') {
304
+ process.stdout.write(`${USAGE}\n`);
305
+ return;
306
+ }
307
+ const operation = args[0];
308
+ const isTransaction = operation === 'transaction' && internalHandlerRoute('manual-validation', 'transaction', operation);
309
+ if (!isTransaction) {
310
+ printFailure('json', { code: 'MANUAL_VALIDATION_ARGS_INVALID', message: 'transaction operation is required' });
311
+ return;
312
+ }
313
+ const taskRef = args[1];
314
+ if (!taskRef || taskRef.startsWith('--')) {
315
+ printFailure('json', { code: 'MANUAL_VALIDATION_ARGS_INVALID', message: 'task ref is required' });
316
+ return;
317
+ }
318
+ const parsed = parse(args, 2);
319
+ if (parsed.error) {
320
+ printFailure('json', { code: 'MANUAL_VALIDATION_ARGS_INVALID', message: parsed.error });
321
+ return;
322
+ }
323
+ const values = parsed.values;
324
+ const format = values.format === 'text' ? 'text' : 'json';
325
+ const output = await executeManualValidationTransaction(taskRef, values, path.resolve(values.cwd ?? process.cwd()));
326
+ process.stdout.write(format === 'json' ? `${JSON.stringify(output)}\n` : `${output.status === 'applied' ? 'Manual validation transaction committed.' : `${output.error?.code}: ${output.error?.message}`}\n`);
327
+ if (output.status === 'failed')
328
+ process.exitCode = 1;
329
+ }
330
+ export { executeManualValidationTransaction, manualValidation };
331
+ //# sourceMappingURL=manual-validation.js.map
@@ -13,10 +13,10 @@ const USAGE = `Usage: agent-infra-internal platform-pr inspect <task-ref> [--cwd
13
13
  agent-infra-internal platform-pr sync-in-labels --pr <N> [--dry-run] [--cwd <path>]
14
14
  agent-infra-internal platform-pr summary-context <task-ref> [--cwd <path>]
15
15
  agent-infra-internal platform-pr change-report <task-ref> --agent <agent> --mechanical-file <path> --precheck-file <path> [--dry-run] [--cwd <path>]
16
- 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>]
16
+ 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>]
17
17
  `;
18
18
  const BOOLEAN_FLAGS = new Set(['--draft', '--dry-run', '--metadata', '--closing-issue', '--strict']);
19
- const VALUE_FLAGS = new Set(['--cwd', '--agent', '--base', '--head', '--title-file', '--body-file', '--change-report-file', '--mechanical-file', '--precheck-file', '--pr', '--result']);
19
+ 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']);
20
20
  function key(flag) {
21
21
  return flag.slice(2).replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
22
22
  }
@@ -122,7 +122,7 @@ async function platformPr(args = []) {
122
122
  'sync-in-labels': ['cwd', 'pr', 'dryRun'],
123
123
  'summary-context': ['cwd'],
124
124
  'change-report': ['cwd', 'agent', 'mechanicalFile', 'precheckFile', 'dryRun'],
125
- 'summary-sync': ['cwd', 'agent', 'bodyFile', 'changeReportFile', 'result', 'dryRun', 'strict']
125
+ 'summary-sync': ['cwd', 'agent', 'bodyFile', 'changeReportFile', 'result', 'manualPhase', 'transactionId', 'receiptDigest', 'prHeadSha', 'dryRun', 'strict']
126
126
  };
127
127
  const unexpected = Object.keys(values).find((name) => !allowed[operation].includes(name));
128
128
  if (unexpected) {
@@ -210,7 +210,21 @@ async function platformPr(args = []) {
210
210
  return;
211
211
  }
212
212
  try {
213
- finish(await syncPullRequestSummary(taskRef, { cwd, agent: values.agent, body: readFile(values.bodyFile, cwd), changeReportFile: values.changeReportFile, primaryResult: primaryResult, dryRun: values.dryRun === true, strict: values.strict === true }));
213
+ const manualPhase = values.manualPhase;
214
+ if (manualPhase !== undefined && manualPhase !== 'pending' && manualPhase !== 'final') {
215
+ fail('--manual-phase must be pending or final');
216
+ return;
217
+ }
218
+ finish(await syncPullRequestSummary(taskRef, {
219
+ cwd, agent: values.agent, body: readFile(values.bodyFile, cwd), changeReportFile: values.changeReportFile,
220
+ primaryResult: primaryResult, dryRun: values.dryRun === true, strict: values.strict === true,
221
+ ...(manualPhase ? { manualValidation: {
222
+ phase: manualPhase,
223
+ ...(typeof values.transactionId === 'string' ? { transactionId: values.transactionId } : {}),
224
+ ...(typeof values.receiptDigest === 'string' ? { receiptDigest: values.receiptDigest } : {}),
225
+ ...(typeof values.prHeadSha === 'string' ? { prHeadSha: values.prHeadSha } : {})
226
+ } } : {})
227
+ }));
214
228
  }
215
229
  catch (error) {
216
230
  fail(`unable to read body file: ${error instanceof Error ? error.message : String(error)}`);
@@ -1,113 +1,78 @@
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
  import { commitExplicitPaths, inspectGitWorkflow, pushGitRefs } from '../git/workflow.js';
7
6
  import { inspectPlatformRelease, reconcileReleaseMilestones } from '../platform/releases.js';
8
7
  import { inspectHomebrewChannel, inspectNpmChannel } from '../release/channels.js';
9
8
  import { releaseSnapshot } from '../release/workflow.js';
9
+ import { collectDemoTranscript } from './demo-transcript.js';
10
+ import { createDemoPromotion, promoteDemoAssets, recoverDemoPromotion } from './demo-promotion.js';
10
11
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
11
- function command(cwd, executable, args) {
12
- return spawnSync(executable, args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
12
+ function command(cwd, executable, args, options = {}) {
13
+ return spawnSync(executable, args, {
14
+ cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, ...options.env }
15
+ });
13
16
  }
14
- // The tape is the canonical init interaction contract. Do not hash all of
15
- // lib/init.ts: non-visual config serialization changes must not re-record it.
16
- const DEMO_INPUT_PATHS = [
17
- 'assets/demo-init.tape',
18
- 'scripts/demo-regen.sh',
19
- 'scripts/normalize-gif-duration.py',
20
- 'bin/cli.ts',
21
- 'lib/log.ts',
22
- 'lib/prompt.ts',
23
- 'lib/paths.ts',
24
- 'lib/render.ts',
25
- 'lib/sandbox/engines/'
26
- ];
27
- const DEMO_DIGEST_PATH = 'assets/demo-init.inputs.sha256';
17
+ const DEMO_DIGEST_PATH = 'assets/demo-init.transcript.sha256';
28
18
  const DEMO_OUTPUT_PATH = 'assets/demo-init.gif';
29
19
  const DEMO_MAX_BYTES = 4 * 1024 * 1024;
30
- function demoInputFiles(cwd) {
31
- const files = new Set();
32
- for (const input of DEMO_INPUT_PATHS) {
33
- if (input.endsWith('/')) {
34
- const listed = command(cwd, 'git', ['ls-files', '--', input]);
35
- if (listed.status !== 0)
36
- throw new Error(String(listed.stderr || `Unable to list ${input}`));
37
- const directoryFiles = String(listed.stdout).split('\n').filter(Boolean);
38
- if (!directoryFiles.length)
39
- throw new Error(`Canonical demo input directory is empty: ${input}`);
40
- for (const file of directoryFiles)
41
- files.add(file.replaceAll('\\', '/'));
42
- }
43
- else {
44
- files.add(input);
45
- }
46
- }
47
- const sorted = [...files].sort((left, right) => Buffer.from(left).compare(Buffer.from(right)));
48
- if (!sorted.length)
49
- throw new Error('Canonical demo input set is empty');
50
- for (const file of sorted) {
51
- const absolute = path.join(cwd, file);
52
- if (!fs.statSync(absolute).isFile())
53
- throw new Error(`Canonical demo input is not a file: ${file}`);
54
- }
55
- return sorted;
56
- }
57
- function computeDemoInputDigest(cwd) {
58
- const hash = crypto.createHash('sha256');
59
- for (const file of demoInputFiles(cwd)) {
60
- const bytes = fs.readFileSync(path.join(cwd, file));
61
- hash.update(file);
62
- hash.update('\0');
63
- hash.update(String(bytes.byteLength));
64
- hash.update('\0');
65
- hash.update(bytes);
66
- hash.update('\0');
67
- }
68
- return hash.digest('hex');
69
- }
70
20
  function validGif(filePath) {
71
21
  if (!fs.existsSync(filePath))
72
22
  return false;
73
23
  const header = fs.readFileSync(filePath).subarray(0, 6).toString('ascii');
74
24
  return header === 'GIF87a' || header === 'GIF89a';
75
25
  }
76
- function writeDigestAtomically(cwd, digest) {
77
- const target = path.join(cwd, DEMO_DIGEST_PATH);
78
- const temporary = `${target}.tmp-${process.pid}`;
79
- fs.writeFileSync(temporary, `${digest}\n`);
80
- fs.renameSync(temporary, target);
81
- }
82
- function runOptionalDemo(cwd, run = command) {
83
- let digest;
84
- try {
85
- digest = computeDemoInputDigest(cwd);
26
+ async function runOptionalDemo(cwd, run = command, collect = collectDemoTranscript) {
27
+ const recovery = recoverDemoPromotion(cwd);
28
+ if (recovery?.status === 'failed') {
29
+ return { status: 'failed', reasonCode: recovery.code === 'DEMO_PROMOTION_CLEANUP_FAILED' ? recovery.code : 'DEMO_PROMOTION_RECOVERY_REQUIRED', message: recovery.message, outputPath: null };
86
30
  }
87
- catch (error) {
88
- return { status: 'failed', reasonCode: 'DEMO_DIGEST_FAILED', message: String(error), outputPath: null };
31
+ const transcript = await collect(cwd);
32
+ if (transcript.status === 'failed') {
33
+ return { status: 'failed', reasonCode: transcript.reasonCode, message: transcript.message, outputPath: null };
89
34
  }
90
35
  const digestPath = path.join(cwd, DEMO_DIGEST_PATH);
91
36
  const priorDigest = fs.existsSync(digestPath) ? fs.readFileSync(digestPath, 'utf8').trim() : '';
92
- if (/^[0-9a-f]{64}$/.test(priorDigest) && priorDigest === digest) {
93
- return { status: 'skipped', reasonCode: 'DEMO_INPUTS_UNCHANGED', message: null, outputPath: null };
37
+ if (/^[0-9a-f]{64}$/.test(priorDigest) && priorDigest === transcript.sha256) {
38
+ return { status: 'skipped', reasonCode: 'DEMO_TRANSCRIPT_UNCHANGED', message: null, outputPath: null };
94
39
  }
40
+ const promotion = createDemoPromotion(cwd, transcript.transcript);
41
+ const discardPromotion = () => fs.rmSync(promotion.directory, { recursive: true, force: true });
95
42
  const lfs = run(cwd, 'git', ['lfs', 'version']);
96
43
  if (lfs.status !== 0) {
44
+ discardPromotion();
97
45
  return { status: 'failed', reasonCode: 'GIT_LFS_MISSING', message: String(lfs.stderr || lfs.stdout), outputPath: null };
98
46
  }
99
47
  const lfsAttribute = run(cwd, 'git', ['check-attr', 'filter', '--', DEMO_OUTPUT_PATH]);
100
48
  if (lfsAttribute.status !== 0 || !String(lfsAttribute.stdout).trim().endsWith(': lfs')) {
49
+ discardPromotion();
101
50
  return { status: 'failed', reasonCode: 'GIT_LFS_MISSING', message: `${DEMO_OUTPUT_PATH} is not tracked by Git LFS`, outputPath: null };
102
51
  }
103
52
  const vhs = run(cwd, 'vhs', ['--version']);
104
- if (vhs.status !== 0)
105
- return { status: 'skipped', reasonCode: 'VHS_MISSING', message: null, outputPath: null };
53
+ if (vhs.status !== 0) {
54
+ discardPromotion();
55
+ return {
56
+ status: 'failed',
57
+ reasonCode: 'VHS_MISSING',
58
+ message: String(vhs.stderr || vhs.stdout) || 'VHS is required to record the changed demo transcript',
59
+ outputPath: null
60
+ };
61
+ }
106
62
  const ffmpeg = run(cwd, 'ffmpeg', ['-version']);
107
- if (ffmpeg.status !== 0)
108
- return { status: 'skipped', reasonCode: 'FFMPEG_MISSING', message: null, outputPath: null };
109
- const demo = run(cwd, 'npm', ['run', 'demo:regen']);
63
+ if (ffmpeg.status !== 0) {
64
+ discardPromotion();
65
+ return {
66
+ status: 'failed',
67
+ reasonCode: 'FFMPEG_MISSING',
68
+ message: String(ffmpeg.stderr || ffmpeg.stdout) || 'ffmpeg is required to record the changed demo transcript',
69
+ outputPath: null
70
+ };
71
+ }
72
+ const stagedOutputPath = path.relative(cwd, promotion.staging.gif).replaceAll(path.sep, '/');
73
+ const demo = run(cwd, 'npm', ['run', 'demo:regen'], { env: { DEMO_OUTPUT_PATH: stagedOutputPath } });
110
74
  if (demo.status !== 0) {
75
+ discardPromotion();
111
76
  return {
112
77
  status: 'failed',
113
78
  reasonCode: 'DEMO_COMMAND_FAILED',
@@ -116,7 +81,8 @@ function runOptionalDemo(cwd, run = command) {
116
81
  };
117
82
  }
118
83
  const outputPath = DEMO_OUTPUT_PATH;
119
- if (!fs.existsSync(path.join(cwd, outputPath))) {
84
+ if (!fs.existsSync(promotion.staging.gif)) {
85
+ discardPromotion();
120
86
  return {
121
87
  status: 'failed',
122
88
  reasonCode: 'DEMO_OUTPUT_MISSING',
@@ -124,18 +90,24 @@ function runOptionalDemo(cwd, run = command) {
124
90
  outputPath: null
125
91
  };
126
92
  }
127
- const absoluteOutput = path.join(cwd, outputPath);
93
+ const absoluteOutput = promotion.staging.gif;
128
94
  if (!validGif(absoluteOutput)) {
95
+ discardPromotion();
129
96
  return { status: 'failed', reasonCode: 'DEMO_OUTPUT_INVALID', message: `${outputPath} is not a GIF`, outputPath: null };
130
97
  }
131
98
  if (fs.statSync(absoluteOutput).size > DEMO_MAX_BYTES) {
99
+ discardPromotion();
132
100
  return { status: 'failed', reasonCode: 'DEMO_OUTPUT_TOO_LARGE', message: `${outputPath} exceeds 4 MiB`, outputPath: null };
133
101
  }
134
- const pointer = run(cwd, 'git', ['lfs', 'pointer', `--file=${outputPath}`]);
102
+ const pointer = run(cwd, 'git', ['lfs', 'pointer', `--file=${stagedOutputPath}`]);
135
103
  if (pointer.status !== 0 || !String(pointer.stdout).includes(`size ${fs.statSync(absoluteOutput).size}`)) {
104
+ discardPromotion();
136
105
  return { status: 'failed', reasonCode: 'DEMO_OUTPUT_INVALID', message: 'Git LFS could not produce a matching pointer', outputPath: null };
137
106
  }
138
- writeDigestAtomically(cwd, digest);
107
+ const promoted = promoteDemoAssets(cwd, promotion.staging, `${process.pid}-${Date.now()}`);
108
+ if (promoted.status === 'failed') {
109
+ return { status: 'failed', reasonCode: promoted.code ?? 'DEMO_PROMOTION_FAILED', message: promoted.message, outputPath: null };
110
+ }
139
111
  return { status: 'recorded', reasonCode: null, message: null, outputPath };
140
112
  }
141
113
  function git(cwd, args, run = command, trim = true) {
@@ -189,7 +161,7 @@ function inspectPostReleaseFacts(cwd, commit, run = command) {
189
161
  remoteHead,
190
162
  newVersion,
191
163
  changedPaths,
192
- demoInputSha256: digest && /^[0-9a-f]{64}$/.test(digest) ? digest : null,
164
+ demoTranscriptSha256: digest && /^[0-9a-f]{64}$/.test(digest) ? digest : null,
193
165
  worktree: snapshot?.worktree ?? [],
194
166
  staged: snapshot?.staged ?? []
195
167
  };
@@ -322,7 +294,8 @@ async function releaseWorkflow(args = []) {
322
294
  if (internalHandlerRoute('release-workflow', 'prepare', action || '')) {
323
295
  if (before.phase !== 'unprepared') {
324
296
  const milestones = await reconcileReleaseMilestones(version, { cwd });
325
- process.stdout.write(`${JSON.stringify({ ...milestones, snapshot: before })}\n`);
297
+ const snapshot = releaseSnapshot(version, await inspectFacts(cwd, version));
298
+ process.stdout.write(`${JSON.stringify({ ...milestones, snapshot })}\n`);
326
299
  process.exitCode = milestones.status === 'failed' ? 1 : milestones.status === 'blocked' ? 2 : 0;
327
300
  return;
328
301
  }
@@ -446,6 +419,12 @@ async function releaseWorkflow(args = []) {
446
419
  process.exitCode = before.facts.smoke === 'failed' ? 1 : 2;
447
420
  return;
448
421
  }
422
+ const recovery = recoverDemoPromotion(cwd);
423
+ if (recovery?.status === 'failed') {
424
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: true, snapshot: before, error: { code: recovery.code, message: recovery.message } })}\n`);
425
+ process.exitCode = 1;
426
+ return;
427
+ }
449
428
  const worktreeError = inspectPostWorktree(cwd);
450
429
  if (worktreeError) {
451
430
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, snapshot: before, error: worktreeError })}\n`);
@@ -458,7 +437,7 @@ async function releaseWorkflow(args = []) {
458
437
  process.exitCode = 1;
459
438
  return;
460
439
  }
461
- const demo = runOptionalDemo(cwd);
440
+ const demo = await runOptionalDemo(cwd);
462
441
  if (demo.status === 'failed') {
463
442
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: true, snapshot: before, demo, error: { code: 'RELEASE_POST_DEMO_FAILED', message: demo.message } })}\n`);
464
443
  process.exitCode = 1;
@@ -487,5 +466,5 @@ async function releaseWorkflow(args = []) {
487
466
  }
488
467
  process.stdout.write(`${JSON.stringify({ status: 'applied', changed: true, demo, snapshot, error: null })}\n`);
489
468
  }
490
- export { changedPaths, computeDemoInputDigest, inspectFacts, inspectLocalReleaseFacts, inspectPostReleaseFacts, inspectPostWorktree, releaseSmokeStatus, releaseWorkflow, runOptionalDemo };
469
+ export { changedPaths, inspectFacts, inspectLocalReleaseFacts, inspectPostReleaseFacts, inspectPostWorktree, releaseSmokeStatus, releaseWorkflow, runOptionalDemo };
491
470
  //# sourceMappingURL=release-workflow.js.map