@fitlab-ai/agent-infra 0.9.14 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/README.md +10 -0
  2. package/README.zh-CN.md +10 -0
  3. package/bin/internal-cli.sh +45 -0
  4. package/dist/bin/internal-cli.js +93 -30
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +9 -4
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
  8. package/dist/lib/defaults.json +103 -103
  9. package/dist/lib/fs/durable-write.js +39 -0
  10. package/dist/lib/fs/file-lock.js +111 -0
  11. package/dist/lib/host-control/audit.js +19 -0
  12. package/dist/lib/host-control/client.js +142 -0
  13. package/dist/lib/host-control/command.js +49 -0
  14. package/dist/lib/host-control/path.js +160 -0
  15. package/dist/lib/host-control/secure-fs.js +140 -0
  16. package/dist/lib/host-control/server.js +159 -0
  17. package/dist/lib/host-control/service.js +112 -0
  18. package/dist/lib/init.js +11 -2
  19. package/dist/lib/internal/cli-route-inventory.js +4 -2
  20. package/dist/lib/internal/codex-lifecycle.js +6 -6
  21. package/dist/lib/internal/demo-promotion.js +322 -0
  22. package/dist/lib/internal/demo-transcript.js +454 -0
  23. package/dist/lib/internal/host-control.js +28 -0
  24. package/dist/lib/internal/manual-validation.js +331 -0
  25. package/dist/lib/internal/platform-pr.js +18 -4
  26. package/dist/lib/internal/release-workflow.js +63 -84
  27. package/dist/lib/internal/sandbox-control.js +149 -2
  28. package/dist/lib/internal/task-artifact.js +16 -263
  29. package/dist/lib/internal/task-create.js +6 -5
  30. package/dist/lib/internal/task-event.js +37 -37
  31. package/dist/lib/internal/task-lifecycle.js +2 -2
  32. package/dist/lib/internal/task-operation-registry.js +101 -4
  33. package/dist/lib/internal/task-review.js +11 -56
  34. package/dist/lib/internal/task-validate.js +30 -10
  35. package/dist/lib/owned-path.js +33 -0
  36. package/dist/lib/platform/check-status.js +11 -0
  37. package/dist/lib/platform/comment-safety.js +4 -1
  38. package/dist/lib/platform/completion-backfill.js +1 -1
  39. package/dist/lib/platform/github-data.js +241 -0
  40. package/dist/lib/platform/github-provider.js +15 -20
  41. package/dist/lib/platform/issue-comments.js +18 -12
  42. package/dist/lib/platform/issues.js +2 -180
  43. package/dist/lib/platform/pr-change-report.js +5 -4
  44. package/dist/lib/platform/pr-checks.js +12 -65
  45. package/dist/lib/platform/pr-summary.js +117 -33
  46. package/dist/lib/platform/pull-requests.js +5 -108
  47. package/dist/lib/platform/snapshots.js +2 -0
  48. package/dist/lib/platform/task-identities.js +2 -9
  49. package/dist/lib/platform/verification-sync.js +132 -143
  50. package/dist/lib/pr-review/evidence-grading.js +4 -8
  51. package/dist/lib/release/workflow.js +1 -1
  52. package/dist/lib/sandbox/commands/create.js +7 -2
  53. package/dist/lib/sandbox/commands/rm.js +19 -1511
  54. package/dist/lib/sandbox/control/audit.js +203 -0
  55. package/dist/lib/sandbox/control/client.js +93 -54
  56. package/dist/lib/sandbox/control/executor.js +129 -65
  57. package/dist/lib/sandbox/control/finalization-response.js +27 -0
  58. package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
  59. package/dist/lib/sandbox/control/lifecycle.js +36 -2
  60. package/dist/lib/sandbox/control/native-file-lock.js +6 -98
  61. package/dist/lib/sandbox/control/protocol.js +55 -4
  62. package/dist/lib/sandbox/control/server.js +518 -127
  63. package/dist/lib/sandbox/control/state.js +64 -0
  64. package/dist/lib/sandbox/control/task-workflow.js +74 -0
  65. package/dist/lib/sandbox/control/workflow-executor.js +89 -0
  66. package/dist/lib/sandbox/index.js +1 -2
  67. package/dist/lib/sandbox/recovery.js +28 -4
  68. package/dist/lib/sandbox/removal.js +822 -0
  69. package/dist/lib/sandbox/task-cleanup.js +217 -0
  70. package/dist/lib/sandbox/workspace-identity.js +17 -1
  71. package/dist/lib/sandbox/workspace-view.js +13 -14
  72. package/dist/lib/task/activity-intent.js +2 -1
  73. package/dist/lib/task/activity-log.js +30 -24
  74. package/dist/lib/task/artifact-command.js +79 -0
  75. package/dist/lib/task/artifact-lifecycle.js +30 -28
  76. package/dist/lib/task/artifact-name.js +36 -0
  77. package/dist/lib/task/artifact-operations.js +29 -289
  78. package/dist/lib/task/artifact-receipts.js +3 -15
  79. package/dist/lib/task/artifact-recovery.js +430 -0
  80. package/dist/lib/task/artifact-repair-intent.js +134 -29
  81. package/dist/lib/task/artifact-schema.js +6 -14
  82. package/dist/lib/task/capabilities.js +12 -19
  83. package/dist/lib/task/codex-orchestration.js +71 -25
  84. package/dist/lib/task/commit-identity.js +28 -0
  85. package/dist/lib/task/commit-operation.js +33 -20
  86. package/dist/lib/task/control-authority.js +403 -6
  87. package/dist/lib/task/control-recovery.js +140 -0
  88. package/dist/lib/task/create-service.js +1 -8
  89. package/dist/lib/task/create.js +62 -9
  90. package/dist/lib/task/decision-details.js +2 -46
  91. package/dist/lib/task/delegation-receipts.js +64 -3
  92. package/dist/lib/task/events.js +275 -22
  93. package/dist/lib/task/finalization-state.js +9 -0
  94. package/dist/lib/task/human-override.js +8 -14
  95. package/dist/lib/task/implementation-inputs.js +3 -3
  96. package/dist/lib/task/intermediate-cleanup.js +441 -0
  97. package/dist/lib/task/invalidation.js +18 -65
  98. package/dist/lib/task/ledger-intents.js +7 -22
  99. package/dist/lib/task/ledger.js +1 -3
  100. package/dist/lib/task/lifecycle-recovery.js +549 -0
  101. package/dist/lib/task/lifecycle.js +31 -4
  102. package/dist/lib/task/local-artifact-finalization.js +197 -197
  103. package/dist/lib/task/manual-validation-completion.js +26 -0
  104. package/dist/lib/task/manual-validation-evidence.js +130 -0
  105. package/dist/lib/task/manual-validation-receipt.js +105 -0
  106. package/dist/lib/task/manual-validation-shared.js +40 -0
  107. package/dist/lib/task/manual-validation-transaction.js +243 -0
  108. package/dist/lib/task/markdown.js +47 -0
  109. package/dist/lib/task/orchestration.js +66 -19
  110. package/dist/lib/task/pr-delivery-fact.js +12 -56
  111. package/dist/lib/task/qualification-audit.js +20 -34
  112. package/dist/lib/task/recovery-warning.js +19 -0
  113. package/dist/lib/task/review-artifacts.js +2 -18
  114. package/dist/lib/task/review-command.js +26 -0
  115. package/dist/lib/task/review-finalization.js +175 -216
  116. package/dist/lib/task/review-fingerprint.js +2 -2
  117. package/dist/lib/task/sections.js +34 -56
  118. package/dist/lib/task/short-id.js +15 -7
  119. package/dist/lib/task/task-execution-lock.js +1 -1
  120. package/dist/lib/task/verification-engine.js +119 -337
  121. package/dist/lib/task/verification.js +1 -1
  122. package/dist/lib/task/workflow-command.js +16 -0
  123. package/dist/lib/task/workflow-warning-intents.js +0 -16
  124. package/dist/lib/task/workflow-warnings.js +28 -2
  125. package/dist/lib/update.js +2 -0
  126. package/dist/lib/version.js +1 -11
  127. package/dist/lifecycle-build-manifest.json +3 -3
  128. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
  129. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +10 -4
  130. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
  131. package/lib/fs/durable-write.ts +30 -0
  132. package/lib/fs/file-lock.ts +113 -0
  133. package/lib/host-control/audit.ts +22 -0
  134. package/lib/host-control/client.ts +172 -0
  135. package/lib/host-control/command.ts +60 -0
  136. package/lib/host-control/path.ts +167 -0
  137. package/lib/host-control/secure-fs.ts +135 -0
  138. package/lib/host-control/server.ts +180 -0
  139. package/lib/host-control/service.ts +133 -0
  140. package/lib/init.ts +15 -2
  141. package/lib/internal/cli-route-inventory.ts +4 -2
  142. package/lib/internal/codex-lifecycle.ts +6 -6
  143. package/lib/internal/demo-promotion.ts +354 -0
  144. package/lib/internal/demo-transcript.ts +419 -0
  145. package/lib/internal/host-control.ts +28 -0
  146. package/lib/internal/manual-validation.ts +315 -0
  147. package/lib/internal/platform-pr.ts +15 -4
  148. package/lib/internal/release-workflow.ts +74 -82
  149. package/lib/internal/sandbox-control.ts +162 -1
  150. package/lib/internal/task-artifact.ts +14 -217
  151. package/lib/internal/task-create.ts +5 -5
  152. package/lib/internal/task-event.ts +20 -11
  153. package/lib/internal/task-lifecycle.ts +4 -3
  154. package/lib/internal/task-operation-registry.ts +111 -4
  155. package/lib/internal/task-review.ts +9 -54
  156. package/lib/internal/task-validate.ts +26 -10
  157. package/lib/owned-path.ts +26 -0
  158. package/lib/platform/check-status.ts +9 -0
  159. package/lib/platform/comment-safety.ts +5 -0
  160. package/lib/platform/completion-backfill.ts +1 -1
  161. package/lib/platform/github-data.ts +322 -0
  162. package/lib/platform/github-provider.ts +15 -20
  163. package/lib/platform/issue-comments.ts +21 -14
  164. package/lib/platform/issues.ts +5 -246
  165. package/lib/platform/pr-change-report.ts +7 -5
  166. package/lib/platform/pr-checks.ts +13 -77
  167. package/lib/platform/pr-summary.ts +123 -35
  168. package/lib/platform/pull-requests.ts +6 -173
  169. package/lib/platform/snapshots.ts +53 -0
  170. package/lib/platform/task-identities.ts +3 -14
  171. package/lib/platform/verification-sync.ts +150 -156
  172. package/lib/pr-review/evidence-grading.ts +4 -9
  173. package/lib/release/workflow.ts +3 -3
  174. package/lib/sandbox/commands/create.ts +7 -6
  175. package/lib/sandbox/commands/rm.ts +21 -1895
  176. package/lib/sandbox/control/audit.ts +253 -0
  177. package/lib/sandbox/control/client.ts +109 -66
  178. package/lib/sandbox/control/executor.ts +134 -74
  179. package/lib/sandbox/control/finalization-response.ts +29 -0
  180. package/lib/sandbox/control/identity-sentinel.ts +137 -0
  181. package/lib/sandbox/control/lifecycle.ts +45 -3
  182. package/lib/sandbox/control/native-file-lock.ts +6 -107
  183. package/lib/sandbox/control/protocol.ts +80 -6
  184. package/lib/sandbox/control/server.ts +567 -132
  185. package/lib/sandbox/control/state.ts +86 -0
  186. package/lib/sandbox/control/task-workflow.ts +95 -0
  187. package/lib/sandbox/control/workflow-executor.ts +92 -0
  188. package/lib/sandbox/index.ts +1 -2
  189. package/lib/sandbox/recovery.ts +28 -5
  190. package/lib/sandbox/removal.ts +1142 -0
  191. package/lib/sandbox/task-cleanup.ts +209 -0
  192. package/lib/sandbox/workspace-identity.ts +19 -3
  193. package/lib/sandbox/workspace-view.ts +17 -15
  194. package/lib/task/activity-intent.ts +2 -2
  195. package/lib/task/activity-log.ts +27 -19
  196. package/lib/task/artifact-command.ts +85 -0
  197. package/lib/task/artifact-lifecycle.ts +31 -26
  198. package/lib/task/artifact-name.ts +55 -0
  199. package/lib/task/artifact-operations.ts +34 -310
  200. package/lib/task/artifact-receipts.ts +3 -14
  201. package/lib/task/artifact-recovery.ts +519 -0
  202. package/lib/task/artifact-repair-intent.ts +162 -33
  203. package/lib/task/artifact-schema.ts +5 -34
  204. package/lib/task/capabilities.ts +13 -22
  205. package/lib/task/codex-orchestration.ts +85 -24
  206. package/lib/task/commit-identity.ts +31 -0
  207. package/lib/task/commit-operation.ts +37 -20
  208. package/lib/task/control-authority.ts +540 -10
  209. package/lib/task/control-recovery.ts +186 -0
  210. package/lib/task/create-service.ts +1 -10
  211. package/lib/task/create.ts +63 -8
  212. package/lib/task/decision-details.ts +3 -71
  213. package/lib/task/delegation-receipts.ts +73 -2
  214. package/lib/task/events.ts +309 -24
  215. package/lib/task/finalization-state.ts +11 -0
  216. package/lib/task/human-override.ts +8 -16
  217. package/lib/task/implementation-inputs.ts +3 -3
  218. package/lib/task/intermediate-cleanup.ts +493 -0
  219. package/lib/task/invalidation.ts +18 -62
  220. package/lib/task/ledger-intents.ts +7 -24
  221. package/lib/task/ledger.ts +1 -3
  222. package/lib/task/lifecycle-recovery.ts +691 -0
  223. package/lib/task/lifecycle.ts +30 -5
  224. package/lib/task/local-artifact-finalization.ts +244 -234
  225. package/lib/task/manual-validation-completion.ts +40 -0
  226. package/lib/task/manual-validation-evidence.ts +209 -0
  227. package/lib/task/manual-validation-receipt.ts +156 -0
  228. package/lib/task/manual-validation-shared.ts +50 -0
  229. package/lib/task/manual-validation-transaction.ts +304 -0
  230. package/lib/task/markdown.ts +70 -0
  231. package/lib/task/orchestration.ts +84 -19
  232. package/lib/task/pr-delivery-fact.ts +15 -62
  233. package/lib/task/qualification-audit.ts +22 -33
  234. package/lib/task/recovery-warning.ts +21 -0
  235. package/lib/task/review-artifacts.ts +1 -20
  236. package/lib/task/review-command.ts +24 -0
  237. package/lib/task/review-finalization.ts +183 -292
  238. package/lib/task/review-fingerprint.ts +2 -2
  239. package/lib/task/sections.ts +33 -54
  240. package/lib/task/short-id.ts +27 -5
  241. package/lib/task/task-execution-lock.ts +1 -1
  242. package/lib/task/verification-engine.ts +113 -383
  243. package/lib/task/verification-types.ts +15 -0
  244. package/lib/task/verification.ts +1 -1
  245. package/lib/task/workflow-command.ts +17 -0
  246. package/lib/task/workflow-warning-intents.ts +0 -13
  247. package/lib/task/workflow-warnings.ts +21 -2
  248. package/lib/update.ts +6 -0
  249. package/lib/version.ts +1 -15
  250. package/package.json +6 -6
  251. package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
  252. package/templates/.agents/rules/create-issue.en.md +1 -1
  253. package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
  254. package/templates/.agents/rules/issue-sync.en.md +4 -2
  255. package/templates/.agents/rules/issue-sync.zh-CN.md +4 -3
  256. package/templates/.agents/rules/local-artifact-repair.en.md +46 -39
  257. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +43 -36
  258. package/templates/.agents/rules/testing-discipline.en.md +2 -0
  259. package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
  260. package/templates/.agents/scripts/package.json +3 -0
  261. package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
  262. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
  263. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
  264. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
  265. package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
  266. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
  267. package/templates/.agents/skills/block-task/config/verify.json +1 -1
  268. package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
  269. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
  270. package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
  271. package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
  272. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +7 -7
  273. package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
  274. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
  275. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
  276. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
  277. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +33 -21
  278. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +33 -21
  279. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
  280. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
  281. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  282. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  283. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
  284. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
  285. package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
  286. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
  287. package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
  288. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
  289. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
  290. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
  291. package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
  292. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
  293. package/templates/.agents/skills/create-pr/config/verify.json +1 -1
  294. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
  295. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
  296. package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
  297. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
  298. package/templates/.agents/skills/create-task/config/verify.json +1 -1
  299. package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
  300. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
  301. package/templates/.agents/skills/import-issue/config/verify.json +2 -2
  302. package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
  303. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
  304. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
  305. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
  306. package/templates/.agents/skills/release/SKILL.en.md +7 -3
  307. package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
  308. package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
  309. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
  310. package/templates/.agents/skills/restore-task/config/verify.json +1 -1
  311. package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
  312. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
  313. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
  314. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
  315. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
  316. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
  317. package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
  318. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
  319. package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
  320. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
  321. package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
  322. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
  323. package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
  324. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
  325. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
  326. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
  327. package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
  328. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
  329. package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
  330. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
  331. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
  332. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
  333. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
  334. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
  335. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
  336. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
  337. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
  338. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
  339. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
  340. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
  341. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  342. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  343. package/templates/.agents/skills/test/SKILL.en.md +17 -6
  344. package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
  345. package/templates/.agents/templates/task.en.md +1 -1
  346. package/templates/.agents/templates/task.zh-CN.md +1 -1
  347. package/templates/.claude/commands/import-issue.en.md +1 -1
  348. package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
  349. package/templates/.claude/commands/restore-task.en.md +1 -1
  350. package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
  351. package/templates/.claude/commands/run-manual-validation.en.md +1 -1
  352. package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
  353. package/dist/lib/platform/adapters.js +0 -56
  354. package/lib/platform/adapters.ts +0 -197
