@fitlab-ai/agent-infra 0.9.12 → 0.9.14

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 (402) hide show
  1. package/README.md +11 -11
  2. package/README.zh-CN.md +11 -11
  3. package/bin/cli.ts +26 -9
  4. package/dist/bin/cli.js +173 -157
  5. package/dist/bin/internal-cli.js +66 -40
  6. package/dist/lib/agent-client.js +5 -0
  7. package/dist/lib/agent-clients/next-steps.js +6 -1
  8. package/dist/lib/decide.js +3 -3
  9. package/dist/lib/internal/agent-client.js +5 -2
  10. package/dist/lib/internal/cli-route-inventory.js +152 -0
  11. package/dist/lib/internal/codex-lifecycle.js +9 -6
  12. package/dist/lib/internal/codex-sandbox-controller.js +8 -2
  13. package/dist/lib/internal/git-workflow.js +10 -6
  14. package/dist/lib/internal/platform-checks.js +19 -7
  15. package/dist/lib/internal/platform-comment.js +25 -13
  16. package/dist/lib/internal/platform-context.js +6 -3
  17. package/dist/lib/internal/platform-issue.js +20 -13
  18. package/dist/lib/internal/platform-metadata.js +88 -0
  19. package/dist/lib/internal/platform-pr-review.js +26 -13
  20. package/dist/lib/internal/platform-pr.js +84 -31
  21. package/dist/lib/internal/platform-release-notes.js +9 -6
  22. package/dist/lib/internal/platform-security.js +93 -0
  23. package/dist/lib/internal/pr-review-grade.js +25 -12
  24. package/dist/lib/internal/release-workflow.js +23 -9
  25. package/dist/lib/internal/sandbox-control.js +50 -7
  26. package/dist/lib/internal/task-activity.js +16 -7
  27. package/dist/lib/internal/task-artifact.js +235 -24
  28. package/dist/lib/internal/task-context.js +4 -1
  29. package/dist/lib/internal/task-create.js +50 -17
  30. package/dist/lib/internal/task-delivery.js +4 -1
  31. package/dist/lib/internal/task-event.js +10 -4
  32. package/dist/lib/internal/task-finalization.js +6 -3
  33. package/dist/lib/internal/task-invalidation.js +63 -0
  34. package/dist/lib/internal/task-ledger.js +20 -6
  35. package/dist/lib/internal/task-lifecycle.js +6 -3
  36. package/dist/lib/internal/task-operation-registry.js +421 -0
  37. package/dist/lib/internal/task-orchestration.js +12 -0
  38. package/dist/lib/internal/task-override.js +14 -7
  39. package/dist/lib/internal/task-qualification.js +90 -0
  40. package/dist/lib/internal/task-review.js +7 -4
  41. package/dist/lib/internal/task-short-id.js +11 -1
  42. package/dist/lib/internal/task-snapshot.js +4 -1
  43. package/dist/lib/internal/task-validate.js +7 -2
  44. package/dist/lib/internal/task-verify.js +11 -7
  45. package/dist/lib/internal/task-warning.js +11 -4
  46. package/dist/lib/platform/adapters.js +1 -3
  47. package/dist/lib/platform/change-request-git-evidence.js +27 -7
  48. package/dist/lib/platform/comment-safety.js +226 -0
  49. package/dist/lib/platform/completion-backfill.js +6 -5
  50. package/dist/lib/platform/context.js +70 -130
  51. package/dist/lib/platform/github-client.js +1 -1
  52. package/dist/lib/platform/github-provider.js +1130 -0
  53. package/dist/lib/platform/in-label-sync.js +143 -0
  54. package/dist/lib/platform/issue-comments.js +237 -72
  55. package/dist/lib/platform/issues.js +335 -189
  56. package/dist/lib/platform/metadata-labels.js +6 -2
  57. package/dist/lib/platform/none-provider.js +28 -0
  58. package/dist/lib/platform/pr-change-report.js +545 -0
  59. package/dist/lib/platform/pr-checks.js +119 -104
  60. package/dist/lib/platform/pr-review.js +88 -72
  61. package/dist/lib/platform/pr-summary.js +541 -50
  62. package/dist/lib/platform/provider-bridge.js +45 -0
  63. package/dist/lib/platform/provider-contract.d.ts +553 -0
  64. package/dist/lib/platform/provider-contract.js +99 -0
  65. package/dist/lib/platform/provider-loader.js +251 -0
  66. package/dist/lib/platform/provider-validation.js +577 -0
  67. package/dist/lib/platform/pull-request-metadata.js +8 -6
  68. package/dist/lib/platform/pull-requests.js +734 -543
  69. package/dist/lib/platform/release-notes.js +78 -55
  70. package/dist/lib/platform/releases.js +79 -50
  71. package/dist/lib/platform/repository-metadata.js +377 -0
  72. package/dist/lib/platform/resource-identity.d.ts +26 -0
  73. package/dist/lib/platform/resource-identity.js +92 -0
  74. package/dist/lib/platform/security-alerts.js +155 -0
  75. package/dist/lib/platform/task-identities.js +31 -0
  76. package/dist/lib/platform/verification-required.js +11 -12
  77. package/dist/lib/platform/verification-sync.js +129 -248
  78. package/dist/lib/pr-review/evidence-grading.js +19 -6
  79. package/dist/lib/process-data/index.js +7 -0
  80. package/dist/lib/run/index.js +68 -23
  81. package/dist/lib/sandbox/commands/control-status.js +22 -0
  82. package/dist/lib/sandbox/commands/create.js +32 -6
  83. package/dist/lib/sandbox/commands/ls.js +5 -0
  84. package/dist/lib/sandbox/commands/rm.js +827 -120
  85. package/dist/lib/sandbox/commands/show.js +16 -0
  86. package/dist/lib/sandbox/control/client.js +174 -19
  87. package/dist/lib/sandbox/control/completed-reentry.js +85 -0
  88. package/dist/lib/sandbox/control/container-identity.js +28 -31
  89. package/dist/lib/sandbox/control/executor.js +151 -16
  90. package/dist/lib/sandbox/control/lifecycle.js +621 -127
  91. package/dist/lib/sandbox/control/native-file-lock.js +177 -0
  92. package/dist/lib/sandbox/control/protocol.js +77 -0
  93. package/dist/lib/sandbox/control/server.js +677 -34
  94. package/dist/lib/sandbox/control/state.js +209 -4
  95. package/dist/lib/sandbox/control/task-view.js +183 -0
  96. package/dist/lib/sandbox/engines/authority.js +289 -0
  97. package/dist/lib/sandbox/engines/docker-exact-discovery.js +97 -0
  98. package/dist/lib/sandbox/index.js +5 -0
  99. package/dist/lib/sandbox/recovery.js +21 -1
  100. package/dist/lib/sandbox/workspace-view.js +2 -1
  101. package/dist/lib/sandbox/worktree-safety.js +4 -1
  102. package/dist/lib/server/index.js +8 -0
  103. package/dist/lib/server/protocol.js +38 -1
  104. package/dist/lib/task/activity-log.js +8 -1
  105. package/dist/lib/task/artifact-lifecycle.js +75 -18
  106. package/dist/lib/task/artifact-operations.js +520 -0
  107. package/dist/lib/task/artifact-repair-intent.js +59 -0
  108. package/dist/lib/task/artifact-schema.js +135 -0
  109. package/dist/lib/task/capabilities.js +310 -0
  110. package/dist/lib/task/commands/status.js +32 -0
  111. package/dist/lib/task/commit-operation.js +12 -1
  112. package/dist/lib/task/control-authority.js +12 -5
  113. package/dist/lib/task/create-service.js +111 -12
  114. package/dist/lib/task/create.js +25 -0
  115. package/dist/lib/task/current-contract.js +5 -1
  116. package/dist/lib/task/decision-details.js +1 -1
  117. package/dist/lib/task/events.js +416 -17
  118. package/dist/lib/task/finalization.js +37 -13
  119. package/dist/lib/task/human-override.js +58 -36
  120. package/dist/lib/task/index.js +11 -3
  121. package/dist/lib/task/invalidation-command.js +86 -0
  122. package/dist/lib/task/invalidation.js +260 -0
  123. package/dist/lib/task/ledger-intents.js +37 -0
  124. package/dist/lib/task/local-artifact-finalization.js +299 -0
  125. package/dist/lib/task/orchestration.js +240 -144
  126. package/dist/lib/task/pr-delivery-fact.js +102 -101
  127. package/dist/lib/task/qualification-audit.js +490 -0
  128. package/dist/lib/task/qualification-confirmation.js +109 -0
  129. package/dist/lib/task/qualification-intents.js +148 -0
  130. package/dist/lib/task/review-artifacts.js +22 -23
  131. package/dist/lib/task/review-finalization.js +121 -4
  132. package/dist/lib/task/review-fingerprint.js +24 -2
  133. package/dist/lib/task/rework-intent.js +138 -0
  134. package/dist/lib/task/sections.js +6 -4
  135. package/dist/lib/task/skill-scope.js +23 -0
  136. package/dist/lib/task/task-execution-lock.js +27 -17
  137. package/dist/lib/task/verification-config.js +11 -0
  138. package/dist/lib/task/verification-engine.js +93 -16
  139. package/dist/lib/task/verification.js +3 -3
  140. package/dist/lib/task/workflow-warning-intents.js +1 -1
  141. package/dist/lib/task/write.js +11 -0
  142. package/dist/lib/version.js +11 -1
  143. package/dist/lifecycle-build-manifest.json +3 -3
  144. package/lib/agent-client.ts +9 -0
  145. package/lib/agent-clients/next-steps.ts +6 -1
  146. package/lib/decide.ts +3 -3
  147. package/lib/init.ts +1 -1
  148. package/lib/internal/agent-client.ts +4 -2
  149. package/lib/internal/cli-route-inventory.ts +140 -0
  150. package/lib/internal/codex-lifecycle.ts +8 -6
  151. package/lib/internal/codex-sandbox-controller.ts +7 -2
  152. package/lib/internal/git-workflow.ts +9 -6
  153. package/lib/internal/platform-checks.ts +14 -6
  154. package/lib/internal/platform-comment.ts +20 -12
  155. package/lib/internal/platform-context.ts +5 -3
  156. package/lib/internal/platform-issue.ts +17 -11
  157. package/lib/internal/platform-metadata.ts +70 -0
  158. package/lib/internal/platform-pr-review.ts +23 -12
  159. package/lib/internal/platform-pr.ts +65 -27
  160. package/lib/internal/platform-release-notes.ts +8 -6
  161. package/lib/internal/platform-security.ts +83 -0
  162. package/lib/internal/pr-review-grade.ts +20 -11
  163. package/lib/internal/release-workflow.ts +21 -9
  164. package/lib/internal/sandbox-control.ts +44 -6
  165. package/lib/internal/task-activity.ts +15 -7
  166. package/lib/internal/task-artifact.ts +204 -22
  167. package/lib/internal/task-context.ts +3 -1
  168. package/lib/internal/task-create.ts +78 -17
  169. package/lib/internal/task-delivery.ts +3 -1
  170. package/lib/internal/task-event.ts +9 -4
  171. package/lib/internal/task-finalization.ts +6 -4
  172. package/lib/internal/task-invalidation.ts +41 -0
  173. package/lib/internal/task-ledger.ts +21 -8
  174. package/lib/internal/task-lifecycle.ts +5 -3
  175. package/lib/internal/task-operation-registry.ts +482 -0
  176. package/lib/internal/task-orchestration.ts +12 -0
  177. package/lib/internal/task-override.ts +15 -9
  178. package/lib/internal/task-qualification.ts +61 -0
  179. package/lib/internal/task-review.ts +6 -4
  180. package/lib/internal/task-short-id.ts +10 -1
  181. package/lib/internal/task-snapshot.ts +3 -1
  182. package/lib/internal/task-validate.ts +6 -2
  183. package/lib/internal/task-verify.ts +10 -7
  184. package/lib/internal/task-warning.ts +10 -4
  185. package/lib/platform/adapters.ts +3 -2
  186. package/lib/platform/change-request-git-evidence.ts +36 -9
  187. package/lib/platform/comment-safety.ts +250 -0
  188. package/lib/platform/completion-backfill.ts +9 -8
  189. package/lib/platform/context.ts +97 -134
  190. package/lib/platform/github-client.ts +2 -2
  191. package/lib/platform/github-provider.ts +1132 -0
  192. package/lib/platform/in-label-sync.ts +208 -0
  193. package/lib/platform/issue-comments.ts +255 -80
  194. package/lib/platform/issues.ts +344 -174
  195. package/lib/platform/metadata-labels.ts +5 -2
  196. package/lib/platform/none-provider.ts +35 -0
  197. package/lib/platform/pr-change-report.ts +639 -0
  198. package/lib/platform/pr-checks.ts +126 -106
  199. package/lib/platform/pr-review.ts +88 -85
  200. package/lib/platform/pr-summary.ts +581 -53
  201. package/lib/platform/provider-bridge.ts +82 -0
  202. package/lib/platform/provider-contract.ts +611 -0
  203. package/lib/platform/provider-loader.ts +307 -0
  204. package/lib/platform/provider-validation.ts +611 -0
  205. package/lib/platform/pull-request-metadata.ts +10 -7
  206. package/lib/platform/pull-requests.ts +781 -550
  207. package/lib/platform/release-notes.ts +76 -59
  208. package/lib/platform/releases.ts +77 -43
  209. package/lib/platform/repository-metadata.ts +453 -0
  210. package/lib/platform/resource-identity.ts +121 -0
  211. package/lib/platform/security-alerts.ts +191 -0
  212. package/lib/platform/task-identities.ts +40 -0
  213. package/lib/platform/types.ts +4 -2
  214. package/lib/platform/verification-required.ts +11 -13
  215. package/lib/platform/verification-sync.ts +128 -304
  216. package/lib/pr-review/evidence-grading.ts +25 -8
  217. package/lib/process-data/index.ts +7 -0
  218. package/lib/run/index.ts +62 -22
  219. package/lib/sandbox/commands/control-status.ts +26 -0
  220. package/lib/sandbox/commands/create.ts +46 -6
  221. package/lib/sandbox/commands/ls.ts +7 -0
  222. package/lib/sandbox/commands/rm.ts +1000 -87
  223. package/lib/sandbox/commands/show.ts +16 -0
  224. package/lib/sandbox/control/client.ts +203 -19
  225. package/lib/sandbox/control/completed-reentry.ts +98 -0
  226. package/lib/sandbox/control/container-identity.ts +27 -43
  227. package/lib/sandbox/control/executor.ts +180 -15
  228. package/lib/sandbox/control/lifecycle.ts +629 -17
  229. package/lib/sandbox/control/native-file-lock.ts +209 -0
  230. package/lib/sandbox/control/protocol.ts +118 -2
  231. package/lib/sandbox/control/server.ts +724 -34
  232. package/lib/sandbox/control/state.ts +257 -4
  233. package/lib/sandbox/control/task-view.ts +240 -0
  234. package/lib/sandbox/engines/authority.ts +365 -0
  235. package/lib/sandbox/engines/docker-exact-discovery.ts +132 -0
  236. package/lib/sandbox/index.ts +7 -0
  237. package/lib/sandbox/recovery.ts +21 -1
  238. package/lib/sandbox/workspace-view.ts +9 -3
  239. package/lib/sandbox/worktree-safety.ts +4 -1
  240. package/lib/server/index.ts +10 -0
  241. package/lib/server/protocol.ts +36 -1
  242. package/lib/task/activity-log.ts +8 -0
  243. package/lib/task/artifact-lifecycle.ts +70 -22
  244. package/lib/task/artifact-operations.ts +626 -0
  245. package/lib/task/artifact-repair-intent.ts +71 -0
  246. package/lib/task/artifact-schema.ts +192 -0
  247. package/lib/task/capabilities.ts +331 -0
  248. package/lib/task/commands/status.ts +38 -0
  249. package/lib/task/commit-operation.ts +12 -1
  250. package/lib/task/control-authority.ts +22 -9
  251. package/lib/task/create-service.ts +139 -14
  252. package/lib/task/create.ts +27 -0
  253. package/lib/task/current-contract.ts +6 -1
  254. package/lib/task/decision-details.ts +6 -2
  255. package/lib/task/events.ts +392 -16
  256. package/lib/task/finalization.ts +50 -14
  257. package/lib/task/human-override.ts +70 -46
  258. package/lib/task/index.ts +16 -3
  259. package/lib/task/invalidation-command.ts +86 -0
  260. package/lib/task/invalidation.ts +332 -0
  261. package/lib/task/ledger-intents.ts +35 -1
  262. package/lib/task/local-artifact-finalization.ts +432 -0
  263. package/lib/task/orchestration.ts +223 -133
  264. package/lib/task/pr-delivery-fact.ts +131 -148
  265. package/lib/task/qualification-audit.ts +599 -0
  266. package/lib/task/qualification-confirmation.ts +117 -0
  267. package/lib/task/qualification-intents.ts +150 -0
  268. package/lib/task/review-artifacts.ts +21 -22
  269. package/lib/task/review-finalization.ts +133 -4
  270. package/lib/task/review-fingerprint.ts +23 -2
  271. package/lib/task/rework-intent.ts +165 -0
  272. package/lib/task/sections.ts +6 -4
  273. package/lib/task/skill-scope.ts +24 -0
  274. package/lib/task/task-execution-lock.ts +23 -14
  275. package/lib/task/verification-config.ts +12 -0
  276. package/lib/task/verification-engine.ts +103 -17
  277. package/lib/task/verification.ts +4 -4
  278. package/lib/task/workflow-warning-intents.ts +1 -1
  279. package/lib/task/write.ts +19 -0
  280. package/lib/update.ts +1 -1
  281. package/lib/version.ts +15 -1
  282. package/package.json +12 -1
  283. package/templates/.agents/rules/README.en.md +3 -1
  284. package/templates/.agents/rules/README.zh-CN.md +3 -1
  285. package/templates/.agents/rules/decision-qualification.en.md +22 -0
  286. package/templates/.agents/rules/decision-qualification.zh-CN.md +22 -0
  287. package/templates/.agents/rules/evidence-reporting.en.md +28 -0
  288. package/templates/.agents/rules/evidence-reporting.zh-CN.md +28 -0
  289. package/templates/.agents/rules/issue-pr-commands.en.md +1 -1
  290. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
  291. package/templates/.agents/rules/issue-sync.en.md +2 -0
  292. package/templates/.agents/rules/issue-sync.zh-CN.md +2 -0
  293. package/templates/.agents/rules/label-milestone-setup.en.md +7 -7
  294. package/templates/.agents/rules/label-milestone-setup.zh-CN.md +7 -7
  295. package/templates/.agents/rules/local-artifact-repair.en.md +13 -2
  296. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -2
  297. package/templates/.agents/rules/pr-sync.en.md +10 -4
  298. package/templates/.agents/rules/pr-sync.zh-CN.md +17 -4
  299. package/templates/.agents/rules/security-alerts.en.md +8 -8
  300. package/templates/.agents/rules/security-alerts.zh-CN.md +8 -8
  301. package/templates/.agents/rules/task-short-id.en.md +1 -4
  302. package/templates/.agents/rules/task-short-id.zh-CN.md +1 -3
  303. package/templates/.agents/skills/analyze-task/SKILL.en.md +28 -5
  304. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +29 -5
  305. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -12
  306. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -12
  307. package/templates/.agents/skills/check-task/SKILL.en.md +16 -37
  308. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -37
  309. package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
  310. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
  311. package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
  312. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
  313. package/templates/.agents/skills/code-task/SKILL.en.md +41 -9
  314. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +43 -11
  315. package/templates/.agents/skills/code-task/config/verify.en.json +6 -14
  316. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +6 -14
  317. package/templates/.agents/skills/code-task/reference/code-rules.en.md +5 -4
  318. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +5 -4
  319. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
  320. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
  321. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +3 -1
  322. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +3 -1
  323. package/templates/.agents/skills/code-task/reference/report-template.en.md +42 -15
  324. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +39 -12
  325. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +2 -0
  326. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +2 -0
  327. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +3 -3
  328. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +3 -3
  329. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +10 -4
  330. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +10 -4
  331. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  332. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  333. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +4 -3
  334. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +4 -3
  335. package/templates/.agents/skills/complete-task/SKILL.en.md +26 -21
  336. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +25 -21
  337. package/templates/.agents/skills/create-pr/SKILL.en.md +7 -6
  338. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -7
  339. package/templates/.agents/skills/create-pr/config/verify.json +1 -0
  340. package/templates/.agents/skills/create-pr/reference/change-report.en.md +16 -4
  341. package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +16 -4
  342. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +5 -3
  343. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +5 -3
  344. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
  345. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
  346. package/templates/.agents/skills/create-pr/scripts/change-report.mjs +3 -0
  347. package/templates/.agents/skills/create-task/SKILL.en.md +8 -0
  348. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +7 -0
  349. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  350. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  351. package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
  352. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  353. package/templates/.agents/skills/init-labels/SKILL.en.md +8 -8
  354. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +8 -8
  355. package/templates/.agents/skills/init-milestones/SKILL.en.md +9 -11
  356. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +9 -11
  357. package/templates/.agents/skills/plan-task/SKILL.en.md +27 -4
  358. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +26 -4
  359. package/templates/.agents/skills/plan-task/config/verify.en.json +4 -14
  360. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +4 -14
  361. package/templates/.agents/skills/review-analysis/SKILL.en.md +17 -4
  362. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +17 -4
  363. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -18
  364. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -18
  365. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +50 -23
  366. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +45 -18
  367. package/templates/.agents/skills/review-code/SKILL.en.md +18 -5
  368. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +18 -5
  369. package/templates/.agents/skills/review-code/config/verify.en.json +1 -23
  370. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -23
  371. package/templates/.agents/skills/review-code/reference/report-template.en.md +49 -22
  372. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +45 -18
  373. package/templates/.agents/skills/review-plan/SKILL.en.md +17 -4
  374. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +17 -4
  375. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -18
  376. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -18
  377. package/templates/.agents/skills/review-plan/reference/report-template.en.md +49 -22
  378. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +45 -18
  379. package/templates/.agents/skills/review-pr/SKILL.en.md +5 -1
  380. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +5 -1
  381. package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -3
  382. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -3
  383. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +9 -3
  384. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +9 -3
  385. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +3 -1
  386. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +3 -1
  387. package/templates/.agents/skills/test/SKILL.en.md +13 -2
  388. package/templates/.agents/skills/test/SKILL.zh-CN.md +13 -2
  389. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +14 -0
  390. package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
  391. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
  392. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +6 -0
  393. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +6 -0
  394. package/templates/.agents/templates/task.en.md +24 -1
  395. package/templates/.agents/templates/task.zh-CN.md +24 -1
  396. package/templates/.github/workflows/pr-label.yml +7 -26
  397. package/templates/.agents/scripts/security-alerts.github.sh +0 -151
  398. package/templates/.agents/scripts/security-alerts.sh +0 -31
  399. package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +0 -146
  400. package/templates/.agents/skills/init-labels/scripts/init-labels.sh +0 -32
  401. package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +0 -294
  402. package/templates/.agents/skills/init-milestones/scripts/init-milestones.sh +0 -6
