@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
@@ -1,9 +1,7 @@
1
1
  import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { randomUUID } from 'node:crypto';
4
2
 
5
- import { locateActivityLog, pairEntries, startedBackedRows } from './activity-log.ts';
6
- import { parseArtifactName, validateCompletedArtifact } from './artifact-lifecycle.ts';
3
+ import { parseArtifactName } from './artifact-name.ts';
4
+ import { validateCompletedArtifact, hasOpenArtifactRound, validateArtifactPublication } from './artifact-lifecycle.ts';
7
5
  import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.ts';
8
6
  import type { LedgerStageStatus, ReviewStage } from './ledger.ts';
9
7
  import { finalizeReviewSummaryContent } from './review-artifacts.ts';
@@ -16,8 +14,15 @@ import { allowsManualOverride } from './guard-override.ts';
16
14
  import type { ManualOverrideCapability } from './guard-override.ts';
17
15
  import { getArtifactSchema } from './artifact-schema.ts';
18
16
  import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.ts';
19
- import { readArtifactRepairIntent, writeArtifactRepairIntent } from './artifact-repair-intent.ts';
20
- import type { ArtifactRepairOperation } from './artifact-operations.ts';
17
+ import { readArtifactRecoveryIntent } from './artifact-repair-intent.ts';
18
+ import {
19
+ beginArtifactRecovery,
20
+ commitArtifactRecovery,
21
+ prepareArtifactRecoveryCommit,
22
+ recoveryContextFromIntent,
23
+ stageArtifactCandidate
24
+ } from './artifact-recovery.ts';
25
+ import type { ArtifactRecoveryContext } from './artifact-recovery.ts';
21
26
 
22
27
  type ReviewFinalizationErrorCode =
23
28
  | ResolveTaskRefErrorCode
@@ -34,8 +39,7 @@ type ReviewFinalizationErrorCode =
34
39
  | 'REVIEW_DECISION_DETAIL_INVALID'
35
40
  | 'REVIEW_ARTIFACT_CONFLICT'
36
41
  | 'REVIEW_PROVENANCE_INVALID'
37
- | 'REVIEW_TEMP_WRITE_FAILED'
38
- | 'REVIEW_RENAME_FAILED';
42
+ | 'REVIEW_RECOVERY_COMMIT_FAILED';
39
43
  type ReviewFinalizationError = { code: ReviewFinalizationErrorCode; message: string };