@@ -0,0 +1,40 @@
1
+ import { readManualValidationReceipt } from './manual-validation-receipt.ts';
2
+ import type { ManualValidationReceipt, ManualValidationReceiptError, ManualValidationReceiptIdentity } from './manual-validation-receipt.ts';
3
+ import { readManualValidationTransaction } from './manual-validation-transaction.ts';
4
+ import type { ManualValidationTransaction, ManualValidationTransactionError } from './manual-validation-transaction.ts';
5
+
6
+ type ManualValidationCompletionIdentity = ManualValidationReceiptIdentity & Readonly<{
7
+ receiptDigest?: string;
8
+ }>;
9
+ type ManualValidationCompletion = Readonly<{
10
+ receipt: ManualValidationReceipt;
11
+ transaction: ManualValidationTransaction;
12
+ }>;
13
+ type ManualValidationCompletionError = ManualValidationReceiptError | ManualValidationTransactionError;
14
+ type ManualValidationCompletionResult =
15
+ | { ok: true; value: ManualValidationCompletion }
16
+ | { ok: false; error: ManualValidationCompletionError };
17
+
18
+ function readManualValidationCompletion(taskDir: string, expected: ManualValidationCompletionIdentity = {}): ManualValidationCompletionResult {
19
+ const { receiptDigest: expectedReceiptDigest, ...receiptIdentity } = expected;
20
+ const receipt = readManualValidationReceipt(taskDir, receiptIdentity);
21
+ if (!receipt.ok) return receipt;
22
+ if (expectedReceiptDigest !== undefined && receipt.value.receiptDigest !== expectedReceiptDigest) {
23
+ return { ok: false, error: { code: 'MANUAL_VALIDATION_RECEIPT_IDENTITY_MISMATCH', message: 'receipt digest does not match the expected completion' } };
24
+ }
25
+ const transaction = readManualValidationTransaction(taskDir, {
26
+ transactionId: receipt.value.transactionId,
27
+ taskId: receipt.value.taskId,
28
+ prNumber: receipt.value.prNumber,
29
+ prHeadSha: receipt.value.prHeadSha,
30
+ artifact: receipt.value.artifact
31
+ });
32
+ if (!transaction.ok) return transaction;
33
+ if (transaction.value.committedReceipt !== receipt.value.receiptDigest) {
34
+ return { ok: false, error: { code: 'MANUAL_VALIDATION_TRANSACTION_PHASE_INVALID', message: 'completion transaction is not bound to the canonical receipt' } };
35
+ }
36
+ return { ok: true, value: { receipt: receipt.value, transaction: transaction.value } };
37
+ }
38
+
39
+ export { readManualValidationCompletion };
40
+ export type { ManualValidationCompletion, ManualValidationCompletionError, ManualValidationCompletionIdentity, ManualValidationCompletionResult };
@@ -0,0 +1,209 @@
1
+ import fs from 'node:fs';
2
+ import { createHash } from 'node:crypto';
3
+ import {
4
+ MANUAL_VALIDATION_SHA40 as SHA40,
5
+ MANUAL_VALIDATION_TASK_ID as TASK_ID,
6
+ exactKeys,
7
+ isRecord,
8
+ validTimestamp
9
+ } from './manual-validation-shared.ts';
10
+
11
+ const MANUAL_VALIDATION_EVIDENCE_SCHEMA = 'agent-infra/branch-only-validation-evidence';
12
+ const MANUAL_VALIDATION_EVIDENCE_VERSION = 1;
13
+
14
+ type ManualValidationMode = 'task-bound' | 'branch-only';
15
+ type ManualValidationScope = 'snapshot' | 'inplace';
16
+ type ManualValidationCleanup = 'completed' | 'pending' | 'failed' | 'not-created';
17
+ type ManualValidationEvidence = Readonly<{
18
+ schema: typeof MANUAL_VALIDATION_EVIDENCE_SCHEMA;
19
+ version: typeof MANUAL_VALIDATION_EVIDENCE_VERSION;
20
+ mode: ManualValidationMode;
21
+ taskId: string | null;
22
+ branch: string;
23
+ commit: string;
24
+ recoverable: boolean;
25
+ source: 'run-manual-validation';
26
+ result: Readonly<{
27
+ scope: ManualValidationScope;
28
+ command: string;
29
+ startedAt: string;
30
+ completedAt: string;
31
+ exitCode: number;
32
+ signal: string | null;
33
+ cleanup: ManualValidationCleanup;
34
+ }>;
35
+ }>;
36
+ type ManualValidationEvidenceInput = {
37
+ mode: ManualValidationMode;
38
+ taskId: string | null;
39
+ branch: string;
40
+ commit: string;
41
+ recoverable: boolean;
42
+ scope: ManualValidationScope;
43
+ command: string;
44
+ startedAt: string;
45
+ completedAt: string;
46
+ exitCode: number;
47
+ signal: string | null;
48
+ cleanup: ManualValidationCleanup;
49
+ };
50
+ type ManualValidationEvidenceIdentity = {
51
+ taskId: string | null;
52
+ branch: string;
53
+ commit?: string;
54
+ };
55
+ type ManualValidationEvidenceErrorCode =
56
+ | 'MANUAL_VALIDATION_EVIDENCE_MISSING'
57
+ | 'MANUAL_VALIDATION_EVIDENCE_INVALID'
58
+ | 'MANUAL_VALIDATION_EVIDENCE_IDENTITY_MISMATCH'
59
+ | 'MANUAL_VALIDATION_EVIDENCE_UNSUCCESSFUL'
60
+ | 'MANUAL_VALIDATION_EVIDENCE_STALE';
61
+ type ManualValidationEvidenceError = { code: ManualValidationEvidenceErrorCode; message: string };
62
+ type ManualValidationEvidenceResult =
63
+ | { ok: true; value: ManualValidationEvidence }
64
+ | { ok: false; error: ManualValidationEvidenceError };
65
+
66
+ function invalid(message: string): ManualValidationEvidenceResult {
67
+ return { ok: false, error: { code: 'MANUAL_VALIDATION_EVIDENCE_INVALID', message } };
68
+ }
69
+
70
+ function validateShape(value: unknown): ManualValidationEvidenceResult {
71
+ if (!isRecord(value)) return invalid('evidence must be a JSON object');
72
+ const topError = exactKeys(value, ['schema', 'version', 'mode', 'taskId', 'branch', 'commit', 'recoverable', 'source', 'result'], 'evidence');
73
+ if (topError) return invalid(topError);
74
+ if (value.schema !== MANUAL_VALIDATION_EVIDENCE_SCHEMA || value.version !== MANUAL_VALIDATION_EVIDENCE_VERSION) {
75
+ return invalid('evidence schema or version is unsupported');
76
+ }
77
+ if (value.mode !== 'task-bound' && value.mode !== 'branch-only') return invalid('mode must be task-bound or branch-only');
78
+ if (value.taskId !== null && (typeof value.taskId !== 'string' || !TASK_ID.test(value.taskId))) return invalid('taskId must be a TASK identifier or null');
79
+ if (typeof value.branch !== 'string' || !value.branch.trim() || /[\r\n]/u.test(value.branch)) return invalid('branch must be a non-empty single line');
80
+ if (typeof value.commit !== 'string' || !SHA40.test(value.commit)) return invalid('commit must be a 40-character lowercase hexadecimal SHA');
81
+ if (typeof value.recoverable !== 'boolean') return invalid('recoverable must be boolean');
82
+ if (value.source !== 'run-manual-validation') return invalid('source is unsupported');
83
+ if (value.mode === 'branch-only' && (value.taskId !== null || value.recoverable !== false)) return invalid('branch-only evidence must be non-recoverable and task-free');
84
+ if (value.mode === 'task-bound' && (value.taskId === null || value.recoverable !== true)) return invalid('task-bound evidence must identify a recoverable task');
85
+ if (!isRecord(value.result)) return invalid('result must be a JSON object');
86
+ const result = value.result;
87
+ const resultError = exactKeys(result, ['scope', 'command', 'startedAt', 'completedAt', 'exitCode', 'signal', 'cleanup'], 'result');
88
+ if (resultError) return invalid(resultError);
89
+ if (result.scope !== 'snapshot' && result.scope !== 'inplace') return invalid('result.scope is invalid');
90
+ if (typeof result.command !== 'string' || !result.command || /[\\/\r\n]/u.test(result.command)) return invalid('result.command must be a basename');
91
+ if (!validTimestamp(result.startedAt) || !validTimestamp(result.completedAt) || Date.parse(result.completedAt) < Date.parse(result.startedAt)) return invalid('result timestamps are invalid');
92
+ const exitCode = result.exitCode;
93
+ if (!Number.isSafeInteger(exitCode) || (exitCode as number) < 0) return invalid('result.exitCode must be a non-negative integer');
94
+ if (result.signal !== null && (typeof result.signal !== 'string' || !result.signal || /[\r\n]/u.test(result.signal))) return invalid('result.signal must be a string or null');
95
+ if (!['completed', 'pending', 'failed', 'not-created'].includes(result.cleanup as string)) return invalid('result.cleanup is invalid');
96
+ return { ok: true, value: value as unknown as ManualValidationEvidence };
97
+ }
98
+
99
+ function createManualValidationEvidence(input: ManualValidationEvidenceInput): ManualValidationEvidence {
100
+ const value: ManualValidationEvidence = {
101
+ schema: MANUAL_VALIDATION_EVIDENCE_SCHEMA,
102
+ version: MANUAL_VALIDATION_EVIDENCE_VERSION,
103
+ mode: input.mode,
104
+ taskId: input.taskId,
105
+ branch: input.branch,
106
+ commit: input.commit,
107
+ recoverable: input.recoverable,
108
+ source: 'run-manual-validation',
109
+ result: {
110
+ scope: input.scope,
111
+ command: input.command,
112
+ startedAt: input.startedAt,
113
+ completedAt: input.completedAt,
114
+ exitCode: input.exitCode,
115
+ signal: input.signal,
116
+ cleanup: input.cleanup
117
+ }
118
+ };
119
+ const checked = validateShape(value);
120
+ if (!checked.ok) throw new Error(`${checked.error.code}: ${checked.error.message}`);
121
+ return checked.value;
122
+ }
123
+
124
+ type ManualValidationEvidenceSummary = Readonly<{
125
+ version: 1;
126
+ taskId: string | null;
127
+ branch: string;
128
+ scope: ManualValidationScope;
129
+ commit: string;
130
+ command: string;
131
+ startedAt: string;
132
+ completedAt: string;
133
+ exitCode: number;
134
+ signal: string | null;
135
+ cleanup: ManualValidationCleanup;
136
+ }>;
137
+
138
+ function manualValidationEvidenceSummary(evidence: ManualValidationEvidence): ManualValidationEvidenceSummary {
139
+ return {
140
+ version: 1,
141
+ taskId: evidence.taskId,
142
+ branch: evidence.branch,
143
+ scope: evidence.result.scope,
144
+ commit: evidence.commit,
145
+ command: evidence.result.command,
146
+ startedAt: evidence.result.startedAt,
147
+ completedAt: evidence.result.completedAt,
148
+ exitCode: evidence.result.exitCode,
149
+ signal: evidence.result.signal,
150
+ cleanup: evidence.result.cleanup
151
+ };
152
+ }
153
+
154
+ function validateManualValidationEvidence(value: unknown, expected?: ManualValidationEvidenceIdentity): ManualValidationEvidenceResult {
155
+ const shape = validateShape(value);
156
+ if (!shape.ok) return shape;
157
+ const evidence = shape.value;
158
+ if (evidence.result.exitCode !== 0 || evidence.result.signal !== null || evidence.result.cleanup !== 'completed') {
159
+ return { ok: false, error: { code: 'MANUAL_VALIDATION_EVIDENCE_UNSUCCESSFUL', message: 'validation must exit 0 without a signal and complete cleanup' } };
160
+ }
161
+ if (expected) {
162
+ if (evidence.taskId !== expected.taskId || evidence.branch !== expected.branch) {
163
+ return { ok: false, error: { code: 'MANUAL_VALIDATION_EVIDENCE_IDENTITY_MISMATCH', message: 'evidence task or branch does not match the expected identity' } };
164
+ }
165
+ if (expected.commit !== undefined && evidence.commit !== expected.commit) {
166
+ return { ok: false, error: { code: 'MANUAL_VALIDATION_EVIDENCE_STALE', message: 'evidence commit does not match the current head' } };
167
+ }
168
+ }
169
+ return shape;
170
+ }
171
+
172
+ function readManualValidationEvidence(file: string, expected?: ManualValidationEvidenceIdentity): ManualValidationEvidenceResult {
173
+ let parsed: unknown;
174
+ try {
175
+ parsed = JSON.parse(fs.readFileSync(file, 'utf8')) as unknown;
176
+ } catch (error) {
177
+ const code = (error as NodeJS.ErrnoException).code === 'ENOENT'
178
+ ? 'MANUAL_VALIDATION_EVIDENCE_MISSING'
179
+ : 'MANUAL_VALIDATION_EVIDENCE_INVALID';
180
+ return { ok: false, error: { code, message: error instanceof Error ? error.message : String(error) } };
181
+ }
182
+ return validateManualValidationEvidence(parsed, expected);
183
+ }
184
+
185
+ function manualValidationEvidenceDigest(evidence: ManualValidationEvidence): string {
186
+ return createHash('sha256').update(JSON.stringify(evidence), 'utf8').digest('hex');
187
+ }
188
+
189
+ export {
190
+ MANUAL_VALIDATION_EVIDENCE_SCHEMA,
191
+ MANUAL_VALIDATION_EVIDENCE_VERSION,
192
+ createManualValidationEvidence,
193
+ manualValidationEvidenceSummary,
194
+ manualValidationEvidenceDigest,
195
+ readManualValidationEvidence,
196
+ validateManualValidationEvidence
197
+ };
198
+ export type {
199
+ ManualValidationCleanup,
200
+ ManualValidationEvidence,
201
+ ManualValidationEvidenceError,
202
+ ManualValidationEvidenceErrorCode,
203
+ ManualValidationEvidenceIdentity,
204
+ ManualValidationEvidenceInput,
205
+ ManualValidationEvidenceResult,
206
+ ManualValidationEvidenceSummary,
207
+ ManualValidationMode,
208
+ ManualValidationScope
209
+ };
@@ -0,0 +1,156 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { createHash } from 'node:crypto';
4
+ import {
5
+ MANUAL_VALIDATION_ARTIFACT as ARTIFACT,
6
+ MANUAL_VALIDATION_SHA40 as SHA40,
7
+ MANUAL_VALIDATION_SHA64 as SHA64,
8
+ MANUAL_VALIDATION_TASK_ID as TASK_ID,
9
+ exactKeys,
10
+ isRecord,
11
+ validTimestamp,
12
+ writeJsonAtomic
13
+ } from './manual-validation-shared.ts';
14
+
15
+ const MANUAL_VALIDATION_RECEIPT_SCHEMA = 'agent-infra/manual-validation-receipt';
16
+ const MANUAL_VALIDATION_RECEIPT_VERSION = 1;
17
+
18
+ type ManualValidationReceipt = Readonly<{
19
+ schema: typeof MANUAL_VALIDATION_RECEIPT_SCHEMA;
20
+ version: typeof MANUAL_VALIDATION_RECEIPT_VERSION;
21
+ transactionId: string;
22
+ taskId: string;
23
+ prNumber: number;
24
+ prHeadSha: string;
25
+ evidenceDigest: string;
26
+ artifact: string;
27
+ artifactSha256: string;
28
+ pendingSummaryDigest: string;
29
+ finalSummaryDigest: string;
30
+ committedAt: string;
31
+ receiptDigest: string;
32
+ }>;
33
+ type ManualValidationReceiptInput = Omit<ManualValidationReceipt, 'schema' | 'version' | 'receiptDigest'>;
34
+ type ManualValidationReceiptErrorCode =
35
+ | 'MANUAL_VALIDATION_RECEIPT_MISSING'
36
+ | 'MANUAL_VALIDATION_RECEIPT_INVALID'
37
+ | 'MANUAL_VALIDATION_RECEIPT_IDENTITY_MISMATCH';
38
+ type ManualValidationReceiptError = { code: ManualValidationReceiptErrorCode; message: string };
39
+ type ManualValidationReceiptResult =
40
+ | { ok: true; value: ManualValidationReceipt }
41
+ | { ok: false; error: ManualValidationReceiptError };
42
+ type ManualValidationReceiptIdentity = Partial<Pick<ManualValidationReceipt, 'transactionId' | 'taskId' | 'prNumber' | 'prHeadSha' | 'evidenceDigest' | 'artifact'>>;
43
+
44
+ function invalid(message: string): ManualValidationReceiptResult {
45
+ return { ok: false, error: { code: 'MANUAL_VALIDATION_RECEIPT_INVALID', message } };
46
+ }
47
+
48
+ function canonicalPayload(receipt: ManualValidationReceipt | ManualValidationReceiptInput): Omit<ManualValidationReceipt, 'receiptDigest'> {
49
+ return {
50
+ schema: MANUAL_VALIDATION_RECEIPT_SCHEMA,
51
+ version: MANUAL_VALIDATION_RECEIPT_VERSION,
52
+ transactionId: receipt.transactionId,
53
+ taskId: receipt.taskId,
54
+ prNumber: receipt.prNumber,
55
+ prHeadSha: receipt.prHeadSha,
56
+ evidenceDigest: receipt.evidenceDigest,
57
+ artifact: receipt.artifact,
58
+ artifactSha256: receipt.artifactSha256,
59
+ pendingSummaryDigest: receipt.pendingSummaryDigest,
60
+ finalSummaryDigest: receipt.finalSummaryDigest,
61
+ committedAt: receipt.committedAt
62
+ };
63
+ }
64
+
65
+ function manualValidationReceiptDigest(receipt: ManualValidationReceipt | ManualValidationReceiptInput): string {
66
+ return createHash('sha256').update(JSON.stringify(canonicalPayload(receipt)), 'utf8').digest('hex');
67
+ }
68
+
69
+ function manualValidationFinalSummaryDigest(body: string): string {
70
+ const heading = /^###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)\s*$/mu.exec(body);
71
+ const afterHeading = heading ? body.slice(heading.index + heading[0].length) : '';
72
+ const nextSection = heading ? /^#{1,3}\s/mu.exec(afterHeading) : null;
73
+ const projection = heading
74
+ ? body.slice(heading.index, heading.index + heading[0].length + (nextSection?.index ?? afterHeading.length)).replace(/\s+$/u, '')
75
+ : body;
76
+ const preimage = projection.replace(/receipt=(?:[a-f0-9]{64}|<receipt>)/gu, 'receipt=<receipt>');
77
+ return createHash('sha256').update(preimage, 'utf8').digest('hex');
78
+ }
79
+
80
+ function manualValidationFinalSummaryProjectionMatches(body: string, receipt: ManualValidationReceipt): boolean {
81
+ const identity = `transaction=${receipt.transactionId}; receipt=${receipt.receiptDigest}; evidence=${receipt.evidenceDigest}; head=${receipt.prHeadSha}`;
82
+ return body.includes(identity) && manualValidationFinalSummaryDigest(body) === receipt.finalSummaryDigest;
83
+ }
84
+
85
+ function validateManualValidationReceipt(value: unknown, expected?: ManualValidationReceiptIdentity): ManualValidationReceiptResult {
86
+ if (!isRecord(value)) return invalid('receipt must be a JSON object');
87
+ const keys = ['schema', 'version', 'transactionId', 'taskId', 'prNumber', 'prHeadSha', 'evidenceDigest', 'artifact', 'artifactSha256', 'pendingSummaryDigest', 'finalSummaryDigest', 'committedAt', 'receiptDigest'];
88
+ const keyError = exactKeys(value, keys, 'receipt');
89
+ if (keyError) return invalid(keyError);
90
+ if (value.schema !== MANUAL_VALIDATION_RECEIPT_SCHEMA || value.version !== MANUAL_VALIDATION_RECEIPT_VERSION) return invalid('receipt schema or version is unsupported');
91
+ if (typeof value.transactionId !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(value.transactionId)) return invalid('transactionId is invalid');
92
+ if (typeof value.taskId !== 'string' || !TASK_ID.test(value.taskId)) return invalid('taskId is invalid');
93
+ const prNumber = value.prNumber;
94
+ if (!Number.isSafeInteger(prNumber) || (prNumber as number) <= 0) return invalid('prNumber is invalid');
95
+ for (const [name, pattern] of [['prHeadSha', SHA40], ['evidenceDigest', SHA64], ['artifactSha256', SHA64], ['pendingSummaryDigest', SHA64], ['finalSummaryDigest', SHA64], ['receiptDigest', SHA64]] as const) {
96
+ if (typeof value[name] !== 'string' || !pattern.test(value[name])) return invalid(`${name} is invalid`);
97
+ }
98
+ if (typeof value.artifact !== 'string' || !ARTIFACT.test(value.artifact)) return invalid('artifact is not a canonical manual-validation artifact');
99
+ if (!validTimestamp(value.committedAt)) return invalid('committedAt is invalid');
100
+ const receipt = value as unknown as ManualValidationReceipt;
101
+ if (manualValidationReceiptDigest(receipt) !== receipt.receiptDigest) return invalid('receiptDigest does not match receipt contents');
102
+ if (expected && Object.entries(expected).some(([key, expectedValue]) => expectedValue !== undefined && receipt[key as keyof ManualValidationReceipt] !== expectedValue)) {
103
+ return { ok: false, error: { code: 'MANUAL_VALIDATION_RECEIPT_IDENTITY_MISMATCH', message: 'receipt identity does not match the expected transaction' } };
104
+ }
105
+ return { ok: true, value: receipt };
106
+ }
107
+
108
+ function createManualValidationReceipt(input: ManualValidationReceiptInput): ManualValidationReceipt {
109
+ const payload = canonicalPayload(input);
110
+ const receipt = { ...payload, receiptDigest: manualValidationReceiptDigest(input) };
111
+ const checked = validateManualValidationReceipt(receipt);
112
+ if (!checked.ok) throw new Error(`${checked.error.code}: ${checked.error.message}`);
113
+ return checked.value;
114
+ }
115
+
116
+ function manualValidationReceiptPath(taskDir: string): string {
117
+ return path.join(taskDir, '.manual-validation', 'receipt.json');
118
+ }
119
+
120
+ function writeManualValidationReceiptAtomic(taskDir: string, receipt: ManualValidationReceipt): string {
121
+ const checked = validateManualValidationReceipt(receipt);
122
+ if (!checked.ok) throw new Error(`${checked.error.code}: ${checked.error.message}`);
123
+ const target = manualValidationReceiptPath(taskDir);
124
+ return writeJsonAtomic(target, receipt);
125
+ }
126
+
127
+ function readManualValidationReceipt(taskDir: string, expected?: ManualValidationReceiptIdentity): ManualValidationReceiptResult {
128
+ const file = manualValidationReceiptPath(taskDir);
129
+ let value: unknown;
130
+ try { value = JSON.parse(fs.readFileSync(file, 'utf8')) as unknown; }
131
+ catch (error) {
132
+ return { ok: false, error: { code: (error as NodeJS.ErrnoException).code === 'ENOENT' ? 'MANUAL_VALIDATION_RECEIPT_MISSING' : 'MANUAL_VALIDATION_RECEIPT_INVALID', message: error instanceof Error ? error.message : String(error) } };
133
+ }
134
+ return validateManualValidationReceipt(value, expected);
135
+ }
136
+
137
+ export {
138
+ MANUAL_VALIDATION_RECEIPT_SCHEMA,
139
+ MANUAL_VALIDATION_RECEIPT_VERSION,
140
+ createManualValidationReceipt,
141
+ manualValidationFinalSummaryDigest,
142
+ manualValidationFinalSummaryProjectionMatches,
143
+ manualValidationReceiptDigest,
144
+ manualValidationReceiptPath,
145
+ readManualValidationReceipt,
146
+ validateManualValidationReceipt,
147
+ writeManualValidationReceiptAtomic
148
+ };
149
+ export type {
150
+ ManualValidationReceipt,
151
+ ManualValidationReceiptError,
152
+ ManualValidationReceiptErrorCode,
153
+ ManualValidationReceiptIdentity,
154
+ ManualValidationReceiptInput,
155
+ ManualValidationReceiptResult
156
+ };
@@ -0,0 +1,50 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { randomUUID } from 'node:crypto';
4
+
5
+ const MANUAL_VALIDATION_TASK_ID = /^TASK-\d{8}-\d{6}$/u;
6
+ const MANUAL_VALIDATION_SHA40 = /^[a-f0-9]{40}$/u;
7
+ const MANUAL_VALIDATION_SHA64 = /^[a-f0-9]{64}$/u;
8
+ const MANUAL_VALIDATION_ARTIFACT = /^manual-validation(?:-r[2-9]|-r[1-9]\d+)?.md$/u;
9
+ const MANUAL_VALIDATION_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u;
10
+
11
+ function isRecord(value: unknown): value is Record<string, unknown> {
12
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
13
+ }
14
+
15
+ function exactKeys(value: Record<string, unknown>, keys: readonly string[], label: string): string | null {
16
+ const allowed = new Set(keys);
17
+ const unknown = Object.keys(value).find((key) => !allowed.has(key));
18
+ if (unknown) return `${label} contains unknown field '${unknown}'`;
19
+ const missing = keys.find((key) => !Object.hasOwn(value, key));
20
+ return missing ? `${label} is missing '${missing}'` : null;
21
+ }
22
+
23
+ function validTimestamp(value: unknown): value is string {
24
+ return typeof value === 'string' && MANUAL_VALIDATION_TIMESTAMP.test(value) && Number.isFinite(Date.parse(value));
25
+ }
26
+
27
+ function writeJsonAtomic(file: string, value: unknown): string {
28
+ const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
29
+ fs.mkdirSync(path.dirname(file), { recursive: true });
30
+ try {
31
+ fs.writeFileSync(temporary, `${JSON.stringify(value)}\n`, { mode: 0o600 });
32
+ fs.renameSync(temporary, file);
33
+ } catch (error) {
34
+ try { fs.unlinkSync(temporary); } catch { /* best effort cleanup of our own temp file */ }
35
+ throw error;
36
+ }
37
+ return file;
38
+ }
39
+
40
+ export {
41
+ MANUAL_VALIDATION_ARTIFACT,
42
+ MANUAL_VALIDATION_SHA40,
43
+ MANUAL_VALIDATION_SHA64,
44
+ MANUAL_VALIDATION_TASK_ID,
45
+ MANUAL_VALIDATION_TIMESTAMP,
46
+ exactKeys,
47
+ isRecord,
48
+ validTimestamp,
49
+ writeJsonAtomic
50
+ };