@fitlab-ai/agent-infra 0.9.12 → 0.9.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (402) hide show
  1. package/README.md +11 -11
  2. package/README.zh-CN.md +11 -11
  3. package/bin/cli.ts +26 -9
  4. package/dist/bin/cli.js +173 -157
  5. package/dist/bin/internal-cli.js +66 -40
  6. package/dist/lib/agent-client.js +5 -0
  7. package/dist/lib/agent-clients/next-steps.js +6 -1
  8. package/dist/lib/decide.js +3 -3
  9. package/dist/lib/internal/agent-client.js +5 -2
  10. package/dist/lib/internal/cli-route-inventory.js +152 -0
  11. package/dist/lib/internal/codex-lifecycle.js +9 -6
  12. package/dist/lib/internal/codex-sandbox-controller.js +8 -2
  13. package/dist/lib/internal/git-workflow.js +10 -6
  14. package/dist/lib/internal/platform-checks.js +19 -7
  15. package/dist/lib/internal/platform-comment.js +25 -13
  16. package/dist/lib/internal/platform-context.js +6 -3
  17. package/dist/lib/internal/platform-issue.js +20 -13
  18. package/dist/lib/internal/platform-metadata.js +88 -0
  19. package/dist/lib/internal/platform-pr-review.js +26 -13
  20. package/dist/lib/internal/platform-pr.js +84 -31
  21. package/dist/lib/internal/platform-release-notes.js +9 -6
  22. package/dist/lib/internal/platform-security.js +93 -0
  23. package/dist/lib/internal/pr-review-grade.js +25 -12
  24. package/dist/lib/internal/release-workflow.js +23 -9
  25. package/dist/lib/internal/sandbox-control.js +50 -7
  26. package/dist/lib/internal/task-activity.js +16 -7
  27. package/dist/lib/internal/task-artifact.js +235 -24
  28. package/dist/lib/internal/task-context.js +4 -1
  29. package/dist/lib/internal/task-create.js +50 -17
  30. package/dist/lib/internal/task-delivery.js +4 -1
  31. package/dist/lib/internal/task-event.js +10 -4
  32. package/dist/lib/internal/task-finalization.js +6 -3
  33. package/dist/lib/internal/task-invalidation.js +63 -0
  34. package/dist/lib/internal/task-ledger.js +20 -6
  35. package/dist/lib/internal/task-lifecycle.js +6 -3
  36. package/dist/lib/internal/task-operation-registry.js +421 -0
  37. package/dist/lib/internal/task-orchestration.js +12 -0
  38. package/dist/lib/internal/task-override.js +14 -7
  39. package/dist/lib/internal/task-qualification.js +90 -0
  40. package/dist/lib/internal/task-review.js +7 -4
  41. package/dist/lib/internal/task-short-id.js +11 -1
  42. package/dist/lib/internal/task-snapshot.js +4 -1
  43. package/dist/lib/internal/task-validate.js +7 -2
  44. package/dist/lib/internal/task-verify.js +11 -7
  45. package/dist/lib/internal/task-warning.js +11 -4
  46. package/dist/lib/platform/adapters.js +1 -3
  47. package/dist/lib/platform/change-request-git-evidence.js +27 -7
  48. package/dist/lib/platform/comment-safety.js +226 -0
  49. package/dist/lib/platform/completion-backfill.js +6 -5
  50. package/dist/lib/platform/context.js +70 -130
  51. package/dist/lib/platform/github-client.js +1 -1
  52. package/dist/lib/platform/github-provider.js +1130 -0
  53. package/dist/lib/platform/in-label-sync.js +143 -0
  54. package/dist/lib/platform/issue-comments.js +237 -72
  55. package/dist/lib/platform/issues.js +335 -189
  56. package/dist/lib/platform/metadata-labels.js +6 -2
  57. package/dist/lib/platform/none-provider.js +28 -0
  58. package/dist/lib/platform/pr-change-report.js +545 -0
  59. package/dist/lib/platform/pr-checks.js +119 -104
  60. package/dist/lib/platform/pr-review.js +88 -72
  61. package/dist/lib/platform/pr-summary.js +541 -50
  62. package/dist/lib/platform/provider-bridge.js +45 -0
  63. package/dist/lib/platform/provider-contract.d.ts +553 -0
  64. package/dist/lib/platform/provider-contract.js +99 -0
  65. package/dist/lib/platform/provider-loader.js +251 -0
  66. package/dist/lib/platform/provider-validation.js +577 -0
  67. package/dist/lib/platform/pull-request-metadata.js +8 -6
  68. package/dist/lib/platform/pull-requests.js +734 -543
  69. package/dist/lib/platform/release-notes.js +78 -55
  70. package/dist/lib/platform/releases.js +79 -50
  71. package/dist/lib/platform/repository-metadata.js +377 -0
  72. package/dist/lib/platform/resource-identity.d.ts +26 -0
  73. package/dist/lib/platform/resource-identity.js +92 -0
  74. package/dist/lib/platform/security-alerts.js +155 -0
  75. package/dist/lib/platform/task-identities.js +31 -0
  76. package/dist/lib/platform/verification-required.js +11 -12
  77. package/dist/lib/platform/verification-sync.js +129 -248
  78. package/dist/lib/pr-review/evidence-grading.js +19 -6
  79. package/dist/lib/process-data/index.js +7 -0
  80. package/dist/lib/run/index.js +68 -23
  81. package/dist/lib/sandbox/commands/control-status.js +22 -0
  82. package/dist/lib/sandbox/commands/create.js +32 -6
  83. package/dist/lib/sandbox/commands/ls.js +5 -0
  84. package/dist/lib/sandbox/commands/rm.js +827 -120
  85. package/dist/lib/sandbox/commands/show.js +16 -0
  86. package/dist/lib/sandbox/control/client.js +174 -19
  87. package/dist/lib/sandbox/control/completed-reentry.js +85 -0
  88. package/dist/lib/sandbox/control/container-identity.js +28 -31
  89. package/dist/lib/sandbox/control/executor.js +151 -16
  90. package/dist/lib/sandbox/control/lifecycle.js +621 -127
  91. package/dist/lib/sandbox/control/native-file-lock.js +177 -0
  92. package/dist/lib/sandbox/control/protocol.js +77 -0
  93. package/dist/lib/sandbox/control/server.js +677 -34
  94. package/dist/lib/sandbox/control/state.js +209 -4
  95. package/dist/lib/sandbox/control/task-view.js +183 -0
  96. package/dist/lib/sandbox/engines/authority.js +289 -0
  97. package/dist/lib/sandbox/engines/docker-exact-discovery.js +97 -0
  98. package/dist/lib/sandbox/index.js +5 -0
  99. package/dist/lib/sandbox/recovery.js +21 -1
  100. package/dist/lib/sandbox/workspace-view.js +2 -1
  101. package/dist/lib/sandbox/worktree-safety.js +4 -1
  102. package/dist/lib/server/index.js +8 -0
  103. package/dist/lib/server/protocol.js +38 -1
  104. package/dist/lib/task/activity-log.js +8 -1
  105. package/dist/lib/task/artifact-lifecycle.js +75 -18
  106. package/dist/lib/task/artifact-operations.js +520 -0
  107. package/dist/lib/task/artifact-repair-intent.js +59 -0
  108. package/dist/lib/task/artifact-schema.js +135 -0
  109. package/dist/lib/task/capabilities.js +310 -0
  110. package/dist/lib/task/commands/status.js +32 -0
  111. package/dist/lib/task/commit-operation.js +12 -1
  112. package/dist/lib/task/control-authority.js +12 -5
  113. package/dist/lib/task/create-service.js +111 -12
  114. package/dist/lib/task/create.js +25 -0
  115. package/dist/lib/task/current-contract.js +5 -1
  116. package/dist/lib/task/decision-details.js +1 -1
  117. package/dist/lib/task/events.js +416 -17
  118. package/dist/lib/task/finalization.js +37 -13
  119. package/dist/lib/task/human-override.js +58 -36
  120. package/dist/lib/task/index.js +11 -3
  121. package/dist/lib/task/invalidation-command.js +86 -0
  122. package/dist/lib/task/invalidation.js +260 -0
  123. package/dist/lib/task/ledger-intents.js +37 -0
  124. package/dist/lib/task/local-artifact-finalization.js +299 -0
  125. package/dist/lib/task/orchestration.js +240 -144
  126. package/dist/lib/task/pr-delivery-fact.js +102 -101
  127. package/dist/lib/task/qualification-audit.js +490 -0
  128. package/dist/lib/task/qualification-confirmation.js +109 -0
  129. package/dist/lib/task/qualification-intents.js +148 -0
  130. package/dist/lib/task/review-artifacts.js +22 -23
  131. package/dist/lib/task/review-finalization.js +121 -4
  132. package/dist/lib/task/review-fingerprint.js +24 -2
  133. package/dist/lib/task/rework-intent.js +138 -0
  134. package/dist/lib/task/sections.js +6 -4
  135. package/dist/lib/task/skill-scope.js +23 -0
  136. package/dist/lib/task/task-execution-lock.js +27 -17
  137. package/dist/lib/task/verification-config.js +11 -0
  138. package/dist/lib/task/verification-engine.js +93 -16
  139. package/dist/lib/task/verification.js +3 -3
  140. package/dist/lib/task/workflow-warning-intents.js +1 -1
  141. package/dist/lib/task/write.js +11 -0
  142. package/dist/lib/version.js +11 -1
  143. package/dist/lifecycle-build-manifest.json +3 -3
  144. package/lib/agent-client.ts +9 -0
  145. package/lib/agent-clients/next-steps.ts +6 -1
  146. package/lib/decide.ts +3 -3
  147. package/lib/init.ts +1 -1
  148. package/lib/internal/agent-client.ts +4 -2
  149. package/lib/internal/cli-route-inventory.ts +140 -0
  150. package/lib/internal/codex-lifecycle.ts +8 -6
  151. package/lib/internal/codex-sandbox-controller.ts +7 -2
  152. package/lib/internal/git-workflow.ts +9 -6
  153. package/lib/internal/platform-checks.ts +14 -6
  154. package/lib/internal/platform-comment.ts +20 -12
  155. package/lib/internal/platform-context.ts +5 -3
  156. package/lib/internal/platform-issue.ts +17 -11
  157. package/lib/internal/platform-metadata.ts +70 -0
  158. package/lib/internal/platform-pr-review.ts +23 -12
  159. package/lib/internal/platform-pr.ts +65 -27
  160. package/lib/internal/platform-release-notes.ts +8 -6
  161. package/lib/internal/platform-security.ts +83 -0
  162. package/lib/internal/pr-review-grade.ts +20 -11
  163. package/lib/internal/release-workflow.ts +21 -9
  164. package/lib/internal/sandbox-control.ts +44 -6
  165. package/lib/internal/task-activity.ts +15 -7
  166. package/lib/internal/task-artifact.ts +204 -22
  167. package/lib/internal/task-context.ts +3 -1
  168. package/lib/internal/task-create.ts +78 -17
  169. package/lib/internal/task-delivery.ts +3 -1
  170. package/lib/internal/task-event.ts +9 -4
  171. package/lib/internal/task-finalization.ts +6 -4
  172. package/lib/internal/task-invalidation.ts +41 -0
  173. package/lib/internal/task-ledger.ts +21 -8
  174. package/lib/internal/task-lifecycle.ts +5 -3
  175. package/lib/internal/task-operation-registry.ts +482 -0
  176. package/lib/internal/task-orchestration.ts +12 -0
  177. package/lib/internal/task-override.ts +15 -9
  178. package/lib/internal/task-qualification.ts +61 -0
  179. package/lib/internal/task-review.ts +6 -4
  180. package/lib/internal/task-short-id.ts +10 -1
  181. package/lib/internal/task-snapshot.ts +3 -1
  182. package/lib/internal/task-validate.ts +6 -2
  183. package/lib/internal/task-verify.ts +10 -7
  184. package/lib/internal/task-warning.ts +10 -4
  185. package/lib/platform/adapters.ts +3 -2
  186. package/lib/platform/change-request-git-evidence.ts +36 -9
  187. package/lib/platform/comment-safety.ts +250 -0
  188. package/lib/platform/completion-backfill.ts +9 -8
  189. package/lib/platform/context.ts +97 -134
  190. package/lib/platform/github-client.ts +2 -2
  191. package/lib/platform/github-provider.ts +1132 -0
  192. package/lib/platform/in-label-sync.ts +208 -0
  193. package/lib/platform/issue-comments.ts +255 -80
  194. package/lib/platform/issues.ts +344 -174
  195. package/lib/platform/metadata-labels.ts +5 -2
  196. package/lib/platform/none-provider.ts +35 -0
  197. package/lib/platform/pr-change-report.ts +639 -0
  198. package/lib/platform/pr-checks.ts +126 -106
  199. package/lib/platform/pr-review.ts +88 -85
  200. package/lib/platform/pr-summary.ts +581 -53
  201. package/lib/platform/provider-bridge.ts +82 -0
  202. package/lib/platform/provider-contract.ts +611 -0
  203. package/lib/platform/provider-loader.ts +307 -0
  204. package/lib/platform/provider-validation.ts +611 -0
  205. package/lib/platform/pull-request-metadata.ts +10 -7
  206. package/lib/platform/pull-requests.ts +781 -550
  207. package/lib/platform/release-notes.ts +76 -59
  208. package/lib/platform/releases.ts +77 -43
  209. package/lib/platform/repository-metadata.ts +453 -0
  210. package/lib/platform/resource-identity.ts +121 -0
  211. package/lib/platform/security-alerts.ts +191 -0
  212. package/lib/platform/task-identities.ts +40 -0
  213. package/lib/platform/types.ts +4 -2
  214. package/lib/platform/verification-required.ts +11 -13
  215. package/lib/platform/verification-sync.ts +128 -304
  216. package/lib/pr-review/evidence-grading.ts +25 -8
  217. package/lib/process-data/index.ts +7 -0
  218. package/lib/run/index.ts +62 -22
  219. package/lib/sandbox/commands/control-status.ts +26 -0
  220. package/lib/sandbox/commands/create.ts +46 -6
  221. package/lib/sandbox/commands/ls.ts +7 -0
  222. package/lib/sandbox/commands/rm.ts +1000 -87
  223. package/lib/sandbox/commands/show.ts +16 -0
  224. package/lib/sandbox/control/client.ts +203 -19
  225. package/lib/sandbox/control/completed-reentry.ts +98 -0
  226. package/lib/sandbox/control/container-identity.ts +27 -43
  227. package/lib/sandbox/control/executor.ts +180 -15
  228. package/lib/sandbox/control/lifecycle.ts +629 -17
  229. package/lib/sandbox/control/native-file-lock.ts +209 -0
  230. package/lib/sandbox/control/protocol.ts +118 -2
  231. package/lib/sandbox/control/server.ts +724 -34
  232. package/lib/sandbox/control/state.ts +257 -4
  233. package/lib/sandbox/control/task-view.ts +240 -0
  234. package/lib/sandbox/engines/authority.ts +365 -0
  235. package/lib/sandbox/engines/docker-exact-discovery.ts +132 -0
  236. package/lib/sandbox/index.ts +7 -0
  237. package/lib/sandbox/recovery.ts +21 -1
  238. package/lib/sandbox/workspace-view.ts +9 -3
  239. package/lib/sandbox/worktree-safety.ts +4 -1
  240. package/lib/server/index.ts +10 -0
  241. package/lib/server/protocol.ts +36 -1
  242. package/lib/task/activity-log.ts +8 -0
  243. package/lib/task/artifact-lifecycle.ts +70 -22
  244. package/lib/task/artifact-operations.ts +626 -0
  245. package/lib/task/artifact-repair-intent.ts +71 -0
  246. package/lib/task/artifact-schema.ts +192 -0
  247. package/lib/task/capabilities.ts +331 -0
  248. package/lib/task/commands/status.ts +38 -0
  249. package/lib/task/commit-operation.ts +12 -1
  250. package/lib/task/control-authority.ts +22 -9
  251. package/lib/task/create-service.ts +139 -14
  252. package/lib/task/create.ts +27 -0
  253. package/lib/task/current-contract.ts +6 -1
  254. package/lib/task/decision-details.ts +6 -2
  255. package/lib/task/events.ts +392 -16
  256. package/lib/task/finalization.ts +50 -14
  257. package/lib/task/human-override.ts +70 -46
  258. package/lib/task/index.ts +16 -3
  259. package/lib/task/invalidation-command.ts +86 -0
  260. package/lib/task/invalidation.ts +332 -0
  261. package/lib/task/ledger-intents.ts +35 -1
  262. package/lib/task/local-artifact-finalization.ts +432 -0
  263. package/lib/task/orchestration.ts +223 -133
  264. package/lib/task/pr-delivery-fact.ts +131 -148
  265. package/lib/task/qualification-audit.ts +599 -0
  266. package/lib/task/qualification-confirmation.ts +117 -0
  267. package/lib/task/qualification-intents.ts +150 -0
  268. package/lib/task/review-artifacts.ts +21 -22
  269. package/lib/task/review-finalization.ts +133 -4
  270. package/lib/task/review-fingerprint.ts +23 -2
  271. package/lib/task/rework-intent.ts +165 -0
  272. package/lib/task/sections.ts +6 -4
  273. package/lib/task/skill-scope.ts +24 -0
  274. package/lib/task/task-execution-lock.ts +23 -14
  275. package/lib/task/verification-config.ts +12 -0
  276. package/lib/task/verification-engine.ts +103 -17
  277. package/lib/task/verification.ts +4 -4
  278. package/lib/task/workflow-warning-intents.ts +1 -1
  279. package/lib/task/write.ts +19 -0
  280. package/lib/update.ts +1 -1
  281. package/lib/version.ts +15 -1
  282. package/package.json +12 -1
  283. package/templates/.agents/rules/README.en.md +3 -1
  284. package/templates/.agents/rules/README.zh-CN.md +3 -1
  285. package/templates/.agents/rules/decision-qualification.en.md +22 -0
  286. package/templates/.agents/rules/decision-qualification.zh-CN.md +22 -0
  287. package/templates/.agents/rules/evidence-reporting.en.md +28 -0
  288. package/templates/.agents/rules/evidence-reporting.zh-CN.md +28 -0
  289. package/templates/.agents/rules/issue-pr-commands.en.md +1 -1
  290. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
  291. package/templates/.agents/rules/issue-sync.en.md +2 -0
  292. package/templates/.agents/rules/issue-sync.zh-CN.md +2 -0
  293. package/templates/.agents/rules/label-milestone-setup.en.md +7 -7
  294. package/templates/.agents/rules/label-milestone-setup.zh-CN.md +7 -7
  295. package/templates/.agents/rules/local-artifact-repair.en.md +13 -2
  296. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -2
  297. package/templates/.agents/rules/pr-sync.en.md +10 -4
  298. package/templates/.agents/rules/pr-sync.zh-CN.md +17 -4
  299. package/templates/.agents/rules/security-alerts.en.md +8 -8
  300. package/templates/.agents/rules/security-alerts.zh-CN.md +8 -8
  301. package/templates/.agents/rules/task-short-id.en.md +1 -4
  302. package/templates/.agents/rules/task-short-id.zh-CN.md +1 -3
  303. package/templates/.agents/skills/analyze-task/SKILL.en.md +28 -5
  304. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +29 -5
  305. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -12
  306. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -12
  307. package/templates/.agents/skills/check-task/SKILL.en.md +16 -37
  308. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -37
  309. package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
  310. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
  311. package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
  312. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
  313. package/templates/.agents/skills/code-task/SKILL.en.md +41 -9
  314. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +43 -11
  315. package/templates/.agents/skills/code-task/config/verify.en.json +6 -14
  316. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +6 -14
  317. package/templates/.agents/skills/code-task/reference/code-rules.en.md +5 -4
  318. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +5 -4
  319. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
  320. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
  321. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +3 -1
  322. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +3 -1
  323. package/templates/.agents/skills/code-task/reference/report-template.en.md +42 -15
  324. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +39 -12
  325. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +2 -0
  326. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +2 -0
  327. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +3 -3
  328. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +3 -3
  329. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +10 -4
  330. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +10 -4
  331. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  332. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  333. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +4 -3
  334. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +4 -3
  335. package/templates/.agents/skills/complete-task/SKILL.en.md +26 -21
  336. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +25 -21
  337. package/templates/.agents/skills/create-pr/SKILL.en.md +7 -6
  338. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -7
  339. package/templates/.agents/skills/create-pr/config/verify.json +1 -0
  340. package/templates/.agents/skills/create-pr/reference/change-report.en.md +16 -4
  341. package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +16 -4
  342. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +5 -3
  343. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +5 -3
  344. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
  345. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
  346. package/templates/.agents/skills/create-pr/scripts/change-report.mjs +3 -0
  347. package/templates/.agents/skills/create-task/SKILL.en.md +8 -0
  348. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +7 -0
  349. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  350. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  351. package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
  352. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  353. package/templates/.agents/skills/init-labels/SKILL.en.md +8 -8
  354. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +8 -8
  355. package/templates/.agents/skills/init-milestones/SKILL.en.md +9 -11
  356. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +9 -11
  357. package/templates/.agents/skills/plan-task/SKILL.en.md +27 -4
  358. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +26 -4
  359. package/templates/.agents/skills/plan-task/config/verify.en.json +4 -14
  360. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +4 -14
  361. package/templates/.agents/skills/review-analysis/SKILL.en.md +17 -4
  362. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +17 -4
  363. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -18
  364. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -18
  365. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +50 -23
  366. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +45 -18
  367. package/templates/.agents/skills/review-code/SKILL.en.md +18 -5
  368. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +18 -5
  369. package/templates/.agents/skills/review-code/config/verify.en.json +1 -23
  370. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -23
  371. package/templates/.agents/skills/review-code/reference/report-template.en.md +49 -22
  372. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +45 -18
  373. package/templates/.agents/skills/review-plan/SKILL.en.md +17 -4
  374. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +17 -4
  375. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -18
  376. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -18
  377. package/templates/.agents/skills/review-plan/reference/report-template.en.md +49 -22
  378. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +45 -18
  379. package/templates/.agents/skills/review-pr/SKILL.en.md +5 -1
  380. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +5 -1
  381. package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -3
  382. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -3
  383. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +9 -3
  384. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +9 -3
  385. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +3 -1
  386. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +3 -1
  387. package/templates/.agents/skills/test/SKILL.en.md +13 -2
  388. package/templates/.agents/skills/test/SKILL.zh-CN.md +13 -2
  389. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +14 -0
  390. package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
  391. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
  392. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +6 -0
  393. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +6 -0
  394. package/templates/.agents/templates/task.en.md +24 -1
  395. package/templates/.agents/templates/task.zh-CN.md +24 -1
  396. package/templates/.github/workflows/pr-label.yml +7 -26
  397. package/templates/.agents/scripts/security-alerts.github.sh +0 -151
  398. package/templates/.agents/scripts/security-alerts.sh +0 -31
  399. package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +0 -146
  400. package/templates/.agents/skills/init-labels/scripts/init-labels.sh +0 -32
  401. package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +0 -294
  402. package/templates/.agents/skills/init-milestones/scripts/init-milestones.sh +0 -6