40
44
  type ReviewFinalizationRequest = {
41
45
  taskRef: string;
@@ -43,6 +47,7 @@ type ReviewFinalizationRequest = {
43
47
  artifact: string;
44
48
  orchestrated?: boolean;
45
49
  dryRun?: boolean;
50
+ recoveryId?: string;
46
51
  };
47
52
  type ReviewFinalizationResult = {
48
53
  status: 'planned' | 'applied' | 'no-op' | 'failed';
@@ -55,8 +60,12 @@ type ReviewFinalizationResult = {
55
60
  stageStatus: LedgerStageStatus | null;
56
61
  artifactSha256: string | null;
57
62
  semanticDigest: string | null;
58
- repairable: boolean;
59
- operation: ArtifactRepairOperation | null;
63
+ recovery?: Readonly<{
64
+ recoveryId: string;
65
+ candidatePath: string;
66
+ baselineSha256: string;
67
+ baselineSemanticDigest: string;
68
+ }>;
60
69
  operations: readonly {
61
70
  kind: 'artifact';
62
71
  artifact: string;
@@ -66,30 +75,22 @@ type ReviewFinalizationResult = {
66
75
  };
67
76
  type ReviewFileSystem = {
68
77
  readFileSync: (file: string) => string;
69
- statModeSync: (file: string) => number;
70
- writeFileSync: (file: string, content: string, mode: number) => void;
71
- renameSync: (from: string, to: string) => void;
72
- unlinkSync: (file: string) => void;
73
78
  };
74
79
  type ReviewFinalizationOptions = {
75
80
  repoRoot?: string;
76
- randomSuffix?: () => string;
77
81
  fileSystem?: Partial<ReviewFileSystem>;
78
82
  manualOverride?: ManualOverrideCapability;
79
83
  lockAlreadyHeld?: boolean;
84
+ startRecovery?: boolean;
80
85
  };
81
86
 
82
- const STAGES: Record<ReviewStage, { family: 'review-analysis' | 'review-plan' | 'review-code'; action: string }> = {
83
- analysis: { family: 'review-analysis', action: 'Review Analysis' },
84
- plan: { family: 'review-plan', action: 'Review Plan' },
85
- code: { family: 'review-code', action: 'Review Code' }
87
+ const STAGES: Record<ReviewStage, { family: 'review-analysis' | 'review-plan' | 'review-code' }> = {
88
+ analysis: { family: 'review-analysis' },
89
+ plan: { family: 'review-plan' },
90
+ code: { family: 'review-code' }
86
91
  };
87
92
  const DEFAULT_FILE_SYSTEM: ReviewFileSystem = {
88
- readFileSync: (file) => fs.readFileSync(file, 'utf8'),
89
- statModeSync: (file) => fs.statSync(file).mode,
90
- writeFileSync: (file, content, mode) => fs.writeFileSync(file, content, { encoding: 'utf8', flag: 'wx', mode }),
91
- renameSync: (from, to) => fs.renameSync(from, to),
92
- unlinkSync: (file) => fs.unlinkSync(file)
93
+ readFileSync: (file) => fs.readFileSync(file, 'utf8')
93
94
  };
94
95
 
95
96
  function failed(
@@ -111,328 +112,217 @@ function failed(
111
112
  stageStatus,
112
113
  artifactSha256: null,
113
114
  semanticDigest: null,
114
- repairable: false,
115
- operation: null,
116
115
  operations: [],
117
116
  error: { code, message },
118
117
  ...extra
119
118
  };
120
119
  }
121
120
 
122
- function openReviewRound(content: string, action: string, round: number): boolean {
123
- const section = locateActivityLog(content);
124
- if (!section) return false;
125
- const expected = `${action} (Round ${round})`;
126
- return startedBackedRows(pairEntries(section.entries)).filter((row) => (
127
- row.step === expected && !row.done
128
- )).length === 1;
129
- }
130
-
131
- function cleanupTemp(fileSystem: ReviewFileSystem, tempPath: string): void {
132
- try {
133
- fileSystem.unlinkSync(tempPath);
134
- } catch {
135
- // Best-effort cleanup preserves the primary failure.
136
- }
137
- }
121
+ type ReviewSummaryCandidatePreparation = Readonly<{
122
+ result: ReviewFinalizationResult;
123
+ content: string;
124
+ recovery?: ArtifactRecoveryContext;
125
+ lockAlreadyHeld?: boolean;
126
+ }>;
138
127
 
139
- function finalizeReviewSummaryUnlocked(
128
+ /** Prepare domain validation, summary bytes and provenance before any publication. */
129
+ function prepareReviewSummaryCandidate(
140
130
  request: ReviewFinalizationRequest,
131
+ artifactContent: string,
141
132
  options: ReviewFinalizationOptions = {}
142
- ): ReviewFinalizationResult {
133
+ ): ReviewSummaryCandidatePreparation {
134
+ let taskId: string | null = null;
135
+ let stageStatus: LedgerStageStatus | null = null;
136
+ const reject = (code: ReviewFinalizationErrorCode, message: string, extra: Partial<ReviewFinalizationResult> = {}): ReviewSummaryCandidatePreparation => ({
137
+ result: failed(request, code, message, taskId, stageStatus, extra), content: artifactContent
138
+ });
143
139
  const stage = request.stage as ReviewStage;
144
140
  const spec = STAGES[stage];
145
- if (!spec) return failed(request, 'REVIEW_STAGE_INVALID', `unsupported review stage '${request.stage}'`);
146
- if (!request.taskRef || !request.artifact) {
147
- return failed(request, 'REVIEW_PAYLOAD_INVALID', 'taskRef, stage, and artifact are required');
148
- }
141
+ if (!spec) return reject('REVIEW_STAGE_INVALID', `unsupported review stage '${request.stage}'`);
142
+ if (!request.taskRef || !request.artifact) return reject('REVIEW_PAYLOAD_INVALID', 'taskRef, stage, and artifact are required');
149
143
  const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
150
- if (!resolved.ok) return failed(request, resolved.code, resolved.message, resolved.taskId);
144
+ taskId = resolved.taskId ?? null;
145
+ if (!resolved.ok) return reject(resolved.code, resolved.message);
151
146
  if (resolved.state !== 'active' && !allowsManualOverride(options.manualOverride, 'review-finalization', 'TASK_STATE_MISMATCH')) {
152
- return failed(request, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
147
+ return reject('TASK_STATE_MISMATCH', `task ${taskId} is ${resolved.state}, expected active`);
153
148
  }
154
- const parsedArtifact = parseArtifactName(request.artifact);
155
- if (!parsedArtifact || parsedArtifact.family !== spec.family) {
156
- return failed(
157
- request,
158
- 'REVIEW_ARTIFACT_IDENTITY_INVALID',
159
- `artifact '${request.artifact}' does not match ${spec.family}`,
160
- resolved.taskId
161
- );
162
- }
163
- const validated = validateCompletedArtifact(
164
- resolved.taskDir,
165
- spec.family,
166
- request.artifact,
167
- parsedArtifact.round
168
- );
169
- if (!validated.ok) {
170
- const code = validated.error.code === 'ARTIFACT_NOT_REGULAR'
171
- ? 'REVIEW_ARTIFACT_NOT_REGULAR'
172
- : 'REVIEW_ARTIFACT_IDENTITY_INVALID';
173
- return failed(request, code, validated.error.message, resolved.taskId);
174
- }
175
-
176
- const fileSystem = { ...DEFAULT_FILE_SYSTEM, ...options.fileSystem };
149
+ const parsed = parseArtifactName(request.artifact);
150
+ if (!parsed || parsed.family !== spec.family) return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', `artifact '${request.artifact}' does not match ${spec.family}`);
151
+ const publicationError = validateArtifactPublication(resolved.taskDir, spec.family, request.artifact);
152
+ if (publicationError) return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', publicationError.message);
177
153
  let taskContent: string;
178
- let artifactContent: string;
179
- try {
180
- taskContent = fileSystem.readFileSync(resolved.taskMdPath);
181
- artifactContent = fileSystem.readFileSync(validated.artifact.path);
182
- } catch (error) {
183
- return failed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId);
154
+ try { taskContent = (options.fileSystem?.readFileSync ?? DEFAULT_FILE_SYSTEM.readFileSync)(resolved.taskMdPath); }
155
+ catch (error) { return reject('REVIEW_ARTIFACT_NOT_REGULAR', String(error)); }
156
+ let recovery: ArtifactRecoveryContext | undefined;
157
+ if (request.recoveryId) {
158
+ try {
159
+ const intent = readArtifactRecoveryIntent(resolved.repoRoot, taskId!, spec.family, request.artifact);
160
+ if (!intent || intent.recoveryOperationId !== request.recoveryId) return reject('REVIEW_PROVENANCE_INVALID', 'recovery id does not match the artifact journal');
161
+ if (!['awaiting-recovery', 'finalize-ready', 'commit-started'].includes(intent.state)) return reject('REVIEW_PROVENANCE_INVALID', `recovery journal is in '${intent.state}' state`);
162
+ recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
163
+ artifactContent = fs.existsSync(recovery.stagingPath)
164
+ ? fs.readFileSync(recovery.stagingPath, 'utf8')
165
+ : fs.readFileSync(recovery.formalPath, 'utf8');
166
+ } catch (error) { return reject('REVIEW_PROVENANCE_INVALID', String(error)); }
184
167
  }
185
- const schema = getArtifactSchema(spec.family);
186
- const structure = schema ? inspectArtifactContract(artifactContent, schema) : null;
187
168
  const artifactSha256 = sha256Content(artifactContent);
188
- const artifactSemanticDigest = canonicalSemanticDigest(artifactContent);
169
+ const semanticDigest = canonicalSemanticDigest(artifactContent);
170
+ const digests = { artifactSha256, semanticDigest };
189
171
  let repairIntent;
190
- try {
191
- repairIntent = readArtifactRepairIntent(resolved.repoRoot, resolved.taskId, spec.family, request.artifact);
192
- } catch (error) {
193
- return failed(request, 'REVIEW_PROVENANCE_INVALID', String(error), resolved.taskId, null, {
194
- artifactSha256,
195
- semanticDigest: artifactSemanticDigest
196
- });
197
- }
198
- if (structure && !structure.ok) {
199
- const repairable = structure.repair !== null && structure.diagnostics.length === 1 && structure.diagnostics[0]?.repairable === true;
200
- if (repairable) {
201
- if (repairIntent && (
202
- repairIntent.state !== 'awaiting-repair' ||
203
- repairIntent.artifactSha256 !== artifactSha256 ||
204
- repairIntent.semanticDigest !== structure.semanticDigest
205
- )) {
206
- return failed(request, 'REVIEW_PROVENANCE_INVALID', 'a different review repair baseline is already recorded for this artifact', resolved.taskId, null, {
207
- artifactSha256,
208
- semanticDigest: structure.semanticDigest
209
- });
210
- }
211
- try {
212
- if (!repairIntent) {
213
- writeArtifactRepairIntent(resolved.repoRoot, {
214
- version: 1,
215
- taskId: resolved.taskId,
216
- family: spec.family,
217
- artifact: request.artifact,
218
- state: 'awaiting-repair',
219
- baselineSemanticDigest: structure.semanticDigest,
220
- artifactSha256,
221
- semanticDigest: structure.semanticDigest
222
- });
223
- }
224
- } catch (error) {
225
- return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repair provenance: ${String(error)}`, resolved.taskId, null, {
226
- artifactSha256,
227
- semanticDigest: structure.semanticDigest,
228
- operation: structure.repair
229
- });
230
- }
172
+ try { repairIntent = readArtifactRecoveryIntent(resolved.repoRoot, taskId!, spec.family, request.artifact); }
173
+ catch (error) { return reject('REVIEW_PROVENANCE_INVALID', String(error), digests); }
174
+ if (repairIntent?.state === 'passed' || repairIntent?.state === 'consumed') {
175
+ if (repairIntent.finalArtifactSha256 !== artifactSha256 || repairIntent.finalSemanticDigest !== semanticDigest) {
176
+ return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact changed after its finalization provenance was recorded', digests);
231
177
  }
232
- return failed(
233
- request,
234
- 'REVIEW_ARTIFACT_STRUCTURE_INVALID',
235
- structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '),
236
- resolved.taskId,
237
- null,
238
- {
239
- artifactSha256,
240
- semanticDigest: structure.semanticDigest,
241
- repairable,
242
- operation: structure.repair
243
- }
244
- );
245
- }
246
- if (repairIntent?.state === 'awaiting-repair' && repairIntent.baselineSemanticDigest !== artifactSemanticDigest) {
247
- return failed(request, 'REVIEW_PROVENANCE_INVALID', 'the repaired review artifact semantic digest does not match the recorded repair baseline', resolved.taskId, null, {
248
- artifactSha256,
249
- semanticDigest: artifactSemanticDigest
250
- });
251
178
  }
252
- if ((repairIntent?.state === 'passed' || repairIntent?.state === 'consumed')
253
- && (repairIntent.artifactSha256 !== artifactSha256 || repairIntent.semanticDigest !== artifactSemanticDigest)) {
254
- return failed(request, 'REVIEW_PROVENANCE_INVALID', 'the review artifact changed after its finalization provenance was recorded', resolved.taskId, null, {
255
- artifactSha256,
256
- semanticDigest: artifactSemanticDigest
257
- });
258
- }
259
- if (!openReviewRound(taskContent, spec.action, parsedArtifact.round)) {
260
- return failed(
261
- request,
262
- 'REVIEW_ARTIFACT_IDENTITY_INVALID',
263
- `${request.artifact} does not have one matching open started review event`,
264
- resolved.taskId
265
- );
179
+ if (!hasOpenArtifactRound(taskContent, spec.family, parsed.round)) {
180
+ return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', `${request.artifact} does not have one matching open started review event`);
266
181
  }
267
182
  const execution = validateLifecycleExecution(request.taskRef, {
268
183
  mode: request.orchestrated ? 'orchestrated' : 'standalone',
269
- identity: {
270
- stage: spec.family,
271
- round: parsedArtifact.round,
272
- artifact: request.artifact,
273
- role: 'reviewer'
274
- },
184
+ identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
275
185
  dryRun: request.dryRun
276
186
  }, { repoRoot: options.repoRoot });
277
187
  if (!execution.ok) {
278
- return failed(
279
- request,
280
- 'REVIEW_PROVENANCE_INVALID',
281
- `${execution.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH'}: ${execution.error?.message ?? 'orchestration provenance validation failed'}`,
282
- resolved.taskId
283
- );
188
+ return reject('REVIEW_PROVENANCE_INVALID', `${execution.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH'}: ${execution.error?.message ?? 'orchestration provenance validation failed'}`);
284
189
  }
285
-
286
- let rows;
287
190
  try {
288
191
  const ledger = parseLedgerDocument(taskContent);
289
- if (!ledger.present) {
290
- return failed(request, 'REVIEW_LEDGER_INVALID', `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}`, resolved.taskId);
291
- }
292
- rows = ledger.rows;
293
- } catch (error) {
294
- return failed(request, 'REVIEW_LEDGER_INVALID', String(error), resolved.taskId);
295
- }
296
- const ledgerError = validateLedgerRows(rows);
297
- if (ledgerError) {
298
- return failed(
299
- request,
300
- 'REVIEW_LEDGER_INVALID',
301
- `${ledgerError.code}: ${ledgerError.message}`,
302
- resolved.taskId
303
- );
304
- }
305
- const stageStatus = summarizeLedgerStage(rows, stage);
306
- const detailInspection = inspectDecisionDetailDuplicates(artifactContent);
307
- if (!detailInspection.ok) {
308
- return failed(
309
- request,
310
- 'REVIEW_DECISION_DETAIL_INVALID',
311
- `${detailInspection.code}: ${detailInspection.message}`,
312
- resolved.taskId,
313
- stageStatus
314
- );
315
- }
316
- const transformed = finalizeReviewSummaryContent(
317
- artifactContent,
318
- stageStatus.unresolvedFindingCounts
319
- );
320
- if (!transformed.ok) {
321
- return failed(request, transformed.code, transformed.message, resolved.taskId, stageStatus);
322
- }
323
- if (!transformed.changed) {
324
- if (repairIntent?.state === 'awaiting-repair') {
192
+ if (!ledger.present) return reject('REVIEW_LEDGER_INVALID', `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}`);
193
+ const error = validateLedgerRows(ledger.rows);
194
+ if (error) return reject('REVIEW_LEDGER_INVALID', `${error.code}: ${error.message}`);
195
+ stageStatus = summarizeLedgerStage(ledger.rows, stage);
196
+ } catch (error) { return reject('REVIEW_LEDGER_INVALID', String(error)); }
197
+ const detail = inspectDecisionDetailDuplicates(artifactContent);
198
+ if (!detail.ok) return reject('REVIEW_DECISION_DETAIL_INVALID', `${detail.code}: ${detail.message}`);
199
+ const schema = getArtifactSchema(spec.family)!;
200
+ const structure = inspectArtifactContract(artifactContent, schema);
201
+ if (!structure.ok) {
202
+ if (!recovery && !request.dryRun && options.startRecovery) {
325
203
  try {
326
- writeArtifactRepairIntent(resolved.repoRoot, {
327
- ...repairIntent,
328
- state: 'passed',
329
- artifactSha256,
330
- semanticDigest: artifactSemanticDigest
331
- });
332
- } catch (error) {
333
- return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repaired review provenance: ${String(error)}`, resolved.taskId, stageStatus, {
334
- artifactSha256,
335
- semanticDigest: artifactSemanticDigest
336
- });
337
- }
204
+ recovery = beginArtifactRecovery({
205
+ taskId: taskId!, family: spec.family, artifact: request.artifact, round: parsed.round,
206
+ requestId: `review-finalize:${taskId!}:${stage}:${parsed.round}`
207
+ }, Buffer.from(artifactContent, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
208
+ } catch (error) { return reject('REVIEW_PROVENANCE_INVALID', String(error), digests); }
338
209
  }
339
210
  return {
340
- ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus, {
341
- artifactSha256,
342
- semanticDigest: artifactSemanticDigest
211
+ ...reject('REVIEW_ARTIFACT_STRUCTURE_INVALID', structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '), {
212
+ artifactSha256, semanticDigest: structure.semanticDigest,
213
+ ...(recovery ? { recovery: {
214
+ recoveryId: recovery.recoveryId,
215
+ candidatePath: recovery.stagingPath,
216
+ baselineSha256: recovery.baselineSha256,
217
+ baselineSemanticDigest: recovery.baselineSemanticDigest
218
+ } } : {})
343
219
  }),
344
- status: 'no-op',
345
- error: null
220
+ ...(recovery ? { recovery } : {})
346
221
  };
347
222
  }
348
- const operations = [{ kind: 'artifact' as const, artifact: request.artifact, operation: 'update' as const }];
349
- if (request.dryRun) {
223
+ const transformed = finalizeReviewSummaryContent(artifactContent, stageStatus.unresolvedFindingCounts);
224
+ if (!transformed.ok) return reject(transformed.code, transformed.message);
225
+ const finalDigests = { artifactSha256: sha256Content(transformed.content), semanticDigest: canonicalSemanticDigest(transformed.content) };
226
+ if (!recovery && (repairIntent?.state === 'passed' || repairIntent?.state === 'consumed')) {
227
+ if (transformed.changed) return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact requires changes after its completed recovery was consumed', finalDigests);
350
228
  return {
351
- ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus, {
352
- artifactSha256: sha256Content(transformed.content),
353
- semanticDigest: canonicalSemanticDigest(transformed.content)
354
- }),
355
- status: 'planned',
356
- changed: true,
357
- operations,
358
- error: null
229
+ content: transformed.content,
230
+ result: {
231
+ ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', taskId, stageStatus, finalDigests),
232
+ status: 'no-op',
233
+ changed: false,
234
+ error: null
235
+ },
236
+ lockAlreadyHeld: options.lockAlreadyHeld
359
237
  };
360
238
  }
361
-
362
- const tempPath = path.join(
363
- resolved.taskDir,
364
- `.${request.artifact}.tmp-${process.pid}-${(options.randomSuffix ?? randomUUID)()}`
365
- );
366
- let mode: number;
367
- try {
368
- mode = fileSystem.statModeSync(validated.artifact.path);
369
- fileSystem.writeFileSync(tempPath, transformed.content, mode);
370
- } catch (error) {
371
- cleanupTemp(fileSystem, tempPath);
372
- return failed(request, 'REVIEW_TEMP_WRITE_FAILED', String(error), resolved.taskId, stageStatus);
239
+ if (!recovery && !request.dryRun && options.startRecovery) {
240
+ try {
241
+ recovery = beginArtifactRecovery({
242
+ taskId: taskId!, family: spec.family, artifact: request.artifact, round: parsed.round,
243
+ requestId: `review-finalize:${taskId!}:${stage}:${parsed.round}`
244
+ }, Buffer.from(artifactContent, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
245
+ } catch (error) { return reject('REVIEW_PROVENANCE_INVALID', String(error), finalDigests); }
373
246
  }
247
+ return {
248
+ content: transformed.content,
249
+ result: {
250
+ ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', taskId, stageStatus, finalDigests),
251
+ status: transformed.changed ? request.dryRun ? 'planned' : 'applied' : 'no-op',
252
+ changed: transformed.changed,
253
+ operations: transformed.changed ? [{ kind: 'artifact', artifact: request.artifact, operation: 'update' }] : [],
254
+ error: null
255
+ },
256
+ ...(recovery ? { recovery } : {}),
257
+ lockAlreadyHeld: options.lockAlreadyHeld
258
+ };
259
+ }
260
+
261
+ function commitReviewSummaryProvenance(
262
+ prepared: ReviewSummaryCandidatePreparation,
263
+ repoRoot: string
264
+ ): ReviewFinalizationResult {
265
+ if (prepared.result.status === 'failed' || !prepared.recovery) return prepared.result;
374
266
  try {
375
- if (fileSystem.readFileSync(validated.artifact.path) !== artifactContent) {
376
- cleanupTemp(fileSystem, tempPath);
377
- return failed(
378
- request,
379
- 'REVIEW_ARTIFACT_CONFLICT',
380
- `${request.artifact} changed during finalization`,
381
- resolved.taskId,
382
- stageStatus
383
- );
267
+ const intent = readArtifactRecoveryIntent(prepared.recovery.repoRoot, prepared.recovery.taskId, prepared.recovery.family, prepared.recovery.artifact);
268
+ if (!intent) throw new Error('ARTIFACT_RECOVERY_INTENT_MISSING');
269
+ let committed;
270
+ if (intent.state === 'finalize-ready' || intent.state === 'commit-started') {
271
+ committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
272
+ } else {
273
+ const staged = stageArtifactCandidate(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
274
+ prepareArtifactRecoveryCommit(prepared.recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: prepared.lockAlreadyHeld });
275
+ committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
384
276
  }
385
- fileSystem.renameSync(tempPath, validated.artifact.path);
277
+ if (committed.state !== 'passed') throw new Error(`ARTIFACT_RECOVERY_STATE_INVALID: commit ended in '${committed.state}'`);
278
+ return {
279
+ ...prepared.result,
280
+ artifactSha256: committed.finalArtifactSha256,
281
+ semanticDigest: committed.finalSemanticDigest,
282
+ error: null
283
+ };
386
284
  } catch (error) {
387
- cleanupTemp(fileSystem, tempPath);
388
- return failed(request, 'REVIEW_RENAME_FAILED', String(error), resolved.taskId, stageStatus);
285
+ return { ...prepared.result, status: 'failed', error: {
286
+ code: 'REVIEW_RECOVERY_COMMIT_FAILED', message: `cannot publish review candidate: ${String(error)}`
287
+ } };
389
288
  }
390
- if (repairIntent?.state === 'awaiting-repair') {
391
- try {
392
- writeArtifactRepairIntent(resolved.repoRoot, {
393
- ...repairIntent,
394
- state: 'passed',
395
- artifactSha256: sha256Content(transformed.content),
396
- semanticDigest: canonicalSemanticDigest(transformed.content)
397
- });
398
- } catch (error) {
399
- return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repaired review provenance: ${String(error)}`, resolved.taskId, stageStatus, {
400
- artifactSha256: sha256Content(transformed.content),
401
- semanticDigest: canonicalSemanticDigest(transformed.content)
402
- });
403
- }
289
+ }
290
+
291
+ function finalizeReviewSummaryUnlocked(
292
+ request: ReviewFinalizationRequest,
293
+ options: ReviewFinalizationOptions = {}
294
+ ): ReviewFinalizationResult {
295
+ const spec = STAGES[request.stage as ReviewStage];
296
+ if (!spec || !request.taskRef || !request.artifact) return prepareReviewSummaryCandidate(request, '', options).result;
297
+ const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
298
+ if (!resolved.ok) return failed(request, resolved.code, resolved.message, resolved.taskId);
299
+ const validated = validateCompletedArtifact(resolved.taskDir, spec.family, request.artifact);
300
+ if (!validated.ok) {
301
+ return failed(request, validated.error.code === 'ARTIFACT_NOT_REGULAR' ? 'REVIEW_ARTIFACT_NOT_REGULAR' : 'REVIEW_ARTIFACT_IDENTITY_INVALID', validated.error.message, resolved.taskId);
404
302
  }
405
- return {
406
- status: 'applied',
407
- changed: true,
408
- intent: 'finalize-summary',
409
- requestRef: request.taskRef,
410
- taskId: resolved.taskId,
411
- stage,
412
- artifact: request.artifact,
413
- stageStatus,
414
- artifactSha256: sha256Content(transformed.content),
415
- semanticDigest: canonicalSemanticDigest(transformed.content),
416
- repairable: false,
417
- operation: null,
418
- operations,
419
- error: null
420
- };
303
+ const fileSystem = { ...DEFAULT_FILE_SYSTEM, ...options.fileSystem };
304
+ let content: string;
305
+ try { content = fileSystem.readFileSync(validated.artifact.path); }
306
+ catch (error) { return failed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId); }
307
+ const prepared = prepareReviewSummaryCandidate(request, content, options);
308
+ if (request.dryRun) return prepared.result;
309
+ return commitReviewSummaryProvenance(prepared, resolved.repoRoot);
421
310
  }
422
311
 
423
312
  function finalizeReviewSummary(
424
313
  request: ReviewFinalizationRequest,
425
314
  options: ReviewFinalizationOptions = {}
426
315
  ): ReviewFinalizationResult {
427
- if (request.dryRun || options.lockAlreadyHeld) return finalizeReviewSummaryUnlocked(request, options);
316
+ const effectiveOptions = { ...options, startRecovery: !request.dryRun };
317
+ if (request.dryRun || options.lockAlreadyHeld) return finalizeReviewSummaryUnlocked(request, effectiveOptions);
428
318
  const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
429
- if (!resolved.ok) return finalizeReviewSummaryUnlocked(request, options);
319
+ if (!resolved.ok) return finalizeReviewSummaryUnlocked(request, effectiveOptions);
430
320
  try {
431
321
  return withTaskExecutionLock(
432
322
  resolved.repoRoot,
433
323
  resolved.taskId,
434
324
  'task-review.finalize-summary',
435
- () => finalizeReviewSummaryUnlocked(request, options)
325
+ () => finalizeReviewSummaryUnlocked(request, { ...effectiveOptions, lockAlreadyHeld: true })
436
326
  );
437
327
  } catch (error) {
438
328
  if (!(error instanceof TaskExecutionLockError)) throw error;
@@ -445,11 +335,12 @@ function finalizeReviewSummary(
445
335
  }
446
336
  }
447
337
 
448
- export { finalizeReviewSummary };
338
+ export { finalizeReviewSummary, prepareReviewSummaryCandidate, commitReviewSummaryProvenance };
449
339
  export type {
450
340
  ReviewFinalizationError,
451
341
  ReviewFinalizationErrorCode,
452
342
  ReviewFinalizationOptions,
453
343
  ReviewFinalizationRequest,
454
- ReviewFinalizationResult
344
+ ReviewFinalizationResult,
345
+ ReviewSummaryCandidatePreparation
455
346
  };
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
- import { artifactName, maxRound } from "./review-artifacts.ts";
4
+ import { artifactName, maxArtifactRound } from "./artifact-name.ts";
5
5
  import { invalidationBlocks, isArtifactInvalidated, parseInvalidationDocument } from "./invalidation.ts";
6
6
 
7
7
  type ReviewedGitTree = string & { readonly __reviewedGitTree: unique symbol };
@@ -45,7 +45,7 @@ export function resolvePostReviewGlobs(
45
45
 
46
46
  export function findAuthoritativeReviewCodeArtifact(taskDir: string) {
47
47
  const entries = fs.existsSync(taskDir) ? fs.readdirSync(taskDir) : [];
48
- const round = maxRound(entries, "review-code");
48
+ const round = maxArtifactRound(entries, "review-code");
49
49
  if (round === 0) {
50
50
  return { ok: false, round: 0, fileName: null, path: null, error: null };
51
51
  }