@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,3 +1,4 @@
1
+ import type { VerificationShared } from "../task/verification-types.ts";
1
2
  import fs from "node:fs";
2
3
  import path from "node:path";
3
4
  import process from "node:process";
@@ -11,6 +12,13 @@ import { planInLabelUpdate, validateInLabelMapping } from "./in-label-sync.ts";
11
12
  import { readPrDeliveryFact } from "../task/pr-delivery-fact.ts";
12
13
  import { providerError, providerOperationContext, resourceIdentityNumber, unsupportedProviderOperation } from "./provider-bridge.ts";
13
14
  import { taskIssueIdentity } from "./task-identities.ts";
15
+ import { isTaskCommentTooLarge } from "./issue-comments.ts";
16
+ import {
17
+ CONTROL_MARKER_PATTERN,
18
+ canonicalizeCommentBody,
19
+ renderSafeCodeFence,
20
+ sanitizeMarkdownDocument
21
+ } from "./comment-safety.ts";
14
22
 
15
23
  const CHECK_TYPE = "platform-sync";
16
24
  const VERSION_LINE_REGEX = /^[0-9]+\.[0-9]+\.x$/;
@@ -27,9 +35,6 @@ const OPTION_LOCALIZATION: Record<string, string> = {
27
35
  "低": "Low"
28
36
  };
29
37
 