@@ -0,0 +1,1130 @@
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
+ import semver from 'semver';
6
+ import { createGitHubClient, MINIMUM_GITHUB_CLI_VERSION } from './github-client.js';
7
+ import { resourceIdentityNumber, resourceIdentityString } from './resource-identity.js';
8
+ import { extractPullRequestFileNames, syncLabelDelta } from './in-label-sync.js';
9
+ const CURRENT_USER_QUERY = 'query { viewer { login } }';
10
+ 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}}}}}}`;
11
+ 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}}}}}}}}`;
12
+ const CLOSING_ISSUES_QUERY = 'query($owner:String!,$name:String!,$number:Int!,$cursor:String){repository(owner:$owner,name:$name){pullRequest(number:$number){closingIssuesReferences(first:100,after:$cursor){nodes{number} pageInfo{hasNextPage endCursor}}}}}';
13
+ function parseGitHubRemote(remote) {
14
+ const trimmed = remote.trim().replace(/\.git$/, '');
15
+ const match = trimmed.match(/^(?:https?:\/\/github\.com\/|ssh:\/\/(?:git@)?github\.com\/|git@github\.com:)([^/\s]+\/[^/\s]+)$/i);
16
+ return match?.[1] || null;
17
+ }
18
+ function defaultGitRemote(cwd) {
19
+ try {
20
+ return execFileSync('git', ['remote', 'get-url', 'origin'], {
21
+ cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe']
22
+ }).trim();
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ function configuredGitRemotes(cwd) {
29
+ try {
30
+ return execFileSync('git', ['remote'], {
31
+ cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe']
32
+ }).split(/\r?\n/).filter(Boolean).flatMap((name) => {
33
+ try {
34
+ const url = execFileSync('git', ['config', '--get', `remote.${name}.url`], {
35
+ cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe']
36
+ }).trim();
37
+ const repository = parseGitHubRemote(url);
38
+ return repository ? [{ name, url, repository }] : [];
39
+ }
40
+ catch {
41
+ return [];
42
+ }
43
+ });
44
+ }
45
+ catch {
46
+ return [];
47
+ }
48
+ }
49
+ function rewriteGitHubRemote(remote, repository) {
50
+ const match = remote.trim().match(/^(https?:\/\/github\.com\/|ssh:\/\/(?:git@)?github\.com\/|git@github\.com:)[^\/\s]+\/[^\/\s]+(?:\.git)?$/i);
51
+ return match ? `${match[1]}${repository}.git` : null;
52
+ }
53
+ function resolveGitHubChangeRequestGitEvidence({ cwd, repository, number, baseRepository, baseRef }) {
54
+ if (baseRepository !== repository || number <= 0) {
55
+ return {
56
+ ok: false,
57
+ error: {
58
+ code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
59
+ message: 'Pull request repository identity is inconsistent',
60
+ retryable: false
61
+ }
62
+ };
63
+ }
64
+ const reviewedHeadRef = `refs/pull/${number}/head`;
65
+ const targetHeadRef = `refs/heads/${baseRef}`;
66
+ const refValid = (ref) => {
67
+ try {
68
+ execFileSync('git', ['check-ref-format', ref], {
69
+ cwd, encoding: 'utf8', stdio: ['ignore', 'ignore', 'ignore']
70
+ });
71
+ return true;
72
+ }
73
+ catch {
74
+ return false;
75
+ }
76
+ };
77
+ if (!refValid(reviewedHeadRef) || !refValid(targetHeadRef)) {
78
+ return {
79
+ ok: false,
80
+ error: {
81
+ code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
82
+ message: 'Pull request Git refs are invalid',
83
+ retryable: false
84
+ }
85
+ };
86
+ }
87
+ const remotes = configuredGitRemotes(cwd);
88
+ const exact = remotes
89
+ .filter((remote) => remote.repository === repository)
90
+ .sort((left, right) => Number(right.name === 'origin') - Number(left.name === 'origin') ||
91
+ left.name.localeCompare(right.name))[0];
92
+ const origin = remotes.find((remote) => remote.name === 'origin');
93
+ const remoteUrl = exact?.url || (origin ? rewriteGitHubRemote(origin.url, repository) : null);
94
+ if (!remoteUrl) {
95
+ return {
96
+ ok: false,
97
+ error: {
98
+ code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
99
+ message: `No GitHub remote can provide evidence for ${repository}`,
100
+ retryable: false
101
+ }
102
+ };
103
+ }
104
+ return {
105
+ ok: true,
106
+ value: { remoteUrl, reviewedHeadRef, targetHeadRef }
107
+ };
108
+ }
109
+ function failure(error) {
110
+ return { ok: false, error };
111
+ }
112
+ function resolveContext(client, input) {
113
+ const cwd = input.workingDirectory;
114
+ const version = client.version({ cwd });
115
+ if (!version.ok)
116
+ return failure(version.error);
117
+ if (!semver.gte(version.value, MINIMUM_GITHUB_CLI_VERSION)) {
118
+ return failure({
119
+ code: 'GH_CLI_VERSION_UNSUPPORTED',
120
+ message: 'GitHub CLI ' + version.value + ' is unsupported; install gh >= ' + MINIMUM_GITHUB_CLI_VERSION,
121
+ retryable: false
122
+ });
123
+ }
124
+ const remote = input.gitRemote || defaultGitRemote(cwd);
125
+ if (!remote) {
126
+ return failure({ code: 'REMOTE_MISSING', message: 'Git origin remote is not configured', retryable: false });
127
+ }
128
+ const ownerRepo = parseGitHubRemote(remote);
129
+ if (!ownerRepo) {
130
+ return failure({
131
+ code: /github\.com/i.test(remote) ? 'REMOTE_INVALID' : 'PLATFORM_UNSUPPORTED',
132
+ message: 'Unable to parse GitHub owner/repo from configured remote',
133
+ retryable: false
134
+ });
135
+ }
136
+ const repository = client.json(['api', 'repos/' + ownerRepo], { cwd });
137
+ if (!repository.ok)
138
+ return failure(repository.error);
139
+ const repositoryValue = repository.value;
140
+ const upstream = repositoryValue?.fork ? repositoryValue.parent?.full_name : repositoryValue?.full_name;
141
+ if (!upstream)
142
+ return failure({
143
+ code: 'UPSTREAM_UNRESOLVED',
144
+ message: 'Unable to resolve the upstream repository',
145
+ retryable: false
146
+ });
147
+ const user = client.json(['api', 'graphql', '-f', 'query=' + CURRENT_USER_QUERY], { cwd });
148
+ if (!user.ok)
149
+ return failure(user.error);
150
+ const userValue = user.value;
151
+ const currentUser = userValue?.data?.viewer?.login || null;
152
+ const permissions = client.json(['api', 'repos/' + upstream], { cwd });
153
+ if (!permissions.ok)
154
+ return failure(permissions.error);
155
+ const permissionValue = permissions.value;
156
+ const values = permissionValue?.permissions || {};
157
+ const capabilities = {
158
+ authenticated: Boolean(currentUser),
159
+ comment: Boolean(currentUser),
160
+ triage: Boolean(values.triage || values.push || values.admin),
161
+ push: Boolean(values.push || values.admin),
162
+ admin: Boolean(values.admin)
163
+ };
164
+ return {
165
+ ok: true,
166
+ value: {
167
+ type: 'github',
168
+ scope: { id: upstream, label: upstream },
169
+ currentUser: currentUser ? { id: currentUser, name: currentUser } : null,
170
+ capabilities,
171
+ authenticated: capabilities.authenticated,
172
+ ...(repositoryValue?.owner?.type ? { metadata: { ownerType: repositoryValue.owner.type } } : {})
173
+ }
174
+ };
175
+ }
176
+ function repository(context) {
177
+ return context.scopeId;
178
+ }
179
+ function expectedHeadRepository(repositoryName, head) {
180
+ const colon = head.indexOf(':');
181
+ if (colon === -1)
182
+ return { repository: repositoryName, ref: head };
183
+ const repoName = repositoryName.split('/')[1] || '';
184
+ return { repository: `${head.slice(0, colon)}/${repoName}`, ref: head.slice(colon + 1) };
185
+ }
186
+ function repositoryHead(cwd) {
187
+ return execFileSync('git', ['rev-parse', 'HEAD'], {
188
+ cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe']
189
+ }).trim();
190
+ }
191
+ function verifyRemoteBranch(cwd, repositoryName, head) {
192
+ const wanted = expectedHeadRepository(repositoryName, head);
193
+ if (!wanted.ref || !/^[A-Za-z0-9._/-]+$/.test(wanted.ref))
194
+ return {
195
+ ok: false,
196
+ error: { code: 'PR_HEAD_INVALID', message: 'Pull request head ref is invalid', retryable: false }
197
+ };
198
+ const remotes = configuredGitRemotes(cwd);
199
+ const remote = remotes.find((item) => item.repository.toLowerCase() === wanted.repository.toLowerCase())
200
+ ?? remotes.find((item) => item.name === 'origin');
201
+ if (!remote)
202
+ return {
203
+ ok: false,
204
+ error: { code: 'PR_REMOTE_BRANCH_MISSING', message: `No configured remote can verify ${wanted.repository}:${wanted.ref}`, retryable: false }
205
+ };
206
+ try {
207
+ const output = execFileSync('git', ['ls-remote', '--refs', remote.name, `refs/heads/${wanted.ref}`], {
208
+ cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe']
209
+ }).trim();
210
+ const match = output.split(/\r?\n/).map((line) => line.trim()).find((line) => line.endsWith(`refs/heads/${wanted.ref}`));
211
+ const sha = match?.split(/\s+/)[0] || null;
212
+ if (!sha || !/^[a-f0-9]{40}$/i.test(sha))
213
+ return {
214
+ ok: false,
215
+ error: { code: 'PR_REMOTE_BRANCH_MISSING', message: `Remote branch ${wanted.repository}:${wanted.ref} does not exist`, retryable: false }
216
+ };
217
+ return { ok: true, value: sha };
218
+ }
219
+ catch (error) {
220
+ return {
221
+ ok: false,
222
+ error: { code: 'PR_REMOTE_BRANCH_UNAVAILABLE', message: error instanceof Error ? error.message : String(error), retryable: true }
223
+ };
224
+ }
225
+ }
226
+ function invalid(code, message) {
227
+ return { ok: false, error: { code, message, retryable: false } };
228
+ }
229
+ function changeRequestSnapshot(value) {
230
+ return {
231
+ id: value.nodeId,
232
+ identity: { kind: 'number', value: value.number },
233
+ number: value.number,
234
+ state: value.state,
235
+ title: value.title,
236
+ body: value.body,
237
+ baseSha: value.base.sha,
238
+ headSha: value.head.sha,
239
+ mergedAt: value.mergedAt,
240
+ displayUrl: value.url,
241
+ draft: value.draft,
242
+ labels: value.labels,
243
+ assignees: value.assignees,
244
+ milestone: value.milestone,
245
+ mergeCommitSha: value.mergeCommitSha,
246
+ mergeability: value.mergeability,
247
+ head: value.head,
248
+ base: value.base
249
+ };
250
+ }
251
+ function issueSnapshot(value, graph) {
252
+ const issueType = graph?.type ? {
253
+ identity: { kind: 'id', value: String(graph.type.id) },
254
+ name: String(graph.type.name),
255
+ fields: (graph.type.fields || []).map((field) => ({
256
+ identity: { kind: 'id', value: String(field.id) },
257
+ name: String(field.name),
258
+ kind: field.kind,
259
+ options: (field.options || []).map((option) => ({ identity: { kind: 'id', value: String(option.id) }, name: String(option.name) }))
260
+ }))
261
+ } : value.issueType ? {
262
+ identity: { kind: 'key', value: String(value.issueType) },
263
+ name: String(value.issueType),
264
+ fields: []
265
+ } : null;
266
+ return {
267
+ id: value.nodeId,
268
+ identity: { kind: 'number', value: value.number },
269
+ number: value.number,
270
+ state: value.state,
271
+ title: value.title,
272
+ body: value.body,
273
+ labels: value.labels,
274
+ assignees: value.assignees,
275
+ milestone: value.milestone,
276
+ fields: graph ? Object.fromEntries(graph.values.map((field) => [field.name, field.value])) : value.fields,
277
+ issueType,
278
+ displayUrl: value.url
279
+ };
280
+ }
281
+ function commentSnapshot(value) {
282
+ return {
283
+ id: String(value.id),
284
+ author: value.user?.login ? { id: String(value.user.login), name: String(value.user.login) } : null,
285
+ body: String(value.body || ''),
286
+ createdAt: String(value.created_at || ''),
287
+ updatedAt: String(value.updated_at || value.created_at || '')
288
+ };
289
+ }
290
+ function createReceipt(remoteId) {
291
+ return { ok: true, value: { changed: true, remoteId } };
292
+ }
293
+ function partialFailure(response, value) {
294
+ return { ok: false, error: response.error, value };
295
+ }
296
+ function labelReconciliation(created, updated, removed, skipped) {
297
+ return {
298
+ changed: created.length > 0 || updated.length > 0 || removed.length > 0,
299
+ created,
300
+ updated,
301
+ removed,
302
+ skipped
303
+ };
304
+ }
305
+ function milestoneInitialization(created, skipped) {
306
+ return { changed: created.length > 0, created, skipped };
307
+ }
308
+ function githubResourceToken(identity) {
309
+ return resourceIdentityString(identity) || (identity?.kind === 'number' ? String(identity.value) : null);
310
+ }
311
+ function syncLabels(client, repositoryName, number, cwd, current, target, prefixes) {
312
+ let changed = false;
313
+ for (const prefix of prefixes) {
314
+ const synced = syncLabelDelta(client, repositoryName, number, cwd, current, target, prefix);
315
+ if (synced.status === 'failed' || synced.status === 'blocked') {
316
+ if (changed || synced.changed)
317
+ return {
318
+ status: 'blocked',
319
+ changed: true,
320
+ error: synced.error
321
+ ? { ...synced.error, code: 'IN_LABEL_SYNC_PARTIAL', message: `In-label synchronization is partial or unknown: ${synced.error.message}` }
322
+ : { code: 'IN_LABEL_SYNC_PARTIAL', message: 'In-label synchronization is partial or unknown', retryable: true }
323
+ };
324
+ return synced;
325
+ }
326
+ changed ||= synced.changed;
327
+ }
328
+ return { status: changed ? 'applied' : 'no-op', changed, error: null };
329
+ }
330
+ function createGitHubOperations(client) {
331
+ const issues = {
332
+ async listLabels({ context }) {
333
+ const response = client.json(['api', '--paginate', '--slurp', `repos/${repository(context)}/labels?per_page=100`], { cwd: context.workingDirectory });
334
+ if (!response.ok)
335
+ return response;
336
+ const values = Array.isArray(response.value)
337
+ ? response.value.flatMap((entry) => Array.isArray(entry) ? entry : [entry])
338
+ : [];
339
+ if (values.some((entry) => !entry || typeof entry.name !== 'string' || !entry.name.trim())) {
340
+ return invalid('IN_LABEL_SYNC_LABELS_INVALID', 'Repository labels response contains an entry without a valid name');
341
+ }
342
+ return { ok: true, value: [...new Set(values.map((entry) => String(entry.name)))].sort() };
343
+ },
344
+ async listMilestones({ context }) {
345
+ const response = client.json(['api', '--paginate', '--slurp', `repos/${repository(context)}/milestones?state=open&per_page=100`], { cwd: context.workingDirectory });
346
+ if (!response.ok)
347
+ return response;
348
+ const values = Array.isArray(response.value)
349
+ ? response.value.flatMap((entry) => Array.isArray(entry) ? entry : [entry])
350
+ : [];
351
+ if (values.some((entry) => !entry || typeof entry.title !== 'string' || !entry.title.trim())) {
352
+ return invalid('MILESTONE_IDENTITY_INVALID', 'Milestones response contains an entry without a valid title');
353
+ }
354
+ return { ok: true, value: [...new Set(values.map((entry) => String(entry.title)))].sort() };
355
+ },
356
+ async describeRepository({ context }) {
357
+ const repositoryName = repository(context);
358
+ const info = client.json(['api', `repos/${repositoryName}`], { cwd: context.workingDirectory });
359
+ if (!info.ok)
360
+ return info;
361
+ const labels = client.json(['api', '--paginate', '--slurp', `repos/${repositoryName}/labels?per_page=100`], { cwd: context.workingDirectory });
362
+ if (!labels.ok)
363
+ return labels;
364
+ const milestones = client.json(['api', '--paginate', '--slurp', `repos/${repositoryName}/milestones?state=open&per_page=100`], { cwd: context.workingDirectory });
365
+ if (!milestones.ok)
366
+ return milestones;
367
+ const [owner] = repositoryName.split('/');
368
+ const issueTypes = client.json(['api', 'graphql', '-f', `query=${ISSUE_TYPES_QUERY}`, '-F', `owner=${owner}`], { cwd: context.workingDirectory });
369
+ if (!issueTypes.ok)
370
+ return issueTypes;
371
+ const flatten = (value) => Array.isArray(value) ? value.flatMap((entry) => Array.isArray(entry) ? entry : [entry]) : [];
372
+ const fields = (raw) => (raw || []).flatMap((field) => {
373
+ const kind = field.__typename === 'IssueFieldSingleSelect' ? 'single-select'
374
+ : field.__typename === 'IssueFieldDate' ? 'date'
375
+ : field.__typename === 'IssueFieldText' ? 'text'
376
+ : field.__typename === 'IssueFieldNumber' ? 'number' : null;
377
+ return field.id && field.name && kind ? [{
378
+ identity: { kind: 'id', value: String(field.id) }, name: String(field.name), kind,
379
+ options: (field.options || []).filter((option) => option?.id && option?.name).map((option) => ({ identity: { kind: 'id', value: String(option.id) }, name: String(option.name) }))
380
+ }] : [];
381
+ });
382
+ const typeNodes = issueTypes.value?.data?.organization?.issueTypes?.nodes || [];
383
+ return {
384
+ ok: true,
385
+ value: {
386
+ repository: {
387
+ identity: { kind: 'key', value: repositoryName },
388
+ name: String(info.value?.full_name || repositoryName),
389
+ url: info.value?.html_url ? String(info.value.html_url) : null
390
+ },
391
+ labels: flatten(labels.value).filter((item) => item?.name).map((item) => ({ identity: { kind: 'key', value: String(item.name) }, name: String(item.name) })),
392
+ 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' })),
393
+ issueTypes: typeNodes.filter((item) => item?.id && item?.name).map((item) => ({ identity: { kind: 'id', value: String(item.id) }, name: String(item.name), fields: fields(item.pinnedFields) })),
394
+ fields: []
395
+ }
396
+ };
397
+ },
398
+ async inspect({ context, target }) {
399
+ const number = resourceIdentityNumber(target);
400
+ if (!number)
401
+ return invalid('ISSUE_NUMBER_INVALID', 'Issue number must be positive');
402
+ const module = await import('./issues.js');
403
+ const fetched = module.inspectGitHubIssue(client, repository(context), number, context.workingDirectory);
404
+ if (!fetched.ok)
405
+ return fetched;
406
+ const graph = module.graphState(client, repository(context), number, context.workingDirectory);
407
+ return { ok: true, value: issueSnapshot(fetched.value, graph || undefined) };
408
+ },
409
+ async create({ context, desired }) {
410
+ let milestone = null;
411
+ if (desired.milestone) {
412
+ const milestones = client.json(['api', '--paginate', '--slurp', `repos/${repository(context)}/milestones?state=open&per_page=100`], { cwd: context.workingDirectory });
413
+ if (!milestones.ok)
414
+ return milestones;
415
+ const values = Array.isArray(milestones.value)
416
+ ? milestones.value.flatMap((entry) => Array.isArray(entry) ? entry : [entry])
417
+ : [];
418
+ const selected = values.find((item) => item?.title === desired.milestone);
419
+ if (!Number.isSafeInteger(selected?.number) || selected.number <= 0) {
420
+ return invalid('MILESTONE_IDENTITY_INVALID', 'Milestone title does not resolve to a positive number');
421
+ }
422
+ milestone = selected.number;
423
+ }
424
+ const response = client.json(['api', `repos/${repository(context)}/issues`, '-X', 'POST', '--input', '-'], {
425
+ cwd: context.workingDirectory,
426
+ method: 'POST',
427
+ input: JSON.stringify({
428
+ title: desired.title,
429
+ body: desired.body,
430
+ labels: desired.labels,
431
+ assignees: desired.assignees,
432
+ ...(milestone ? { milestone } : {})
433
+ })
434
+ });
435
+ if (!response.ok)
436
+ return response;
437
+ const number = Number(response.value?.number);
438
+ if (!Number.isInteger(number) || number <= 0)
439
+ return invalid('ISSUE_CREATE_RESPONSE_INVALID', 'Issue create response lacks issue number');
440
+ return createReceipt(String(number));
441
+ },
442
+ async update({ context, target, currentLabels, patch }) {
443
+ const number = resourceIdentityNumber(target);
444
+ if (!number)
445
+ return invalid('ISSUE_NUMBER_INVALID', 'Issue number must be positive');
446
+ const { issueType, fields, labels, ...restPatch } = patch;
447
+ let requestPatch = restPatch;
448
+ let changed = false;
449
+ if (labels !== undefined) {
450
+ let before = currentLabels;
451
+ if (!before) {
452
+ const current = client.json(['api', `repos/${repository(context)}/issues/${number}`], { cwd: context.workingDirectory });
453
+ if (!current.ok)
454
+ return current;
455
+ before = Array.isArray(current.value?.labels)
456
+ ? current.value.labels.map((label) => typeof label === 'string' ? label : label?.name).filter((label) => typeof label === 'string')
457
+ : [];
458
+ }
459
+ const synced = syncLabels(client, repository(context), number, context.workingDirectory, before || [], labels, ['status:', 'in:']);
460
+ if (synced.status === 'failed' || synced.status === 'blocked')
461
+ return synced.error
462
+ ? { ok: false, error: synced.error }
463
+ : invalid('IN_LABEL_SYNC_FAILED', 'Issue label synchronization failed');
464
+ changed ||= synced.changed;
465
+ }
466
+ if (typeof patch.milestone === 'string') {
467
+ const milestones = client.json(['api', '--paginate', '--slurp', `repos/${repository(context)}/milestones?state=open&per_page=100`], { cwd: context.workingDirectory });
468
+ if (!milestones.ok)
469
+ return milestones;
470
+ const values = Array.isArray(milestones.value)
471
+ ? milestones.value.flatMap((entry) => Array.isArray(entry) ? entry : [entry])
472
+ : [];
473
+ const selected = values.find((item) => item?.title === patch.milestone);
474
+ if (!Number.isSafeInteger(selected?.number) || selected.number <= 0) {
475
+ return invalid('MILESTONE_IDENTITY_INVALID', 'Milestone title does not resolve to a positive number');
476
+ }
477
+ requestPatch = { ...restPatch, milestone: selected.number };
478
+ }
479
+ if (issueType !== undefined || fields !== undefined) {
480
+ const [owner, name] = repository(context).split('/');
481
+ const current = client.json(['api', 'graphql', '-f', `query=${ISSUE_FIELDS_QUERY}`, '-F', `owner=${owner}`, '-F', `name=${name}`, '-F', `number=${number}`], { cwd: context.workingDirectory });
482
+ if (!current.ok)
483
+ return current;
484
+ const currentIssue = current.value?.data?.repository?.issue;
485
+ const issueId = currentIssue?.id;
486
+ if (!issueId)
487
+ return invalid('ISSUE_GRAPH_IDENTITY_INVALID', 'Issue GraphQL response lacks issue identity');
488
+ let targetType = currentIssue.issueType;
489
+ if (issueType !== undefined) {
490
+ const types = client.json(['api', 'graphql', '-f', `query=${ISSUE_TYPES_QUERY}`, '-F', `owner=${owner}`], { cwd: context.workingDirectory });
491
+ if (!types.ok)
492
+ return types;
493
+ targetType = (types.value?.data?.organization?.issueTypes?.nodes || []).find((item) => item?.name === issueType);
494
+ if (!targetType)
495
+ return invalid('ISSUE_TYPE_NOT_FOUND', 'Issue type was not found');
496
+ if (targetType.id !== currentIssue.issueType?.id) {
497
+ const updatedType = client.json(['api', 'graphql', '--input', '-'], {
498
+ cwd: context.workingDirectory,
499
+ method: 'POST',
500
+ input: JSON.stringify({
501
+ query: 'mutation($issueId:ID!,$issueTypeId:ID){updateIssueIssueType(input:{issueId:$issueId,issueTypeId:$issueTypeId}){issue{id}}}',
502
+ variables: { issueId, issueTypeId: targetType.id }
503
+ })
504
+ });
505
+ if (!updatedType.ok)
506
+ return updatedType;
507
+ changed = true;
508
+ }
509
+ }
510
+ if (fields !== undefined) {
511
+ const values = Object.entries(fields).flatMap(([name, value]) => {
512
+ const field = (targetType?.pinnedFields || []).find((candidate) => candidate?.name === name);
513
+ if (!field?.id)
514
+ return [];
515
+ if (field.__typename === 'IssueFieldSingleSelect') {
516
+ const option = (field.options || []).find((candidate) => candidate?.name === value);
517
+ return option?.id ? [{ fieldId: field.id, singleSelectOptionId: option.id }] : [];
518
+ }
519
+ const key = field.__typename === 'IssueFieldDate' ? 'dateValue' : field.__typename === 'IssueFieldNumber' ? 'numberValue' : 'textValue';
520
+ return [{ fieldId: field.id, [key]: value }];
521
+ });
522
+ if (values.length > 0) {
523
+ const updatedFields = client.json(['api', 'graphql', '--input', '-'], {
524
+ cwd: context.workingDirectory,
525
+ method: 'POST',
526
+ input: JSON.stringify({
527
+ query: 'mutation($issueId:ID!,$issueFields:[IssueFieldCreateOrUpdateInput!]!){setIssueFieldValue(input:{issueId:$issueId,issueFields:$issueFields}){issue{id}}}',
528
+ variables: { issueId, issueFields: values }
529
+ })
530
+ });
531
+ if (!updatedFields.ok)
532
+ return updatedFields;
533
+ changed = true;
534
+ }
535
+ }
536
+ }
537
+ if (Object.keys(requestPatch).length > 0) {
538
+ const response = client.json(['api', `repos/${repository(context)}/issues/${number}`, '-X', 'PATCH', '--input', '-'], {
539
+ cwd: context.workingDirectory,
540
+ method: 'PATCH',
541
+ input: JSON.stringify(requestPatch)
542
+ });
543
+ if (!response.ok)
544
+ return response;
545
+ changed = true;
546
+ }
547
+ return { ok: true, value: { changed, remoteId: String(number) } };
548
+ }
549
+ };
550
+ const comments = {
551
+ async list({ context, parent }) {
552
+ const number = resourceIdentityNumber(parent);
553
+ if (!number)
554
+ return invalid('ISSUE_NUMBER_INVALID', 'Issue number must be positive');
555
+ const response = client.json(['api', '--paginate', '--slurp', `repos/${repository(context)}/issues/${number}/comments?per_page=100`], { cwd: context.workingDirectory });
556
+ if (!response.ok)
557
+ return response;
558
+ const values = Array.isArray(response.value) ? response.value.flatMap((entry) => Array.isArray(entry) ? entry : [entry]) : [];
559
+ return { ok: true, value: values.filter((entry) => entry && entry.id !== undefined).map(commentSnapshot) };
560
+ },
561
+ async write({ context, parent, body, existingComment }) {
562
+ const number = resourceIdentityNumber(parent);
563
+ if (!number)
564
+ return invalid('ISSUE_NUMBER_INVALID', 'Issue number must be positive');
565
+ const commentId = githubResourceToken(existingComment);
566
+ const endpoint = commentId ? `repos/${repository(context)}/issues/comments/${commentId}` : `repos/${repository(context)}/issues/${number}/comments`;
567
+ const response = client.json(['api', endpoint, '-X', commentId ? 'PATCH' : 'POST', '--input', '-'], {
568
+ cwd: context.workingDirectory,
569
+ method: commentId ? 'PATCH' : 'POST',
570
+ input: JSON.stringify({ body })
571
+ });
572
+ if (!response.ok)
573
+ return response;
574
+ return createReceipt(String(response.value?.id || commentId || ''));
575
+ },
576
+ async delete({ context, comment }) {
577
+ const commentId = githubResourceToken(comment);
578
+ if (!commentId)
579
+ return invalid('COMMENT_ID_INVALID', 'Comment id is required');
580
+ const response = client.text(['api', `repos/${repository(context)}/issues/comments/${commentId}`, '-X', 'DELETE'], { cwd: context.workingDirectory, method: 'DELETE' });
581
+ if (!response.ok)
582
+ return response;
583
+ return createReceipt(commentId);
584
+ }
585
+ };
586
+ const changeRequests = {
587
+ async verifyHead({ context, head }) {
588
+ let localHead;
589
+ try {
590
+ localHead = repositoryHead(context.workingDirectory);
591
+ }
592
+ catch (error) {
593
+ return { ok: false, error: { code: 'PR_LOCAL_HEAD_UNAVAILABLE', message: error instanceof Error ? error.message : String(error), retryable: false } };
594
+ }
595
+ const remote = verifyRemoteBranch(context.workingDirectory, repository(context), head);
596
+ if (!remote.ok)
597
+ return remote;
598
+ if (remote.value !== localHead)
599
+ return {
600
+ ok: false,
601
+ error: { code: 'PR_REMOTE_HEAD_MISMATCH', message: `Remote head ${remote.value} does not match local HEAD ${localHead}`, retryable: false }
602
+ };
603
+ return { ok: true, value: { sha: localHead } };
604
+ },
605
+ async inspect({ context, target }) {
606
+ const number = resourceIdentityNumber(target);
607
+ if (!number)
608
+ return invalid('PR_NUMBER_INVALID', 'Pull request number must be positive');
609
+ const module = await import('./pull-requests.js');
610
+ const fetched = module.inspectGitHubPullRequest(client, repository(context), number, context.workingDirectory);
611
+ if (!fetched.ok)
612
+ return fetched;
613
+ return { ok: true, value: changeRequestSnapshot(fetched.value) };
614
+ },
615
+ async listFiles({ context, target }) {
616
+ const number = resourceIdentityNumber(target);
617
+ if (!number)
618
+ return invalid('PR_NUMBER_INVALID', 'Pull request number must be positive');
619
+ const response = client.json(['api', '--paginate', '--slurp', `repos/${repository(context)}/pulls/${number}/files?per_page=100`], { cwd: context.workingDirectory });
620
+ if (!response.ok)
621
+ return response;
622
+ const files = extractPullRequestFileNames(response.value);
623
+ return files
624
+ ? { ok: true, value: files }
625
+ : invalid('IN_LABEL_SYNC_FILES_INVALID', 'Pull request files response is incomplete');
626
+ },
627
+ async listClosingIssues({ context, target }) {
628
+ const number = resourceIdentityNumber(target);
629
+ if (!number)
630
+ return invalid('PR_NUMBER_INVALID', 'Pull request number must be positive');
631
+ const [owner, name] = repository(context).split('/');
632
+ if (!owner || !name)
633
+ return invalid('IN_LABEL_SYNC_REPOSITORY_INVALID', 'Repository identity is invalid');
634
+ const identities = [];
635
+ let cursor = null;
636
+ for (;;) {
637
+ const args = ['api', 'graphql', '-f', `query=${CLOSING_ISSUES_QUERY}`, '-F', `owner=${owner}`, '-F', `name=${name}`, '-F', `number=${number}`];
638
+ if (cursor)
639
+ args.push('-F', `cursor=${cursor}`);
640
+ const response = client.json(args, { cwd: context.workingDirectory });
641
+ if (!response.ok)
642
+ return response;
643
+ const connection = response.value?.data?.repository?.pullRequest?.closingIssuesReferences;
644
+ if (!connection || !Array.isArray(connection.nodes) || !connection.pageInfo) {
645
+ return invalid('IN_LABEL_SYNC_ASSOCIATION_INVALID', 'Closing Issue response is incomplete');
646
+ }
647
+ for (const node of connection.nodes) {
648
+ const issue = Number(node?.number);
649
+ if (!Number.isSafeInteger(issue) || issue <= 0) {
650
+ return invalid('IN_LABEL_SYNC_ASSOCIATION_INVALID', 'Closing Issue identity is invalid');
651
+ }
652
+ identities.push({ kind: 'number', value: issue });
653
+ }
654
+ if (!connection.pageInfo.hasNextPage) {
655
+ const seen = new Set();
656
+ return { ok: true, value: identities.filter((identity) => {
657
+ const key = `${identity.kind}:${identity.value}`;
658
+ if (seen.has(key))
659
+ return false;
660
+ seen.add(key);
661
+ return true;
662
+ }) };
663
+ }
664
+ if (!connection.pageInfo.endCursor || connection.pageInfo.endCursor === cursor) {
665
+ return invalid('IN_LABEL_SYNC_ASSOCIATION_INVALID', 'Closing Issue pagination cursor is invalid');
666
+ }
667
+ cursor = connection.pageInfo.endCursor;
668
+ }
669
+ },
670
+ async listClosing({ context, issue }) {
671
+ const number = resourceIdentityNumber(issue);
672
+ if (!number)
673
+ return invalid('ISSUE_NUMBER_INVALID', 'Issue number must be positive');
674
+ const module = await import('./pull-requests.js');
675
+ const fetched = module.inspectGitHubIssueClosingChangeRequests(client, repository(context), number, context.workingDirectory);
676
+ if (!fetched.ok)
677
+ return fetched;
678
+ return { ok: true, value: fetched.value.map(changeRequestSnapshot) };
679
+ },
680
+ async create({ context, base, head, title, body, draft }) {
681
+ const response = client.json(['api', `repos/${repository(context)}/pulls`, '-X', 'POST', '--input', '-'], {
682
+ cwd: context.workingDirectory,
683
+ method: 'POST',
684
+ input: JSON.stringify({ title, body, head, base, draft })
685
+ });
686
+ if (!response.ok)
687
+ return response;
688
+ const number = Number(response.value?.number);
689
+ if (!Number.isInteger(number) || number <= 0)
690
+ return invalid('PR_CREATE_RESPONSE_INVALID', 'Pull request create response lacks number');
691
+ return createReceipt(String(number));
692
+ },
693
+ async update({ context, target, currentLabels, patch }) {
694
+ const number = resourceIdentityNumber(target);
695
+ if (!number)
696
+ return invalid('PR_NUMBER_INVALID', 'Pull request number must be positive');
697
+ const { labels, assignees, milestone, ...pullRequestPatch } = patch;
698
+ let changed = false;
699
+ if (labels !== undefined) {
700
+ let before = currentLabels;
701
+ if (!before) {
702
+ const current = client.json(['api', `repos/${repository(context)}/pulls/${number}`], { cwd: context.workingDirectory });
703
+ if (!current.ok)
704
+ return current;
705
+ before = Array.isArray(current.value?.labels)
706
+ ? current.value.labels.map((label) => typeof label === 'string' ? label : label?.name).filter((label) => typeof label === 'string')
707
+ : [];
708
+ }
709
+ const synced = syncLabels(client, repository(context), number, context.workingDirectory, before || [], labels, ['in:', 'type:']);
710
+ if (synced.status === 'failed' || synced.status === 'blocked')
711
+ return synced.error
712
+ ? { ok: false, error: synced.error }
713
+ : invalid('IN_LABEL_SYNC_FAILED', 'Pull request label synchronization failed');
714
+ changed ||= synced.changed;
715
+ }
716
+ if (Object.keys(pullRequestPatch).length > 0) {
717
+ const response = client.json(['api', `repos/${repository(context)}/pulls/${number}`, '-X', 'PATCH', '--input', '-'], {
718
+ cwd: context.workingDirectory,
719
+ method: 'PATCH',
720
+ input: JSON.stringify(pullRequestPatch)
721
+ });
722
+ if (!response.ok)
723
+ return response;
724
+ changed = true;
725
+ }
726
+ const issuePatch = {};
727
+ if (assignees !== undefined)
728
+ issuePatch.assignees = assignees;
729
+ if (milestone !== undefined) {
730
+ if (milestone === null)
731
+ issuePatch.milestone = null;
732
+ else {
733
+ const milestones = client.json(['api', '--paginate', '--slurp', `repos/${repository(context)}/milestones?state=open&per_page=100`], { cwd: context.workingDirectory });
734
+ if (!milestones.ok)
735
+ return milestones;
736
+ const values = Array.isArray(milestones.value) ? milestones.value.flatMap((entry) => Array.isArray(entry) ? entry : [entry]) : [];
737
+ const selected = values.find((entry) => entry?.title === milestone);
738
+ if (!Number.isSafeInteger(selected?.number) || selected.number <= 0)
739
+ return invalid('MILESTONE_IDENTITY_INVALID', 'Milestone title does not resolve to a positive number');
740
+ issuePatch.milestone = selected.number;
741
+ }
742
+ }
743
+ if (Object.keys(issuePatch).length > 0) {
744
+ const response = client.json(['api', `repos/${repository(context)}/issues/${number}`, '-X', 'PATCH', '--input', '-'], {
745
+ cwd: context.workingDirectory,
746
+ method: 'PATCH',
747
+ input: JSON.stringify(issuePatch)
748
+ });
749
+ if (!response.ok)
750
+ return response;
751
+ changed = true;
752
+ }
753
+ return { ok: true, value: { changed, remoteId: String(number) } };
754
+ },
755
+ async resolveGitEvidence({ context, target, expected }) {
756
+ const number = resourceIdentityNumber(target);
757
+ if (!number)
758
+ return invalid('PR_NUMBER_INVALID', 'Pull request number must be positive');
759
+ const module = await import('./github-provider.js');
760
+ return module.resolveGitHubChangeRequestGitEvidence({
761
+ cwd: context.workingDirectory,
762
+ repository: repository(context),
763
+ number,
764
+ baseRepository: repository(context),
765
+ baseRef: expected.targetBranch || 'main'
766
+ });
767
+ }
768
+ };
769
+ const checks = {
770
+ async inspectRequired({ context, changeRequest }) {
771
+ const number = resourceIdentityNumber(changeRequest);
772
+ if (!number)
773
+ return invalid('PR_NUMBER_INVALID', 'Pull request number must be positive');
774
+ const module = await import('./pr-checks.js');
775
+ const fetched = module.inspectGitHubRequiredChecks(client, repository(context), number, context.workingDirectory);
776
+ if (!fetched.ok)
777
+ return fetched;
778
+ return { ok: true, value: fetched.value.map((check) => ({ name: check.name, status: check.bucket, conclusion: check.conclusion, detailsUrl: check.detailsUrl })) };
779
+ },
780
+ async resolveRun({ context, changeRequest, checkName, detailsUrl }) {
781
+ const number = resourceIdentityNumber(changeRequest);
782
+ if (!number)
783
+ return invalid('PR_NUMBER_INVALID', 'Pull request number must be positive');
784
+ const module = await import('./pr-checks.js');
785
+ const direct = detailsUrl ? module.parseRunJobIdentity(detailsUrl) : null;
786
+ if (direct) {
787
+ const run = client.json(['api', `repos/${repository(context)}/actions/runs/${direct.runId}`], { cwd: context.workingDirectory });
788
+ if (!run.ok)
789
+ return run;
790
+ return { ok: true, value: { runId: String(direct.runId), jobId: direct.jobId ? String(direct.jobId) : undefined } };
791
+ }
792
+ const listed = client.json(['api', `repos/${repository(context)}/commits/${number}/check-runs`], { cwd: context.workingDirectory });
793
+ if (!listed.ok)
794
+ return listed;
795
+ const candidate = (listed.value?.check_runs || []).find((run) => run.name === checkName && run.id);
796
+ return candidate
797
+ ? { 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 } }
798
+ : invalid('CHECK_RUN_NOT_FOUND', 'Check run was not found');
799
+ },
800
+ async fetchLogs({ context, runId, jobId }) {
801
+ const module = await import('./pr-checks.js');
802
+ const args = jobId
803
+ ? ['api', `repos/${repository(context)}/actions/jobs/${jobId}/logs`]
804
+ : ['run', 'view', runId, '--repo', repository(context), '--log-failed'];
805
+ const fetched = module.fetchCheckLogText(client, args, context.workingDirectory);
806
+ if (!fetched.ok)
807
+ return fetched;
808
+ return { ok: true, value: { runId, jobId, text: fetched.value || '' } };
809
+ }
810
+ };
811
+ const reviews = {
812
+ async list({ context, changeRequest }) {
813
+ const number = resourceIdentityNumber(changeRequest);
814
+ if (!number)
815
+ return invalid('PR_NUMBER_INVALID', 'Pull request number must be positive');
816
+ const response = client.json(['api', '--paginate', '--slurp', `repos/${repository(context)}/pulls/${number}/reviews?per_page=100`], { cwd: context.workingDirectory });
817
+ if (!response.ok)
818
+ return response;
819
+ const values = Array.isArray(response.value) ? response.value.flatMap((entry) => Array.isArray(entry) ? entry : [entry]) : [];
820
+ return { ok: true, value: values.filter((review) => review?.body).map((review) => ({ 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 })) };
821
+ },
822
+ async publish({ context, changeRequest, identity, event, body }) {
823
+ const number = resourceIdentityNumber(changeRequest);
824
+ if (!number)
825
+ return invalid('PR_NUMBER_INVALID', 'Pull request number must be positive');
826
+ const response = client.json(['api', `repos/${repository(context)}/pulls/${number}/reviews`, '-X', 'POST', '--input', '-'], {
827
+ cwd: context.workingDirectory,
828
+ method: 'POST',
829
+ input: JSON.stringify({ commit_id: identity.commitSha, body, event })
830
+ });
831
+ if (!response.ok)
832
+ return response;
833
+ return createReceipt(String(response.value?.id || ''));
834
+ }
835
+ };
836
+ const releases = {
837
+ async inspect({ context, tag }) {
838
+ const response = client.json(['release', 'view', tag, '--repo', repository(context), '--json', 'tagName,isDraft,url'], { cwd: context.workingDirectory });
839
+ if (!response.ok)
840
+ return response;
841
+ const runs = client.json(['run', 'list', '--repo', repository(context), '--limit', '100', '--json', 'name,workflowName,displayTitle,event,headBranch,headSha,status,conclusion,createdAt,databaseId,attempt,url'], { cwd: context.workingDirectory });
842
+ if (!runs.ok)
843
+ return runs;
844
+ const value = { 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 : [] };
845
+ return { ok: true, value };
846
+ },
847
+ async create({ context, tag, title, notes }) {
848
+ const args = ['release', 'create', tag, '--repo', repository(context), '--title', title];
849
+ if (notes)
850
+ args.push('--notes', notes.text);
851
+ const response = client.text(args, { cwd: context.workingDirectory, method: 'POST' });
852
+ if (!response.ok)
853
+ return response;
854
+ return createReceipt(tag);
855
+ },
856
+ async update({ context, release, patch }) {
857
+ const tag = resourceIdentityString(release) || '';
858
+ const args = ['release', 'edit', tag, '--repo', repository(context)];
859
+ if (patch.title)
860
+ args.push('--title', patch.title);
861
+ if (patch.body)
862
+ args.push('--notes', patch.body);
863
+ const response = client.text(args, { cwd: context.workingDirectory, method: 'PATCH' });
864
+ if (!response.ok)
865
+ return response;
866
+ return createReceipt(tag);
867
+ },
868
+ async reconcileMilestones({ context, desired }) {
869
+ const listed = client.json(['api', '--paginate', '--slurp', `repos/${repository(context)}/milestones?state=all&per_page=100`], { cwd: context.workingDirectory });
870
+ if (!listed.ok)
871
+ return listed;
872
+ const values = Array.isArray(listed.value) ? listed.value.flatMap((entry) => Array.isArray(entry) ? entry : [entry]) : [];
873
+ const created = [];
874
+ const closed = [];
875
+ for (const item of desired) {
876
+ const current = values.find((candidate) => candidate.title === item.title);
877
+ if (current) {
878
+ if (item.state === 'closed' && current.state !== 'closed') {
879
+ const response = client.json(['api', '--method', 'PATCH', `repos/${repository(context)}/milestones/${current.number}`, '-f', 'state=closed'], { cwd: context.workingDirectory });
880
+ if (!response.ok)
881
+ return response;
882
+ closed.push(item.title);
883
+ }
884
+ continue;
885
+ }
886
+ const response = client.json(['api', '--method', 'POST', `repos/${repository(context)}/milestones`, '-f', `title=${item.title}`, '-f', `description=${item.description}`], { cwd: context.workingDirectory });
887
+ if (!response.ok)
888
+ return response;
889
+ created.push(item.title);
890
+ }
891
+ return { ok: true, value: { changed: created.length > 0 || closed.length > 0, created, closed } };
892
+ },
893
+ async publishNotes({ context, release, title, notes }) {
894
+ const module = await import('./github-release-notes.js');
895
+ const temporaryRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-infra-release-notes-'));
896
+ const notesFile = path.join(temporaryRoot, 'notes.md');
897
+ fs.writeFileSync(notesFile, notes.text);
898
+ try {
899
+ const tag = resourceIdentityString(release) || '';
900
+ const result = module.publishGitHubReleaseNotes({ repository: repository(context), tag, title, notesFile }, { cwd: context.workingDirectory, client });
901
+ if (result.status === 'failed' || result.status === 'blocked')
902
+ return { ok: false, error: result.error || { code: 'RELEASE_NOTES_PUBLISH_FAILED', message: 'Release notes publish failed', retryable: false } };
903
+ return { ok: true, value: { changed: result.changed, remoteId: tag } };
904
+ }
905
+ finally {
906
+ fs.rmSync(temporaryRoot, { recursive: true, force: true });
907
+ }
908
+ },
909
+ async collectNotes({ context, fromTime, toTime, commitOids, branch, historyLimit }) {
910
+ const module = await import('./github-release-notes.js');
911
+ const collected = module.fetchGitHubReleaseNoteData({
912
+ repository: repository(context), commitOids, branch, historyLimit, fromTime, toTime
913
+ }, { cwd: context.workingDirectory, client });
914
+ if (collected.status === 'failed' || collected.status === 'blocked' || !('pullRequests' in collected)) {
915
+ return { ok: false, error: collected.error || { code: 'RELEASE_NOTES_COLLECTION_FAILED', message: 'Release notes could not be collected', retryable: false } };
916
+ }
917
+ const mergedPullRequests = collected.pullRequests.map((item) => ({
918
+ id: String(item.number), identity: { kind: 'number', value: Number(item.number) }, number: Number(item.number),
919
+ state: 'closed', title: String(item.title || ''), body: String(item.body || ''),
920
+ mergedAt: new Date(String(item.mergedAt)).toISOString(), displayUrl: String(item.url || ''),
921
+ labels: Array.isArray(item.labels) ? item.labels.map((label) => String(label.name || label)) : [],
922
+ assignees: [], mergeCommitSha: null
923
+ }));
924
+ const closingIssues = collected.pullRequests.flatMap((item) => Array.isArray(item.closingIssuesReferences) ? item.closingIssuesReferences.map((issue) => ({
925
+ id: String(issue.number), identity: { kind: 'number', value: Number(issue.number) }, number: Number(issue.number),
926
+ state: 'closed', title: String(issue.title || ''), body: '', labels: Array.isArray(issue.labels) ? issue.labels.map((label) => String(label.name || label)) : [],
927
+ assignees: [], milestone: null, fields: {}, displayUrl: String(issue.url || '')
928
+ })) : []);
929
+ const actors = [...collected.authors.values()].flatMap((items) => items.map((item) => ({
930
+ ...(item.login ? { id: String(item.login) } : {}), ...(item.name ? { name: String(item.name) } : {})
931
+ })));
932
+ return {
933
+ ok: true,
934
+ value: {
935
+ history: commitOids.map((sha) => ({ sha, message: '', authoredAt: toTime, author: actors.find((actor) => actor.id) || null })),
936
+ mergedPullRequests,
937
+ closingIssues,
938
+ actors
939
+ }
940
+ };
941
+ }
942
+ };
943
+ const securityAlerts = {
944
+ async inspect({ context, kind, number }) {
945
+ if (!Number.isSafeInteger(number) || number <= 0)
946
+ return invalid('SECURITY_NUMBER_INVALID', 'Alert number must be a positive integer');
947
+ const suffix = kind === 'dependabot' ? 'dependabot' : kind === 'code-scanning' ? 'code-scanning' : null;
948
+ if (!suffix)
949
+ return invalid('SECURITY_KIND_INVALID', 'Alert kind is invalid');
950
+ const response = client.json(['api', `repos/${repository(context)}/${suffix}/alerts/${number}`], { cwd: context.workingDirectory });
951
+ if (!response.ok)
952
+ return response;
953
+ const data = response.value;
954
+ if (!data || typeof data !== 'object' || Array.isArray(data))
955
+ return invalid('SECURITY_RESPONSE_INVALID', 'The platform returned an invalid security alert');
956
+ const value = {
957
+ kind,
958
+ number,
959
+ state: typeof data.state === 'string' ? data.state : 'invalid',
960
+ data
961
+ };
962
+ return { ok: true, value };
963
+ },
964
+ async dismiss({ context, kind, number, reason, comment }) {
965
+ const suffix = kind === 'dependabot' ? 'dependabot' : kind === 'code-scanning' ? 'code-scanning' : null;
966
+ if (!suffix)
967
+ return invalid('SECURITY_KIND_INVALID', 'Alert kind is invalid');
968
+ if (!Number.isSafeInteger(number) || number <= 0)
969
+ return invalid('SECURITY_NUMBER_INVALID', 'Alert number must be a positive integer');
970
+ const response = client.json(['api', `repos/${repository(context)}/${suffix}/alerts/${number}`, '-X', 'PATCH', '--input', '-'], {
971
+ cwd: context.workingDirectory,
972
+ method: 'PATCH',
973
+ input: JSON.stringify({ state: 'dismissed', dismissed_reason: reason, dismissed_comment: comment })
974
+ });
975
+ if (!response.ok)
976
+ return response;
977
+ return createReceipt(String(response.value?.id || `${kind}:${number}`));
978
+ }
979
+ };
980
+ const repositoryMetadata = {
981
+ async reconcileLabels({ context, desired, cleanupStaleIn }) {
982
+ const endpoint = `repos/${repository(context)}/labels?per_page=100`;
983
+ const listed = client.json(['api', '--paginate', '--slurp', endpoint], { cwd: context.workingDirectory });
984
+ if (!listed.ok)
985
+ return listed;
986
+ if (!Array.isArray(listed.value))
987
+ return invalid('LABEL_RESPONSE_INVALID', 'The platform returned an invalid label list');
988
+ const values = Array.isArray(listed.value)
989
+ ? listed.value.flatMap((entry) => Array.isArray(entry) ? entry : [entry])
990
+ : [];
991
+ if (values.some((entry) => !entry || typeof entry.name !== 'string' || !entry.name)) {
992
+ return invalid('LABEL_RESPONSE_INVALID', 'The platform returned a label without a valid name');
993
+ }
994
+ const current = new Map(values.map((entry) => [String(entry.name), entry]));
995
+ const created = [];
996
+ const updated = [];
997
+ const skipped = [];
998
+ const removed = [];
999
+ for (const label of desired) {
1000
+ const existing = current.get(label.name);
1001
+ if (!existing) {
1002
+ const response = client.json(['api', `repos/${repository(context)}/labels`, '-X', 'POST', '--input', '-'], {
1003
+ cwd: context.workingDirectory,
1004
+ method: 'POST',
1005
+ input: JSON.stringify({ name: label.name, color: label.color, description: label.description })
1006
+ });
1007
+ if (!response.ok)
1008
+ return partialFailure(response, labelReconciliation(created, updated, removed, skipped));
1009
+ created.push(label.name);
1010
+ continue;
1011
+ }
1012
+ const sameColor = typeof existing.color === 'string' && existing.color.toUpperCase() === label.color.toUpperCase();
1013
+ const sameDescription = String(existing.description || '') === label.description;
1014
+ if (sameColor && sameDescription) {
1015
+ skipped.push(label.name);
1016
+ continue;
1017
+ }
1018
+ const response = client.json(['api', `repos/${repository(context)}/labels/${encodeURIComponent(label.name)}`, '-X', 'PATCH', '--input', '-'], {
1019
+ cwd: context.workingDirectory,
1020
+ method: 'PATCH',
1021
+ input: JSON.stringify({ new_name: label.name, color: label.color, description: label.description })
1022
+ });
1023
+ if (!response.ok)
1024
+ return partialFailure(response, labelReconciliation(created, updated, removed, skipped));
1025
+ updated.push(label.name);
1026
+ }
1027
+ const desiredNames = new Set(desired.map((label) => label.name));
1028
+ if (cleanupStaleIn) {
1029
+ for (const name of current.keys()) {
1030
+ if (!name.startsWith('in:') || desiredNames.has(name))
1031
+ continue;
1032
+ const response = client.text(['api', `repos/${repository(context)}/labels/${encodeURIComponent(name)}`, '-X', 'DELETE'], {
1033
+ cwd: context.workingDirectory,
1034
+ method: 'DELETE'
1035
+ });
1036
+ if (!response.ok)
1037
+ return partialFailure(response, labelReconciliation(created, updated, removed, skipped));
1038
+ removed.push(name);
1039
+ }
1040
+ }
1041
+ return {
1042
+ ok: true,
1043
+ value: {
1044
+ changed: created.length > 0 || updated.length > 0 || removed.length > 0,
1045
+ created,
1046
+ updated,
1047
+ removed,
1048
+ skipped
1049
+ }
1050
+ };
1051
+ },
1052
+ async reconcileMilestones({ context, desired }) {
1053
+ const listed = client.json(['api', '--paginate', '--slurp', `repos/${repository(context)}/milestones?state=all&per_page=100`], { cwd: context.workingDirectory });
1054
+ if (!listed.ok)
1055
+ return listed;
1056
+ if (!Array.isArray(listed.value))
1057
+ return invalid('MILESTONE_RESPONSE_INVALID', 'The platform returned an invalid milestone list');
1058
+ const values = Array.isArray(listed.value)
1059
+ ? listed.value.flatMap((entry) => Array.isArray(entry) ? entry : [entry])
1060
+ : [];
1061
+ if (values.some((entry) => !entry || typeof entry.title !== 'string' || !entry.title)) {
1062
+ return invalid('MILESTONE_RESPONSE_INVALID', 'The platform returned a milestone without a valid title');
1063
+ }
1064
+ const existing = new Set(values.map((entry) => String(entry.title)));
1065
+ const created = [];
1066
+ const skipped = [];
1067
+ for (const milestone of desired) {
1068
+ if (existing.has(milestone.title)) {
1069
+ skipped.push(milestone.title);
1070
+ continue;
1071
+ }
1072
+ const response = client.json(['api', `repos/${repository(context)}/milestones`, '-X', 'POST', '--input', '-'], {
1073
+ cwd: context.workingDirectory,
1074
+ method: 'POST',
1075
+ input: JSON.stringify({ title: milestone.title, description: milestone.description, state: milestone.state })
1076
+ });
1077
+ if (!response.ok)
1078
+ return partialFailure(response, milestoneInitialization(created, skipped));
1079
+ existing.add(milestone.title);
1080
+ created.push(milestone.title);
1081
+ }
1082
+ return { ok: true, value: { changed: created.length > 0, created, skipped } };
1083
+ }
1084
+ };
1085
+ const verification = {
1086
+ async fetchRemoteFacts(input) {
1087
+ const issue = input.issue && issues.inspect
1088
+ ? await issues.inspect({ context: input.context, target: input.issue })
1089
+ : { ok: true, value: null };
1090
+ if (!issue.ok)
1091
+ return { ok: false, error: issue.error };
1092
+ const commentFacts = input.includeComments && input.issue && comments.list
1093
+ ? await comments.list({ context: input.context, parent: input.issue })
1094
+ : { ok: true, value: [] };
1095
+ if (!commentFacts.ok)
1096
+ return { ok: false, error: commentFacts.error };
1097
+ const changeRequest = input.changeRequest && changeRequests.inspect
1098
+ ? await changeRequests.inspect({ context: input.context, target: input.changeRequest })
1099
+ : { ok: true, value: null };
1100
+ if (!changeRequest.ok)
1101
+ return { ok: false, error: changeRequest.error };
1102
+ return {
1103
+ ok: true,
1104
+ value: {
1105
+ issue: issue.value,
1106
+ comments: commentFacts.value,
1107
+ changeRequest: changeRequest.value,
1108
+ commit: null,
1109
+ fields: input.includeFields ? issue.value?.fields || {} : {}
1110
+ }
1111
+ };
1112
+ }
1113
+ };
1114
+ return { issues, comments, changeRequests, checks, reviews, releases, securityAlerts, repositoryMetadata, verification };
1115
+ }
1116
+ function createGitHubProvider(input, client = createGitHubClient()) {
1117
+ return {
1118
+ type: input.providerType,
1119
+ contractVersion: 1,
1120
+ identity: { issue: 'number', 'pull-request': 'number', comment: 'number', release: 'key' },
1121
+ ...createGitHubOperations(client),
1122
+ context: {
1123
+ async resolve(contextInput) {
1124
+ return resolveContext(client, contextInput);
1125
+ }
1126
+ }
1127
+ };
1128
+ }
1129
+ export { configuredGitRemotes, createGitHubProvider, defaultGitRemote, parseGitHubRemote, resolveGitHubChangeRequestGitEvidence };
1130
+ //# sourceMappingURL=github-provider.js.map