@@ -1,29 +1,109 @@
1
1
  import fs from 'node:fs';
2
- import { execFileSync } from 'node:child_process';
2
+ import path from 'node:path';
3
3
 
4
4
  import { enumerateArtifacts } from '../task/artifacts.ts';
5
5
  import { parseTypedTaskFrontmatter } from '../task/frontmatter.ts';
6
6
  import { renderHumanOverrideAudit } from '../task/human-override.ts';
7
7
  import { resolveTaskRef } from '../task/resolve-ref.ts';
8
- import { resolvePlatformContext } from './context.ts';
9
- import { createGitHubClient } from './github-client.ts';
10
- import type { GitHubClient } from './github-client.ts';
11
- import { listRemoteComments, normalizeCommentContent, writeComment } from './issue-comments.ts';
8
+ import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
9
+ import { readPrDeliveryFact } from '../task/pr-delivery-fact.ts';
10
+ import type { PrDeliveryFact } from '../task/pr-delivery-fact.ts';
11
+ import { resolvePlatformProviderContext } from './context.ts';
12
+ import type { LoadedContext, PlatformClient } from './context.ts';
13
+ import { normalizeCommentContent } from './issue-comments.ts';
14
+ import { CONTROL_MARKER_PATTERN, escapeHtmlText, sanitizeMarkdownDocument, splitDocumentPlaceholder } from './comment-safety.ts';
12
15
  import { platformResult } from './types.ts';