30
- let activeShared: any = null;
31
- let repoRoot = "";
32
-
33
38
  export function getDefaults(): any {
34
39
  return {
35
40
  statusLabels: {
@@ -50,59 +55,18 @@ export function getDefaults(): any {
50
55
  };
51
56
  }
52
57
 
53
- function getShared(): any {
54
- if (!activeShared) {
55
- throw new Error("platform-sync adapter shared utilities are unavailable");
56
- }
57
-
58
- return activeShared;
59
- }
60
-
61
- function loadTask(...args: any[]): any {
62
- return getShared().loadTask(...args);
63
- }
64
-
65
- function getCheckedRequirements(...args: any[]): any {
66
- return getShared().getCheckedRequirements(...args);
67
- }
68
-
69
- function normalizeContent(...args: any[]): any {
70
- return getShared().normalizeContent(...args);
71
- }
72
-
73
- function isBlank(...args: any[]): any {
74
- return getShared().isBlank(...args);
75
- }
76
-
77
- function escapeRegExp(...args: any[]): any {
78
- return getShared().escapeRegExp(...args);
79
- }
80
-
81
- function passResult(...args: any[]): any {
82
- return getShared().passResult(...args);
83
- }
84
-
85
- function failResult(...args: any[]): any {
86
- return getShared().failResult(...args);
87
- }
88
-
89
- function blockedResult(...args: any[]): any {
90
- return getShared().blockedResult(...args);
91
- }
92
-
93
- function safeStat(...args: any[]): any {
94
- return getShared().safeStat(...args);
58
+ function sanitizeCommentContent(content: string): string | null {
59
+ const result = sanitizeMarkdownDocument(content, { reservedMarkers: [CONTROL_MARKER_PATTERN] });
60
+ return result.ok ? result.value : null;
95
61
  }
96
62
 
97
- export async function check({ taskDir, config, artifactFile }: any, shared: any): Promise<any> {
98
- activeShared = shared;
99
- repoRoot = shared.repoRoot;
100
- const context = await buildSyncContext({ taskDir, config, artifactFile });
63
+ export async function check({ taskDir, config, artifactFile }: any, shared: VerificationShared): Promise<any> {
64
+ const context = await buildSyncContext({ taskDir, config, artifactFile }, shared);
101
65
  if (context.earlyReturn) {
102
66
  return context.earlyReturn;
103
67
  }
104
68
 
105
- const remoteData = await fetchRemoteData(context);
69
+ const remoteData = await fetchRemoteData(context, shared);
106
70
  if (remoteData.earlyReturn) {
107
71
  return remoteData.earlyReturn;
108
72
  }
@@ -127,57 +91,57 @@ export async function check({ taskDir, config, artifactFile }: any, shared: any)
127
91
  ];
128
92
 
129
93
  for (const subCheck of subChecks) {
130
- const result = subCheck(context, remoteData);
94
+ const result = subCheck(context, remoteData, shared);
131
95
  if (result) {
132
96
  return result;
133
97
  }
134
98
  }
135
99
 
136
- return passResult(CHECK_TYPE, `Platform sync checks passed for Issue ${context.issueNumber || "identity"}`);
100
+ return shared.passResult(CHECK_TYPE, `Platform sync checks passed for Issue ${context.issueNumber || "identity"}`);
137
101
  }
138
102
 
139
- async function buildSyncContext({ taskDir, config, artifactFile }: any): Promise<any> {
140
- const task = loadTask(taskDir);
103
+ async function buildSyncContext({ taskDir, config, artifactFile }: any, shared: VerificationShared): Promise<any> {
104
+ const task = shared.loadTask(taskDir);
141
105
  if (!task.ok) {
142
- return { earlyReturn: failResult(CHECK_TYPE, task.message) };
106
+ return { earlyReturn: shared.failResult(CHECK_TYPE, task.message) };
143
107
  }
144
108
 
145
109
  const issueIdentity = taskIssueIdentity(task.metadata);
146
110
  const issueNumber = resourceIdentityNumber(issueIdentity);
147
111
  const fact = readPrDeliveryFact(task.metadata);
148
112
  if (fact.status === "invalid") {
149
- return { earlyReturn: failResult(CHECK_TYPE, fact.error.message, "check_failed") };
113
+ return { earlyReturn: shared.failResult(CHECK_TYPE, fact.error.message, "check_failed") };
150
114
  }
151
115
  const prIdentity = fact.status === "valid" && fact.fact.state === "bound" ? fact.fact.identity.resource : null;
152
116
  const prNumber = resourceIdentityNumber(prIdentity);
153
- if (config.when === "issue_number_exists" && !issueIdentity) {
154
- return { earlyReturn: passResult(CHECK_TYPE, "Skipped: task has no issue_number") };
117
+ if (config.when === "platform_issue_identity_exists" && !issueIdentity) {
118
+ return { earlyReturn: shared.passResult(CHECK_TYPE, "Skipped: task has no platform_issue_identity") };
155
119
  }
156
120
  if (config.when === "pr_fact_bound" && !prIdentity) {
157
- return { earlyReturn: passResult(CHECK_TYPE, "Skipped: task has no verified bound pull request") };
121
+ return { earlyReturn: shared.passResult(CHECK_TYPE, "Skipped: task has no verified bound pull request") };
158
122
  }
159
123
 
160
124
  if (!issueIdentity) {
161
- return { earlyReturn: passResult(CHECK_TYPE, "Skipped: platform-sync not required for this task") };
125
+ return { earlyReturn: shared.passResult(CHECK_TYPE, "Skipped: platform-sync not required for this task") };
162
126
  }
163
127
 
164
- const loaded = await resolvePlatformProviderContext({ cwd: repoRoot });
128
+ const loaded = await resolvePlatformProviderContext({ cwd: shared.repoRoot });
165
129
  const platformContext = loaded.ok ? loaded.value.context : loaded.context;
166
130
  if (platformContext.status === "failed") {
167
- return { earlyReturn: failResult(CHECK_TYPE, platformContext.error?.message || "Platform context failed", "check_failed") };
131
+ return { earlyReturn: shared.failResult(CHECK_TYPE, platformContext.error?.message || "Platform context failed", "check_failed") };
168
132
  }
169
133
  if (platformContext.status === "blocked") {
170
- return { earlyReturn: blockedResult(CHECK_TYPE, platformContext.error?.message || "Platform context blocked", "network_error") };
134
+ return { earlyReturn: shared.blockedResult(CHECK_TYPE, platformContext.error?.message || "Platform context blocked", "network_error") };
171
135
  }
172
136
  if (!platformContext.platform.repository) {
173
137
  if (platformContext.error?.code === "REMOTE_MISSING" || platformContext.error?.code === "REMOTE_INVALID") {
174
- return { earlyReturn: blockedResult(CHECK_TYPE, platformContext.error.message, "network_error") };
138
+ return { earlyReturn: shared.blockedResult(CHECK_TYPE, platformContext.error.message, "network_error") };
175
139
  }
176
- return { earlyReturn: passResult(CHECK_TYPE, `Skipped: ${platformContext.error?.message || "platform unavailable"}`) };
140
+ return { earlyReturn: shared.passResult(CHECK_TYPE, `Skipped: ${platformContext.error?.message || "platform unavailable"}`) };
177
141
  }
178
142
  const expectedValues = resolveExpectedValues(config);
179
143
  if (!expectedValues.ok) {
180
- return { earlyReturn: failResult(CHECK_TYPE, expectedValues.message, "check_failed") };
144
+ return { earlyReturn: shared.failResult(CHECK_TYPE, expectedValues.message, "check_failed") };
181
145
  }
182
146
 
183
147
  const marker = expectedValues.commentMarker
@@ -263,7 +227,7 @@ function resolveDefaultValue({ collection, key, value, configKey }: any): any {
263
227
  return { ok: true, value: resolvedValue };
264
228
  }
265
229
 
266
- async function fetchRemoteData(context: any): Promise<any> {
230
+ async function fetchRemoteData(context: any, shared: VerificationShared): Promise<any> {
267
231
  const provider = context.provider;
268
232
  const operationContext = providerOperationContext(context.loadedContext, context.taskDir);
269
233
  const facts = provider?.verification?.fetchRemoteFacts
@@ -279,8 +243,8 @@ async function fetchRemoteData(context: any): Promise<any> {
279
243
  if (!facts.ok) {
280
244
  return {
281
245
  earlyReturn: facts.error.retryable
282
- ? blockedResult(CHECK_TYPE, providerError(facts.error, "PLATFORM_PROVIDER_OPERATION_FAILED").message, "network_error")
283
- : failResult(CHECK_TYPE, providerError(facts.error, "PLATFORM_PROVIDER_OPERATION_FAILED").message, "check_failed")
246
+ ? shared.blockedResult(CHECK_TYPE, providerError(facts.error, "PLATFORM_PROVIDER_OPERATION_FAILED").message, "network_error")
247
+ : shared.failResult(CHECK_TYPE, providerError(facts.error, "PLATFORM_PROVIDER_OPERATION_FAILED").message, "check_failed")
284
248
  };
285
249
  }
286
250
  const issueSnapshot = facts.value.issue;
@@ -305,8 +269,8 @@ async function fetchRemoteData(context: any): Promise<any> {
305
269
  const listed = await provider.comments.list({ context: operationContext, parent: context.prIdentity });
306
270
  if (!listed.ok) return {
307
271
  earlyReturn: listed.error.retryable
308
- ? blockedResult(CHECK_TYPE, listed.error.message, "network_error")
309
- : failResult(CHECK_TYPE, listed.error.message, "check_failed")
272
+ ? shared.blockedResult(CHECK_TYPE, listed.error.message, "network_error")
273
+ : shared.failResult(CHECK_TYPE, listed.error.message, "check_failed")
310
274
  };
311
275
  prComments = listed.value.map((comment: { id: string; body: string }) => ({ id: comment.id, body: comment.body }));
312
276
  }
@@ -314,8 +278,8 @@ async function fetchRemoteData(context: any): Promise<any> {
314
278
  let inLabelMapping: Record<string, string[]> = {};
315
279
  let repositoryLabels: string[] = [];
316
280
  if (context.config.verify_in_labels_computed && context.hasTriage) {
317
- const mapping = loadInLabelMapping();
318
- if (!mapping.ok) return { earlyReturn: failResult(CHECK_TYPE, mapping.message, "check_failed") };
281
+ const mapping = loadInLabelMapping(shared);
282
+ if (!mapping.ok) return { earlyReturn: shared.failResult(CHECK_TYPE, mapping.message, "check_failed") };
319
283
  inLabelMapping = mapping.value;
320
284
  if (Object.keys(inLabelMapping).length > 0) {
321
285
  const labels = provider?.issues?.listLabels
@@ -325,8 +289,8 @@ async function fetchRemoteData(context: any): Promise<any> {
325
289
  const error = providerError(labels.error, "PLATFORM_PROVIDER_OPERATION_FAILED");
326
290
  return {
327
291
  earlyReturn: error.retryable
328
- ? blockedResult(CHECK_TYPE, `${error.code}: ${error.message}`, "network_error")
329
- : failResult(CHECK_TYPE, `${error.code}: ${error.message}`, "check_failed")
292
+ ? shared.blockedResult(CHECK_TYPE, `${error.code}: ${error.message}`, "network_error")
293
+ : shared.failResult(CHECK_TYPE, `${error.code}: ${error.message}`, "check_failed")
330
294
  };
331
295
  }
332
296
  repositoryLabels = labels.value;
@@ -367,7 +331,7 @@ function shouldFetchComments(config: any): any {
367
331
  );
368
332
  }
369
333
 
370
- function checkStatusLabel(context: any, remoteData: any): any {
334
+ function checkStatusLabel(context: any, remoteData: any, shared: VerificationShared): any {
371
335
  if (!context.expectedStatusLabel || !context.hasTriage) {
372
336
  return null;
373
337
  }
@@ -381,13 +345,13 @@ function checkStatusLabel(context: any, remoteData: any): any {
381
345
  return null;
382
346
  }
383
347
 
384
- return failResult(CHECK_TYPE,
348
+ return shared.failResult(CHECK_TYPE,
385
349
  `Expected label '${context.expectedStatusLabel}' not found on Issue #${context.issueNumber}`,
386
350
  "check_failed"
387
351
  );
388
352
  }
389
353
 
390
- function checkClosedIssueStatusLabels(context: any, remoteData: any): any {
354
+ function checkClosedIssueStatusLabels(context: any, remoteData: any, shared: VerificationShared): any {
391
355
  if (!context.config.verify_closed_issue_has_no_status_labels) {
392
356
  return null;
393
357
  }
@@ -402,13 +366,13 @@ function checkClosedIssueStatusLabels(context: any, remoteData: any): any {
402
366
  return null;
403
367
  }
404
368
 
405
- return failResult(CHECK_TYPE,
369
+ return shared.failResult(CHECK_TYPE,
406
370
  `Closed Issue #${context.issueNumber} retains status labels: ${statusLabels.join(", ")}`,
407
371
  "check_failed"
408
372
  );
409
373
  }
410
374
 
411
- function checkCommentMarker(context: any, remoteData: any): any {
375
+ function checkCommentMarker(context: any, remoteData: any, shared: VerificationShared): any {
412
376
  if (!context.marker) {
413
377
  return null;
414
378
  }
@@ -418,13 +382,13 @@ function checkCommentMarker(context: any, remoteData: any): any {
418
382
  return null;
419
383
  }
420
384
 
421
- return failResult(CHECK_TYPE,
385
+ return shared.failResult(CHECK_TYPE,
422
386
  `Expected comment marker '${context.marker}' not found on Issue #${context.issueNumber}`,
423
387
  "check_failed"
424
388
  );
425
389
  }
426
390
 
427
- function checkPrCommentMarker(context: any, remoteData: any): any {
391
+ function checkPrCommentMarker(context: any, remoteData: any, shared: VerificationShared): any {
428
392
  if (!context.prMarker) {
429
393
  return null;
430
394
  }
@@ -434,19 +398,19 @@ function checkPrCommentMarker(context: any, remoteData: any): any {
434
398
  return null;
435
399
  }
436
400
 
437
- return failResult(CHECK_TYPE,
401
+ return shared.failResult(CHECK_TYPE,
438
402
  `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`,
439
403
  "check_failed"
440
404
  );
441
405
  }
442
406
 
443
- function checkPrCommentLastCommit(context: any, remoteData: any): any {
407
+ function checkPrCommentLastCommit(context: any, remoteData: any, shared: VerificationShared): any {
444
408
  if (!context.config.verify_pr_comment_last_commit_matches_head) {
445
409
  return null;
446
410
  }
447
411
 
448
412
  if (!context.prMarker) {
449
- return failResult(CHECK_TYPE,
413
+ return shared.failResult(CHECK_TYPE,
450
414
  "verify_pr_comment_last_commit_matches_head requires expected_pr_comment_marker",
451
415
  "check_failed"
452
416
  );
@@ -454,7 +418,7 @@ function checkPrCommentLastCommit(context: any, remoteData: any): any {
454
418
 
455
419
  const comment = findCommentByMarker(remoteData.prComments, context.prMarker);
456
420
  if (!comment) {
457
- return failResult(CHECK_TYPE,
421
+ return shared.failResult(CHECK_TYPE,
458
422
  `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`,
459
423
  "check_failed"
460
424
  );
@@ -462,33 +426,33 @@ function checkPrCommentLastCommit(context: any, remoteData: any): any {
462
426
 
463
427
  const match = String(comment.body || "").match(/<!--\s*last-commit:\s*([0-9a-f]{7,40})\s*-->/i);
464
428
  if (!match) {
465
- return failResult(CHECK_TYPE,
429
+ return shared.failResult(CHECK_TYPE,
466
430
  `PR #${context.prNumber} summary comment is missing '<!-- last-commit: <sha> -->' metadata`,
467
431
  "check_failed"
468
432
  );
469
433
  }
470
434
 
471
435
  const expectedHead = String(remoteData.prHeadSha || "").trim();
472
- if (!expectedHead) return blockedResult(CHECK_TYPE, "Unable to resolve the PR head SHA", "network_error");
436
+ if (!expectedHead) return shared.blockedResult(CHECK_TYPE, "Unable to resolve the PR head SHA", "network_error");
473
437
  const actualHead = match[1]!.trim();
474
438
  if (expectedHead === actualHead) {
475
439
  return null;
476
440
  }
477
441
 
478
- return failResult(CHECK_TYPE,
442
+ return shared.failResult(CHECK_TYPE,
479
443
  `PR #${context.prNumber} summary comment last-commit metadata mismatch: expected ${expectedHead}, got ${actualHead}`,
480
444
  "check_failed"
481
445
  );
482
446
  }
483
447
 
484
- function checkPrCommentRequiredPatterns(context: any, remoteData: any): any {
448
+ function checkPrCommentRequiredPatterns(context: any, remoteData: any, shared: VerificationShared): any {
485
449
  const patterns = context.config.expected_pr_comment_required_patterns || [];
486
450
  if (!Array.isArray(patterns) || patterns.length === 0) {
487
451
  return null;
488
452
  }
489
453
 
490
454
  if (!context.prMarker) {
491
- return failResult(CHECK_TYPE,
455
+ return shared.failResult(CHECK_TYPE,
492
456
  "expected_pr_comment_required_patterns requires expected_pr_comment_marker",
493
457
  "check_failed"
494
458
  );
@@ -496,7 +460,7 @@ function checkPrCommentRequiredPatterns(context: any, remoteData: any): any {
496
460
 
497
461
  const comment = findCommentByMarker(remoteData.prComments, context.prMarker);
498
462
  if (!comment) {
499
- return failResult(CHECK_TYPE,
463
+ return shared.failResult(CHECK_TYPE,
500
464
  `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`,
501
465
  "check_failed"
502
466
  );
@@ -506,7 +470,7 @@ function checkPrCommentRequiredPatterns(context: any, remoteData: any): any {
506
470
  for (const pattern of patterns) {
507
471
  const regex = new RegExp(pattern, "m");
508
472
  if (!regex.test(body)) {
509
- return failResult(CHECK_TYPE,
473
+ return shared.failResult(CHECK_TYPE,
510
474
  `PR #${context.prNumber} summary comment is missing required pattern: ${pattern}`,
511
475
  "check_failed"
512
476
  );
@@ -516,31 +480,54 @@ function checkPrCommentRequiredPatterns(context: any, remoteData: any): any {
516
480
  return null;
517
481
  }
518
482
 
519
- function checkCommentContent(context: any, remoteData: any): any {
483
+ function checkCommentContent(context: any, remoteData: any, shared: VerificationShared): any {
520
484
  if (!context.config.verify_comment_content) {
521
485
  return null;
522
486
  }
523
487
 
524
488
  if (!context.marker) {
525
- return failResult(CHECK_TYPE, "verify_comment_content requires expected_comment_marker", "check_failed");
489
+ return shared.failResult(CHECK_TYPE, "verify_comment_content requires expected_comment_marker", "check_failed");
526
490
  }
527
491
 
528
- if (!context.artifactPath || !safeStat(context.artifactPath)) {
529
- return failResult(CHECK_TYPE,
492
+ if (!context.artifactPath || !shared.safeStat(context.artifactPath)) {
493
+ return shared.failResult(CHECK_TYPE,
530
494
  `Artifact not found for comment verification: ${context.artifactFile || "(missing artifactFile)"}`,
531
495
  "check_failed"
532
496
  );
533
497
  }
534
498
 
535
499
  const comment = findCommentByMarker(remoteData.comments, context.marker);
536
- const localContent = normalizeContent(fs.readFileSync(context.artifactPath, "utf8"));
537
- const commentContent = normalizeContent(extractCommentBody(comment?.body || ""));
500
+ let localContent: string;
501
+ let commentContent: string;
502
+ try {
503
+ const localCanonical = canonicalizeCommentBody(fs.readFileSync(context.artifactPath, "utf8"));
504
+ const commentCanonical = canonicalizeCommentBody(extractCommentBody(comment?.body || ""));
505
+ if (!localCanonical.ok) {
506
+ return shared.failResult(CHECK_TYPE,
507
+ `Comment content cannot be canonicalized for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${localCanonical.error.message}`,
508
+ "check_failed"
509
+ );
510
+ }
511
+ if (!commentCanonical.ok) {
512
+ return shared.failResult(CHECK_TYPE,
513
+ `Comment content cannot be canonicalized for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${commentCanonical.error.message}`,
514
+ "check_failed"
515
+ );
516
+ }
517
+ localContent = shared.normalizeContent(localCanonical.value);
518
+ commentContent = shared.normalizeContent(commentCanonical.value);
519
+ } catch (error) {
520
+ return shared.failResult(CHECK_TYPE,
521
+ `Comment content cannot be read for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${error instanceof Error ? error.message : String(error)}`,
522
+ "check_failed"
523
+ );
524
+ }
538
525
 
539
526
  if (localContent === commentContent) {
540
527
  return null;
541
528
  }
542
529
 
543
- return failResult(CHECK_TYPE,
530
+ return shared.failResult(CHECK_TYPE,
544
531
  buildCommentContentMismatchMessage(
545
532
  path.basename(context.artifactPath, path.extname(context.artifactPath)),
546
533
  context.issueNumber,
@@ -551,34 +538,44 @@ function checkCommentContent(context: any, remoteData: any): any {
551
538
  );
552
539
  }
553
540
 
554
- function checkTaskCommentContent(context: any, remoteData: any): any {
541
+ function checkTaskCommentContent(context: any, remoteData: any, shared: VerificationShared): any {
555
542
  if (!context.config.verify_task_comment_content) {
556
543
  return null;
557
544
  }
545
+ if (isTaskCommentTooLarge(context.task.content)) {
546
+ return null;
547
+ }
558
548
 
559
549
  const taskMarker = `<!-- sync-issue:${context.task.metadata.id}:task -->`;
560
550
  const comment = findCommentByMarker(remoteData.comments, taskMarker);
561
551
  if (!comment) {
562
- return failResult(CHECK_TYPE,
552
+ return shared.failResult(CHECK_TYPE,
563
553
  `Expected comment marker '${taskMarker}' not found on Issue #${context.issueNumber}`,
564
554
  "check_failed"
565
555
  );
566
556
  }
567
557
 
568
- const expectedBody = normalizeContent(buildExpectedTaskBody(context.task.content));
569
- const commentBody = normalizeContent(extractCommentBody(comment.body || ""));
558
+ const expectedTaskBody = buildExpectedTaskBody(context.task.content, shared);
559
+ if (expectedTaskBody === null) {
560
+ return shared.failResult(CHECK_TYPE,
561
+ "Task content cannot be rendered safely for comment verification",
562
+ "check_failed"
563
+ );
564
+ }
565
+ const expectedBody = shared.normalizeContent(expectedTaskBody);
566
+ const commentBody = shared.normalizeContent(extractCommentBody(comment.body || ""));
570
567
 
571
568
  if (expectedBody === commentBody) {
572
569
  return null;
573
570
  }
574
571
 
575
- return failResult(CHECK_TYPE,
572
+ return shared.failResult(CHECK_TYPE,
576
573
  buildCommentContentMismatchMessage("task", context.issueNumber, expectedBody, commentBody),
577
574
  "check_failed"
578
575
  );
579
576
  }
580
577
 
581
- function checkPrTypeLabel(context: any, remoteData: any): any {
578
+ function checkPrTypeLabel(context: any, remoteData: any, shared: VerificationShared): any {
582
579
  if (!context.config.verify_pr_type_label || !context.hasTriage || !context.prNumber || !remoteData.prLabels) {
583
580
  return null;
584
581
  }
@@ -592,13 +589,13 @@ function checkPrTypeLabel(context: any, remoteData: any): any {
592
589
  return null;
593
590
  }
594
591
 
595
- return failResult(CHECK_TYPE,
592
+ return shared.failResult(CHECK_TYPE,
596
593
  `Expected type label '${expectedLabel}' not found on PR #${context.prNumber}`,
597
594
  "check_failed"
598
595
  );
599
596
  }
600
597
 
601
- function checkInLabelsMatchPr(context: any, remoteData: any): any {
598
+ function checkInLabelsMatchPr(context: any, remoteData: any, shared: VerificationShared): any {
602
599
  if (!context.config.verify_in_labels_match_pr || !context.hasTriage || !context.prNumber || !remoteData.prLabels) {
603
600
  return null;
604
601
  }
@@ -614,22 +611,22 @@ function checkInLabelsMatchPr(context: any, remoteData: any): any {
614
611
  return null;
615
612
  }
616
613
 
617
- return failResult(CHECK_TYPE,
614
+ return shared.failResult(CHECK_TYPE,
618
615
  `in: labels mismatch — PR #${context.prNumber} has [${formatLabelList(prInLabels)}], Issue #${context.issueNumber} has [${formatLabelList(issueInLabels)}]`,
619
616
  "check_failed"
620
617
  );
621
618
  }
622
619
 
623
- function checkInLabelsComputed(context: any, remoteData: any): any {
620
+ function checkInLabelsComputed(context: any, remoteData: any, shared: VerificationShared): any {
624
621
  if (!context.config.verify_in_labels_computed || !context.hasTriage) {
625
622
  return null;
626
623
  }
627
624
 
628
- const expectedInLabels = computeExpectedInLabels(context.taskDir, remoteData.repositoryLabels, remoteData.inLabelMapping);
625
+ const expectedInLabels = computeExpectedInLabels(context.taskDir, remoteData.repositoryLabels, remoteData.inLabelMapping, shared);
629
626
  if (!expectedInLabels.ok) {
630
627
  return expectedInLabels.type === "check_failed"
631
- ? failResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type)
632
- : blockedResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type);
628
+ ? shared.failResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type)
629
+ : shared.blockedResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type);
633
630
  }
634
631
 
635
632
  if (expectedInLabels.mode === "skipped") {
@@ -644,19 +641,19 @@ function checkInLabelsComputed(context: any, remoteData: any): any {
644
641
  return null;
645
642
  }
646
643
 
647
- return failResult(
644
+ return shared.failResult(
648
645
  CHECK_TYPE,
649
646
  `Issue #${context.issueNumber} in: labels do not match committed changes: expected [${formatLabelList(expectedInLabels.labels)}], got [${formatLabelList(actualInLabels)}]`,
650
647
  "check_failed"
651
648
  );
652
649
  }
653
650
 
654
- function checkSyncedRequirements(context: any, remoteData: any): any {
651
+ function checkSyncedRequirements(context: any, remoteData: any, shared: VerificationShared): any {
655
652
  if (!context.config.sync_checked_requirements || !context.hasTriage) {
656
653
  return null;
657
654
  }
658
655
 
659
- const checkedRequirements = getCheckedRequirements(context.task.content);
656
+ const checkedRequirements = shared.getCheckedRequirements(context.task.content);
660
657
  if (checkedRequirements.length === 0) {
661
658
  return null;
662
659
  }
@@ -664,13 +661,13 @@ function checkSyncedRequirements(context: any, remoteData: any): any {
664
661
  const issueBody = remoteData.issue.body || "";
665
662
  const resolution = resolveRequirementSection(
666
663
  issueBody,
667
- requirementSectionAnchors(repoRoot, context.task.metadata.type || "task")
664
+ requirementSectionAnchors(shared.repoRoot, context.task.metadata.type || "task")
668
665
  );
669
666
  if (resolution.status === "missing") {
670
667
  return null;
671
668
  }
672
669
  if (resolution.status === "ambiguous") {
673
- return failResult(CHECK_TYPE,
670
+ return shared.failResult(CHECK_TYPE,
674
671
  `Issue #${context.issueNumber} requirements section is ambiguous`,
675
672
  "check_failed"
676
673
  );
@@ -683,13 +680,13 @@ function checkSyncedRequirements(context: any, remoteData: any): any {
683
680
  return null;
684
681
  }
685
682
 
686
- return failResult(CHECK_TYPE,
683
+ return shared.failResult(CHECK_TYPE,
687
684
  `Issue body is missing checked requirements: ${missingRequirements.join(", ")}`,
688
685
  "check_failed"
689
686
  );
690
687
  }
691
688
 
692
- function checkIssueType(context: any, remoteData: any): any {
689
+ function checkIssueType(context: any, remoteData: any, shared: VerificationShared): any {
693
690
  if (!context.config.verify_issue_type || !context.hasPush) {
694
691
  return null;
695
692
  }
@@ -703,7 +700,7 @@ function checkIssueType(context: any, remoteData: any): any {
703
700
  return null;
704
701
  }
705
702
 
706
- return failResult(CHECK_TYPE,
703
+ return shared.failResult(CHECK_TYPE,
707
704
  `Issue #${context.issueNumber} has no Issue Type set`,
708
705
  "check_failed"
709
706
  );
@@ -711,7 +708,7 @@ function checkIssueType(context: any, remoteData: any): any {
711
708
 
712
709
  const expectedType = mapTaskTypeToIssueType(context.task.metadata.type);
713
710
  if (expectedType && remoteData.issueType !== expectedType) {
714
- return failResult(CHECK_TYPE,
711
+ return shared.failResult(CHECK_TYPE,
715
712
  `Issue #${context.issueNumber} has type '${remoteData.issueType}', expected '${expectedType}' (from task type '${context.task.metadata.type}')`,
716
713
  "check_failed"
717
714
  );
@@ -720,7 +717,7 @@ function checkIssueType(context: any, remoteData: any): any {
720
717
  return null;
721
718
  }
722
719
 
723
- function checkIssueFields(context: any, remoteData: any): any {
720
+ function checkIssueFields(context: any, remoteData: any, shared: VerificationShared): any {
724
721
  if (!context.config.verify_issue_fields || !context.hasPush) {
725
722
  return null;
726
723
  }
@@ -731,7 +728,7 @@ function checkIssueFields(context: any, remoteData: any): any {
731
728
 
732
729
  for (const [metadataKey, fieldName] of Object.entries(FRONTMATTER_FIELD_MAP)) {
733
730
  const expectedRaw = context.task.metadata[metadataKey];
734
- if (isBlank(expectedRaw) || !remoteData.issueFields.pinnedNames.has(fieldName)) {
731
+ if (shared.isBlank(expectedRaw) || !remoteData.issueFields.pinnedNames.has(fieldName)) {
735
732
  continue;
736
733
  }
737
734
 
@@ -742,14 +739,14 @@ function checkIssueFields(context: any, remoteData: any): any {
742
739
  }
743
740
 
744
741
  if (!actual) {
745
- return failResult(CHECK_TYPE,
742
+ return shared.failResult(CHECK_TYPE,
746
743
  `Issue #${context.issueNumber} field '${fieldName}' is missing, expected '${expected.value}'`,
747
744
  "check_failed"
748
745
  );
749
746
  }
750
747
 
751
748
  if (actual.kind !== expected.kind || actual.value !== expected.value) {
752
- return failResult(CHECK_TYPE,
749
+ return shared.failResult(CHECK_TYPE,
753
750
  `Issue #${context.issueNumber} field '${fieldName}' is '${actual.value}', expected '${expected.value}'`,
754
751
  "check_failed"
755
752
  );
@@ -759,13 +756,13 @@ function checkIssueFields(context: any, remoteData: any): any {
759
756
  return null;
760
757
  }
761
758
 
762
- function checkPrAssignee(context: any, remoteData: any): any {
759
+ function checkPrAssignee(context: any, remoteData: any, shared: VerificationShared): any {
763
760
  if (!context.config.verify_pr_assignee || !context.hasPush || !context.prNumber) {
764
761
  return null;
765
762
  }
766
763
 
767
764
  if (!remoteData.prAssignees || remoteData.prAssignees.length === 0) {
768
- return failResult(CHECK_TYPE,
765
+ return shared.failResult(CHECK_TYPE,
769
766
  `PR #${context.prNumber} has no assignee`,
770
767
  "check_failed"
771
768
  );
@@ -774,20 +771,20 @@ function checkPrAssignee(context: any, remoteData: any): any {
774
771
  return null;
775
772
  }
776
773
 
777
- function checkMilestone(context: any, remoteData: any): any {
774
+ function checkMilestone(context: any, remoteData: any, shared: VerificationShared): any {
778
775
  if (!context.config.verify_milestone || !context.hasTriage) {
779
776
  return null;
780
777
  }
781
778
 
782
779
  if (!remoteData.issue?.milestone?.title) {
783
- return failResult(CHECK_TYPE,
780
+ return shared.failResult(CHECK_TYPE,
784
781
  `Issue #${context.issueNumber} has no milestone set`,
785
782
  "check_failed"
786
783
  );
787
784
  }
788
785
 
789
786
  if (context.prNumber && remoteData.prMilestone !== undefined && !remoteData.prMilestone?.title) {
790
- return failResult(CHECK_TYPE,
787
+ return shared.failResult(CHECK_TYPE,
791
788
  `PR #${context.prNumber} has no milestone set`,
792
789
  "check_failed"
793
790
  );
@@ -796,13 +793,13 @@ function checkMilestone(context: any, remoteData: any): any {
796
793
  if (context.config.verify_milestone_specific) {
797
794
  const issueTitle = remoteData.issue.milestone.title;
798
795
  if (VERSION_LINE_REGEX.test(issueTitle)) {
799
- return failResult(CHECK_TYPE,
796
+ return shared.failResult(CHECK_TYPE,
800
797
  `Issue #${context.issueNumber} milestone '${issueTitle}' is a release line; narrow to a specific version (e.g. ${issueTitle.replace(/\.x$/, ".N")}) before continuing`,
801
798
  "check_failed"
802
799
  );
803
800
  }
804
801
  if (context.prNumber && remoteData.prMilestone?.title && VERSION_LINE_REGEX.test(remoteData.prMilestone.title)) {
805
- return failResult(CHECK_TYPE,
802
+ return shared.failResult(CHECK_TYPE,
806
803
  `PR #${context.prNumber} milestone '${remoteData.prMilestone.title}' is a release line; narrow to a specific version before continuing`,
807
804
  "check_failed"
808
805
  );
@@ -863,19 +860,20 @@ function extractCommentBody(commentBody: any): any {
863
860
  return lines.slice(start, end).join("\n");
864
861
  }
865
862
 
866
- function buildExpectedTaskBody(taskContent: any): any {
863
+ function buildExpectedTaskBody(taskContent: any, shared: VerificationShared): any {
867
864
  const frontmatterMatch = taskContent.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
868
865
  if (!frontmatterMatch) {
869
- return taskContent.trim();
866
+ return sanitizeCommentContent(taskContent.trim());
870
867
  }
871
868
 
872
- const body = taskContent.slice(frontmatterMatch[0].length).trim();
869
+ const body = sanitizeCommentContent(taskContent.slice(frontmatterMatch[0].length).trim());
870
+ if (body === null) {
871
+ return null;
872
+ }
873
873
  return [
874
- buildTaskFrontmatterSummary(),
874
+ buildTaskFrontmatterSummary(shared),
875
875
  "",
876
- "```yaml",
877
- frontmatterMatch[0].trim(),
878
- "```",
876
+ renderSafeCodeFence(frontmatterMatch[0].trim(), "yaml"),
879
877
  "",
880
878
  "</details>",
881
879
  "",
@@ -883,8 +881,8 @@ function buildExpectedTaskBody(taskContent: any): any {
883
881
  ].join("\n").trim();
884
882
  }
885
883
 
886
- function buildTaskFrontmatterSummary(): any {
887
- const language = loadProjectLanguage();
884
+ function buildTaskFrontmatterSummary(shared: VerificationShared): any {
885
+ const language = loadProjectLanguage(shared);
888
886
  if (language === "en" || language === "en-US") {
889
887
  return "<details><summary>Metadata (frontmatter)</summary>";
890
888
  }
@@ -892,13 +890,13 @@ function buildTaskFrontmatterSummary(): any {
892
890
  return "<details><summary>元数据 (frontmatter)</summary>";
893
891
  }
894
892
 
895
- function loadProjectLanguage(): any {
893
+ function loadProjectLanguage(shared: VerificationShared): any {
896
894
  const override = process.env.VALIDATE_ARTIFACT_LANGUAGE;
897
- if (!isBlank(override)) {
895
+ if (!shared.isBlank(override)) {
898
896
  return String(override).trim();
899
897
  }
900
898
 
901
- const configPath = path.join(repoRoot, ".agents", ".airc.json");
899
+ const configPath = path.join(shared.repoRoot, ".agents", ".airc.json");
902
900
  if (!fs.existsSync(configPath)) {
903
901
  return "";
904
902
  }
@@ -1035,8 +1033,8 @@ function formatLabelList(labels: any): any {
1035
1033
  return labels.length > 0 ? labels.join(", ") : "none";
1036
1034
  }
1037
1035
 
1038
- function computeExpectedInLabels(taskDir: any, repositoryLabels: string[] = [], mappingOverride?: Record<string, string[]>): any {
1039
- const task = loadTask(taskDir);
1036
+ function computeExpectedInLabels(taskDir: any, repositoryLabels: string[], mappingOverride: Record<string, string[]> | undefined, shared: VerificationShared): any {
1037
+ const task = shared.loadTask(taskDir);
1040
1038
  if (!task.ok) {
1041
1039
  return task;
1042
1040
  }
@@ -1071,8 +1069,8 @@ function computeExpectedInLabels(taskDir: any, repositoryLabels: string[] = [],
1071
1069
  return { ok: true, labels: planned.target, mode: "mapped" };
1072
1070
  }
1073
1071
 
1074
- function loadInLabelMapping(): any {
1075
- const configPath = path.join(repoRoot, ".agents", ".airc.json");
1072
+ function loadInLabelMapping(shared: VerificationShared): any {
1073
+ const configPath = path.join(shared.repoRoot, ".agents", ".airc.json");
1076
1074
  if (!fs.existsSync(configPath)) {
1077
1075
  return { ok: true, value: {} };
1078
1076
  }
@@ -1105,13 +1103,13 @@ function gitText(args: any, cwd: any): any {
1105
1103
  }
1106
1104
 
1107
1105
  function resolvePrHeadSha(context: any): any {
1108
- const fallback = () => withRetry(() => gitText(["rev-parse", "HEAD"], context.taskDir));
1106
+ const fallback = () => gitText(["rev-parse", "HEAD"], context.taskDir);
1109
1107
  const branch = String(context.task?.metadata?.branch || "").trim();
1110
1108
  if (!branch) {
1111
1109
  return fallback();
1112
1110
  }
1113
1111
 
1114
- const worktreeList = withRetry(() => gitText(["worktree", "list", "--porcelain"], context.taskDir));
1112
+ const worktreeList = gitText(["worktree", "list", "--porcelain"], context.taskDir);
1115
1113
  if (!worktreeList.ok) {
1116
1114
  return fallback();
1117
1115
  }
@@ -1121,7 +1119,7 @@ function resolvePrHeadSha(context: any): any {
1121
1119
  return fallback();
1122
1120
  }
1123
1121
 
1124
- const headInWorktree = withRetry(() => gitText(["rev-parse", "HEAD"], matchedWorktree));
1122
+ const headInWorktree = gitText(["rev-parse", "HEAD"], matchedWorktree);
1125
1123
  if (!headInWorktree.ok) {
1126
1124
  return fallback();
1127
1125
  }
@@ -1149,10 +1147,6 @@ function findWorktreeForBranch(porcelainOutput: any, branch: any): any {
1149
1147
  return null;
1150
1148
  }
1151
1149
 
1152
- function withRetry(operation: any): any {
1153
- return operation();
1154
- }
1155
-
1156
1150
  function interpolate(template: any, taskDir: any, artifactFile: any): any {
1157
1151
  const artifactStem = artifactFile ? path.basename(artifactFile, path.extname(artifactFile)) : "";
1158
1152
  return template