@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
@@ -0,0 +1,910 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+
6
+ import semver from 'semver';
7
+
8
+ import {
9
+ createGitHubClient,
10
+ MINIMUM_GITHUB_CLI_VERSION
11
+ } from './github-client.ts';
12
+ import type { GitHubClient } from './github-client.ts';
13
+ import type {
14
+ ChangeRequestSnapshot,
15
+ CheckLogSnapshot,
16
+ CheckRunSnapshot,
17
+ GitEvidenceSnapshot,
18
+ IssueSnapshot,
19
+ MutationReceipt,
20
+ PlatformContextSnapshot,
21
+ ProviderOperationContext,
22
+ PlatformProvider,
23
+ PlatformProviderFactoryInput,
24
+ ProviderResult,
25
+ ResourceIdentity,
26
+ ReleaseNotesFacts,
27
+ RepositoryMetadataSnapshot,
28
+ ReleaseSnapshot,
29
+ RemoteCommentSnapshot,
30
+ RequiredCheckSnapshot,
31
+ ReviewSnapshot,
32
+ VerificationRemoteFacts
33
+ } from './provider-contract.ts';
34
+ import { resourceIdentityNumber, resourceIdentityString } from './resource-identity.ts';
35
+ import { syncLabelDelta } from './in-label-sync.ts';
36
+
37
+ const CURRENT_USER_QUERY = 'query { viewer { login } }';
38
+ const ISSUE_TYPES_QUERY = `query($owner:String!){organization(login:$owner){issueTypes(first:20){nodes{id name pinnedFields{__typename ... on IssueFieldSingleSelect{id name options{id name}} ... on IssueFieldDate{id name} ... on IssueFieldText{id name} ... on IssueFieldNumber{id name}}}}}}`;
39
+ const ISSUE_FIELDS_QUERY = `query($owner:String!,$name:String!,$number:Int!){repository(owner:$owner,name:$name){issue(number:$number){id issueType{id name pinnedFields{__typename ... on IssueFieldSingleSelect{id name options{id name}} ... on IssueFieldDate{id name} ... on IssueFieldText{id name} ... on IssueFieldNumber{id name}}} issueFieldValues(first:50){nodes{__typename ... on IssueFieldSingleSelectValue{name optionId field{... on IssueFieldSingleSelect{id name}}} ... on IssueFieldDateValue{value field{... on IssueFieldDate{id name}}} ... on IssueFieldTextValue{value field{... on IssueFieldText{id name}}} ... on IssueFieldNumberValue{value field{... on IssueFieldNumber{id name}}}}}}}}`;
40
+
41
+ function parseGitHubRemote(remote: string): string | null {
42
+ const trimmed = remote.trim().replace(/\.git$/, '');
43
+ const match = trimmed.match(/^(?:https?:\/\/github\.com\/|ssh:\/\/(?:git@)?github\.com\/|git@github\.com:)([^/\s]+\/[^/\s]+)$/i);
44
+ return match?.[1] || null;
45
+ }
46
+
47
+ function defaultGitRemote(cwd: string): string | null {
48
+ try {
49
+ return execFileSync('git', ['remote', 'get-url', 'origin'], {
50
+ cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe']
51
+ }).trim();
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+
57
+ function configuredGitRemotes(cwd: string): Array<{ name: string; url: string; repository: string }> {
58
+ try {
59
+ return execFileSync('git', ['remote'], {
60
+ cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe']
61
+ }).split(/\r?\n/).filter(Boolean).flatMap((name) => {
62
+ try {
63
+ const url = execFileSync('git', ['config', '--get', `remote.${name}.url`], {
64
+ cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe']
65
+ }).trim();
66
+ const repository = parseGitHubRemote(url);
67
+ return repository ? [{ name, url, repository }] : [];
68
+ } catch {
69
+ return [];
70
+ }
71
+ });
72
+ } catch {
73
+ return [];
74
+ }
75
+ }
76
+
77
+ function rewriteGitHubRemote(remote: string, repository: string): string | null {
78
+ const match = remote.trim().match(
79
+ /^(https?:\/\/github\.com\/|ssh:\/\/(?:git@)?github\.com\/|git@github\.com:)[^\/\s]+\/[^\/\s]+(?:\.git)?$/i
80
+ );
81
+ return match ? `${match[1]}${repository}.git` : null;
82
+ }
83
+
84
+ function resolveGitHubChangeRequestGitEvidence({
85
+ cwd,
86
+ repository,
87
+ number,
88
+ baseRepository,
89
+ baseRef
90
+ }: {
91
+ cwd: string;
92
+ repository: string;
93
+ number: number;
94
+ baseRepository: string;
95
+ baseRef: string;
96
+ }) {
97
+ if (baseRepository !== repository || number <= 0) {
98
+ return {
99
+ ok: false as const,
100
+ error: {
101
+ code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
102
+ message: 'Pull request repository identity is inconsistent',
103
+ retryable: false
104
+ }
105
+ };
106
+ }
107
+ const reviewedHeadRef = `refs/pull/${number}/head`;
108
+ const targetHeadRef = `refs/heads/${baseRef}`;
109
+ const refValid = (ref: string) => {
110
+ try {
111
+ execFileSync('git', ['check-ref-format', ref], {
112
+ cwd, encoding: 'utf8', stdio: ['ignore', 'ignore', 'ignore']
113
+ });
114
+ return true;
115
+ } catch {
116
+ return false;
117
+ }
118
+ };
119
+ if (!refValid(reviewedHeadRef) || !refValid(targetHeadRef)) {
120
+ return {
121
+ ok: false as const,
122
+ error: {
123
+ code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
124
+ message: 'Pull request Git refs are invalid',
125
+ retryable: false
126
+ }
127
+ };
128
+ }
129
+
130
+ const remotes = configuredGitRemotes(cwd);
131
+ const exact = remotes
132
+ .filter((remote) => remote.repository === repository)
133
+ .sort((left, right) => Number(right.name === 'origin') - Number(left.name === 'origin') ||
134
+ left.name.localeCompare(right.name))[0];
135
+ const origin = remotes.find((remote) => remote.name === 'origin');
136
+ const remoteUrl = exact?.url || (origin ? rewriteGitHubRemote(origin.url, repository) : null);
137
+ if (!remoteUrl) {
138
+ return {
139
+ ok: false as const,
140
+ error: {
141
+ code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
142
+ message: `No GitHub remote can provide evidence for ${repository}`,
143
+ retryable: false
144
+ }
145
+ };
146
+ }
147
+ return {
148
+ ok: true as const,
149
+ value: { remoteUrl, reviewedHeadRef, targetHeadRef }
150
+ };
151
+ }
152
+
153
+ function failure(error: { code: string; message: string; retryable: boolean }): ProviderResult<PlatformContextSnapshot> {
154
+ return { ok: false, error };
155
+ }
156
+
157
+ function resolveContext(
158
+ client: GitHubClient,
159
+ input: Parameters<PlatformProvider['context']['resolve']>[0]
160
+ ): ProviderResult<PlatformContextSnapshot> {
161
+ const cwd = input.workingDirectory;
162
+ const version = client.version({ cwd });
163
+ if (!version.ok) return failure(version.error);
164
+ if (!semver.gte(version.value, MINIMUM_GITHUB_CLI_VERSION)) {
165
+ return failure({
166
+ code: 'GH_CLI_VERSION_UNSUPPORTED',
167
+ message: 'GitHub CLI ' + version.value + ' is unsupported; install gh >= ' + MINIMUM_GITHUB_CLI_VERSION,
168
+ retryable: false
169
+ });
170
+ }
171
+ const remote = input.gitRemote || defaultGitRemote(cwd);
172
+ if (!remote) {
173
+ return failure({ code: 'REMOTE_MISSING', message: 'Git origin remote is not configured', retryable: false });
174
+ }
175
+ const ownerRepo = parseGitHubRemote(remote);
176
+ if (!ownerRepo) {
177
+ return failure({
178
+ code: /github\.com/i.test(remote) ? 'REMOTE_INVALID' : 'PLATFORM_UNSUPPORTED',
179
+ message: 'Unable to parse GitHub owner/repo from configured remote',
180
+ retryable: false
181
+ });
182
+ }
183
+ const repository = client.json(['api', 'repos/' + ownerRepo], { cwd });
184
+ if (!repository.ok) return failure(repository.error);
185
+ const repositoryValue = repository.value as { fork?: boolean; full_name?: string; owner?: { type?: string }; parent?: { full_name?: string } } | null;
186
+ const upstream = repositoryValue?.fork ? repositoryValue.parent?.full_name : repositoryValue?.full_name;
187
+ if (!upstream) return failure({
188
+ code: 'UPSTREAM_UNRESOLVED',
189
+ message: 'Unable to resolve the upstream repository',
190
+ retryable: false
191
+ });
192
+ const user = client.json(['api', 'graphql', '-f', 'query=' + CURRENT_USER_QUERY], { cwd });
193
+ if (!user.ok) return failure(user.error);
194
+ const userValue = user.value as { data?: { viewer?: { login?: string } } } | null;
195
+ const currentUser = userValue?.data?.viewer?.login || null;
196
+ const permissions = client.json(['api', 'repos/' + upstream], { cwd });
197
+ if (!permissions.ok) return failure(permissions.error);
198
+ const permissionValue = permissions.value as { permissions?: Record<string, boolean> } | null;
199
+ const values = permissionValue?.permissions || {};
200
+ const capabilities = {
201
+ authenticated: Boolean(currentUser),
202
+ comment: Boolean(currentUser),
203
+ triage: Boolean(values.triage || values.push || values.admin),
204
+ push: Boolean(values.push || values.admin),
205
+ admin: Boolean(values.admin)
206
+ };
207
+ return {
208
+ ok: true,
209
+ value: {
210
+ type: 'github',
211
+ scope: { id: upstream, label: upstream },
212
+ currentUser: currentUser ? { id: currentUser, name: currentUser } : null,
213
+ capabilities,
214
+ authenticated: capabilities.authenticated,
215
+ ...(repositoryValue?.owner?.type ? { metadata: { ownerType: repositoryValue.owner.type } } : {})
216
+ }
217
+ };
218
+ }
219
+
220
+ function repository(context: ProviderOperationContext): string {
221
+ return context.scopeId;
222
+ }
223
+
224
+ function expectedHeadRepository(repositoryName: string, head: string): { repository: string; ref: string } {
225
+ const colon = head.indexOf(':');
226
+ if (colon === -1) return { repository: repositoryName, ref: head };
227
+ const repoName = repositoryName.split('/')[1] || '';
228
+ return { repository: `${head.slice(0, colon)}/${repoName}`, ref: head.slice(colon + 1) };
229
+ }
230
+
231
+ function repositoryHead(cwd: string): string {
232
+ return execFileSync('git', ['rev-parse', 'HEAD'], {
233
+ cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe']
234
+ }).trim();
235
+ }
236
+
237
+ function verifyRemoteBranch(cwd: string, repositoryName: string, head: string):
238
+ | { ok: true; value: string }
239
+ | { ok: false; error: { code: string; message: string; retryable: boolean } } {
240
+ const wanted = expectedHeadRepository(repositoryName, head);
241
+ if (!wanted.ref || !/^[A-Za-z0-9._/-]+$/.test(wanted.ref)) return {
242
+ ok: false,
243
+ error: { code: 'PR_HEAD_INVALID', message: 'Pull request head ref is invalid', retryable: false }
244
+ };
245
+ const remotes = configuredGitRemotes(cwd);
246
+ const remote = remotes.find((item) => item.repository.toLowerCase() === wanted.repository.toLowerCase())
247
+ ?? remotes.find((item) => item.name === 'origin');
248
+ if (!remote) return {
249
+ ok: false,
250
+ error: { code: 'PR_REMOTE_BRANCH_MISSING', message: `No configured remote can verify ${wanted.repository}:${wanted.ref}`, retryable: false }
251
+ };
252
+ try {
253
+ const output = execFileSync('git', ['ls-remote', '--refs', remote.name, `refs/heads/${wanted.ref}`], {
254
+ cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe']
255
+ }).trim();
256
+ const match = output.split(/\r?\n/).map((line) => line.trim()).find((line) => line.endsWith(`refs/heads/${wanted.ref}`));
257
+ const sha = match?.split(/\s+/)[0] || null;
258
+ if (!sha || !/^[a-f0-9]{40}$/i.test(sha)) return {
259
+ ok: false,
260
+ error: { code: 'PR_REMOTE_BRANCH_MISSING', message: `Remote branch ${wanted.repository}:${wanted.ref} does not exist`, retryable: false }
261
+ };
262
+ return { ok: true, value: sha };
263
+ } catch (error) {
264
+ return {
265
+ ok: false,
266
+ error: { code: 'PR_REMOTE_BRANCH_UNAVAILABLE', message: error instanceof Error ? error.message : String(error), retryable: true }
267
+ };
268
+ }
269
+ }
270
+
271
+ function invalid(code: string, message: string): ProviderResult<never> {
272
+ return { ok: false, error: { code, message, retryable: false } };
273
+ }
274
+
275
+ function changeRequestSnapshot(value: any): ChangeRequestSnapshot {
276
+ return {
277
+ id: value.nodeId,
278
+ identity: { kind: 'number', value: value.number },
279
+ number: value.number,
280
+ state: value.state,
281
+ title: value.title,
282
+ body: value.body,
283
+ baseSha: value.base.sha,
284
+ headSha: value.head.sha,
285
+ mergedAt: value.mergedAt,
286
+ displayUrl: value.url,
287
+ draft: value.draft,
288
+ labels: value.labels,
289
+ assignees: value.assignees,
290
+ milestone: value.milestone,
291
+ mergeCommitSha: value.mergeCommitSha,
292
+ mergeability: value.mergeability,
293
+ head: value.head,
294
+ base: value.base
295
+ };
296
+ }
297
+
298
+ function issueSnapshot(value: any, graph?: { type: any; values: Array<{ name: string; value: string | number | null }> }): IssueSnapshot {
299
+ const issueType = graph?.type ? {
300
+ identity: { kind: 'id' as const, value: String(graph.type.id) },
301
+ name: String(graph.type.name),
302
+ fields: (graph.type.fields || []).map((field: any) => ({
303
+ identity: { kind: 'id' as const, value: String(field.id) },
304
+ name: String(field.name),
305
+ kind: field.kind,
306
+ options: (field.options || []).map((option: any) => ({ identity: { kind: 'id' as const, value: String(option.id) }, name: String(option.name) }))
307
+ }))
308
+ } : value.issueType ? {
309
+ identity: { kind: 'key' as const, value: String(value.issueType) },
310
+ name: String(value.issueType),
311
+ fields: []
312
+ } : null;
313
+ return {
314
+ id: value.nodeId,
315
+ identity: { kind: 'number', value: value.number },
316
+ number: value.number,
317
+ state: value.state,
318
+ title: value.title,
319
+ body: value.body,
320
+ labels: value.labels,
321
+ assignees: value.assignees,
322
+ milestone: value.milestone,
323
+ fields: graph ? Object.fromEntries(graph.values.map((field) => [field.name, field.value])) : value.fields,
324
+ issueType,
325
+ displayUrl: value.url
326
+ };
327
+ }
328
+
329
+ function commentSnapshot(value: any): RemoteCommentSnapshot {
330
+ return {
331
+ id: String(value.id),
332
+ author: value.user?.login ? { id: String(value.user.login), name: String(value.user.login) } : null,
333
+ body: String(value.body || ''),
334
+ createdAt: String(value.created_at || ''),
335
+ updatedAt: String(value.updated_at || value.created_at || '')
336
+ };
337
+ }
338
+
339
+ function createReceipt(remoteId: string): ProviderResult<MutationReceipt> {
340
+ return { ok: true, value: { changed: true, remoteId } };
341
+ }
342
+
343
+ function githubResourceToken(identity: ResourceIdentity | null | undefined): string | null {
344
+ return resourceIdentityString(identity) || (identity?.kind === 'number' ? String(identity.value) : null);
345
+ }
346
+
347
+ function syncLabels(
348
+ client: GitHubClient,
349
+ repositoryName: string,
350
+ number: number,
351
+ cwd: string,
352
+ current: readonly string[],
353
+ target: readonly string[],
354
+ prefixes: readonly string[]
355
+ ) {
356
+ let changed = false;
357
+ for (const prefix of prefixes) {
358
+ const synced = syncLabelDelta(client, repositoryName, number, cwd, current, target, prefix);
359
+ if (synced.status === 'failed' || synced.status === 'blocked') {
360
+ if (changed || synced.changed) return {
361
+ status: 'blocked' as const,
362
+ changed: true,
363
+ error: synced.error
364
+ ? { ...synced.error, code: 'IN_LABEL_SYNC_PARTIAL', message: `In-label synchronization is partial or unknown: ${synced.error.message}` }
365
+ : { code: 'IN_LABEL_SYNC_PARTIAL', message: 'In-label synchronization is partial or unknown', retryable: true }
366
+ };
367
+ return synced;
368
+ }
369
+ changed ||= synced.changed;
370
+ }
371
+ return { status: changed ? 'applied' as const : 'no-op' as const, changed, error: null };
372
+ }
373
+
374
+ function createGitHubOperations(client: GitHubClient): Pick<PlatformProvider, 'issues' | 'comments' | 'changeRequests' | 'checks' | 'reviews' | 'releases' | 'verification'> {
375
+ const issues: NonNullable<PlatformProvider['issues']> = {
376
+ async listLabels({ context }) {
377
+ const response = client.json<any>(['api', '--paginate', '--slurp', `repos/${repository(context)}/labels?per_page=100`], { cwd: context.workingDirectory });
378
+ if (!response.ok) return response;
379
+ const values = Array.isArray(response.value)
380
+ ? response.value.flatMap((entry: any) => Array.isArray(entry) ? entry : [entry])
381
+ : [];
382
+ if (values.some((entry: any) => !entry || typeof entry.name !== 'string' || !entry.name.trim())) {
383
+ return invalid('IN_LABEL_SYNC_LABELS_INVALID', 'Repository labels response contains an entry without a valid name');
384
+ }
385
+ return { ok: true, value: [...new Set(values.map((entry: any) => String(entry.name)))].sort() };
386
+ },
387
+ async listMilestones({ context }) {
388
+ const response = client.json<any>(['api', '--paginate', '--slurp', `repos/${repository(context)}/milestones?state=open&per_page=100`], { cwd: context.workingDirectory });
389
+ if (!response.ok) return response;
390
+ const values = Array.isArray(response.value)
391
+ ? response.value.flatMap((entry: any) => Array.isArray(entry) ? entry : [entry])
392
+ : [];
393
+ if (values.some((entry: any) => !entry || typeof entry.title !== 'string' || !entry.title.trim())) {
394
+ return invalid('MILESTONE_IDENTITY_INVALID', 'Milestones response contains an entry without a valid title');
395
+ }
396
+ return { ok: true, value: [...new Set(values.map((entry: any) => String(entry.title)))].sort() };
397
+ },
398
+ async describeRepository({ context }): Promise<ProviderResult<RepositoryMetadataSnapshot>> {
399
+ const repositoryName = repository(context);
400
+ const info = client.json<any>(['api', `repos/${repositoryName}`], { cwd: context.workingDirectory });
401
+ if (!info.ok) return info;
402
+ const labels = client.json<any>(['api', '--paginate', '--slurp', `repos/${repositoryName}/labels?per_page=100`], { cwd: context.workingDirectory });
403
+ if (!labels.ok) return labels;
404
+ const milestones = client.json<any>(['api', '--paginate', '--slurp', `repos/${repositoryName}/milestones?state=open&per_page=100`], { cwd: context.workingDirectory });
405
+ if (!milestones.ok) return milestones;
406
+ const [owner] = repositoryName.split('/');
407
+ const issueTypes = client.json<any>(['api', 'graphql', '-f', `query=${ISSUE_TYPES_QUERY}`, '-F', `owner=${owner}`], { cwd: context.workingDirectory });
408
+ if (!issueTypes.ok) return issueTypes;
409
+ const flatten = (value: unknown): any[] => Array.isArray(value) ? value.flatMap((entry) => Array.isArray(entry) ? entry : [entry]) : [];
410
+ const fields = (raw: any): any[] => (raw || []).flatMap((field: any) => {
411
+ const kind = field.__typename === 'IssueFieldSingleSelect' ? 'single-select'
412
+ : field.__typename === 'IssueFieldDate' ? 'date'
413
+ : field.__typename === 'IssueFieldText' ? 'text'
414
+ : field.__typename === 'IssueFieldNumber' ? 'number' : null;
415
+ return field.id && field.name && kind ? [{
416
+ identity: { kind: 'id', value: String(field.id) }, name: String(field.name), kind,
417
+ options: (field.options || []).filter((option: any) => option?.id && option?.name).map((option: any) => ({ identity: { kind: 'id', value: String(option.id) }, name: String(option.name) }))
418
+ }] : [];
419
+ });
420
+ const typeNodes = issueTypes.value?.data?.organization?.issueTypes?.nodes || [];
421
+ return {
422
+ ok: true,
423
+ value: {
424
+ repository: {
425
+ identity: { kind: 'key', value: repositoryName },
426
+ name: String(info.value?.full_name || repositoryName),
427
+ url: info.value?.html_url ? String(info.value.html_url) : null
428
+ },
429
+ labels: flatten(labels.value).filter((item) => item?.name).map((item) => ({ identity: { kind: 'key', value: String(item.name) }, name: String(item.name) })),
430
+ milestones: flatten(milestones.value).filter((item) => item?.title).map((item) => ({ identity: { kind: 'key', value: String(item.title) }, title: String(item.title), state: item.state === 'closed' ? 'closed' : 'open' })),
431
+ issueTypes: typeNodes.filter((item: any) => item?.id && item?.name).map((item: any) => ({ identity: { kind: 'id', value: String(item.id) }, name: String(item.name), fields: fields(item.pinnedFields) })),
432
+ fields: []
433
+ }
434
+ };
435
+ },
436
+ async inspect({ context, target }) {
437
+ const number = resourceIdentityNumber(target);
438
+ if (!number) return invalid('ISSUE_NUMBER_INVALID', 'Issue number must be positive');
439
+ const module = await import('./issues.ts');
440
+ const fetched = module.inspectGitHubIssue(client, repository(context), number, context.workingDirectory);
441
+ if (!fetched.ok) return fetched;
442
+ const graph = module.graphState(client, repository(context), number, context.workingDirectory);
443
+ return { ok: true, value: issueSnapshot(fetched.value, graph || undefined) };
444
+ },
445
+ async create({ context, desired }) {
446
+ const response = client.json<any>(['api', `repos/${repository(context)}/issues`, '-X', 'POST', '--input', '-'], {
447
+ cwd: context.workingDirectory,
448
+ method: 'POST',
449
+ input: JSON.stringify({
450
+ title: desired.title,
451
+ body: desired.body,
452
+ labels: desired.labels,
453
+ assignees: desired.assignees,
454
+ ...(desired.milestone ? { milestone: desired.milestone } : {})
455
+ })
456
+ });
457
+ if (!response.ok) return response;
458
+ const number = Number(response.value?.number);
459
+ if (!Number.isInteger(number) || number <= 0) return invalid('ISSUE_CREATE_RESPONSE_INVALID', 'Issue create response lacks issue number');
460
+ return createReceipt(String(number));
461
+ },
462
+ async update({ context, target, currentLabels, patch }) {
463
+ const number = resourceIdentityNumber(target);
464
+ if (!number) return invalid('ISSUE_NUMBER_INVALID', 'Issue number must be positive');
465
+ const { issueType, fields, labels, ...restPatch } = patch;
466
+ let requestPatch = restPatch;
467
+ let changed = false;
468
+ if (labels !== undefined) {
469
+ let before = currentLabels;
470
+ if (!before) {
471
+ const current = client.json<any>(['api', `repos/${repository(context)}/issues/${number}`], { cwd: context.workingDirectory });
472
+ if (!current.ok) return current;
473
+ before = Array.isArray(current.value?.labels)
474
+ ? current.value.labels.map((label: any) => typeof label === 'string' ? label : label?.name).filter((label: any): label is string => typeof label === 'string')
475
+ : [];
476
+ }
477
+ const synced = syncLabels(client, repository(context), number, context.workingDirectory, before || [], labels!, ['status:', 'in:']);
478
+ if (synced.status === 'failed' || synced.status === 'blocked') return synced.error
479
+ ? { ok: false, error: synced.error }
480
+ : invalid('IN_LABEL_SYNC_FAILED', 'Issue label synchronization failed');
481
+ changed ||= synced.changed;
482
+ }
483
+ if (typeof patch.milestone === 'string') {
484
+ const milestones = client.json<any>(['api', '--paginate', '--slurp', `repos/${repository(context)}/milestones?state=open&per_page=100`], { cwd: context.workingDirectory });
485
+ if (!milestones.ok) return milestones;
486
+ const values = Array.isArray(milestones.value)
487
+ ? milestones.value.flatMap((entry: any) => Array.isArray(entry) ? entry : [entry])
488
+ : [];
489
+ const selected = values.find((item: any) => item?.title === patch.milestone);
490
+ if (!Number.isSafeInteger(selected?.number) || selected.number <= 0) {
491
+ return invalid('MILESTONE_IDENTITY_INVALID', 'Milestone title does not resolve to a positive number');
492
+ }
493
+ requestPatch = { ...restPatch, milestone: selected.number } as typeof restPatch;
494
+ }
495
+ if (issueType !== undefined || fields !== undefined) {
496
+ const [owner, name] = repository(context).split('/');
497
+ const current = client.json<any>(['api', 'graphql', '-f', `query=${ISSUE_FIELDS_QUERY}`, '-F', `owner=${owner}`, '-F', `name=${name}`, '-F', `number=${number}`], { cwd: context.workingDirectory });
498
+ if (!current.ok) return current;
499
+ const currentIssue = current.value?.data?.repository?.issue;
500
+ const issueId = currentIssue?.id;
501
+ if (!issueId) return invalid('ISSUE_GRAPH_IDENTITY_INVALID', 'Issue GraphQL response lacks issue identity');
502
+ let targetType = currentIssue.issueType;
503
+ if (issueType !== undefined) {
504
+ const types = client.json<any>(['api', 'graphql', '-f', `query=${ISSUE_TYPES_QUERY}`, '-F', `owner=${owner}`], { cwd: context.workingDirectory });
505
+ if (!types.ok) return types;
506
+ targetType = (types.value?.data?.organization?.issueTypes?.nodes || []).find((item: any) => item?.name === issueType);
507
+ if (!targetType) return invalid('ISSUE_TYPE_NOT_FOUND', 'Issue type was not found');
508
+ if (targetType.id !== currentIssue.issueType?.id) {
509
+ const updatedType = client.json(['api', 'graphql', '--input', '-'], {
510
+ cwd: context.workingDirectory,
511
+ method: 'POST',
512
+ input: JSON.stringify({
513
+ query: 'mutation($issueId:ID!,$issueTypeId:ID){updateIssueIssueType(input:{issueId:$issueId,issueTypeId:$issueTypeId}){issue{id}}}',
514
+ variables: { issueId, issueTypeId: targetType.id }
515
+ })
516
+ });
517
+ if (!updatedType.ok) return updatedType;
518
+ changed = true;
519
+ }
520
+ }
521
+ if (fields !== undefined) {
522
+ const values = Object.entries(fields).flatMap(([name, value]) => {
523
+ const field = (targetType?.pinnedFields || []).find((candidate: any) => candidate?.name === name);
524
+ if (!field?.id) return [];
525
+ if (field.__typename === 'IssueFieldSingleSelect') {
526
+ const option = (field.options || []).find((candidate: any) => candidate?.name === value);
527
+ return option?.id ? [{ fieldId: field.id, singleSelectOptionId: option.id }] : [];
528
+ }
529
+ const key = field.__typename === 'IssueFieldDate' ? 'dateValue' : field.__typename === 'IssueFieldNumber' ? 'numberValue' : 'textValue';
530
+ return [{ fieldId: field.id, [key]: value }];
531
+ });
532
+ if (values.length > 0) {
533
+ const updatedFields = client.json(['api', 'graphql', '--input', '-'], {
534
+ cwd: context.workingDirectory,
535
+ method: 'POST',
536
+ input: JSON.stringify({
537
+ query: 'mutation($issueId:ID!,$issueFields:[IssueFieldCreateOrUpdateInput!]!){setIssueFieldValue(input:{issueId:$issueId,issueFields:$issueFields}){issue{id}}}',
538
+ variables: { issueId, issueFields: values }
539
+ })
540
+ });
541
+ if (!updatedFields.ok) return updatedFields;
542
+ changed = true;
543
+ }
544
+ }
545
+ }
546
+ if (Object.keys(requestPatch).length > 0) {
547
+ const response = client.json<any>(['api', `repos/${repository(context)}/issues/${number}`, '-X', 'PATCH', '--input', '-'], {
548
+ cwd: context.workingDirectory,
549
+ method: 'PATCH',
550
+ input: JSON.stringify(requestPatch)
551
+ });
552
+ if (!response.ok) return response;
553
+ changed = true;
554
+ }
555
+ return { ok: true, value: { changed, remoteId: String(number) } };
556
+ }
557
+ };
558
+
559
+ const comments: NonNullable<PlatformProvider['comments']> = {
560
+ async list({ context, parent }) {
561
+ const number = resourceIdentityNumber(parent);
562
+ if (!number) return invalid('ISSUE_NUMBER_INVALID', 'Issue number must be positive');
563
+ const response = client.json<any>(['api', '--paginate', '--slurp', `repos/${repository(context)}/issues/${number}/comments?per_page=100`], { cwd: context.workingDirectory });
564
+ if (!response.ok) return response;
565
+ const values = Array.isArray(response.value) ? response.value.flatMap((entry: any) => Array.isArray(entry) ? entry : [entry]) : [];
566
+ return { ok: true, value: values.filter((entry: any) => entry && entry.id !== undefined).map(commentSnapshot) };
567
+ },
568
+ async write({ context, parent, body, existingComment }) {
569
+ const number = resourceIdentityNumber(parent);
570
+ if (!number) return invalid('ISSUE_NUMBER_INVALID', 'Issue number must be positive');
571
+ const commentId = githubResourceToken(existingComment);
572
+ const endpoint = commentId ? `repos/${repository(context)}/issues/comments/${commentId}` : `repos/${repository(context)}/issues/${number}/comments`;
573
+ const response = client.json<any>(['api', endpoint, '-X', commentId ? 'PATCH' : 'POST', '--input', '-'], {
574
+ cwd: context.workingDirectory,
575
+ method: commentId ? 'PATCH' : 'POST',
576
+ input: JSON.stringify({ body })
577
+ });
578
+ if (!response.ok) return response;
579
+ return createReceipt(String(response.value?.id || commentId || ''));
580
+ },
581
+ async delete({ context, comment }) {
582
+ const commentId = githubResourceToken(comment);
583
+ if (!commentId) return invalid('COMMENT_ID_INVALID', 'Comment id is required');
584
+ const response = client.text(['api', `repos/${repository(context)}/issues/comments/${commentId}`, '-X', 'DELETE'], { cwd: context.workingDirectory, method: 'DELETE' });
585
+ if (!response.ok) return response;
586
+ return createReceipt(commentId);
587
+ }
588
+ };
589
+
590
+ const changeRequests: NonNullable<PlatformProvider['changeRequests']> = {
591
+ async verifyHead({ context, head }) {
592
+ let localHead: string;
593
+ try {
594
+ localHead = repositoryHead(context.workingDirectory);
595
+ } catch (error) {
596
+ return { ok: false, error: { code: 'PR_LOCAL_HEAD_UNAVAILABLE', message: error instanceof Error ? error.message : String(error), retryable: false } };
597
+ }
598
+ const remote = verifyRemoteBranch(context.workingDirectory, repository(context), head);
599
+ if (!remote.ok) return remote;
600
+ if (remote.value !== localHead) return {
601
+ ok: false,
602
+ error: { code: 'PR_REMOTE_HEAD_MISMATCH', message: `Remote head ${remote.value} does not match local HEAD ${localHead}`, retryable: false }
603
+ };
604
+ return { ok: true, value: { sha: localHead } };
605
+ },
606
+ async inspect({ context, target }) {
607
+ const number = resourceIdentityNumber(target);
608
+ if (!number) return invalid('PR_NUMBER_INVALID', 'Pull request number must be positive');
609
+ const module = await import('./pull-requests.ts');
610
+ const fetched = module.inspectGitHubPullRequest(client, repository(context), number, context.workingDirectory);
611
+ if (!fetched.ok) return fetched;
612
+ return { ok: true, value: changeRequestSnapshot(fetched.value) };
613
+ },
614
+ async listClosing({ context, issue }) {
615
+ const number = resourceIdentityNumber(issue);
616
+ if (!number) return invalid('ISSUE_NUMBER_INVALID', 'Issue number must be positive');
617
+ const module = await import('./pull-requests.ts');
618
+ const fetched = module.inspectGitHubIssueClosingChangeRequests(client, repository(context), number, context.workingDirectory);
619
+ if (!fetched.ok) return fetched;
620
+ return { ok: true, value: fetched.value.map(changeRequestSnapshot) };
621
+ },
622
+ async create({ context, base, head, title, body, draft }) {
623
+ const response = client.json<any>(['api', `repos/${repository(context)}/pulls`, '-X', 'POST', '--input', '-'], {
624
+ cwd: context.workingDirectory,
625
+ method: 'POST',
626
+ input: JSON.stringify({ title, body, head, base, draft })
627
+ });
628
+ if (!response.ok) return response;
629
+ const number = Number(response.value?.number);
630
+ if (!Number.isInteger(number) || number <= 0) return invalid('PR_CREATE_RESPONSE_INVALID', 'Pull request create response lacks number');
631
+ return createReceipt(String(number));
632
+ },
633
+ async update({ context, target, currentLabels, patch }) {
634
+ const number = resourceIdentityNumber(target);
635
+ if (!number) return invalid('PR_NUMBER_INVALID', 'Pull request number must be positive');
636
+ const { labels, assignees, milestone, ...pullRequestPatch } = patch;
637
+ let changed = false;
638
+ if (labels !== undefined) {
639
+ let before = currentLabels;
640
+ if (!before) {
641
+ const current = client.json<any>(['api', `repos/${repository(context)}/pulls/${number}`], { cwd: context.workingDirectory });
642
+ if (!current.ok) return current;
643
+ before = Array.isArray(current.value?.labels)
644
+ ? current.value.labels.map((label: any) => typeof label === 'string' ? label : label?.name).filter((label: any): label is string => typeof label === 'string')
645
+ : [];
646
+ }
647
+ const synced = syncLabels(client, repository(context), number, context.workingDirectory, before || [], labels!, ['in:', 'type:']);
648
+ if (synced.status === 'failed' || synced.status === 'blocked') return synced.error
649
+ ? { ok: false, error: synced.error }
650
+ : invalid('IN_LABEL_SYNC_FAILED', 'Pull request label synchronization failed');
651
+ changed ||= synced.changed;
652
+ }
653
+ if (Object.keys(pullRequestPatch).length > 0) {
654
+ const response = client.json<any>(['api', `repos/${repository(context)}/pulls/${number}`, '-X', 'PATCH', '--input', '-'], {
655
+ cwd: context.workingDirectory,
656
+ method: 'PATCH',
657
+ input: JSON.stringify(pullRequestPatch)
658
+ });
659
+ if (!response.ok) return response;
660
+ changed = true;
661
+ }
662
+ const issuePatch: Record<string, unknown> = {};
663
+ if (assignees !== undefined) issuePatch.assignees = assignees;
664
+ if (milestone !== undefined) {
665
+ if (milestone === null) issuePatch.milestone = null;
666
+ else {
667
+ const milestones = client.json<any>(['api', '--paginate', '--slurp', `repos/${repository(context)}/milestones?state=open&per_page=100`], { cwd: context.workingDirectory });
668
+ if (!milestones.ok) return milestones;
669
+ const values = Array.isArray(milestones.value) ? milestones.value.flatMap((entry: any) => Array.isArray(entry) ? entry : [entry]) : [];
670
+ const selected = values.find((entry: any) => entry?.title === milestone);
671
+ if (!Number.isSafeInteger(selected?.number) || selected.number <= 0) return invalid('MILESTONE_IDENTITY_INVALID', 'Milestone title does not resolve to a positive number');
672
+ issuePatch.milestone = selected.number;
673
+ }
674
+ }
675
+ if (Object.keys(issuePatch).length > 0) {
676
+ const response = client.json<any>(['api', `repos/${repository(context)}/issues/${number}`, '-X', 'PATCH', '--input', '-'], {
677
+ cwd: context.workingDirectory,
678
+ method: 'PATCH',
679
+ input: JSON.stringify(issuePatch)
680
+ });
681
+ if (!response.ok) return response;
682
+ changed = true;
683
+ }
684
+ return { ok: true, value: { changed, remoteId: String(number) } };
685
+ },
686
+ async resolveGitEvidence({ context, target, expected }) {
687
+ const number = resourceIdentityNumber(target);
688
+ if (!number) return invalid('PR_NUMBER_INVALID', 'Pull request number must be positive');
689
+ const module = await import('./github-provider.ts');
690
+ return module.resolveGitHubChangeRequestGitEvidence({
691
+ cwd: context.workingDirectory,
692
+ repository: repository(context),
693
+ number,
694
+ baseRepository: repository(context),
695
+ baseRef: expected.targetBranch || 'main'
696
+ });
697
+ }
698
+ };
699
+
700
+ const checks: NonNullable<PlatformProvider['checks']> = {
701
+ async inspectRequired({ context, changeRequest }) {
702
+ const number = resourceIdentityNumber(changeRequest);
703
+ if (!number) return invalid('PR_NUMBER_INVALID', 'Pull request number must be positive');
704
+ const module = await import('./pr-checks.ts');
705
+ const fetched = module.inspectGitHubRequiredChecks(client, repository(context), number, context.workingDirectory);
706
+ if (!fetched.ok) return fetched;
707
+ return { ok: true, value: fetched.value.map((check: any): RequiredCheckSnapshot => ({ name: check.name, status: check.bucket, conclusion: check.conclusion, detailsUrl: check.detailsUrl })) };
708
+ },
709
+ async resolveRun({ context, changeRequest, checkName, detailsUrl }) {
710
+ const number = resourceIdentityNumber(changeRequest);
711
+ if (!number) return invalid('PR_NUMBER_INVALID', 'Pull request number must be positive');
712
+ const module = await import('./pr-checks.ts');
713
+ const direct = detailsUrl ? module.parseRunJobIdentity(detailsUrl) : null;
714
+ if (direct) {
715
+ const run = client.json<any>(['api', `repos/${repository(context)}/actions/runs/${direct.runId}`], { cwd: context.workingDirectory });
716
+ if (!run.ok) return run;
717
+ return { ok: true, value: { runId: String(direct.runId), jobId: direct.jobId ? String(direct.jobId) : undefined } } as ProviderResult<CheckRunSnapshot>;
718
+ }
719
+ const listed = client.json<any>(['api', `repos/${repository(context)}/commits/${number}/check-runs`], { cwd: context.workingDirectory });
720
+ if (!listed.ok) return listed;
721
+ const candidate = (listed.value?.check_runs || []).find((run: any) => run.name === checkName && run.id);
722
+ return candidate
723
+ ? { ok: true, value: { runId: String(candidate.id), jobId: candidate.job_id ? String(candidate.job_id) : undefined, name: checkName, status: String(candidate.status || ''), conclusion: candidate.conclusion, detailsUrl: candidate.details_url } }
724
+ : invalid('CHECK_RUN_NOT_FOUND', 'Check run was not found');
725
+ },
726
+ async fetchLogs({ context, runId, jobId }) {
727
+ const module = await import('./pr-checks.ts');
728
+ const args = jobId
729
+ ? ['api', `repos/${repository(context)}/actions/jobs/${jobId}/logs`]
730
+ : ['run', 'view', runId, '--repo', repository(context), '--log-failed'];
731
+ const fetched = module.fetchCheckLogText(client, args, context.workingDirectory);
732
+ if (!fetched.ok) return fetched;
733
+ return { ok: true, value: { runId, jobId, text: fetched.value || '' } } as ProviderResult<CheckLogSnapshot>;
734
+ }
735
+ };
736
+
737
+ const reviews: NonNullable<PlatformProvider['reviews']> = {
738
+ async list({ context, changeRequest }) {
739
+ const number = resourceIdentityNumber(changeRequest);
740
+ if (!number) return invalid('PR_NUMBER_INVALID', 'Pull request number must be positive');
741
+ const response = client.json<any[]>(['api', '--paginate', '--slurp', `repos/${repository(context)}/pulls/${number}/reviews?per_page=100`], { cwd: context.workingDirectory });
742
+ if (!response.ok) return response;
743
+ const values = Array.isArray(response.value) ? response.value.flatMap((entry: any) => Array.isArray(entry) ? entry : [entry]) : [];
744
+ return { ok: true, value: values.filter((review: any) => review?.body).map((review: any): ReviewSnapshot => ({ id: String(review.id || review.node_id || ''), author: review.user?.login ? { id: String(review.user.login), name: String(review.user.login) } : null, commitSha: String(review.commit_id || ''), body: String(review.body), state: String(review.state || ''), submittedAt: String(review.submitted_at || ''), displayUrl: review.html_url })) };
745
+ },
746
+ async publish({ context, changeRequest, identity, event, body }) {
747
+ const number = resourceIdentityNumber(changeRequest);
748
+ if (!number) return invalid('PR_NUMBER_INVALID', 'Pull request number must be positive');
749
+ const response = client.json<any>(['api', `repos/${repository(context)}/pulls/${number}/reviews`, '-X', 'POST', '--input', '-'], {
750
+ cwd: context.workingDirectory,
751
+ method: 'POST',
752
+ input: JSON.stringify({ commit_id: identity.commitSha, body, event })
753
+ });
754
+ if (!response.ok) return response;
755
+ return createReceipt(String(response.value?.id || ''));
756
+ }
757
+ };
758
+
759
+ const releases: NonNullable<PlatformProvider['releases']> = {
760
+ async inspect({ context, tag }) {
761
+ const response = client.json<any>(['release', 'view', tag, '--repo', repository(context), '--json', 'tagName,isDraft,url'], { cwd: context.workingDirectory });
762
+ if (!response.ok) return response;
763
+ const runs = client.json<any[]>(['run', 'list', '--repo', repository(context), '--limit', '100', '--json', 'name,workflowName,displayTitle,event,headBranch,headSha,status,conclusion,createdAt,databaseId,attempt,url'], { cwd: context.workingDirectory });
764
+ if (!runs.ok) return runs;
765
+ const value: ReleaseSnapshot = { id: String(response.value?.id || tag), tag: String(response.value?.tagName || tag), title: String(response.value?.name || tag), body: String(response.value?.body || ''), draft: Boolean(response.value?.isDraft), prerelease: Boolean(response.value?.isPrerelease), publishedAt: response.value?.publishedAt ? new Date(String(response.value.publishedAt)).toISOString() : null, displayUrl: response.value?.url ? String(response.value.url) : undefined, workflows: Array.isArray(runs.value) ? runs.value as Array<Record<string, import('./provider-contract.ts').JsonValue>> : [] };
766
+ return { ok: true, value };
767
+ },
768
+ async create({ context, tag, title, notes }) {
769
+ const args = ['release', 'create', tag, '--repo', repository(context), '--title', title];
770
+ if (notes) args.push('--notes', notes.text);
771
+ const response = client.text(args, { cwd: context.workingDirectory, method: 'POST' });
772
+ if (!response.ok) return response;
773
+ return createReceipt(tag);
774
+ },
775
+ async update({ context, release, patch }) {
776
+ const tag = resourceIdentityString(release) || '';
777
+ const args = ['release', 'edit', tag, '--repo', repository(context)];
778
+ if (patch.title) args.push('--title', patch.title);
779
+ if (patch.body) args.push('--notes', patch.body);
780
+ const response = client.text(args, { cwd: context.workingDirectory, method: 'PATCH' });
781
+ if (!response.ok) return response;
782
+ return createReceipt(tag);
783
+ },
784
+ async reconcileMilestones({ context, desired }) {
785
+ const listed = client.json<any>(['api', '--paginate', '--slurp', `repos/${repository(context)}/milestones?state=all&per_page=100`], { cwd: context.workingDirectory });
786
+ if (!listed.ok) return listed;
787
+ const values = Array.isArray(listed.value) ? listed.value.flatMap((entry: any) => Array.isArray(entry) ? entry : [entry]) : [];
788
+ const created: string[] = [];
789
+ const closed: string[] = [];
790
+ for (const item of desired) {
791
+ const current = values.find((candidate: any) => candidate.title === item.title);
792
+ if (current) {
793
+ if (item.state === 'closed' && current.state !== 'closed') {
794
+ const response = client.json(['api', '--method', 'PATCH', `repos/${repository(context)}/milestones/${current.number}`, '-f', 'state=closed'], { cwd: context.workingDirectory });
795
+ if (!response.ok) return response;
796
+ closed.push(item.title);
797
+ }
798
+ continue;
799
+ }
800
+ const response = client.json(['api', '--method', 'POST', `repos/${repository(context)}/milestones`, '-f', `title=${item.title}`, '-f', `description=${item.description}`], { cwd: context.workingDirectory });
801
+ if (!response.ok) return response;
802
+ created.push(item.title);
803
+ }
804
+ return { ok: true, value: { changed: created.length > 0 || closed.length > 0, created, closed } };
805
+ },
806
+ async publishNotes({ context, release, title, notes }) {
807
+ const module = await import('./github-release-notes.ts');
808
+ const temporaryRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-infra-release-notes-'));
809
+ const notesFile = path.join(temporaryRoot, 'notes.md');
810
+ fs.writeFileSync(notesFile, notes.text);
811
+ try {
812
+ const tag = resourceIdentityString(release) || '';
813
+ const result = module.publishGitHubReleaseNotes({ repository: repository(context), tag, title, notesFile }, { cwd: context.workingDirectory, client });
814
+ if (result.status === 'failed' || result.status === 'blocked') return { ok: false, error: result.error || { code: 'RELEASE_NOTES_PUBLISH_FAILED', message: 'Release notes publish failed', retryable: false } };
815
+ return { ok: true, value: { changed: result.changed, remoteId: tag } };
816
+ } finally {
817
+ fs.rmSync(temporaryRoot, { recursive: true, force: true });
818
+ }
819
+ },
820
+ async collectNotes({ context, fromTime, toTime, commitOids, branch, historyLimit }): Promise<ProviderResult<ReleaseNotesFacts>> {
821
+ const module = await import('./github-release-notes.ts');
822
+ const collected = module.fetchGitHubReleaseNoteData({
823
+ repository: repository(context), commitOids, branch, historyLimit, fromTime, toTime
824
+ }, { cwd: context.workingDirectory, client });
825
+ if (collected.status === 'failed' || collected.status === 'blocked' || !('pullRequests' in collected)) {
826
+ return { ok: false, error: collected.error || { code: 'RELEASE_NOTES_COLLECTION_FAILED', message: 'Release notes could not be collected', retryable: false } };
827
+ }
828
+ const mergedPullRequests = collected.pullRequests.map((item: any): ChangeRequestSnapshot => ({
829
+ id: String(item.number), identity: { kind: 'number', value: Number(item.number) }, number: Number(item.number),
830
+ state: 'closed', title: String(item.title || ''), body: String(item.body || ''),
831
+ mergedAt: new Date(String(item.mergedAt)).toISOString(), displayUrl: String(item.url || ''),
832
+ labels: Array.isArray(item.labels) ? item.labels.map((label: any) => String(label.name || label)) : [],
833
+ assignees: [], mergeCommitSha: null
834
+ }));
835
+ const closingIssues: IssueSnapshot[] = collected.pullRequests.flatMap((item: any) =>
836
+ Array.isArray(item.closingIssuesReferences) ? item.closingIssuesReferences.map((issue: any) => ({
837
+ id: String(issue.number), identity: { kind: 'number', value: Number(issue.number) }, number: Number(issue.number),
838
+ state: 'closed' as const, title: String(issue.title || ''), body: '', labels: Array.isArray(issue.labels) ? issue.labels.map((label: any) => String(label.name || label)) : [],
839
+ assignees: [], milestone: null, fields: {}, displayUrl: String(issue.url || '')
840
+ })) : []
841
+ );
842
+ const actors = [...collected.authors.values()].flatMap((items) => items.map((item: any) => ({
843
+ ...(item.login ? { id: String(item.login) } : {}), ...(item.name ? { name: String(item.name) } : {})
844
+ })));
845
+ return {
846
+ ok: true,
847
+ value: {
848
+ history: commitOids.map((sha) => ({ sha, message: '', authoredAt: toTime, author: actors.find((actor) => actor.id) || null })),
849
+ mergedPullRequests,
850
+ closingIssues,
851
+ actors
852
+ }
853
+ };
854
+ }
855
+ };
856
+
857
+ const verification: NonNullable<PlatformProvider['verification']> = {
858
+ async fetchRemoteFacts(input) {
859
+ const issue = input.issue && issues.inspect
860
+ ? await issues.inspect({ context: input.context, target: input.issue })
861
+ : { ok: true as const, value: null };
862
+ if (!issue.ok) return issue;
863
+ const commentFacts = input.includeComments && input.issue && comments.list
864
+ ? await comments.list({ context: input.context, parent: input.issue })
865
+ : { ok: true as const, value: [] as RemoteCommentSnapshot[] };
866
+ if (!commentFacts.ok) return commentFacts;
867
+ const changeRequest = input.changeRequest && changeRequests.inspect
868
+ ? await changeRequests.inspect({ context: input.context, target: input.changeRequest })
869
+ : { ok: true as const, value: null };
870
+ if (!changeRequest.ok) return changeRequest;
871
+ return {
872
+ ok: true,
873
+ value: {
874
+ issue: issue.value,
875
+ comments: commentFacts.value,
876
+ changeRequest: changeRequest.value,
877
+ commit: null,
878
+ fields: input.includeFields ? issue.value?.fields || {} : {}
879
+ }
880
+ } as ProviderResult<VerificationRemoteFacts>;
881
+ }
882
+ };
883
+
884
+ return { issues, comments, changeRequests, checks, reviews, releases, verification };
885
+ }
886
+
887
+ function createGitHubProvider(
888
+ input: PlatformProviderFactoryInput,
889
+ client: GitHubClient = createGitHubClient()
890
+ ): PlatformProvider {
891
+ return {
892
+ type: input.providerType,
893
+ contractVersion: 1,
894
+ identity: { issue: 'number', 'pull-request': 'number', comment: 'number', release: 'key' },
895
+ ...createGitHubOperations(client),
896
+ context: {
897
+ async resolve(contextInput) {
898
+ return resolveContext(client, contextInput);
899
+ }
900
+ }
901
+ };
902
+ }
903
+
904
+ export {
905
+ configuredGitRemotes,
906
+ createGitHubProvider,
907
+ defaultGitRemote,
908
+ parseGitHubRemote,
909
+ resolveGitHubChangeRequestGitEvidence
910
+ };