13
16
  import type { PlatformResult } from './types.ts';
14
17
  import type { OperationWarning } from '../task/operation-outcome.ts';
15
- import { readPrDeliveryFact } from '../task/pr-delivery-fact.ts';
18
+ import { providerError, providerOperationContext, providerStatus, providerResourceToken, resourceIdentityNumber, unsupportedProviderOperation } from './provider-bridge.ts';
19
+ import type { PlatformChangeRequestSnapshot } from './adapters.ts';
20
+ import type { ChangeRequestSnapshot } from './provider-contract.ts';
21
+ import {
22
+ buildPrChangeReport,
23
+ CANONICAL_REPORT_PLACEHOLDER,
24
+ readPrChangeReport,
25
+ replaceCanonicalReportPlaceholder,
26
+ runMechanicalChangeReport,
27
+ taskIntentDigest,
28
+ validateMechanicalReport,
29
+ validatePrecheckCandidate,
30
+ writePrChangeReportAtomic
31
+ } from './pr-change-report.ts';
32
+ import type {
33
+ MechanicalChangeReport,
34
+ PrChangeReport
35
+ } from './pr-change-report.ts';
16
36
 
17
- type SummaryComment = { id: number; body: string };
37
+ type SummaryComment = { id: number | string; body: string };
38
+ type ChangeReportState = 'ready' | 'missing' | 'stale' | 'invalid';
18
39
  type SummaryContextResult = PlatformResult & {
19
40
  task: { id: string | null; prNumber: number | null };
41
+ pullRequest: PlatformChangeRequestSnapshot | null;
42
+ changeReport: { status: ChangeReportState; path: string; taskIntentSha256: string | null; reason?: string };
20
43
  artifacts: Array<{ family: string; name: string; path: string }>;
21
44
  };
