@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,8 +1,6 @@
1
1
  import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { randomUUID } from 'node:crypto';
4
- import { locateActivityLog, pairEntries, startedBackedRows } from './activity-log.js';
5
- import { parseArtifactName, validateCompletedArtifact } from './artifact-lifecycle.js';
2
+ import { parseArtifactName } from './artifact-name.js';
3
+ import { validateCompletedArtifact, hasOpenArtifactRound, validateArtifactPublication } from './artifact-lifecycle.js';
6
4
  import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
7
5
  import { finalizeReviewSummaryContent } from './review-artifacts.js';
8
6
  import { inspectDecisionDetailDuplicates } from './decision-details.js';
@@ -12,18 +10,15 @@ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-
12
10
  import { allowsManualOverride } from './guard-override.js';
13
11
  import { getArtifactSchema } from './artifact-schema.js';
14
12
  import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.js';
15
- import { readArtifactRepairIntent, writeArtifactRepairIntent } from './artifact-repair-intent.js';
13
+ import { readArtifactRecoveryIntent } from './artifact-repair-intent.js';
14
+ import { beginArtifactRecovery, commitArtifactRecovery, prepareArtifactRecoveryCommit, recoveryContextFromIntent, stageArtifactCandidate } from './artifact-recovery.js';
16
15
  const STAGES = {
17
- analysis: { family: 'review-analysis', action: 'Review Analysis' },
18
- plan: { family: 'review-plan', action: 'Review Plan' },
19
- code: { family: 'review-code', action: 'Review Code' }
16
+ analysis: { family: 'review-analysis' },
17
+ plan: { family: 'review-plan' },
18
+ code: { family: 'review-code' }
20
19
  };
21
20
  const DEFAULT_FILE_SYSTEM = {
22
- readFileSync: (file) => fs.readFileSync(file, 'utf8'),
23
- statModeSync: (file) => fs.statSync(file).mode,
24
- writeFileSync: (file, content, mode) => fs.writeFileSync(file, content, { encoding: 'utf8', flag: 'wx', mode }),
25
- renameSync: (from, to) => fs.renameSync(from, to),
26
- unlinkSync: (file) => fs.unlinkSync(file)
21
+ readFileSync: (file) => fs.readFileSync(file, 'utf8')
27
22
  };
