@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
@@ -10,6 +10,8 @@ import { planInLabelUpdate, validateInLabelMapping } from "./in-label-sync.js";
10
10
  import { readPrDeliveryFact } from "../task/pr-delivery-fact.js";
11
11
  import { providerError, providerOperationContext, resourceIdentityNumber, unsupportedProviderOperation } from "./provider-bridge.js";
12
12
  import { taskIssueIdentity } from "./task-identities.js";
13
+ import { isTaskCommentTooLarge } from "./issue-comments.js";
14
+ import { CONTROL_MARKER_PATTERN, canonicalizeCommentBody, renderSafeCodeFence, sanitizeMarkdownDocument } from "./comment-safety.js";
13
15
  const CHECK_TYPE = "platform-sync";
14
16
  const VERSION_LINE_REGEX = /^[0-9]+\.[0-9]+\.x$/;
15
17
  const FRONTMATTER_FIELD_MAP = {
@@ -24,8 +26,6 @@ const OPTION_LOCALIZATION = {
24
26
  "中": "Medium",
25
27
  "低": "Low"
26
28
  };
27
- let activeShared = null;
28
- let repoRoot = "";
29
29
  export function getDefaults() {
30
30
  return {
31
31
  statusLabels: {
@@ -45,47 +45,16 @@ export function getDefaults() {
45
45
  }
46
46
  };
47
47
  }
48
- function getShared() {
49
- if (!activeShared) {
50
- throw new Error("platform-sync adapter shared utilities are unavailable");
51
- }
52
- return activeShared;
53
- }
54
- function loadTask(...args) {
55
- return getShared().loadTask(...args);
56
- }
57
- function getCheckedRequirements(...args) {
58
- return getShared().getCheckedRequirements(...args);
59
- }
60
- function normalizeContent(...args) {
61
- return getShared().normalizeContent(...args);
62
- }
63
- function isBlank(...args) {
64
- return getShared().isBlank(...args);
65
- }
66
- function escapeRegExp(...args) {
67
- return getShared().escapeRegExp(...args);
68
- }
69
- function passResult(...args) {
70
- return getShared().passResult(...args);
71
- }
72
- function failResult(...args) {
73
- return getShared().failResult(...args);
74
- }
75
- function blockedResult(...args) {
76
- return getShared().blockedResult(...args);
77
- }
78
- function safeStat(...args) {
79
- return getShared().safeStat(...args);
48
+ function sanitizeCommentContent(content) {
49
+ const result = sanitizeMarkdownDocument(content, { reservedMarkers: [CONTROL_MARKER_PATTERN] });
50
+ return result.ok ? result.value : null;
80
51
  }
81
52
  export async function check({ taskDir, config, artifactFile }, shared) {
82
- activeShared = shared;
83
- repoRoot = shared.repoRoot;
84
- const context = await buildSyncContext({ taskDir, config, artifactFile });
53
+ const context = await buildSyncContext({ taskDir, config, artifactFile }, shared);
85
54
  if (context.earlyReturn) {
86
55
  return context.earlyReturn;
87
56
  }
88
- const remoteData = await fetchRemoteData(context);
57
+ const remoteData = await fetchRemoteData(context, shared);
89
58
  if (remoteData.earlyReturn) {
90
59
  return remoteData.earlyReturn;
91
60
  }
@@ -108,52 +77,52 @@ export async function check({ taskDir, config, artifactFile }, shared) {
108
77
  checkMilestone
109
78
  ];
110
79
  for (const subCheck of subChecks) {
111
- const result = subCheck(context, remoteData);
80
+ const result = subCheck(context, remoteData, shared);
112
81
  if (result) {
113
82
  return result;
114
83
  }
115
84
  }
116
- return passResult(CHECK_TYPE, `Platform sync checks passed for Issue ${context.issueNumber || "identity"}`);
85
+ return shared.passResult(CHECK_TYPE, `Platform sync checks passed for Issue ${context.issueNumber || "identity"}`);
117
86
  }
118
- async function buildSyncContext({ taskDir, config, artifactFile }) {
119
- const task = loadTask(taskDir);
87
+ async function buildSyncContext({ taskDir, config, artifactFile }, shared) {
88
+ const task = shared.loadTask(taskDir);
120
89
  if (!task.ok) {
121
- return { earlyReturn: failResult(CHECK_TYPE, task.message) };
90
+ return { earlyReturn: shared.failResult(CHECK_TYPE, task.message) };
122
91
  }
123
92
  const issueIdentity = taskIssueIdentity(task.metadata);
124
93
  const issueNumber = resourceIdentityNumber(issueIdentity);
125
94
  const fact = readPrDeliveryFact(task.metadata);
126
95
  if (fact.status === "invalid") {
127
- return { earlyReturn: failResult(CHECK_TYPE, fact.error.message, "check_failed") };
96
+ return { earlyReturn: shared.failResult(CHECK_TYPE, fact.error.message, "check_failed") };
128
97
  }
129
98
  const prIdentity = fact.status === "valid" && fact.fact.state === "bound" ? fact.fact.identity.resource : null;
130
99
  const prNumber = resourceIdentityNumber(prIdentity);
131
- if (config.when === "issue_number_exists" && !issueIdentity) {
132
- return { earlyReturn: passResult(CHECK_TYPE, "Skipped: task has no issue_number") };
100
+ if (config.when === "platform_issue_identity_exists" && !issueIdentity) {
101
+ return { earlyReturn: shared.passResult(CHECK_TYPE, "Skipped: task has no platform_issue_identity") };
133
102
  }
134
103
  if (config.when === "pr_fact_bound" && !prIdentity) {
135
- return { earlyReturn: passResult(CHECK_TYPE, "Skipped: task has no verified bound pull request") };
104
+ return { earlyReturn: shared.passResult(CHECK_TYPE, "Skipped: task has no verified bound pull request") };
136
105
  }
137
106
  if (!issueIdentity) {
138
- return { earlyReturn: passResult(CHECK_TYPE, "Skipped: platform-sync not required for this task") };
107
+ return { earlyReturn: shared.passResult(CHECK_TYPE, "Skipped: platform-sync not required for this task") };
139
108
  }
140
- const loaded = await resolvePlatformProviderContext({ cwd: repoRoot });
109
+ const loaded = await resolvePlatformProviderContext({ cwd: shared.repoRoot });
141
110
  const platformContext = loaded.ok ? loaded.value.context : loaded.context;
142
111
  if (platformContext.status === "failed") {
143
- return { earlyReturn: failResult(CHECK_TYPE, platformContext.error?.message || "Platform context failed", "check_failed") };
112
+ return { earlyReturn: shared.failResult(CHECK_TYPE, platformContext.error?.message || "Platform context failed", "check_failed") };
144
113
  }
145
114
  if (platformContext.status === "blocked") {
146
- return { earlyReturn: blockedResult(CHECK_TYPE, platformContext.error?.message || "Platform context blocked", "network_error") };
115
+ return { earlyReturn: shared.blockedResult(CHECK_TYPE, platformContext.error?.message || "Platform context blocked", "network_error") };
147
116
  }
148
117
  if (!platformContext.platform.repository) {
149
118
  if (platformContext.error?.code === "REMOTE_MISSING" || platformContext.error?.code === "REMOTE_INVALID") {
150
- return { earlyReturn: blockedResult(CHECK_TYPE, platformContext.error.message, "network_error") };
119
+ return { earlyReturn: shared.blockedResult(CHECK_TYPE, platformContext.error.message, "network_error") };
151
120
  }
152
- return { earlyReturn: passResult(CHECK_TYPE, `Skipped: ${platformContext.error?.message || "platform unavailable"}`) };
121
+ return { earlyReturn: shared.passResult(CHECK_TYPE, `Skipped: ${platformContext.error?.message || "platform unavailable"}`) };
153
122
  }
154
123
  const expectedValues = resolveExpectedValues(config);
155
124
  if (!expectedValues.ok) {
156
- return { earlyReturn: failResult(CHECK_TYPE, expectedValues.message, "check_failed") };
125
+ return { earlyReturn: shared.failResult(CHECK_TYPE, expectedValues.message, "check_failed") };
157
126
  }
158
127
  const marker = expectedValues.commentMarker
159
128
  ? interpolate(expectedValues.commentMarker, taskDir, artifactFile)
@@ -229,7 +198,7 @@ function resolveDefaultValue({ collection, key, value, configKey }) {
229
198
  }
230
199
  return { ok: true, value: resolvedValue };
231
200
  }
232
- async function fetchRemoteData(context) {
201
+ async function fetchRemoteData(context, shared) {
233
202
  const provider = context.provider;
234
203
  const operationContext = providerOperationContext(context.loadedContext, context.taskDir);
235
204
  const facts = provider?.verification?.fetchRemoteFacts
@@ -245,8 +214,8 @@ async function fetchRemoteData(context) {
245
214
  if (!facts.ok) {
246
215
  return {
247
216
  earlyReturn: facts.error.retryable
248
- ? blockedResult(CHECK_TYPE, providerError(facts.error, "PLATFORM_PROVIDER_OPERATION_FAILED").message, "network_error")
249
- : failResult(CHECK_TYPE, providerError(facts.error, "PLATFORM_PROVIDER_OPERATION_FAILED").message, "check_failed")
217
+ ? shared.blockedResult(CHECK_TYPE, providerError(facts.error, "PLATFORM_PROVIDER_OPERATION_FAILED").message, "network_error")
218
+ : shared.failResult(CHECK_TYPE, providerError(facts.error, "PLATFORM_PROVIDER_OPERATION_FAILED").message, "check_failed")
250
219
  };
251
220
  }
252
221
  const issueSnapshot = facts.value.issue;
@@ -272,8 +241,8 @@ async function fetchRemoteData(context) {
272
241
  if (!listed.ok)
273
242
  return {
274
243
  earlyReturn: listed.error.retryable
275
- ? blockedResult(CHECK_TYPE, listed.error.message, "network_error")
276
- : failResult(CHECK_TYPE, listed.error.message, "check_failed")
244
+ ? shared.blockedResult(CHECK_TYPE, listed.error.message, "network_error")
245
+ : shared.failResult(CHECK_TYPE, listed.error.message, "check_failed")
277
246
  };
278
247
  prComments = listed.value.map((comment) => ({ id: comment.id, body: comment.body }));
279
248
  }
@@ -281,9 +250,9 @@ async function fetchRemoteData(context) {
281
250
  let inLabelMapping = {};
282
251
  let repositoryLabels = [];
283
252
  if (context.config.verify_in_labels_computed && context.hasTriage) {
284
- const mapping = loadInLabelMapping();
253
+ const mapping = loadInLabelMapping(shared);
285
254
  if (!mapping.ok)
286
- return { earlyReturn: failResult(CHECK_TYPE, mapping.message, "check_failed") };
255
+ return { earlyReturn: shared.failResult(CHECK_TYPE, mapping.message, "check_failed") };
287
256
  inLabelMapping = mapping.value;
288
257
  if (Object.keys(inLabelMapping).length > 0) {
289
258
  const labels = provider?.issues?.listLabels
@@ -293,8 +262,8 @@ async function fetchRemoteData(context) {
293
262
  const error = providerError(labels.error, "PLATFORM_PROVIDER_OPERATION_FAILED");
294
263
  return {
295
264
  earlyReturn: error.retryable
296
- ? blockedResult(CHECK_TYPE, `${error.code}: ${error.message}`, "network_error")
297
- : failResult(CHECK_TYPE, `${error.code}: ${error.message}`, "check_failed")
265
+ ? shared.blockedResult(CHECK_TYPE, `${error.code}: ${error.message}`, "network_error")
266
+ : shared.failResult(CHECK_TYPE, `${error.code}: ${error.message}`, "check_failed")
298
267
  };
299
268
  }
300
269
  repositoryLabels = labels.value;
@@ -330,7 +299,7 @@ function shouldFetchComments(config) {
330
299
  || config.verify_comment_content
331
300
  || config.verify_task_comment_content);
332
301
  }
333
- function checkStatusLabel(context, remoteData) {
302
+ function checkStatusLabel(context, remoteData, shared) {
334
303
  if (!context.expectedStatusLabel || !context.hasTriage) {
335
304
  return null;
336
305
  }
@@ -341,9 +310,9 @@ function checkStatusLabel(context, remoteData) {
341
310
  if (labels.includes(context.expectedStatusLabel)) {
342
311
  return null;
343
312
  }
344
- return failResult(CHECK_TYPE, `Expected label '${context.expectedStatusLabel}' not found on Issue #${context.issueNumber}`, "check_failed");
313
+ return shared.failResult(CHECK_TYPE, `Expected label '${context.expectedStatusLabel}' not found on Issue #${context.issueNumber}`, "check_failed");
345
314
  }
346
- function checkClosedIssueStatusLabels(context, remoteData) {
315
+ function checkClosedIssueStatusLabels(context, remoteData, shared) {
347
316
  if (!context.config.verify_closed_issue_has_no_status_labels) {
348
317
  return null;
349
318
  }
@@ -355,9 +324,9 @@ function checkClosedIssueStatusLabels(context, remoteData) {
355
324
  if (statusLabels.length === 0) {
356
325
  return null;
357
326
  }
358
- return failResult(CHECK_TYPE, `Closed Issue #${context.issueNumber} retains status labels: ${statusLabels.join(", ")}`, "check_failed");
327
+ return shared.failResult(CHECK_TYPE, `Closed Issue #${context.issueNumber} retains status labels: ${statusLabels.join(", ")}`, "check_failed");
359
328
  }
360
- function checkCommentMarker(context, remoteData) {
329
+ function checkCommentMarker(context, remoteData, shared) {
361
330
  if (!context.marker) {
362
331
  return null;
363
332
  }
@@ -365,9 +334,9 @@ function checkCommentMarker(context, remoteData) {
365
334
  if (comment) {
366
335
  return null;
367
336
  }
368
- return failResult(CHECK_TYPE, `Expected comment marker '${context.marker}' not found on Issue #${context.issueNumber}`, "check_failed");
337
+ return shared.failResult(CHECK_TYPE, `Expected comment marker '${context.marker}' not found on Issue #${context.issueNumber}`, "check_failed");
369
338
  }
370
- function checkPrCommentMarker(context, remoteData) {
339
+ function checkPrCommentMarker(context, remoteData, shared) {
371
340
  if (!context.prMarker) {
372
341
  return null;
373
342
  }
@@ -375,88 +344,110 @@ function checkPrCommentMarker(context, remoteData) {
375
344
  if (comment) {
376
345
  return null;
377
346
  }
378
- return failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed");
347
+ return shared.failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed");
379
348
  }
380
- function checkPrCommentLastCommit(context, remoteData) {
349
+ function checkPrCommentLastCommit(context, remoteData, shared) {
381
350
  if (!context.config.verify_pr_comment_last_commit_matches_head) {
382
351
  return null;
383
352
  }
384
353
  if (!context.prMarker) {
385
- return failResult(CHECK_TYPE, "verify_pr_comment_last_commit_matches_head requires expected_pr_comment_marker", "check_failed");
354
+ return shared.failResult(CHECK_TYPE, "verify_pr_comment_last_commit_matches_head requires expected_pr_comment_marker", "check_failed");
386
355
  }
387
356
  const comment = findCommentByMarker(remoteData.prComments, context.prMarker);
388
357
  if (!comment) {
389
- return failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed");
358
+ return shared.failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed");
390
359
  }
391
360
  const match = String(comment.body || "").match(/<!--\s*last-commit:\s*([0-9a-f]{7,40})\s*-->/i);
392
361
  if (!match) {
393
- return failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment is missing '<!-- last-commit: <sha> -->' metadata`, "check_failed");
362
+ return shared.failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment is missing '<!-- last-commit: <sha> -->' metadata`, "check_failed");
394
363
  }
395
364
  const expectedHead = String(remoteData.prHeadSha || "").trim();
396
365
  if (!expectedHead)
397
- return blockedResult(CHECK_TYPE, "Unable to resolve the PR head SHA", "network_error");
366
+ return shared.blockedResult(CHECK_TYPE, "Unable to resolve the PR head SHA", "network_error");
398
367
  const actualHead = match[1].trim();
399
368
  if (expectedHead === actualHead) {
400
369
  return null;
401
370
  }
402
- return failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment last-commit metadata mismatch: expected ${expectedHead}, got ${actualHead}`, "check_failed");
371
+ return shared.failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment last-commit metadata mismatch: expected ${expectedHead}, got ${actualHead}`, "check_failed");
403
372
  }
404
- function checkPrCommentRequiredPatterns(context, remoteData) {
373
+ function checkPrCommentRequiredPatterns(context, remoteData, shared) {
405
374
  const patterns = context.config.expected_pr_comment_required_patterns || [];
406
375
  if (!Array.isArray(patterns) || patterns.length === 0) {
407
376
  return null;
408
377
  }
409
378
  if (!context.prMarker) {
410
- return failResult(CHECK_TYPE, "expected_pr_comment_required_patterns requires expected_pr_comment_marker", "check_failed");
379
+ return shared.failResult(CHECK_TYPE, "expected_pr_comment_required_patterns requires expected_pr_comment_marker", "check_failed");
411
380
  }
412
381
  const comment = findCommentByMarker(remoteData.prComments, context.prMarker);
413
382
  if (!comment) {
414
- return failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed");
383
+ return shared.failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed");
415
384
  }
416
385
  const body = String(comment.body || "");
417
386
  for (const pattern of patterns) {
418
387
  const regex = new RegExp(pattern, "m");
419
388
  if (!regex.test(body)) {
420
- return failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment is missing required pattern: ${pattern}`, "check_failed");
389
+ return shared.failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment is missing required pattern: ${pattern}`, "check_failed");
421
390
  }
422
391
  }
423
392
  return null;
424
393
  }
425
- function checkCommentContent(context, remoteData) {
394
+ function checkCommentContent(context, remoteData, shared) {
426
395
  if (!context.config.verify_comment_content) {
427
396
  return null;
428
397
  }
429
398
  if (!context.marker) {
430
- return failResult(CHECK_TYPE, "verify_comment_content requires expected_comment_marker", "check_failed");
399
+ return shared.failResult(CHECK_TYPE, "verify_comment_content requires expected_comment_marker", "check_failed");
431
400
  }
432
- if (!context.artifactPath || !safeStat(context.artifactPath)) {
433
- return failResult(CHECK_TYPE, `Artifact not found for comment verification: ${context.artifactFile || "(missing artifactFile)"}`, "check_failed");
401
+ if (!context.artifactPath || !shared.safeStat(context.artifactPath)) {
402
+ return shared.failResult(CHECK_TYPE, `Artifact not found for comment verification: ${context.artifactFile || "(missing artifactFile)"}`, "check_failed");
434
403
  }
435
404
  const comment = findCommentByMarker(remoteData.comments, context.marker);
436
- const localContent = normalizeContent(fs.readFileSync(context.artifactPath, "utf8"));
437
- const commentContent = normalizeContent(extractCommentBody(comment?.body || ""));
405
+ let localContent;
406
+ let commentContent;
407
+ try {
408
+ const localCanonical = canonicalizeCommentBody(fs.readFileSync(context.artifactPath, "utf8"));
409
+ const commentCanonical = canonicalizeCommentBody(extractCommentBody(comment?.body || ""));
410
+ if (!localCanonical.ok) {
411
+ return shared.failResult(CHECK_TYPE, `Comment content cannot be canonicalized for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${localCanonical.error.message}`, "check_failed");
412
+ }
413
+ if (!commentCanonical.ok) {
414
+ return shared.failResult(CHECK_TYPE, `Comment content cannot be canonicalized for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${commentCanonical.error.message}`, "check_failed");
415
+ }
416
+ localContent = shared.normalizeContent(localCanonical.value);
417
+ commentContent = shared.normalizeContent(commentCanonical.value);
418
+ }
419
+ catch (error) {
420
+ return shared.failResult(CHECK_TYPE, `Comment content cannot be read for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${error instanceof Error ? error.message : String(error)}`, "check_failed");
421
+ }
438
422
  if (localContent === commentContent) {
439
423
  return null;
440
424
  }
441
- return failResult(CHECK_TYPE, buildCommentContentMismatchMessage(path.basename(context.artifactPath, path.extname(context.artifactPath)), context.issueNumber, localContent, commentContent), "check_failed");
425
+ return shared.failResult(CHECK_TYPE, buildCommentContentMismatchMessage(path.basename(context.artifactPath, path.extname(context.artifactPath)), context.issueNumber, localContent, commentContent), "check_failed");
442
426
  }
443
- function checkTaskCommentContent(context, remoteData) {
427
+ function checkTaskCommentContent(context, remoteData, shared) {
444
428
  if (!context.config.verify_task_comment_content) {
445
429
  return null;
446
430
  }
431
+ if (isTaskCommentTooLarge(context.task.content)) {
432
+ return null;
433
+ }
447
434
  const taskMarker = `<!-- sync-issue:${context.task.metadata.id}:task -->`;
448
435
  const comment = findCommentByMarker(remoteData.comments, taskMarker);
449
436
  if (!comment) {
450
- return failResult(CHECK_TYPE, `Expected comment marker '${taskMarker}' not found on Issue #${context.issueNumber}`, "check_failed");
437
+ return shared.failResult(CHECK_TYPE, `Expected comment marker '${taskMarker}' not found on Issue #${context.issueNumber}`, "check_failed");
438
+ }
439
+ const expectedTaskBody = buildExpectedTaskBody(context.task.content, shared);
440
+ if (expectedTaskBody === null) {
441
+ return shared.failResult(CHECK_TYPE, "Task content cannot be rendered safely for comment verification", "check_failed");
451
442
  }
452
- const expectedBody = normalizeContent(buildExpectedTaskBody(context.task.content));
453
- const commentBody = normalizeContent(extractCommentBody(comment.body || ""));
443
+ const expectedBody = shared.normalizeContent(expectedTaskBody);
444
+ const commentBody = shared.normalizeContent(extractCommentBody(comment.body || ""));
454
445
  if (expectedBody === commentBody) {
455
446
  return null;
456
447
  }
457
- return failResult(CHECK_TYPE, buildCommentContentMismatchMessage("task", context.issueNumber, expectedBody, commentBody), "check_failed");
448
+ return shared.failResult(CHECK_TYPE, buildCommentContentMismatchMessage("task", context.issueNumber, expectedBody, commentBody), "check_failed");
458
449
  }
459
- function checkPrTypeLabel(context, remoteData) {
450
+ function checkPrTypeLabel(context, remoteData, shared) {
460
451
  if (!context.config.verify_pr_type_label || !context.hasTriage || !context.prNumber || !remoteData.prLabels) {
461
452
  return null;
462
453
  }
@@ -467,9 +458,9 @@ function checkPrTypeLabel(context, remoteData) {
467
458
  if (remoteData.prLabels.includes(expectedLabel)) {
468
459
  return null;
469
460
  }
470
- return failResult(CHECK_TYPE, `Expected type label '${expectedLabel}' not found on PR #${context.prNumber}`, "check_failed");
461
+ return shared.failResult(CHECK_TYPE, `Expected type label '${expectedLabel}' not found on PR #${context.prNumber}`, "check_failed");
471
462
  }
472
- function checkInLabelsMatchPr(context, remoteData) {
463
+ function checkInLabelsMatchPr(context, remoteData, shared) {
473
464
  if (!context.config.verify_in_labels_match_pr || !context.hasTriage || !context.prNumber || !remoteData.prLabels) {
474
465
  return null;
475
466
  }
@@ -482,17 +473,17 @@ function checkInLabelsMatchPr(context, remoteData) {
482
473
  if (arraysEqual(issueInLabels, prInLabels)) {
483
474
  return null;
484
475
  }
485
- return failResult(CHECK_TYPE, `in: labels mismatch — PR #${context.prNumber} has [${formatLabelList(prInLabels)}], Issue #${context.issueNumber} has [${formatLabelList(issueInLabels)}]`, "check_failed");
476
+ return shared.failResult(CHECK_TYPE, `in: labels mismatch — PR #${context.prNumber} has [${formatLabelList(prInLabels)}], Issue #${context.issueNumber} has [${formatLabelList(issueInLabels)}]`, "check_failed");
486
477
  }
487
- function checkInLabelsComputed(context, remoteData) {
478
+ function checkInLabelsComputed(context, remoteData, shared) {
488
479
  if (!context.config.verify_in_labels_computed || !context.hasTriage) {
489
480
  return null;
490
481
  }
491
- const expectedInLabels = computeExpectedInLabels(context.taskDir, remoteData.repositoryLabels, remoteData.inLabelMapping);
482
+ const expectedInLabels = computeExpectedInLabels(context.taskDir, remoteData.repositoryLabels, remoteData.inLabelMapping, shared);
492
483
  if (!expectedInLabels.ok) {
493
484
  return expectedInLabels.type === "check_failed"
494
- ? failResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type)
495
- : blockedResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type);
485
+ ? shared.failResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type)
486
+ : shared.blockedResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type);
496
487
  }
497
488
  if (expectedInLabels.mode === "skipped") {
498
489
  return null;
@@ -503,32 +494,32 @@ function checkInLabelsComputed(context, remoteData) {
503
494
  if (arraysEqual(expectedInLabels.labels, actualInLabels)) {
504
495
  return null;
505
496
  }
506
- return failResult(CHECK_TYPE, `Issue #${context.issueNumber} in: labels do not match committed changes: expected [${formatLabelList(expectedInLabels.labels)}], got [${formatLabelList(actualInLabels)}]`, "check_failed");
497
+ return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} in: labels do not match committed changes: expected [${formatLabelList(expectedInLabels.labels)}], got [${formatLabelList(actualInLabels)}]`, "check_failed");
507
498
  }
508
- function checkSyncedRequirements(context, remoteData) {
499
+ function checkSyncedRequirements(context, remoteData, shared) {
509
500
  if (!context.config.sync_checked_requirements || !context.hasTriage) {
510
501
  return null;
511
502
  }
512
- const checkedRequirements = getCheckedRequirements(context.task.content);
503
+ const checkedRequirements = shared.getCheckedRequirements(context.task.content);
513
504
  if (checkedRequirements.length === 0) {
514
505
  return null;
515
506
  }
516
507
  const issueBody = remoteData.issue.body || "";
517
- const resolution = resolveRequirementSection(issueBody, requirementSectionAnchors(repoRoot, context.task.metadata.type || "task"));
508
+ const resolution = resolveRequirementSection(issueBody, requirementSectionAnchors(shared.repoRoot, context.task.metadata.type || "task"));
518
509
  if (resolution.status === "missing") {
519
510
  return null;
520
511
  }
521
512
  if (resolution.status === "ambiguous") {
522
- return failResult(CHECK_TYPE, `Issue #${context.issueNumber} requirements section is ambiguous`, "check_failed");
513
+ return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} requirements section is ambiguous`, "check_failed");
523
514
  }
524
515
  const requirementBody = issueBody.slice(resolution.bodyStart, resolution.bodyEnd);
525
516
  const missingRequirements = checkedRequirements.filter((item) => !hasCheckedRequirement(requirementBody, item));
526
517
  if (missingRequirements.length === 0) {
527
518
  return null;
528
519
  }
529
- return failResult(CHECK_TYPE, `Issue body is missing checked requirements: ${missingRequirements.join(", ")}`, "check_failed");
520
+ return shared.failResult(CHECK_TYPE, `Issue body is missing checked requirements: ${missingRequirements.join(", ")}`, "check_failed");
530
521
  }
531
- function checkIssueType(context, remoteData) {
522
+ function checkIssueType(context, remoteData, shared) {
532
523
  if (!context.config.verify_issue_type || !context.hasPush) {
533
524
  return null;
534
525
  }
@@ -539,15 +530,15 @@ function checkIssueType(context, remoteData) {
539
530
  if (context.repoOwnerType === "User") {
540
531
  return null;
541
532
  }
542
- return failResult(CHECK_TYPE, `Issue #${context.issueNumber} has no Issue Type set`, "check_failed");
533
+ return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} has no Issue Type set`, "check_failed");
543
534
  }
544
535
  const expectedType = mapTaskTypeToIssueType(context.task.metadata.type);
545
536
  if (expectedType && remoteData.issueType !== expectedType) {
546
- return failResult(CHECK_TYPE, `Issue #${context.issueNumber} has type '${remoteData.issueType}', expected '${expectedType}' (from task type '${context.task.metadata.type}')`, "check_failed");
537
+ return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} has type '${remoteData.issueType}', expected '${expectedType}' (from task type '${context.task.metadata.type}')`, "check_failed");
547
538
  }
548
539
  return null;
549
540
  }
550
- function checkIssueFields(context, remoteData) {
541
+ function checkIssueFields(context, remoteData, shared) {
551
542
  if (!context.config.verify_issue_fields || !context.hasPush) {
552
543
  return null;
553
544
  }
@@ -556,7 +547,7 @@ function checkIssueFields(context, remoteData) {
556
547
  }
557
548
  for (const [metadataKey, fieldName] of Object.entries(FRONTMATTER_FIELD_MAP)) {
558
549
  const expectedRaw = context.task.metadata[metadataKey];
559
- if (isBlank(expectedRaw) || !remoteData.issueFields.pinnedNames.has(fieldName)) {
550
+ if (shared.isBlank(expectedRaw) || !remoteData.issueFields.pinnedNames.has(fieldName)) {
560
551
  continue;
561
552
  }
562
553
  const actual = remoteData.issueFields.values.get(fieldName);
@@ -565,40 +556,40 @@ function checkIssueFields(context, remoteData) {
565
556
  continue;
566
557
  }
567
558
  if (!actual) {
568
- return failResult(CHECK_TYPE, `Issue #${context.issueNumber} field '${fieldName}' is missing, expected '${expected.value}'`, "check_failed");
559
+ return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} field '${fieldName}' is missing, expected '${expected.value}'`, "check_failed");
569
560
  }
570
561
  if (actual.kind !== expected.kind || actual.value !== expected.value) {
571
- return failResult(CHECK_TYPE, `Issue #${context.issueNumber} field '${fieldName}' is '${actual.value}', expected '${expected.value}'`, "check_failed");
562
+ return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} field '${fieldName}' is '${actual.value}', expected '${expected.value}'`, "check_failed");
572
563
  }
573
564
  }
574
565
  return null;
575
566
  }
576
- function checkPrAssignee(context, remoteData) {
567
+ function checkPrAssignee(context, remoteData, shared) {
577
568
  if (!context.config.verify_pr_assignee || !context.hasPush || !context.prNumber) {
578
569
  return null;
579
570
  }
580
571
  if (!remoteData.prAssignees || remoteData.prAssignees.length === 0) {
581
- return failResult(CHECK_TYPE, `PR #${context.prNumber} has no assignee`, "check_failed");
572
+ return shared.failResult(CHECK_TYPE, `PR #${context.prNumber} has no assignee`, "check_failed");
582
573
  }
583
574
  return null;
584
575
  }
585
- function checkMilestone(context, remoteData) {
576
+ function checkMilestone(context, remoteData, shared) {
586
577
  if (!context.config.verify_milestone || !context.hasTriage) {
587
578
  return null;
588
579
  }
589
580
  if (!remoteData.issue?.milestone?.title) {
590
- return failResult(CHECK_TYPE, `Issue #${context.issueNumber} has no milestone set`, "check_failed");
581
+ return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} has no milestone set`, "check_failed");
591
582
  }
592
583
  if (context.prNumber && remoteData.prMilestone !== undefined && !remoteData.prMilestone?.title) {
593
- return failResult(CHECK_TYPE, `PR #${context.prNumber} has no milestone set`, "check_failed");
584
+ return shared.failResult(CHECK_TYPE, `PR #${context.prNumber} has no milestone set`, "check_failed");
594
585
  }
595
586
  if (context.config.verify_milestone_specific) {
596
587
  const issueTitle = remoteData.issue.milestone.title;
597
588
  if (VERSION_LINE_REGEX.test(issueTitle)) {
598
- return failResult(CHECK_TYPE, `Issue #${context.issueNumber} milestone '${issueTitle}' is a release line; narrow to a specific version (e.g. ${issueTitle.replace(/\.x$/, ".N")}) before continuing`, "check_failed");
589
+ return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} milestone '${issueTitle}' is a release line; narrow to a specific version (e.g. ${issueTitle.replace(/\.x$/, ".N")}) before continuing`, "check_failed");
599
590
  }
600
591
  if (context.prNumber && remoteData.prMilestone?.title && VERSION_LINE_REGEX.test(remoteData.prMilestone.title)) {
601
- return failResult(CHECK_TYPE, `PR #${context.prNumber} milestone '${remoteData.prMilestone.title}' is a release line; narrow to a specific version before continuing`, "check_failed");
592
+ return shared.failResult(CHECK_TYPE, `PR #${context.prNumber} milestone '${remoteData.prMilestone.title}' is a release line; narrow to a specific version before continuing`, "check_failed");
602
593
  }
603
594
  }
604
595
  return null;
@@ -643,37 +634,38 @@ function extractCommentBody(commentBody) {
643
634
  }
644
635
  return lines.slice(start, end).join("\n");
645
636
  }
646
- function buildExpectedTaskBody(taskContent) {
637
+ function buildExpectedTaskBody(taskContent, shared) {
647
638
  const frontmatterMatch = taskContent.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
648
639
  if (!frontmatterMatch) {
649
- return taskContent.trim();
640
+ return sanitizeCommentContent(taskContent.trim());
641
+ }
642
+ const body = sanitizeCommentContent(taskContent.slice(frontmatterMatch[0].length).trim());
643
+ if (body === null) {
644
+ return null;
650
645
  }
651
- const body = taskContent.slice(frontmatterMatch[0].length).trim();
652
646
  return [
653
- buildTaskFrontmatterSummary(),
647
+ buildTaskFrontmatterSummary(shared),
654
648
  "",
655
- "```yaml",
656
- frontmatterMatch[0].trim(),
657
- "```",
649
+ renderSafeCodeFence(frontmatterMatch[0].trim(), "yaml"),
658
650
  "",
659
651
  "</details>",
660
652
  "",
661
653
  body
662
654
  ].join("\n").trim();
663
655
  }
664
- function buildTaskFrontmatterSummary() {
665
- const language = loadProjectLanguage();
656
+ function buildTaskFrontmatterSummary(shared) {
657
+ const language = loadProjectLanguage(shared);
666
658
  if (language === "en" || language === "en-US") {
667
659
  return "<details><summary>Metadata (frontmatter)</summary>";
668
660
  }
669
661
  return "<details><summary>元数据 (frontmatter)</summary>";
670
662
  }
671
- function loadProjectLanguage() {
663
+ function loadProjectLanguage(shared) {
672
664
  const override = process.env.VALIDATE_ARTIFACT_LANGUAGE;
673
- if (!isBlank(override)) {
665
+ if (!shared.isBlank(override)) {
674
666
  return String(override).trim();
675
667
  }
676
- const configPath = path.join(repoRoot, ".agents", ".airc.json");
668
+ const configPath = path.join(shared.repoRoot, ".agents", ".airc.json");
677
669
  if (!fs.existsSync(configPath)) {
678
670
  return "";
679
671
  }
@@ -788,8 +780,8 @@ function arraysEqual(left, right) {
788
780
  function formatLabelList(labels) {
789
781
  return labels.length > 0 ? labels.join(", ") : "none";
790
782
  }
791
- function computeExpectedInLabels(taskDir, repositoryLabels = [], mappingOverride) {
792
- const task = loadTask(taskDir);
783
+ function computeExpectedInLabels(taskDir, repositoryLabels, mappingOverride, shared) {
784
+ const task = shared.loadTask(taskDir);
793
785
  if (!task.ok) {
794
786
  return task;
795
787
  }
@@ -820,8 +812,8 @@ function computeExpectedInLabels(taskDir, repositoryLabels = [], mappingOverride
820
812
  }
821
813
  return { ok: true, labels: planned.target, mode: "mapped" };
822
814
  }
823
- function loadInLabelMapping() {
824
- const configPath = path.join(repoRoot, ".agents", ".airc.json");
815
+ function loadInLabelMapping(shared) {
816
+ const configPath = path.join(shared.repoRoot, ".agents", ".airc.json");
825
817
  if (!fs.existsSync(configPath)) {
826
818
  return { ok: true, value: {} };
827
819
  }
@@ -852,12 +844,12 @@ function gitText(args, cwd) {
852
844
  }
853
845
  }
854
846
  function resolvePrHeadSha(context) {
855
- const fallback = () => withRetry(() => gitText(["rev-parse", "HEAD"], context.taskDir));
847
+ const fallback = () => gitText(["rev-parse", "HEAD"], context.taskDir);
856
848
  const branch = String(context.task?.metadata?.branch || "").trim();
857
849
  if (!branch) {
858
850
  return fallback();
859
851
  }
860
- const worktreeList = withRetry(() => gitText(["worktree", "list", "--porcelain"], context.taskDir));
852
+ const worktreeList = gitText(["worktree", "list", "--porcelain"], context.taskDir);
861
853
  if (!worktreeList.ok) {
862
854
  return fallback();
863
855
  }
@@ -865,7 +857,7 @@ function resolvePrHeadSha(context) {
865
857
  if (!matchedWorktree) {
866
858
  return fallback();
867
859
  }
868
- const headInWorktree = withRetry(() => gitText(["rev-parse", "HEAD"], matchedWorktree));
860
+ const headInWorktree = gitText(["rev-parse", "HEAD"], matchedWorktree);
869
861
  if (!headInWorktree.ok) {
870
862
  return fallback();
871
863
  }
@@ -888,9 +880,6 @@ function findWorktreeForBranch(porcelainOutput, branch) {
888
880
  }
889
881
  return null;
890
882
  }
891
- function withRetry(operation) {
892
- return operation();
893
- }
894
883
  function interpolate(template, taskDir, artifactFile) {
895
884
  const artifactStem = artifactFile ? path.basename(artifactFile, path.extname(artifactFile)) : "";
896
885
  return template