22
45
  type PullRequestSummaryResult = PlatformResult & {
23
46
  result: 'pr_created_with_warnings' | 'pr_reused_with_warnings' | 'no_op_with_warnings' | null;
24
47
  warnings: readonly OperationWarning[];
48
+ precheckVerdict?: 'clear' | 'needs-review';
49
+ nextAction?: 'watch-pr' | 'review-code';
25
50
  };
26
51
  type PullRequestPrimaryResult = 'pr_created' | 'pr_reused' | 'no_op';
52
+ type SummaryOptions = { cwd?: string; client?: PlatformClient; runtimeVersion?: string };
53
+ type ReportWriteResult = PlatformResult & {
54
+ report: { path: string; status: 'written' | 'no-op' | 'planned'; precheckVerdict: 'clear' | 'needs-review'; nextAction: 'watch-pr' | 'review-code' } | null;
55
+ };
56
+
57
+ function summaryMarker(taskId: string): string {
58
+ return `<!-- sync-pr:${taskId}:summary -->`;
59
+ }
60
+
61
+ function escapeSummaryAuditText(value: string): string {
62
+ return escapeHtmlText(value).replace(/\r\n?/g, '\n');
63
+ }
64
+
65
+ function renderSummaryAuditLine(value: string): string {
66
+ const safe = escapeSummaryAuditText(value);
67
+ const longestRun = Math.max(0, ...(safe.match(/`+/g) || []).map((run) => run.length));
68
+ const delimiter = '`'.repeat(longestRun + 1);
69
+ return `${delimiter} ${safe} ${delimiter}`;
70
+ }
71
+
72
+ function renderSafeHumanOverrideAudit(value: string): string {
73
+ const normalized = value.trim().replace(/\r\n?/g, '\n');
74
+ if (!normalized) return '';
75
+ const [heading, ...rows] = normalized.split('\n');
76
+ const safeHeading = heading === '## Human Override Audit' ? heading : renderSummaryAuditLine(heading || '');
77
+ return [safeHeading, ...rows.map((row) => row ? renderSummaryAuditLine(row) : '')].join('\n');
78
+ }
79
+
80
+ function isSafeSummaryEnvelope(value: string, taskId: string): boolean {
81
+ return value.includes(summaryMarker(taskId))
82
+ && (value.match(/<!--\s*sync-pr:/gi) || []).length === 1
83
+ && (value.match(/<!--\s*last-commit:/gi) || []).length === 1
84
+ && !/<!--\s*canonical-pr-change-report\b/i.test(value);
85
+ }
86
+
87
+ function taskReportPath(taskDir: string): string {
88
+ return path.join(taskDir, 'pr-change-report.json');
89
+ }
90
+
91
+ function readBoundTaskSnapshot(taskMdPath: string): { taskContent: string; boundFact: Extract<PrDeliveryFact, { state: 'bound' }> | null } {
92
+ const taskContent = fs.readFileSync(taskMdPath, 'utf8');
93
+ const frontmatter = parseTypedTaskFrontmatter(taskContent);
94
+ const fact = readPrDeliveryFact(frontmatter);
95
+ const boundFact = fact.status === 'valid' && fact.fact.state === 'bound' ? fact.fact : null;
96
+ return { taskContent, boundFact };
97
+ }
98
+
99
+ function boundPrNumberForWarning(taskMdPath: string): number | null {
100
+ try {
101
+ const { boundFact } = readBoundTaskSnapshot(taskMdPath);
102
+ return boundFact ? resourceIdentityNumber(boundFact.identity.resource) : null;
103
+ } catch {
104
+ return null;
105
+ }
106
+ }
27
107
 
28
108
  function warningResultForPrimary(primaryResult: PullRequestPrimaryResult): NonNullable<PullRequestSummaryResult['result']> {
29
109
  if (primaryResult === 'pr_created') return 'pr_created_with_warnings';
@@ -31,17 +111,17 @@ function warningResultForPrimary(primaryResult: PullRequestPrimaryResult): NonNu
31
111
  return 'no_op_with_warnings';
32
112
  }
33
113
 
34
- function summaryMarker(taskId: string): string {
35
- return `<!-- sync-pr:${taskId}:summary -->`;
114
+ function platformError(error: { code: string; message: string; retryable?: boolean }): NonNullable<PlatformResult['error']> {
115
+ return { code: error.code, message: error.message, retryable: error.retryable ?? false };
36
116
  }
37
117
 
38
118
  function buildPullRequestSummary(taskId: string, body: string, headSha: string, humanOverrideAudit = ''): string {
39
- const sections = [body.replace(/\s+$/, ''), humanOverrideAudit.trim()].filter(Boolean).join('\n\n');
119
+ const sections = [body.replace(/\s+$/, ''), renderSafeHumanOverrideAudit(humanOverrideAudit)].filter(Boolean).join('\n\n');
40
120
  return normalizeCommentContent(`${summaryMarker(taskId)}\n<!-- last-commit: ${headSha} -->\n\n${sections}\n`);
41
121
  }
42
122
 
43
123
  function reconcileSummaryComment(comments: SummaryComment[], taskId: string, desired: string):
44
- { action: 'create' | 'update' | 'no-op' | 'conflict'; commentId: number | null } {
124
+ { action: 'create' | 'update' | 'no-op' | 'conflict'; commentId: number | string | null } {
45
125
  const marker = summaryMarker(taskId);
46
126
  const matches = comments.filter((comment) => comment.body.includes(marker));
47
127
  if (matches.length > 1) return { action: 'conflict', commentId: null };
@@ -62,27 +142,323 @@ function canonicalArtifacts(taskDir: string) {
62
142
  const current = byFamily.get(family);
63
143
  if (!current || round > current.round) byFamily.set(family, { family, name: artifact.name, path: artifact.path, round });
64
144
  }
65
- return [...byFamily.values()].map(({ family, name, path }) => ({ family, name, path }));
145
+ return [...byFamily.values()].map(({ family, name, path: artifactPath }) => ({ family, name, path: artifactPath }));
146
+ }
147
+
148
+ function basePlatformResult(
149
+ status: PlatformResult['status'],
150
+ context: PlatformResult,
151
+ taskId: string | null,
152
+ prNumber: number | null,
153
+ error: PlatformResult['error'] = null
154
+ ): PlatformResult {
155
+ return platformResult(status, {
156
+ platform: context.platform,
157
+ capabilities: context.capabilities,
158
+ operations: context.operations,
159
+ resource: { kind: 'pull-request', number: prNumber },
160
+ error,
161
+ ...(taskId ? { task: { id: taskId, prNumber } } : {})
162
+ });
163
+ }
164
+
165
+ function isHardSummaryFailure(error: PlatformResult['error']): boolean {
166
+ return error?.code === 'PR_SUMMARY_HEAD_RACE'
167
+ || error?.code === 'PR_SUMMARY_POSTWRITE_VERIFY_FAILED'
168
+ || error?.code === 'PR_SUMMARY_RACE_COMPENSATION_FAILED';
169
+ }
170
+
171
+ function inspectionError(error: { code: string; message: string; retryable: boolean }): PlatformResult['status'] {
172
+ return error.retryable ? 'blocked' : 'failed';
173
+ }
174
+
175
+ async function inspectBoundPullRequest(
176
+ context: PlatformResult,
177
+ repoRoot: string,
178
+ prNumber: number,
179
+ loadedContext?: LoadedContext
180
+ ): Promise<{ ok: true; value: PlatformChangeRequestSnapshot } | { ok: false; error: PlatformResult['error']; status: PlatformResult['status'] }> {
181
+ if (!context.platform.repository) {
182
+ return { ok: false, status: context.status, error: context.error };
183
+ }
184
+ const loaded = loadedContext
185
+ ? { ok: true as const, value: loadedContext }
186
+ : await resolvePlatformProviderContext({ cwd: repoRoot });
187
+ if (!loaded.ok) return { ok: false, status: loaded.context.status, error: loaded.context.error };
188
+ const target = providerResourceToken(loaded.value.provider, 'pull-request', String(prNumber));
189
+ const inspected = loaded.value.provider.changeRequests?.inspect
190
+ ? await loaded.value.provider.changeRequests.inspect({ context: providerOperationContext(loaded.value), target })
191
+ : unsupportedProviderOperation(loaded.value.provider, 'changeRequests.inspect');
192
+ if (!inspected.ok) {
193
+ const error = providerError(inspected.error, 'PLATFORM_PROVIDER_OPERATION_FAILED');
194
+ return { ok: false, status: inspectionError(error), error };
195
+ }
196
+ const remote: ChangeRequestSnapshot = inspected.value;
197
+ const number = remote.number ?? resourceIdentityNumber(remote.identity) ?? prNumber;
198
+ const head = remote.head || { repository: context.platform.repository, ref: '', sha: remote.headSha || '' };
199
+ const base = remote.base || { repository: context.platform.repository, ref: '', sha: remote.baseSha || '' };
200
+ return {
201
+ ok: true,
202
+ value: {
203
+ repository: context.platform.repository,
204
+ number,
205
+ nodeId: remote.id,
206
+ url: remote.displayUrl || '',
207
+ state: remote.state === 'closed' ? 'closed' : 'open',
208
+ title: remote.title,
209
+ body: remote.body,
210
+ draft: remote.draft ?? false,
211
+ head: { ...head, sha: remote.headSha || head.sha },
212
+ base: { ...base, sha: remote.baseSha || base.sha },
213
+ mergedAt: remote.mergedAt ?? null,
214
+ mergeCommitSha: remote.mergeCommitSha ?? null,
215
+ labels: [...(remote.labels || [])].sort(),
216
+ assignees: [...(remote.assignees || [])].sort(),
217
+ milestone: remote.milestone ?? null,
218
+ mergeability: remote.mergeability || { state: 'unknown', detail: null }
219
+ }
220
+ };
221
+ }
222
+
223
+ function sameIdentity(left: PlatformChangeRequestSnapshot, right: PlatformChangeRequestSnapshot): boolean {
224
+ return JSON.stringify({
225
+ repository: left.repository,
226
+ number: left.number,
227
+ base: left.base,
228
+ head: left.head
229
+ }) === JSON.stringify({
230
+ repository: right.repository,
231
+ number: right.number,
232
+ base: right.base,
233
+ head: right.head
234
+ });
66
235
  }
67
236
 
68
- function summaryContext(taskRef: string, options: { cwd?: string; client?: GitHubClient } = {}): SummaryContextResult {
237
+ function identityFromSnapshot(snapshot: PlatformChangeRequestSnapshot) {
238
+ return {
239
+ repository: snapshot.repository,
240
+ number: snapshot.number,
241
+ base: { ...snapshot.base },
242
+ head: { ...snapshot.head }
243
+ };
244
+ }
245
+
246
+ function readJsonInput(file: string): { ok: true; value: unknown } | { ok: false; error: { code: string; message: string; retryable: boolean } } {
247
+ try {
248
+ return { ok: true, value: JSON.parse(fs.readFileSync(file, 'utf8')) as unknown };
249
+ } catch (error) {
250
+ const errno = (error as NodeJS.ErrnoException).code;
251
+ return {
252
+ ok: false,
253
+ error: {
254
+ code: errno === 'ENOENT' ? 'PR_CHANGE_REPORT_INPUT_MISSING' : 'PR_CHANGE_REPORT_INPUT_INVALID',
255
+ message: error instanceof Error ? error.message : String(error),
256
+ retryable: false
257
+ }
258
+ };
259
+ }
260
+ }
261
+
262
+ function sameMechanical(left: MechanicalChangeReport, right: MechanicalChangeReport): boolean {
263
+ return JSON.stringify(left) === JSON.stringify(right);
264
+ }
265
+
266
+ function currentReportCheck(
267
+ report: PrChangeReport,
268
+ snapshot: PlatformChangeRequestSnapshot,
269
+ taskContent: string,
270
+ repoRoot: string
271
+ ): { ok: true; digest: string; mechanical: MechanicalChangeReport } | { ok: false; error: { code: string; message: string; retryable: boolean } } {
272
+ const digest = taskIntentDigest(taskContent);
273
+ if (!digest.ok) return { ok: false, error: platformError(digest.error) };
274
+ const reportIdentity = report.identity;
275
+ if (reportIdentity.repository !== snapshot.repository || reportIdentity.number !== snapshot.number ||
276
+ JSON.stringify(reportIdentity.base) !== JSON.stringify(snapshot.base) || JSON.stringify(reportIdentity.head) !== JSON.stringify(snapshot.head)) return {
277
+ ok: false,
278
+ error: { code: 'PR_CHANGE_REPORT_STALE', message: 'Change report identity does not match the authoritative pull request snapshot', retryable: false }
279
+ };
280
+ if (report.inputs.taskIntentSha256 !== digest.value.sha256) return {
281
+ ok: false,
282
+ error: { code: 'PR_CHANGE_REPORT_STALE', message: 'Change report task intent digest is stale', retryable: false }
283
+ };
284
+ let mechanical: MechanicalChangeReport;
285
+ try {
286
+ mechanical = runMechanicalChangeReport(repoRoot, snapshot.base.sha, snapshot.head.sha);
287
+ } catch (error) {
288
+ return { ok: false, error: { code: 'PR_CHANGE_REPORT_GIT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false } };
289
+ }
290
+ const reportMechanical: MechanicalChangeReport = {
291
+ version: 1,
292
+ base: snapshot.base.sha,
293
+ head: snapshot.head.sha,
294
+ mergeBase: report.diff.mergeBase,
295
+ patchSha256: report.diff.patchSha256,
296
+ files: report.diff.files,
297
+ totals: report.diff.totals
298
+ };
299
+ if (!sameMechanical(reportMechanical, mechanical)) return {
300
+ ok: false,
301
+ error: { code: 'PR_CHANGE_REPORT_STALE', message: 'Change report patch or mechanical statistics are stale', retryable: false }
302
+ };
303
+ return { ok: true, digest: digest.value.sha256, mechanical };
304
+ }
305
+
306
+ function summaryReportStatus(
307
+ reportPath: string,
308
+ snapshot: PlatformChangeRequestSnapshot | null,
309
+ taskContent: string,
310
+ repoRoot: string
311
+ ): { status: ChangeReportState; taskIntentSha256: string | null; reason?: string } {
312
+ const digest = taskIntentDigest(taskContent);
313
+ if (!digest.ok) return { status: 'invalid', taskIntentSha256: null, reason: digest.error.message };
314
+ const report = readPrChangeReport(reportPath);
315
+ if (!report.ok) return { status: report.error.code === 'PR_CHANGE_REPORT_MISSING' ? 'missing' : 'invalid', taskIntentSha256: digest.value.sha256, reason: report.error.message };
316
+ if (!snapshot) return { status: 'stale', taskIntentSha256: digest.value.sha256, reason: 'No bound pull request snapshot is available' };
317
+ const checked = currentReportCheck(report.value, snapshot, taskContent, repoRoot);
318
+ return checked.ok
319
+ ? { status: 'ready', taskIntentSha256: checked.digest }
320
+ : { status: checked.error.code === 'PR_CHANGE_REPORT_STALE' ? 'stale' : 'invalid', taskIntentSha256: digest.value.sha256, reason: checked.error.message };
321
+ }
322
+
323
+ async function summaryContext(taskRef: string, options: SummaryOptions = {}): Promise<SummaryContextResult> {
69
324
  const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
70
- if (!resolved.ok) return { ...platformResult('failed', { error: { code: resolved.code, message: resolved.message, retryable: false } }), task: { id: resolved.taskId, prNumber: null }, artifacts: [] };
71
- const frontmatter = parseTypedTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8'));
72
- const fact = readPrDeliveryFact(frontmatter);
73
- const prNumber = fact.status === 'valid' && fact.fact.state === 'bound' ? fact.fact.identity.number : null;
74
- return { ...platformResult('no-op'), task: { id: resolved.taskId, prNumber }, artifacts: canonicalArtifacts(resolved.taskDir) };
325
+ if (!resolved.ok) return {
326
+ ...platformResult('failed', { error: { code: resolved.code, message: resolved.message, retryable: false } }),
327
+ task: { id: resolved.taskId, prNumber: null }, pullRequest: null,
328
+ changeReport: { status: 'invalid', path: '', taskIntentSha256: null, reason: resolved.message }, artifacts: []
329
+ };
330
+ const taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
331
+ const frontmatter = parseTypedTaskFrontmatter(taskContent);
332
+ const fact = readPrDeliveryFact(frontmatter, options.runtimeVersion);
333
+ const prNumber = fact.status === 'valid' && fact.fact.state === 'bound' ? resourceIdentityNumber(fact.fact.identity.resource) : null;
334
+ const reportPath = taskReportPath(resolved.taskDir);
335
+ const loaded = await resolvePlatformProviderContext({ cwd: resolved.repoRoot, client: options.client });
336
+ const context = loaded.ok ? loaded.value.context : loaded.context;
337
+ let pullRequest: PlatformChangeRequestSnapshot | null = null;
338
+ if (loaded.ok && prNumber && context.platform.repository && ['no-op', 'degraded'].includes(context.status)) {
339
+ const inspected = await inspectBoundPullRequest(context, resolved.repoRoot, prNumber, loaded.value);
340
+ if (inspected.ok) pullRequest = inspected.value;
341
+ }
342
+ const report = summaryReportStatus(reportPath, pullRequest, taskContent, resolved.repoRoot);
343
+ return {
344
+ ...platformResult(context.status, {
345
+ platform: context.platform,
346
+ capabilities: context.capabilities,
347
+ operations: context.operations,
348
+ error: context.error
349
+ }),
350
+ task: { id: resolved.taskId, prNumber },
351
+ pullRequest,
352
+ changeReport: { status: report.status, path: reportPath, taskIntentSha256: report.taskIntentSha256, ...(report.reason ? { reason: report.reason } : {}) },
353
+ artifacts: canonicalArtifacts(resolved.taskDir)
354
+ };
75
355
  }
76
356
 
77
- function syncPullRequestSummary(taskRef: string, options: { agent: string; body: string; cwd?: string; client?: GitHubClient; dryRun?: boolean; primaryResult: PullRequestPrimaryResult }): PullRequestSummaryResult {
357
+ type ReportWriteOptions = {
358
+ agent: string;
359
+ mechanicalFile: string;
360
+ precheckFile: string;
361
+ cwd?: string;
362
+ client?: PlatformClient;
363
+ dryRun?: boolean;
364
+ };
365
+
366
+ async function reportWrite(taskRef: string, options: ReportWriteOptions): Promise<ReportWriteResult> {
367
+ const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
368
+ if (!resolved.ok) return { ...platformResult('failed', { error: { code: resolved.code, message: resolved.message, retryable: false } }), report: null };
369
+ const loaded = await resolvePlatformProviderContext({ cwd: resolved.repoRoot, client: options.client });
370
+ const context = loaded.ok ? loaded.value.context : loaded.context;
371
+ if (!loaded.ok || !context.platform.repository || !['no-op', 'degraded'].includes(context.status)) return { ...context, report: null };
372
+ let knownPrNumber: number | null = null;
373
+ try {
374
+ return await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, options.agent, async () => {
375
+ const { taskContent, boundFact } = readBoundTaskSnapshot(resolved.taskMdPath);
376
+ if (!boundFact) return {
377
+ ...basePlatformResult('failed', context, resolved.taskId, null, { code: 'PR_NOT_LINKED', message: 'Task has no verified bound pull request', retryable: false }),
378
+ report: null
379
+ };
380
+ const boundPrNumber = resourceIdentityNumber(boundFact.identity.resource);
381
+ if (!boundPrNumber) return {
382
+ ...basePlatformResult('failed', context, resolved.taskId, null, { code: 'PR_NUMBER_INVALID', message: 'Bound pull request has no numeric identifier', retryable: false }),
383
+ report: null
384
+ };
385
+ knownPrNumber = boundPrNumber;
386
+ const initial = await inspectBoundPullRequest(context, resolved.repoRoot, boundPrNumber, loaded.value);
387
+ if (!initial.ok) return { ...basePlatformResult(initial.status, context, resolved.taskId, boundPrNumber, initial.error), report: null };
388
+ const digest = taskIntentDigest(taskContent);
389
+ if (!digest.ok) return { ...basePlatformResult('failed', context, resolved.taskId, boundPrNumber, platformError(digest.error)), report: null };
390
+ const mechanicalInput = readJsonInput(path.resolve(resolved.repoRoot, options.mechanicalFile));
391
+ if (!mechanicalInput.ok) return { ...basePlatformResult('failed', context, resolved.taskId, boundPrNumber, mechanicalInput.error), report: null };
392
+ const mechanical = validateMechanicalReport(mechanicalInput.value);
393
+ if (!mechanical.ok) return { ...basePlatformResult('failed', context, resolved.taskId, boundPrNumber, platformError(mechanical.error)), report: null };
394
+ const candidateInput = readJsonInput(path.resolve(resolved.repoRoot, options.precheckFile));
395
+ if (!candidateInput.ok) return { ...basePlatformResult('failed', context, resolved.taskId, boundPrNumber, candidateInput.error), report: null };
396
+ const candidate = validatePrecheckCandidate(candidateInput.value);
397
+ if (!candidate.ok) return { ...basePlatformResult('failed', context, resolved.taskId, boundPrNumber, platformError(candidate.error)), report: null };
398
+ let recomputed: MechanicalChangeReport;
399
+ try {
400
+ recomputed = runMechanicalChangeReport(resolved.repoRoot, initial.value.base.sha, initial.value.head.sha);
401
+ } catch (error) {
402
+ return { ...basePlatformResult('failed', context, resolved.taskId, boundPrNumber, { code: 'PR_CHANGE_REPORT_GIT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false }), report: null };
403
+ }
404
+ if (!sameMechanical(mechanical.value, recomputed)) return {
405
+ ...basePlatformResult('failed', context, resolved.taskId, boundPrNumber, { code: 'PR_CHANGE_REPORT_MECHANICAL_MISMATCH', message: 'Mechanical report input does not match the current complete diff', retryable: false }),
406
+ report: null
407
+ };
408
+ const built = buildPrChangeReport(identityFromSnapshot(initial.value), digest.value.sha256, mechanical.value, candidate.value);
409
+ if (!built.ok) return { ...basePlatformResult('failed', context, resolved.taskId, boundPrNumber, platformError(built.error)), report: null };
410
+ const final = await inspectBoundPullRequest(context, resolved.repoRoot, boundPrNumber, loaded.value);
411
+ if (!final.ok) return { ...basePlatformResult(final.status, context, resolved.taskId, boundPrNumber, final.error), report: null };
412
+ if (!sameIdentity(initial.value, final.value)) return {
413
+ ...basePlatformResult('blocked', context, resolved.taskId, boundPrNumber, { code: 'PR_CHANGE_REPORT_HEAD_RACE', message: 'Pull request identity changed while generating the change report', retryable: true }),
414
+ report: null
415
+ };
416
+ const target = taskReportPath(resolved.taskDir);
417
+ const existing = readPrChangeReport(target);
418
+ const reportInfo = {
419
+ path: target,
420
+ precheckVerdict: built.value.precheck.verdict,
421
+ nextAction: built.value.precheck.route
422
+ } as const;
423
+ if (existing.ok && JSON.stringify(existing.value) === JSON.stringify(built.value)) return {
424
+ ...basePlatformResult('no-op', context, resolved.taskId, boundPrNumber),
425
+ report: { ...reportInfo, status: 'no-op' }
426
+ };
427
+ if (options.dryRun) return {
428
+ ...basePlatformResult('planned', context, resolved.taskId, boundPrNumber),
429
+ operations: [{ name: 'change-report:write', status: 'planned', reasonCode: null }],
430
+ report: { ...reportInfo, status: 'planned' }
431
+ };
432
+ try {
433
+ writePrChangeReportAtomic(target, built.value);
434
+ } catch (error) {
435
+ return { ...basePlatformResult('failed', context, resolved.taskId, boundPrNumber, { code: 'PR_CHANGE_REPORT_WRITE_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false }), report: null };
436
+ }
437
+ return {
438
+ ...basePlatformResult('applied', context, resolved.taskId, boundPrNumber),
439
+ operations: [{ name: 'change-report:write', status: 'applied', reasonCode: null }],
440
+ report: { ...reportInfo, status: 'written' }
441
+ };
442
+ });
443
+ } catch (error) {
444
+ const lockError = error instanceof TaskExecutionLockError
445
+ ? { code: error.code, message: error.message, retryable: error.code === 'ORCHESTRATION_LOCK_BUSY' }
446
+ : { code: 'PR_CHANGE_REPORT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false };
447
+ return { ...basePlatformResult(lockError.retryable ? 'blocked' : 'failed', context, resolved.taskId, knownPrNumber, lockError), report: null };
448
+ }
449
+ }
450
+
451
+ async function syncPullRequestSummary(
452
+ taskRef: string,
453
+ options: { agent: string; body: string; changeReportFile?: string; cwd?: string; client?: PlatformClient; dryRun?: boolean; strict?: boolean; primaryResult: PullRequestPrimaryResult; runtimeVersion?: string }
454
+ ): Promise<PullRequestSummaryResult> {
78
455
  const warningResult = warningResultForPrimary(options.primaryResult);
79
456
  let knownPrNumber: number | null = null;
80
457
  const softenFailure = (output: PlatformResult): PullRequestSummaryResult => {
81
458
  const prNumber = output.resource.kind === 'pull-request' && output.resource.number
82
459
  ? output.resource.number
83
460
  : knownPrNumber;
84
- const warning = output.error && prNumber
85
- && output.error.code !== 'PR_NOT_LINKED'
461
+ const warning = output.error && prNumber && output.error.code !== 'PR_NOT_LINKED'
86
462
  ? {
87
463
  code: output.error.code,
88
464
  message: output.error.message,
@@ -104,41 +480,193 @@ function syncPullRequestSummary(taskRef: string, options: { agent: string; body:
104
480
  }
105
481
  : { ...output, result: null, warnings: [] };
106
482
  };
483
+ const preserveFailure = (output: PlatformResult): PullRequestSummaryResult => options.strict
484
+ ? { ...output, result: null, warnings: [] }
485
+ : softenFailure(output);
107
486
  const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
108
487
  if (!resolved.ok) return softenFailure(platformResult('failed', { error: { code: resolved.code, message: resolved.message, retryable: false } }));
488
+ const fail = (status: PlatformResult['status'], context: PlatformResult, error: PlatformResult['error'], prNumber = knownPrNumber): PullRequestSummaryResult => {
489
+ const output = basePlatformResult(status, context, resolved.taskId, prNumber, error);
490
+ return options.strict || isHardSummaryFailure(error)
491
+ ? { ...output, result: null, warnings: [] }
492
+ : { ...softenFailure(output) };
493
+ };
109
494
  const frontmatter = parseTypedTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8'));
110
- const fact = readPrDeliveryFact(frontmatter);
111
- if (fact.status === 'invalid') return softenFailure(platformResult('failed', { error: { code: 'PR_DELIVERY_FACT_INVALID', message: fact.error.message, retryable: false } }));
112
- const prNumber = fact.status === 'valid' && fact.fact.state === 'bound' ? fact.fact.identity.number : null;
113
- if (!prNumber) return softenFailure(platformResult('failed', { error: { code: fact.status === 'missing' ? 'PR_DELIVERY_FACT_MISSING' : 'PR_NOT_LINKED', message: 'Task has no verified bound pull request', retryable: false } }));
114
- knownPrNumber = prNumber;
115
- const client = options.client || createGitHubClient();
116
- const context = resolvePlatformContext({ cwd: resolved.repoRoot, client });
117
- if (!context.platform.repository || !['no-op', 'degraded'].includes(context.status)) return softenFailure(context);
118
- let headSha: string;
495
+ const fact = readPrDeliveryFact(frontmatter, options.runtimeVersion);
496
+ if (fact.status === 'invalid') return preserveFailure(platformResult('failed', { error: { code: fact.error.code, message: fact.error.message, retryable: false } }));
497
+ if (!options.changeReportFile) return preserveFailure(platformResult('failed', { error: { code: 'PR_CHANGE_REPORT_MISSING', message: 'summary-sync requires --change-report-file', retryable: false } }));
498
+ const loaded = await resolvePlatformProviderContext({ cwd: resolved.repoRoot, client: options.client });
499
+ const context = loaded.ok ? loaded.value.context : loaded.context;
500
+ if (!loaded.ok || !context.platform.repository || !['no-op', 'degraded'].includes(context.status)) {
501
+ // This diagnostic-only read preserves the known PR target when context resolution fails before the execution lock.
502
+ knownPrNumber = boundPrNumberForWarning(resolved.taskMdPath);
503
+ return preserveFailure(context);
504
+ }
119
505
  try {
120
- headSha = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: resolved.repoRoot, encoding: 'utf8' }).trim();
506
+ return await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, options.agent, async () => {
507
+ const { taskContent, boundFact } = readBoundTaskSnapshot(resolved.taskMdPath);
508
+ if (!boundFact) return {
509
+ ...platformResult('failed', { resource: { kind: 'pull-request', number: null }, error: { code: 'PR_NOT_LINKED', message: 'Task has no verified bound pull request', retryable: false } }),
510
+ result: null, warnings: []
511
+ };
512
+ const prIdentity = boundFact.identity.resource;
513
+ const boundPrNumber = resourceIdentityNumber(prIdentity);
514
+ if (!boundPrNumber) return {
515
+ ...platformResult('failed', { resource: { kind: 'pull-request', number: null }, error: { code: 'PR_NUMBER_INVALID', message: 'Bound pull request has no numeric identifier', retryable: false } }),
516
+ result: null, warnings: []
517
+ };
518
+ knownPrNumber = boundPrNumber;
519
+ const prNumber = boundPrNumber;
520
+ const initial = await inspectBoundPullRequest(context, resolved.repoRoot, boundPrNumber, loaded.value);
521
+ if (!initial.ok) return fail(initial.status, context, initial.error);
522
+ const expectedReportPath = taskReportPath(resolved.taskDir);
523
+ const suppliedReportPath = path.resolve(resolved.repoRoot, options.changeReportFile!);
524
+ if (suppliedReportPath !== expectedReportPath) return fail('failed', context, { code: 'PR_CHANGE_REPORT_PATH_INVALID', message: 'summary-sync must consume the task-bound pr-change-report.json', retryable: false });
525
+ const report = readPrChangeReport(suppliedReportPath);
526
+ if (!report.ok) return fail('failed', context, platformError(report.error));
527
+ const checked = currentReportCheck(report.value, initial.value, taskContent, resolved.repoRoot);
528
+ if (!checked.ok) return fail('failed', context, checked.error);
529
+ const placeholder = CANONICAL_REPORT_PLACEHOLDER;
530
+ const split = splitDocumentPlaceholder(options.body, placeholder);
531
+ if (!split.ok) return fail('failed', context, {
532
+ code: 'PR_SUMMARY_BODY_CONTRACT_INVALID',
533
+ message: split.error.message,
534
+ retryable: false
535
+ });
536
+ const prefix = sanitizeMarkdownDocument(split.value.prefix, { reservedMarkers: [CONTROL_MARKER_PATTERN] });
537
+ const suffix = sanitizeMarkdownDocument(split.value.suffix, { reservedMarkers: [CONTROL_MARKER_PATTERN] });
538
+ if (!prefix.ok) {
539
+ return fail('failed', context, {
540
+ code: 'PR_SUMMARY_BODY_CONTRACT_INVALID',
541
+ message: `${prefix.error.message} at offset ${prefix.error.offset}`,
542
+ retryable: false
543
+ });
544
+ }
545
+ if (!suffix.ok) {
546
+ return fail('failed', context, {
547
+ code: 'PR_SUMMARY_BODY_CONTRACT_INVALID',
548
+ message: `${suffix.error.message} at offset ${suffix.error.offset}`,
549
+ retryable: false
550
+ });
551
+ }
552
+ const replaced = replaceCanonicalReportPlaceholder(`${prefix.value}${placeholder}${suffix.value}`, report.value);
553
+ if (!replaced.ok) return fail('failed', context, platformError(replaced.error));
554
+ const desired = buildPullRequestSummary(resolved.taskId, replaced.value, initial.value.head.sha, renderHumanOverrideAudit(taskContent));
555
+ if (!isSafeSummaryEnvelope(desired, resolved.taskId)) return fail('failed', context, {
556
+ code: 'PR_SUMMARY_RENDER_INVALID',
557
+ message: 'Summary contains an invalid or duplicated reserved control marker',
558
+ retryable: false
559
+ });
560
+ const listed = loaded.value.provider.comments?.list
561
+ ? await loaded.value.provider.comments.list({ context: providerOperationContext(loaded.value), parent: prIdentity }).then((response) => response.ok
562
+ ? { ok: true as const, value: response.value.map((comment) => ({ id: /^\d+$/.test(comment.id) ? Number(comment.id) : comment.id, body: comment.body })) }
563
+ : response)
564
+ : unsupportedProviderOperation(loaded.value.provider, 'comments.list');
565
+ if (!listed.ok) return fail(providerStatus(listed.error) === 'blocked' ? 'blocked' : 'failed', context, providerError(listed.error, 'PLATFORM_PROVIDER_OPERATION_FAILED'));
566
+ const reconciliation = reconcileSummaryComment(listed.value, resolved.taskId, desired);
567
+ if (reconciliation.action === 'conflict') return fail('failed', context, { code: 'PR_SUMMARY_MARKER_AMBIGUOUS', message: 'Multiple PR comments contain the summary marker', retryable: false });
568
+ const previousBody = reconciliation.action === 'update'
569
+ ? listed.value.find((comment) => comment.id === reconciliation.commentId)?.body ?? null
570
+ : null;
571
+ const info = { precheckVerdict: report.value.precheck.verdict, nextAction: report.value.precheck.route } as const;
572
+ const beforeWrite = await inspectBoundPullRequest(context, resolved.repoRoot, boundPrNumber, loaded.value);
573
+ if (!beforeWrite.ok) return fail(beforeWrite.status, context, beforeWrite.error, prNumber);
574
+ if (!sameIdentity(initial.value, beforeWrite.value)) return fail('blocked', context, {
575
+ code: 'PR_SUMMARY_HEAD_RACE',
576
+ message: 'Pull request head changed before publishing the summary',
577
+ retryable: true
578
+ }, prNumber);
579
+ if (reconciliation.action === 'no-op') return {
580
+ ...basePlatformResult('no-op', context, resolved.taskId, prNumber),
581
+ comment: { kind: 'summary', marker: summaryMarker(resolved.taskId), ids: [reconciliation.commentId!], parts: 1 },
582
+ result: null, warnings: [], ...info
583
+ };
584
+ if (options.dryRun) return {
585
+ ...basePlatformResult('planned', context, resolved.taskId, prNumber),
586
+ operations: [{ name: `summary:${reconciliation.action}`, status: 'planned', reasonCode: null }],
587
+ result: null, warnings: [], ...info
588
+ };
589
+ const existingComment = reconciliation.commentId === null
590
+ ? undefined
591
+ : providerResourceToken(loaded.value.provider, 'comment', String(reconciliation.commentId));
592
+ const written = loaded.value.provider.comments?.write
593
+ ? await loaded.value.provider.comments.write({
594
+ context: providerOperationContext(loaded.value),
595
+ parent: prIdentity,
596
+ body: desired,
597
+ ...(existingComment ? { existingComment } : {}),
598
+ mutation: { idempotencyKey: `pr-summary:${resolved.taskId}` }
599
+ })
600
+ : unsupportedProviderOperation(loaded.value.provider, 'comments.write');
601
+ if (!written.ok) return fail(providerStatus(written.error) === 'blocked' ? 'blocked' : 'failed', context, providerError(written.error, 'PLATFORM_PROVIDER_OPERATION_FAILED'), prNumber);
602
+ const after = await inspectBoundPullRequest(context, resolved.repoRoot, prNumber, loaded.value);
603
+ const id = /^\d+$/.test(written.value.remoteId) ? Number(written.value.remoteId) : written.value.remoteId;
604
+ const compensateWrittenSummary = async () => reconciliation.action === 'create'
605
+ ? written.value.remoteId && loaded.value.provider.comments?.delete
606
+ ? await loaded.value.provider.comments.delete({
607
+ context: providerOperationContext(loaded.value),
608
+ parent: prIdentity,
609
+ comment: providerResourceToken(loaded.value.provider, 'comment', written.value.remoteId),
610
+ mutation: { idempotencyKey: `pr-summary-delete:${resolved.taskId}:${written.value.remoteId}` }
611
+ })
612
+ : { ok: false as const, error: { code: 'PR_SUMMARY_RACE_COMPENSATION_FAILED', message: 'Created summary comment has no valid id', retryable: true } }
613
+ : reconciliation.commentId && previousBody !== null && loaded.value.provider.comments?.write
614
+ ? await loaded.value.provider.comments.write({
615
+ context: providerOperationContext(loaded.value),
616
+ parent: prIdentity,
617
+ body: previousBody,
618
+ existingComment: providerResourceToken(loaded.value.provider, 'comment', String(reconciliation.commentId)),
619
+ mutation: { idempotencyKey: `pr-summary-restore:${resolved.taskId}` }
620
+ })
621
+ : { ok: false as const, error: { code: 'PR_SUMMARY_RACE_COMPENSATION_FAILED', message: 'Previous summary body is unavailable for restoration', retryable: true } };
622
+ if (!after.ok) {
623
+ const compensated = await compensateWrittenSummary();
624
+ if (!compensated.ok) return fail('blocked', context, {
625
+ code: 'PR_SUMMARY_RACE_COMPENSATION_FAILED',
626
+ message: `Summary post-write verification failed and compensation failed: ${compensated.error.message}`,
627
+ retryable: true
628
+ }, prNumber);
629
+ return fail('blocked', context, {
630
+ code: 'PR_SUMMARY_POSTWRITE_VERIFY_FAILED',
631
+ message: `Summary was written but pull-request verification failed: ${after.error?.message || 'unknown verification error'}; the previous summary was restored or the new comment was deleted`,
632
+ retryable: true
633
+ }, prNumber);
634
+ }
635
+ if (!sameIdentity(initial.value, after.value)) {
636
+ const compensated = await compensateWrittenSummary();
637
+ if (!compensated.ok) return fail('blocked', context, {
638
+ code: 'PR_SUMMARY_RACE_COMPENSATION_FAILED',
639
+ message: `Pull-request head changed and summary compensation failed: ${compensated.error.message}`,
640
+ retryable: true
641
+ }, prNumber);
642
+ return fail('blocked', context, {
643
+ code: 'PR_SUMMARY_HEAD_RACE',
644
+ message: 'Pull-request head changed while publishing; the previous summary was restored or the new comment was deleted; rerun report and summary sync',
645
+ retryable: true
646
+ }, prNumber);
647
+ }
648
+ return {
649
+ ...basePlatformResult('applied', context, resolved.taskId, prNumber),
650
+ comment: { kind: 'summary', marker: summaryMarker(resolved.taskId), ids: Number.isInteger(id) ? [id] : [], parts: 1 },
651
+ operations: [{ name: `summary:${reconciliation.action}`, status: 'applied', reasonCode: null }],
652
+ result: null, warnings: [], ...info
653
+ };
654
+ });
121
655
  } catch (error) {
122
- return softenFailure(platformResult('failed', { platform: context.platform, capabilities: context.capabilities, error: { code: 'GIT_HEAD_UNRESOLVED', message: error instanceof Error ? error.message : String(error), retryable: false } }));
656
+ const lockError = error instanceof TaskExecutionLockError
657
+ ? { code: error.code, message: error.message, retryable: error.code === 'ORCHESTRATION_LOCK_BUSY' }
658
+ : { code: 'PR_SUMMARY_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false };
659
+ return fail(lockError.retryable ? 'blocked' : 'failed', context, lockError);
123
660
  }
124
- const desired = buildPullRequestSummary(
125
- resolved.taskId,
126
- options.body,
127
- headSha,
128
- renderHumanOverrideAudit(fs.readFileSync(resolved.taskMdPath, 'utf8'))
129
- );
130
- const listed = listRemoteComments(client, context.platform.repository, prNumber, resolved.repoRoot);
131
- if (!listed.ok) return softenFailure(platformResult(listed.error.retryable ? 'blocked' : 'failed', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, error: listed.error }));
132
- const reconciliation = reconcileSummaryComment(listed.value, resolved.taskId, desired);
133
- if (reconciliation.action === 'conflict') return softenFailure(platformResult('failed', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, error: { code: 'PR_SUMMARY_MARKER_AMBIGUOUS', message: 'Multiple PR comments contain the summary marker', retryable: false } }));
134
- if (reconciliation.action === 'no-op') return softenFailure(platformResult('no-op', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, comment: { kind: 'summary', marker: summaryMarker(resolved.taskId), ids: [reconciliation.commentId!], parts: 1 }, error: null }));
135
- if (options.dryRun) return softenFailure(platformResult('planned', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, operations: [{ name: `summary:${reconciliation.action}`, status: 'planned', reasonCode: null }], error: null }));
136
- const written = writeComment(client, context.platform.repository, prNumber, resolved.repoRoot, desired, reconciliation.commentId || undefined);
137
- if (!written.ok) return softenFailure(platformResult(written.error.retryable ? 'blocked' : 'failed', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, error: written.error }));
138
- const id = Number(written.value.id || reconciliation.commentId);
139
- return softenFailure(platformResult('applied', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, comment: { kind: 'summary', marker: summaryMarker(resolved.taskId), ids: Number.isInteger(id) ? [id] : [], parts: 1 }, operations: [{ name: `summary:${reconciliation.action}`, status: 'applied', reasonCode: null }], error: null }));
140
- }
141
-
142
- export { buildPullRequestSummary, reconcileSummaryComment, summaryContext, summaryMarker, syncPullRequestSummary };
143
- export type { PullRequestSummaryResult, SummaryContextResult };
144
- export { warningResultForPrimary };
661
+ }
662
+
663
+ export {
664
+ buildPullRequestSummary,
665
+ reconcileSummaryComment,
666
+ reportWrite,
667
+ summaryContext,
668
+ summaryMarker,
669
+ syncPullRequestSummary,
670
+ warningResultForPrimary
671
+ };
672
+ export type { PullRequestSummaryResult, ReportWriteOptions, ReportWriteResult, SummaryContextResult };