@fitlab-ai/agent-infra 0.9.11 → 0.9.13

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