28
23
  function failed(request, code, message, taskId = null, stageStatus = null, extra = {}) {
29
24
  return {
@@ -37,271 +32,235 @@ function failed(request, code, message, taskId = null, stageStatus = null, extra
37
32
  stageStatus,
38
33
  artifactSha256: null,
39
34
  semanticDigest: null,
40
- repairable: false,
41
- operation: null,
42
35
  operations: [],
43
36
  error: { code, message },
44
37
  ...extra
45
38
  };
46
39
  }
47
- function openReviewRound(content, action, round) {
48
- const section = locateActivityLog(content);
49
- if (!section)
50
- return false;
51
- const expected = `${action} (Round ${round})`;
52
- return startedBackedRows(pairEntries(section.entries)).filter((row) => (row.step === expected && !row.done)).length === 1;
53
- }
54
- function cleanupTemp(fileSystem, tempPath) {
55
- try {
56
- fileSystem.unlinkSync(tempPath);
57
- }
58
- catch {
59
- // Best-effort cleanup preserves the primary failure.
60
- }
61
- }
62
- function finalizeReviewSummaryUnlocked(request, options = {}) {
40
+ /** Prepare domain validation, summary bytes and provenance before any publication. */
41
+ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
42
+ let taskId = null;
43
+ let stageStatus = null;
44
+ const reject = (code, message, extra = {}) => ({
45
+ result: failed(request, code, message, taskId, stageStatus, extra), content: artifactContent
46
+ });
63
47
  const stage = request.stage;
64
48
  const spec = STAGES[stage];
65
49
  if (!spec)
66
- return failed(request, 'REVIEW_STAGE_INVALID', `unsupported review stage '${request.stage}'`);
67
- if (!request.taskRef || !request.artifact) {
68
- return failed(request, 'REVIEW_PAYLOAD_INVALID', 'taskRef, stage, and artifact are required');
69
- }
50
+ return reject('REVIEW_STAGE_INVALID', `unsupported review stage '${request.stage}'`);
51
+ if (!request.taskRef || !request.artifact)
52
+ return reject('REVIEW_PAYLOAD_INVALID', 'taskRef, stage, and artifact are required');
70
53
  const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
54
+ taskId = resolved.taskId ?? null;
71
55
  if (!resolved.ok)
72
- return failed(request, resolved.code, resolved.message, resolved.taskId);
56
+ return reject(resolved.code, resolved.message);
73
57
  if (resolved.state !== 'active' && !allowsManualOverride(options.manualOverride, 'review-finalization', 'TASK_STATE_MISMATCH')) {
74
- return failed(request, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
58
+ return reject('TASK_STATE_MISMATCH', `task ${taskId} is ${resolved.state}, expected active`);
75
59
  }
76
- const parsedArtifact = parseArtifactName(request.artifact);
77
- if (!parsedArtifact || parsedArtifact.family !== spec.family) {
78
- return failed(request, 'REVIEW_ARTIFACT_IDENTITY_INVALID', `artifact '${request.artifact}' does not match ${spec.family}`, resolved.taskId);
79
- }
80
- const validated = validateCompletedArtifact(resolved.taskDir, spec.family, request.artifact, parsedArtifact.round);
81
- if (!validated.ok) {
82
- const code = validated.error.code === 'ARTIFACT_NOT_REGULAR'
83
- ? 'REVIEW_ARTIFACT_NOT_REGULAR'
84
- : 'REVIEW_ARTIFACT_IDENTITY_INVALID';
85
- return failed(request, code, validated.error.message, resolved.taskId);
86
- }
87
- const fileSystem = { ...DEFAULT_FILE_SYSTEM, ...options.fileSystem };
60
+ const parsed = parseArtifactName(request.artifact);
61
+ if (!parsed || parsed.family !== spec.family)
62
+ return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', `artifact '${request.artifact}' does not match ${spec.family}`);
63
+ const publicationError = validateArtifactPublication(resolved.taskDir, spec.family, request.artifact);
64
+ if (publicationError)
65
+ return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', publicationError.message);
88
66
  let taskContent;
89
- let artifactContent;
90
67
  try {
91
- taskContent = fileSystem.readFileSync(resolved.taskMdPath);
92
- artifactContent = fileSystem.readFileSync(validated.artifact.path);
68
+ taskContent = (options.fileSystem?.readFileSync ?? DEFAULT_FILE_SYSTEM.readFileSync)(resolved.taskMdPath);
93
69
  }
94
70
  catch (error) {
95
- return failed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId);
71
+ return reject('REVIEW_ARTIFACT_NOT_REGULAR', String(error));
72
+ }
73
+ let recovery;
74
+ if (request.recoveryId) {
75
+ try {
76
+ const intent = readArtifactRecoveryIntent(resolved.repoRoot, taskId, spec.family, request.artifact);
77
+ if (!intent || intent.recoveryOperationId !== request.recoveryId)
78
+ return reject('REVIEW_PROVENANCE_INVALID', 'recovery id does not match the artifact journal');
79
+ if (!['awaiting-recovery', 'finalize-ready', 'commit-started'].includes(intent.state))
80
+ return reject('REVIEW_PROVENANCE_INVALID', `recovery journal is in '${intent.state}' state`);
81
+ recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
82
+ artifactContent = fs.existsSync(recovery.stagingPath)
83
+ ? fs.readFileSync(recovery.stagingPath, 'utf8')
84
+ : fs.readFileSync(recovery.formalPath, 'utf8');
85
+ }
86
+ catch (error) {
87
+ return reject('REVIEW_PROVENANCE_INVALID', String(error));
88
+ }
96
89
  }
97
- const schema = getArtifactSchema(spec.family);
98
- const structure = schema ? inspectArtifactContract(artifactContent, schema) : null;
99
90
  const artifactSha256 = sha256Content(artifactContent);
100
- const artifactSemanticDigest = canonicalSemanticDigest(artifactContent);
91
+ const semanticDigest = canonicalSemanticDigest(artifactContent);
92
+ const digests = { artifactSha256, semanticDigest };
101
93
  let repairIntent;
102
94
  try {
103
- repairIntent = readArtifactRepairIntent(resolved.repoRoot, resolved.taskId, spec.family, request.artifact);
95
+ repairIntent = readArtifactRecoveryIntent(resolved.repoRoot, taskId, spec.family, request.artifact);
104
96
  }
105
97
  catch (error) {
106
- return failed(request, 'REVIEW_PROVENANCE_INVALID', String(error), resolved.taskId, null, {
107
- artifactSha256,
108
- semanticDigest: artifactSemanticDigest
109
- });
98
+ return reject('REVIEW_PROVENANCE_INVALID', String(error), digests);
110
99
  }
111
- if (structure && !structure.ok) {
112
- const repairable = structure.repair !== null && structure.diagnostics.length === 1 && structure.diagnostics[0]?.repairable === true;
113
- if (repairable) {
114
- if (repairIntent && (repairIntent.state !== 'awaiting-repair' ||
115
- repairIntent.artifactSha256 !== artifactSha256 ||
116
- repairIntent.semanticDigest !== structure.semanticDigest)) {
117
- return failed(request, 'REVIEW_PROVENANCE_INVALID', 'a different review repair baseline is already recorded for this artifact', resolved.taskId, null, {
118
- artifactSha256,
119
- semanticDigest: structure.semanticDigest
120
- });
121
- }
122
- try {
123
- if (!repairIntent) {
124
- writeArtifactRepairIntent(resolved.repoRoot, {
125
- version: 1,
126
- taskId: resolved.taskId,
127
- family: spec.family,
128
- artifact: request.artifact,
129
- state: 'awaiting-repair',
130
- baselineSemanticDigest: structure.semanticDigest,
131
- artifactSha256,
132
- semanticDigest: structure.semanticDigest
133
- });
134
- }
135
- }
136
- catch (error) {
137
- return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repair provenance: ${String(error)}`, resolved.taskId, null, {
138
- artifactSha256,
139
- semanticDigest: structure.semanticDigest,
140
- operation: structure.repair
141
- });
142
- }
100
+ if (repairIntent?.state === 'passed' || repairIntent?.state === 'consumed') {
101
+ if (repairIntent.finalArtifactSha256 !== artifactSha256 || repairIntent.finalSemanticDigest !== semanticDigest) {
102
+ return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact changed after its finalization provenance was recorded', digests);
143
103
  }
144
- return failed(request, 'REVIEW_ARTIFACT_STRUCTURE_INVALID', structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '), resolved.taskId, null, {
145
- artifactSha256,
146
- semanticDigest: structure.semanticDigest,
147
- repairable,
148
- operation: structure.repair
149
- });
150
104
  }
151
- if (repairIntent?.state === 'awaiting-repair' && repairIntent.baselineSemanticDigest !== artifactSemanticDigest) {
152
- return failed(request, 'REVIEW_PROVENANCE_INVALID', 'the repaired review artifact semantic digest does not match the recorded repair baseline', resolved.taskId, null, {
153
- artifactSha256,
154
- semanticDigest: artifactSemanticDigest
155
- });
156
- }
157
- if ((repairIntent?.state === 'passed' || repairIntent?.state === 'consumed')
158
- && (repairIntent.artifactSha256 !== artifactSha256 || repairIntent.semanticDigest !== artifactSemanticDigest)) {
159
- return failed(request, 'REVIEW_PROVENANCE_INVALID', 'the review artifact changed after its finalization provenance was recorded', resolved.taskId, null, {
160
- artifactSha256,
161
- semanticDigest: artifactSemanticDigest
162
- });
163
- }
164
- if (!openReviewRound(taskContent, spec.action, parsedArtifact.round)) {
165
- return failed(request, 'REVIEW_ARTIFACT_IDENTITY_INVALID', `${request.artifact} does not have one matching open started review event`, resolved.taskId);
105
+ if (!hasOpenArtifactRound(taskContent, spec.family, parsed.round)) {
106
+ return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', `${request.artifact} does not have one matching open started review event`);
166
107
  }
167
108
  const execution = validateLifecycleExecution(request.taskRef, {
168
109
  mode: request.orchestrated ? 'orchestrated' : 'standalone',
169
- identity: {
170
- stage: spec.family,
171
- round: parsedArtifact.round,
172
- artifact: request.artifact,
173
- role: 'reviewer'
174
- },
110
+ identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
175
111
  dryRun: request.dryRun
176
112
  }, { repoRoot: options.repoRoot });
177
113
  if (!execution.ok) {
178
- return failed(request, 'REVIEW_PROVENANCE_INVALID', `${execution.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH'}: ${execution.error?.message ?? 'orchestration provenance validation failed'}`, resolved.taskId);
114
+ return reject('REVIEW_PROVENANCE_INVALID', `${execution.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH'}: ${execution.error?.message ?? 'orchestration provenance validation failed'}`);
179
115
  }
180
- let rows;
181
116
  try {
182
117
  const ledger = parseLedgerDocument(taskContent);
183
- if (!ledger.present) {
184
- return failed(request, 'REVIEW_LEDGER_INVALID', `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}`, resolved.taskId);
185
- }
186
- rows = ledger.rows;
118
+ if (!ledger.present)
119
+ return reject('REVIEW_LEDGER_INVALID', `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}`);
120
+ const error = validateLedgerRows(ledger.rows);
121
+ if (error)
122
+ return reject('REVIEW_LEDGER_INVALID', `${error.code}: ${error.message}`);
123
+ stageStatus = summarizeLedgerStage(ledger.rows, stage);
187
124
  }
188
125
  catch (error) {
189
- return failed(request, 'REVIEW_LEDGER_INVALID', String(error), resolved.taskId);
126
+ return reject('REVIEW_LEDGER_INVALID', String(error));
190
127
  }
191
- const ledgerError = validateLedgerRows(rows);
192
- if (ledgerError) {
193
- return failed(request, 'REVIEW_LEDGER_INVALID', `${ledgerError.code}: ${ledgerError.message}`, resolved.taskId);
194
- }
195
- const stageStatus = summarizeLedgerStage(rows, stage);
196
- const detailInspection = inspectDecisionDetailDuplicates(artifactContent);
197
- if (!detailInspection.ok) {
198
- return failed(request, 'REVIEW_DECISION_DETAIL_INVALID', `${detailInspection.code}: ${detailInspection.message}`, resolved.taskId, stageStatus);
199
- }
200
- const transformed = finalizeReviewSummaryContent(artifactContent, stageStatus.unresolvedFindingCounts);
201
- if (!transformed.ok) {
202
- return failed(request, transformed.code, transformed.message, resolved.taskId, stageStatus);
203
- }
204
- if (!transformed.changed) {
205
- if (repairIntent?.state === 'awaiting-repair') {
128
+ const detail = inspectDecisionDetailDuplicates(artifactContent);
129
+ if (!detail.ok)
130
+ return reject('REVIEW_DECISION_DETAIL_INVALID', `${detail.code}: ${detail.message}`);
131
+ const schema = getArtifactSchema(spec.family);
132
+ const structure = inspectArtifactContract(artifactContent, schema);
133
+ if (!structure.ok) {
134
+ if (!recovery && !request.dryRun && options.startRecovery) {
206
135
  try {
207
- writeArtifactRepairIntent(resolved.repoRoot, {
208
- ...repairIntent,
209
- state: 'passed',
210
- artifactSha256,
211
- semanticDigest: artifactSemanticDigest
212
- });
136
+ recovery = beginArtifactRecovery({
137
+ taskId: taskId, family: spec.family, artifact: request.artifact, round: parsed.round,
138
+ requestId: `review-finalize:${taskId}:${stage}:${parsed.round}`
139
+ }, Buffer.from(artifactContent, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
213
140
  }
214
141
  catch (error) {
215
- return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repaired review provenance: ${String(error)}`, resolved.taskId, stageStatus, {
216
- artifactSha256,
217
- semanticDigest: artifactSemanticDigest
218
- });
142
+ return reject('REVIEW_PROVENANCE_INVALID', String(error), digests);
219
143
  }
220
144
  }
221
145
  return {
222
- ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus, {
223
- artifactSha256,
224
- semanticDigest: artifactSemanticDigest
146
+ ...reject('REVIEW_ARTIFACT_STRUCTURE_INVALID', structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '), {
147
+ artifactSha256, semanticDigest: structure.semanticDigest,
148
+ ...(recovery ? { recovery: {
149
+ recoveryId: recovery.recoveryId,
150
+ candidatePath: recovery.stagingPath,
151
+ baselineSha256: recovery.baselineSha256,
152
+ baselineSemanticDigest: recovery.baselineSemanticDigest
153
+ } } : {})
225
154
  }),
226
- status: 'no-op',
227
- error: null
155
+ ...(recovery ? { recovery } : {})
228
156
  };
229
157
  }
230
- const operations = [{ kind: 'artifact', artifact: request.artifact, operation: 'update' }];
231
- if (request.dryRun) {
158
+ const transformed = finalizeReviewSummaryContent(artifactContent, stageStatus.unresolvedFindingCounts);
159
+ if (!transformed.ok)
160
+ return reject(transformed.code, transformed.message);
161
+ const finalDigests = { artifactSha256: sha256Content(transformed.content), semanticDigest: canonicalSemanticDigest(transformed.content) };
162
+ if (!recovery && (repairIntent?.state === 'passed' || repairIntent?.state === 'consumed')) {
163
+ if (transformed.changed)
164
+ return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact requires changes after its completed recovery was consumed', finalDigests);
232
165
  return {
233
- ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus, {
234
- artifactSha256: sha256Content(transformed.content),
235
- semanticDigest: canonicalSemanticDigest(transformed.content)
236
- }),
237
- status: 'planned',
238
- changed: true,
239
- operations,
240
- error: null
166
+ content: transformed.content,
167
+ result: {
168
+ ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', taskId, stageStatus, finalDigests),
169
+ status: 'no-op',
170
+ changed: false,
171
+ error: null
172
+ },
173
+ lockAlreadyHeld: options.lockAlreadyHeld
241
174
  };
242
175
  }
243
- const tempPath = path.join(resolved.taskDir, `.${request.artifact}.tmp-${process.pid}-${(options.randomSuffix ?? randomUUID)()}`);
244
- let mode;
176
+ if (!recovery && !request.dryRun && options.startRecovery) {
177
+ try {
178
+ recovery = beginArtifactRecovery({
179
+ taskId: taskId, family: spec.family, artifact: request.artifact, round: parsed.round,
180
+ requestId: `review-finalize:${taskId}:${stage}:${parsed.round}`
181
+ }, Buffer.from(artifactContent, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
182
+ }
183
+ catch (error) {
184
+ return reject('REVIEW_PROVENANCE_INVALID', String(error), finalDigests);
185
+ }
186
+ }
187
+ return {
188
+ content: transformed.content,
189
+ result: {
190
+ ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', taskId, stageStatus, finalDigests),
191
+ status: transformed.changed ? request.dryRun ? 'planned' : 'applied' : 'no-op',
192
+ changed: transformed.changed,
193
+ operations: transformed.changed ? [{ kind: 'artifact', artifact: request.artifact, operation: 'update' }] : [],
194
+ error: null
195
+ },
196
+ ...(recovery ? { recovery } : {}),
197
+ lockAlreadyHeld: options.lockAlreadyHeld
198
+ };
199
+ }
200
+ function commitReviewSummaryProvenance(prepared, repoRoot) {
201
+ if (prepared.result.status === 'failed' || !prepared.recovery)
202
+ return prepared.result;
245
203
  try {
246
- mode = fileSystem.statModeSync(validated.artifact.path);
247
- fileSystem.writeFileSync(tempPath, transformed.content, mode);
204
+ const intent = readArtifactRecoveryIntent(prepared.recovery.repoRoot, prepared.recovery.taskId, prepared.recovery.family, prepared.recovery.artifact);
205
+ if (!intent)
206
+ throw new Error('ARTIFACT_RECOVERY_INTENT_MISSING');
207
+ let committed;
208
+ if (intent.state === 'finalize-ready' || intent.state === 'commit-started') {
209
+ committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
210
+ }
211
+ else {
212
+ const staged = stageArtifactCandidate(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
213
+ prepareArtifactRecoveryCommit(prepared.recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: prepared.lockAlreadyHeld });
214
+ committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
215
+ }
216
+ if (committed.state !== 'passed')
217
+ throw new Error(`ARTIFACT_RECOVERY_STATE_INVALID: commit ended in '${committed.state}'`);
218
+ return {
219
+ ...prepared.result,
220
+ artifactSha256: committed.finalArtifactSha256,
221
+ semanticDigest: committed.finalSemanticDigest,
222
+ error: null
223
+ };
248
224
  }
249
225
  catch (error) {
250
- cleanupTemp(fileSystem, tempPath);
251
- return failed(request, 'REVIEW_TEMP_WRITE_FAILED', String(error), resolved.taskId, stageStatus);
226
+ return { ...prepared.result, status: 'failed', error: {
227
+ code: 'REVIEW_RECOVERY_COMMIT_FAILED', message: `cannot publish review candidate: ${String(error)}`
228
+ } };
229
+ }
230
+ }
231
+ function finalizeReviewSummaryUnlocked(request, options = {}) {
232
+ const spec = STAGES[request.stage];
233
+ if (!spec || !request.taskRef || !request.artifact)
234
+ return prepareReviewSummaryCandidate(request, '', options).result;
235
+ const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
236
+ if (!resolved.ok)
237
+ return failed(request, resolved.code, resolved.message, resolved.taskId);
238
+ const validated = validateCompletedArtifact(resolved.taskDir, spec.family, request.artifact);
239
+ if (!validated.ok) {
240
+ return failed(request, validated.error.code === 'ARTIFACT_NOT_REGULAR' ? 'REVIEW_ARTIFACT_NOT_REGULAR' : 'REVIEW_ARTIFACT_IDENTITY_INVALID', validated.error.message, resolved.taskId);
252
241
  }
242
+ const fileSystem = { ...DEFAULT_FILE_SYSTEM, ...options.fileSystem };
243
+ let content;
253
244
  try {
254
- if (fileSystem.readFileSync(validated.artifact.path) !== artifactContent) {
255
- cleanupTemp(fileSystem, tempPath);
256
- return failed(request, 'REVIEW_ARTIFACT_CONFLICT', `${request.artifact} changed during finalization`, resolved.taskId, stageStatus);
257
- }
258
- fileSystem.renameSync(tempPath, validated.artifact.path);
245
+ content = fileSystem.readFileSync(validated.artifact.path);
259
246
  }
260
247
  catch (error) {
261
- cleanupTemp(fileSystem, tempPath);
262
- return failed(request, 'REVIEW_RENAME_FAILED', String(error), resolved.taskId, stageStatus);
263
- }
264
- if (repairIntent?.state === 'awaiting-repair') {
265
- try {
266
- writeArtifactRepairIntent(resolved.repoRoot, {
267
- ...repairIntent,
268
- state: 'passed',
269
- artifactSha256: sha256Content(transformed.content),
270
- semanticDigest: canonicalSemanticDigest(transformed.content)
271
- });
272
- }
273
- catch (error) {
274
- return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repaired review provenance: ${String(error)}`, resolved.taskId, stageStatus, {
275
- artifactSha256: sha256Content(transformed.content),
276
- semanticDigest: canonicalSemanticDigest(transformed.content)
277
- });
278
- }
248
+ return failed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId);
279
249
  }
280
- return {
281
- status: 'applied',
282
- changed: true,
283
- intent: 'finalize-summary',
284
- requestRef: request.taskRef,
285
- taskId: resolved.taskId,
286
- stage,
287
- artifact: request.artifact,
288
- stageStatus,
289
- artifactSha256: sha256Content(transformed.content),
290
- semanticDigest: canonicalSemanticDigest(transformed.content),
291
- repairable: false,
292
- operation: null,
293
- operations,
294
- error: null
295
- };
250
+ const prepared = prepareReviewSummaryCandidate(request, content, options);
251
+ if (request.dryRun)
252
+ return prepared.result;
253
+ return commitReviewSummaryProvenance(prepared, resolved.repoRoot);
296
254
  }
297
255
  function finalizeReviewSummary(request, options = {}) {
256
+ const effectiveOptions = { ...options, startRecovery: !request.dryRun };
298
257
  if (request.dryRun || options.lockAlreadyHeld)
299
- return finalizeReviewSummaryUnlocked(request, options);
258
+ return finalizeReviewSummaryUnlocked(request, effectiveOptions);
300
259
  const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
301
260
  if (!resolved.ok)
302
- return finalizeReviewSummaryUnlocked(request, options);
261
+ return finalizeReviewSummaryUnlocked(request, effectiveOptions);
303
262
  try {
304
- return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-review.finalize-summary', () => finalizeReviewSummaryUnlocked(request, options));
263
+ return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-review.finalize-summary', () => finalizeReviewSummaryUnlocked(request, { ...effectiveOptions, lockAlreadyHeld: true }));
305
264
  }
306
265
  catch (error) {
307
266
  if (!(error instanceof TaskExecutionLockError))
@@ -309,5 +268,5 @@ function finalizeReviewSummary(request, options = {}) {
309
268
  return failed(request, 'REVIEW_PROVENANCE_INVALID', `${error.code}: ${error.message}`, resolved.taskId);
310
269
  }
311
270
  }
312
- export { finalizeReviewSummary };
271
+ export { finalizeReviewSummary, prepareReviewSummaryCandidate, commitReviewSummaryProvenance };
313
272
  //# sourceMappingURL=review-finalization.js.map
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { artifactName, maxRound } from "./review-artifacts.js";
3
+ import { artifactName, maxArtifactRound } from "./artifact-name.js";
4
4
  import { invalidationBlocks, isArtifactInvalidated, parseInvalidationDocument } from "./invalidation.js";
5
5
  const REVIEWED_TREE_RE = /^[a-f0-9]{40,64}$/;
6
6
  export function parseReviewedGitTree(value) {
@@ -29,7 +29,7 @@ export function resolvePostReviewGlobs(config = {}, reviewConfig = {}) {
29
29
  }
30
30
  export function findAuthoritativeReviewCodeArtifact(taskDir) {
31
31
  const entries = fs.existsSync(taskDir) ? fs.readdirSync(taskDir) : [];
32
- const round = maxRound(entries, "review-code");
32
+ const round = maxArtifactRound(entries, "review-code");
33
33
  if (round === 0) {
34
34
  return { ok: false, round: 0, fileName: null, path: null, error: null };
35
35
  }
@@ -1,3 +1,4 @@
1
+ import { scanVisibleMarkdown } from './markdown.js';
1
2
  function escapeRegExp(value) {
2
3
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
3
4
  }
@@ -27,10 +28,13 @@ function parseTable(content, input) {
27
28
  throw new DocumentMutationError('TASK_DOCUMENT_INVALID', 'table section is ambiguous');
28
29
  }
29
30
  const section = sections[0];
31
+ const visibleStarts = new Set(scanVisibleMarkdown(content).lines.map((line) => line.start));
30
32
  const sectionLines = linesOf(content).filter((line) => line.start >= section.bodyStart && line.start < section.end);
31
33
  const firstContent = sectionLines.findIndex((line) => line.text.trim() !== '');
32
34
  const tables = [];
33
35
  for (let index = 0; index + 1 < sectionLines.length; index += 1) {
36
+ if (!visibleStarts.has(sectionLines[index].start) || !visibleStarts.has(sectionLines[index + 1].start))
37
+ continue;
34
38
  const header = splitTableCells(sectionLines[index].text);
35
39
  const separator = splitTableCells(sectionLines[index + 1].text);
36
40
  if (!header || !separator || header.cells.length !== input.columns.length)
@@ -97,24 +101,23 @@ function documentEol(content) {
97
101
  }
98
102
  function matchingSections(content, aliases) {
99
103
  const allowed = new Set(aliases);
100
- return linesOf(content)
101
- .map((line, index, lines) => {
102
- const match = /^(#{2,3})\s+(.+?)\s*$/.exec(line.text);
103
- if (!match?.[2] || !allowed.has(match[2]))
104
+ const lines = new Map(linesOf(content).map((line) => [line.start, line]));
105
+ const headings = scanVisibleMarkdown(content).headings.filter((heading) => heading.level === 2 || heading.level === 3);
106
+ return headings
107
+ .map((heading, index) => {
108
+ if (!allowed.has(heading.text))
104
109
  return null;
105
- const level = match[1].length;
106
- let end = content.length;
107
- for (let i = index + 1; i < lines.length; i += 1) {
108
- const nextHeading = /^(#{2,3})\s+/.exec(lines[i].text);
109
- if (nextHeading && nextHeading[1].length <= level) {
110
- end = lines[i].start;
111
- break;
112
- }
113
- }
114
- return { heading: match[2], line, bodyStart: line.end, end };
110
+ const next = headings.slice(index + 1).find((candidate) => candidate.level <= heading.level);
111
+ const line = lines.get(heading.start);
112
+ return { heading: heading.text, level: heading.level, line, bodyStart: line.end, end: next?.start ?? content.length };
115
113
  })
116
114
  .filter((match) => match !== null);
117
115
  }
116
+ // Select the first visible section at the requested level, in document order.
117
+ function findSectionRange(content, aliases, level = 2) {
118
+ const section = matchingSections(content, aliases).find((candidate) => candidate.level === level);
119
+ return section ? { start: section.line.start, bodyStart: section.bodyStart, end: section.end, heading: section.heading } : null;
120
+ }
118
121
  function validateAliases(aliases, name) {
119
122
  if (!Array.isArray(aliases) || aliases.length === 0 || aliases.some((alias) => !alias || /[\r\n]/.test(alias))) {
120
123
  throw new DocumentMutationError('MUTATION_INVALID', `${name} must contain headings`);
@@ -261,9 +264,12 @@ function mutateTableRow(content, mutation) {
261
264
  throw new DocumentMutationError(sections.length === 0 ? 'TABLE_NOT_FOUND' : 'TASK_DOCUMENT_INVALID', sections.length === 0 ? 'table section not found' : 'table section is ambiguous');
262
265
  }
263
266
  const section = sections[0];
267
+ const visibleStarts = new Set(scanVisibleMarkdown(content).lines.map((line) => line.start));
264
268
  const sectionLines = linesOf(content).filter((line) => line.start >= section.bodyStart && line.start < section.end);
265
269
  const tables = [];
266
270
  for (let i = 0; i + 1 < sectionLines.length; i += 1) {
271
+ if (!visibleStarts.has(sectionLines[i].start) || !visibleStarts.has(sectionLines[i + 1].start))
272
+ continue;
267
273
  const header = splitTableCells(sectionLines[i].text);
268
274
  const separator = splitTableCells(sectionLines[i + 1].text);
269
275
  if (!header || !separator || header.cells.length !== mutation.columns.length)
@@ -368,25 +374,12 @@ function mutateTableRow(content, mutation) {
368
374
  * lines are trimmed. Returns '' when no alias heading is present.
369
375
  */
370
376
  function extractSection(content, aliases) {
371
- const lines = content.split('\n');
372
- let start = -1;
373
- for (let i = 0; i < lines.length; i += 1) {
374
- const line = lines[i].trim();
375
- if (aliases.some((alias) => new RegExp(`^##\\s+${escapeRegExp(alias)}\\s*$`).test(line))) {
376
- start = i + 1;
377
- break;
378
- }
379
- }
380
- if (start === -1)
377
+ const headings = scanVisibleMarkdown(content).headings.filter((heading) => heading.level === 2);
378
+ const index = headings.findIndex((heading) => aliases.includes(heading.text));
379
+ if (index === -1)
381
380
  return '';
382
- let end = lines.length;
383
- for (let i = start; i < lines.length; i += 1) {
384
- if (/^##\s+/.test(lines[i])) {
385
- end = i;
386
- break;
387
- }
388
- }
389
- return lines.slice(start, end).join('\n').replace(/^\n+/, '').replace(/\n+$/, '');
381
+ return content.slice(headings[index].end + 1, headings[index + 1]?.start ?? content.length)
382
+ .replace(/^(?:\r?\n)+|(?:\r?\n)+$/g, '');
390
383
  }
391
384
  /**
392
385
  * Return the heading alias that actually appears as a `## {alias}` line, so a
@@ -394,11 +387,8 @@ function extractSection(content, aliases) {
394
387
  * alias when none is present.
395
388
  */
396
389
  function findSectionHeading(content, aliases) {
397
- for (const alias of aliases) {
398
- if (new RegExp(`^##\\s+${escapeRegExp(alias)}\\s*$`, 'm').test(content))
399
- return alias;
400
- }
401
- return aliases[0];
390
+ const headings = scanVisibleMarkdown(content).headings.filter((heading) => heading.level === 2);
391
+ return aliases.find((alias) => headings.some((heading) => heading.text === alias)) ?? aliases[0];
402
392
  }
403
393
  /**
404
394
  * Return the body of the first `### {headingPrefix}` sub-section, from the
@@ -409,25 +399,13 @@ function findSectionHeading(content, aliases) {
409
399
  * and trailing blank lines are trimmed. Returns '' when no match is present.
410
400
  */
411
401
  function extractSubSection(content, headingPrefix) {
412
- const lines = content.split('\n');
413
- const headRe = new RegExp(`^###\\s+${escapeRegExp(headingPrefix)}(?![\\w-])`);
414
- let start = -1;
415
- for (let i = 0; i < lines.length; i += 1) {
416
- if (headRe.test(lines[i].trim())) {
417
- start = i;
418
- break;
419
- }
420
- }
421
- if (start === -1)
402
+ const headings = scanVisibleMarkdown(content).headings.filter((heading) => heading.level === 2 || heading.level === 3);
403
+ const headRe = new RegExp(`^${escapeRegExp(headingPrefix)}(?![\\w-])`);
404
+ const index = headings.findIndex((heading) => heading.level === 3 && headRe.test(heading.text));
405
+ if (index === -1)
422
406
  return '';
423
- let end = lines.length;
424
- for (let i = start + 1; i < lines.length; i += 1) {
425
- if (/^###?\s+/.test(lines[i])) {
426
- end = i;
427
- break;
428
- }
429
- }
430
- return lines.slice(start, end).join('\n').replace(/^\n+/, '').replace(/\n+$/, '');
407
+ return content.slice(headings[index].start, headings[index + 1]?.start ?? content.length)
408
+ .replace(/^(?:\r?\n)+|(?:\r?\n)+$/g, '');
431
409
  }
432
- export { extractSection, findSectionHeading, extractSubSection, parseTable, upsertSection, mutateTableRow, DocumentMutationError };
410
+ export { findSectionRange, extractSection, findSectionHeading, extractSubSection, parseTable, upsertSection, mutateTableRow, DocumentMutationError };
433
411
  //# sourceMappingURL=sections.js.map