@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,143 @@
1
+ import { computeInLabels } from './metadata-labels.js';
2
+ function inLabels(labels) {
3
+ return [...new Set(labels.filter((label) => label.startsWith('in:')))].sort();
4
+ }
5
+ function mergeInLabels(labels, target) {
6
+ return [...new Set([
7
+ ...labels.filter((label) => !label.startsWith('in:')),
8
+ ...target
9
+ ])].sort();
10
+ }
11
+ function invalidInLabelInput(code, message) {
12
+ return { ok: false, error: { code, message, retryable: false } };
13
+ }
14
+ function validateInLabelMapping(value) {
15
+ if (value === undefined)
16
+ return { ok: true, value: {} };
17
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
18
+ return invalidInLabelInput('IN_LABEL_SYNC_MAPPING_INVALID', 'labels.in must be an object of non-empty string arrays');
19
+ }
20
+ const normalized = {};
21
+ for (const [name, rawPrefixes] of Object.entries(value)) {
22
+ if (!name.trim() || !Array.isArray(rawPrefixes) || rawPrefixes.length === 0 || rawPrefixes.some((prefix) => typeof prefix !== 'string' || !prefix.trim())) {
23
+ return invalidInLabelInput('IN_LABEL_SYNC_MAPPING_INVALID', `labels.in entry '${name}' must be a non-empty string array`);
24
+ }
25
+ normalized[name] = rawPrefixes.map((prefix) => prefix.trim());
26
+ }
27
+ return { ok: true, value: normalized };
28
+ }
29
+ function repositoryLabelPages(value) {
30
+ if (!Array.isArray(value))
31
+ return null;
32
+ if (value.length === 0)
33
+ return [];
34
+ if (value.every((page) => Array.isArray(page)))
35
+ return value;
36
+ if (value.every((entry) => entry && typeof entry === 'object' && !Array.isArray(entry)))
37
+ return [value];
38
+ return null;
39
+ }
40
+ function validateRepositoryLabelPayload(value) {
41
+ const pages = repositoryLabelPages(value);
42
+ if (!pages)
43
+ return invalidInLabelInput('IN_LABEL_SYNC_LABELS_INVALID', 'Repository labels response must be an array of pages');
44
+ const names = [];
45
+ for (const page of pages) {
46
+ for (const entry of page) {
47
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry) || typeof entry.name !== 'string' || !entry.name.trim()) {
48
+ return invalidInLabelInput('IN_LABEL_SYNC_LABELS_INVALID', 'Repository labels response contains an entry without a valid name');
49
+ }
50
+ names.push(entry.name);
51
+ }
52
+ }
53
+ return { ok: true, value: [...new Set(names)].sort() };
54
+ }
55
+ function validateRepositoryLabelSet(value) {
56
+ if (!(value instanceof Set) || [...value].some((label) => typeof label !== 'string' || !label.trim())) {
57
+ return invalidInLabelInput('IN_LABEL_SYNC_LABELS_INVALID', 'Repository labels must be a set of non-empty names');
58
+ }
59
+ return { ok: true, value: new Set([...value]) };
60
+ }
61
+ function planInLabelUpdate(input) {
62
+ const current = inLabels(input.currentLabels);
63
+ const mapping = validateInLabelMapping(input.mapping);
64
+ const repositoryLabels = validateRepositoryLabelSet(input.repositoryLabels);
65
+ if (!mapping.ok)
66
+ return { current, target: [...current], labels: [...input.currentLabels].sort(), changed: false, error: mapping.error };
67
+ if (!repositoryLabels.ok)
68
+ return { current, target: [...current], labels: [...input.currentLabels].sort(), changed: false, error: repositoryLabels.error };
69
+ const target = computeInLabels([...input.changedFiles], mapping.value, repositoryLabels.value);
70
+ const labels = mergeInLabels(input.currentLabels, target);
71
+ return { current, target, labels, changed: labels.join('\0') !== [...input.currentLabels].sort().join('\0'), error: null };
72
+ }
73
+ function startsWithPrefix(label, prefix) {
74
+ return (Array.isArray(prefix) ? prefix : [prefix]).some((value) => label.startsWith(value));
75
+ }
76
+ function labelDelta(current, target, prefix = 'in:') {
77
+ const currentLabels = [...new Set(current)].sort();
78
+ const targetLabels = [...new Set(target.filter((label) => startsWithPrefix(label, prefix)))].sort();
79
+ return {
80
+ add: targetLabels.filter((label) => !currentLabels.includes(label)),
81
+ remove: currentLabels.filter((label) => startsWithPrefix(label, prefix) && !targetLabels.includes(label))
82
+ };
83
+ }
84
+ function syncLabelDelta(client, repository, number, cwd, current, target, prefix = 'in:') {
85
+ const delta = labelDelta(current, target, prefix);
86
+ let changed = false;
87
+ const failure = (error) => {
88
+ if (changed || error.retryable) {
89
+ return {
90
+ status: 'blocked',
91
+ changed,
92
+ error: { ...error, code: 'IN_LABEL_SYNC_PARTIAL', message: `In-label synchronization is partial or unknown: ${error.message}` }
93
+ };
94
+ }
95
+ return { status: 'failed', changed: false, error };
96
+ };
97
+ for (const label of delta.remove) {
98
+ const removed = client.json([
99
+ 'api', `repos/${repository}/issues/${number}/labels/${encodeURIComponent(label)}`, '-X', 'DELETE'
100
+ ], { cwd, method: 'DELETE' });
101
+ if (!removed.ok)
102
+ return failure(removed.error);
103
+ changed = true;
104
+ }
105
+ for (const label of delta.add) {
106
+ const added = client.json([
107
+ 'api', `repos/${repository}/issues/${number}/labels`, '-X', 'POST', '--input', '-'
108
+ ], { cwd, method: 'POST', input: JSON.stringify({ labels: [label] }) });
109
+ if (!added.ok)
110
+ return failure(added.error);
111
+ changed = true;
112
+ }
113
+ return { status: changed ? 'applied' : 'no-op', changed, error: null };
114
+ }
115
+ function flattenPages(value) {
116
+ if (!Array.isArray(value))
117
+ return [];
118
+ return value.flatMap((page) => Array.isArray(page) ? page : [page]);
119
+ }
120
+ function extractRepositoryLabelNames(value) {
121
+ return flattenPages(value).flatMap((entry) => {
122
+ if (typeof entry === 'string')
123
+ return [entry];
124
+ if (entry && typeof entry === 'object' && typeof entry.name === 'string') {
125
+ return [entry.name];
126
+ }
127
+ return [];
128
+ }).filter(Boolean).sort();
129
+ }
130
+ function extractPullRequestFileNames(value) {
131
+ const entries = flattenPages(value);
132
+ if (!Array.isArray(value) || entries.some((entry) => !entry || typeof entry !== 'object'))
133
+ return null;
134
+ const names = entries.map((entry) => entry.filename);
135
+ if (names.some((name) => typeof name !== 'string' || !name.trim()))
136
+ return null;
137
+ return [...new Set(names)].sort();
138
+ }
139
+ function resourceSnapshot(kind, number, labels) {
140
+ return { kind, number, labels: [...labels].sort() };
141
+ }
142
+ export { extractPullRequestFileNames, extractRepositoryLabelNames, flattenPages, inLabels, labelDelta, mergeInLabels, planInLabelUpdate, resourceSnapshot, syncLabelDelta, validateInLabelMapping, validateRepositoryLabelPayload };
143
+ //# sourceMappingURL=in-label-sync.js.map
@@ -2,9 +2,15 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { parseTaskFrontmatter } from '../task/frontmatter.js';
4
4
  import { resolveTaskRef } from '../task/resolve-ref.js';
5
- import { createGitHubClient } from './github-client.js';
6
- import { resolvePlatformContext } from './context.js';
5
+ import { resolvePlatformProviderContext } from './context.js';
7
6
  import { platformResult } from './types.js';
7
+ import { providerError, providerOperationContext, providerStatus, providerResourceToken, unsupportedProviderOperation } from './provider-bridge.js';
8
+ import { resourceIdentityNumber } from './resource-identity.js';
9
+ import { taskIssueIdentity, taskIssueIdentityError } from './task-identities.js';
10
+ import { CONTROL_MARKER_PATTERN, escapeHtmlText, fenceRanges, renderSafeCodeFence, sanitizeMarkdownDocument } from './comment-safety.js';
11
+ function providerCommentId(id, provider) {
12
+ return provider.identity?.comment === 'number' && /^\d+$/.test(id) ? Number(id) : id;
13
+ }
8
14
  const MARKERS = {
9
15
  task: (taskId) => `<!-- sync-issue:${taskId}:task -->`,
10
16
  artifact: (taskId, stem) => `<!-- sync-issue:${taskId}:${stem} -->`,
@@ -32,14 +38,21 @@ function splitFrontmatter(content) {
32
38
  return { frontmatter: null, body: content };
33
39
  return { frontmatter: match[1], body: content.slice(match[0].length).replace(/^\r?\n/, '') };
34
40
  }
41
+ function sanitizeCommentBody(value, label) {
42
+ const result = sanitizeMarkdownDocument(value, { reservedMarkers: [CONTROL_MARKER_PATTERN] });
43
+ if (!result.ok)
44
+ throw new Error(`${result.error.code}: ${label}: ${result.error.message} at offset ${result.error.offset}`);
45
+ return result.value;
46
+ }
35
47
  function footer(agent, taskId) {
36
48
  return `---\n*由 ${agent} 自动生成 · 内部追踪:${taskId}*`;
37
49
  }
38
50
  function renderTaskComment(content, taskId, agent) {
39
51
  const split = splitFrontmatter(content);
52
+ const safeBody = sanitizeCommentBody(split.body, 'task body');
40
53
  const taskBody = split.frontmatter === null
41
- ? split.body
42
- : `<details><summary>元数据 (frontmatter)</summary>\n\n\`\`\`yaml\n---\n${split.frontmatter}\n---\n\`\`\`\n\n</details>\n\n${split.body}`;
54
+ ? safeBody
55
+ : `<details><summary>元数据 (frontmatter)</summary>\n\n${renderSafeCodeFence(`---\n${split.frontmatter}\n---`, 'yaml')}\n\n</details>\n\n${safeBody}`;
43
56
  return normalizeCommentContent([
44
57
  MARKERS.task(taskId),
45
58
  '## 任务文件',
@@ -64,10 +77,11 @@ function chunkByUtf8(content, maxBytes) {
64
77
  if (maxBytes <= 0)
65
78
  throw new Error('comment byte limit is too small');
66
79
  const chunks = [];
67
- let remaining = content;
68
- while (remaining) {
80
+ let start = 0;
81
+ while (start < content.length) {
82
+ const remaining = content.slice(start);
69
83
  if (Buffer.byteLength(remaining, 'utf8') <= maxBytes) {
70
- chunks.push(remaining);
84
+ chunks.push({ content: remaining, start, end: content.length });
71
85
  break;
72
86
  }
73
87
  let bytes = 0;
@@ -85,12 +99,109 @@ function chunkByUtf8(content, maxBytes) {
85
99
  const cut = newlineIndex > 0 ? newlineIndex : index;
86
100
  if (cut === 0)
87
101
  throw new Error('comment byte limit cannot fit one Unicode code point');
88
- chunks.push(remaining.slice(0, cut));
89
- remaining = remaining.slice(cut);
102
+ chunks.push({ content: remaining.slice(0, cut), start, end: start + cut });
103
+ start += cut;
104
+ }
105
+ return chunks.length > 0 ? chunks : [{ content: '', start: 0, end: 0 }];
106
+ }
107
+ function longestFenceLineRun(content, character) {
108
+ const pattern = new RegExp(`^ {0,3}(${character}+)[ \\t]*(?:\\n|$)`, 'gm');
109
+ let longest = 0;
110
+ for (const match of content.matchAll(pattern))
111
+ longest = Math.max(longest, match[1].length);
112
+ return longest;
113
+ }
114
+ function boundedFence(content, sourceCharacter, maxBytes) {
115
+ const candidates = sourceCharacter === '`' ? ['~', '`'] : ['`', '~'];
116
+ for (const character of candidates) {
117
+ const length = Math.max(3, longestFenceLineRun(content, character) + 1);
118
+ const delimiter = character.repeat(length);
119
+ if (Buffer.byteLength(delimiter, 'utf8') * 2 + 2 <= maxBytes)
120
+ return { opening: `${delimiter}\n`, closing: `${delimiter}\n` };
121
+ }
122
+ return null;
123
+ }
124
+ function sourceSlice(piece, start, end) {
125
+ return piece.content.slice(Math.max(0, start - piece.start), Math.max(0, end - piece.start));
126
+ }
127
+ function renderOversizedFenceChunk(piece, fences, maxBytes) {
128
+ const insertions = new Map();
129
+ const omitted = [];
130
+ const insert = (position, value, ensureLineStart = false) => {
131
+ const values = insertions.get(position) || [];
132
+ values.push({ value, ensureLineStart });
133
+ insertions.set(position, values);
134
+ };
135
+ for (const fence of fences) {
136
+ if (piece.end <= fence.start || piece.start >= fence.end)
137
+ continue;
138
+ const oversized = Buffer.byteLength(fence.opening, 'utf8') > maxBytes
139
+ || Buffer.byteLength(fence.closing, 'utf8') > maxBytes;
140
+ if (!oversized) {
141
+ if (piece.start > fence.start && piece.start < fence.end)
142
+ insert(piece.start, fence.opening);
143
+ if (piece.end > fence.start && piece.end < fence.end)
144
+ insert(piece.end, fence.closing, true);
145
+ continue;
146
+ }
147
+ const code = sourceSlice(piece, Math.max(piece.start, fence.openingEnd), Math.min(piece.end, fence.closingStart));
148
+ const delimiter = boundedFence(code, fence.character, maxBytes);
149
+ if (!delimiter)
150
+ return escapeHtmlText(piece.content);
151
+ insert(Math.max(piece.start, fence.start), delimiter.opening);
152
+ insert(Math.min(piece.end, fence.end), delimiter.closing, true);
153
+ const openingStart = Math.max(piece.start, fence.start);
154
+ const openingEnd = Math.min(piece.end, fence.openingEnd);
155
+ if (openingStart < openingEnd)
156
+ omitted.push({ start: openingStart, end: openingEnd });
157
+ const closingStart = Math.max(piece.start, fence.closingStart);
158
+ const closingEnd = Math.min(piece.end, fence.end);
159
+ if (closingStart < closingEnd)
160
+ omitted.push({ start: closingStart, end: closingEnd });
161
+ }
162
+ const boundaries = new Set([piece.start, piece.end]);
163
+ for (const position of insertions.keys())
164
+ boundaries.add(position);
165
+ for (const range of omitted) {
166
+ boundaries.add(range.start);
167
+ boundaries.add(range.end);
90
168
  }
91
- return chunks.length > 0 ? chunks : [''];
169
+ const points = [...boundaries].sort((left, right) => left - right);
170
+ let content = '';
171
+ for (let index = 0; index < points.length; index += 1) {
172
+ const point = points[index];
173
+ for (const insertion of insertions.get(point) || []) {
174
+ if (insertion.ensureLineStart && content.length > 0 && !content.endsWith('\n'))
175
+ content += '\n';
176
+ content += insertion.value;
177
+ }
178
+ const next = points[index + 1];
179
+ if (next === undefined)
180
+ continue;
181
+ const skipped = omitted.some((range) => range.start <= point && next <= range.end);
182
+ if (!skipped)
183
+ content += sourceSlice(piece, point, next);
184
+ }
185
+ return content;
92
186
  }
93
- function buildArtifactChunk(taskId, stem, title, agent, content, part, total, chunked, backfill) {
187
+ function independentChunkContent(piece, fences, maxBytes) {
188
+ const oversizedFence = fences.some((fence) => piece.end > fence.start && piece.start < fence.end
189
+ && (Buffer.byteLength(fence.opening, 'utf8') > maxBytes || Buffer.byteLength(fence.closing, 'utf8') > maxBytes));
190
+ if (oversizedFence)
191
+ return renderOversizedFenceChunk(piece, fences, maxBytes);
192
+ const openingFence = fences.find((fence) => piece.start > fence.start && piece.start < fence.end);
193
+ const closingFence = fences.find((fence) => piece.end > fence.start && piece.end < fence.end);
194
+ let content = piece.content;
195
+ if (openingFence)
196
+ content = openingFence.opening + content;
197
+ if (closingFence) {
198
+ if (!content.endsWith('\n'))
199
+ content += '\n';
200
+ content += closingFence.closing;
201
+ }
202
+ return content;
203
+ }
204
+ function buildArtifactChunk(taskId, stem, title, agent, content, part, total, chunked, backfill, renderedContent = content) {
94
205
  const marker = chunked ? MARKERS.artifactChunk(taskId, stem, part, total) : MARKERS.artifact(taskId, stem);
95
206
  const heading = chunked ? `## ${title}(${part}/${total})` : `## ${title}`;
96
207
  const timelineHint = backfill ? '> 历史产物补发' : null;
@@ -101,7 +212,7 @@ function buildArtifactChunk(taskId, stem, title, agent, content, part, total, ch
101
212
  '',
102
213
  `> **${agent}** · ${taskId}`,
103
214
  '',
104
- content,
215
+ renderedContent,
105
216
  '',
106
217
  footer(agent, taskId)
107
218
  ].join('\n'));
@@ -110,25 +221,32 @@ function buildArtifactChunk(taskId, stem, title, agent, content, part, total, ch
110
221
  function chunkArtifactComment(input) {
111
222
  const byteLimit = input.byteLimit || 60_000;
112
223
  const identity = artifactIdentity(input.artifact);
113
- const single = buildArtifactChunk(input.taskId, identity.stem, identity.title, input.agent, input.body, 1, 1, false, Boolean(input.backfill));
224
+ const safeBody = sanitizeCommentBody(input.body, 'artifact body');
225
+ const single = buildArtifactChunk(input.taskId, identity.stem, identity.title, input.agent, safeBody, 1, 1, false, Boolean(input.backfill));
114
226
  if (Buffer.byteLength(single.body, 'utf8') <= byteLimit)
115
227
  return [single];
116
228
  let total = 2;
117
- let pieces = [];
229
+ let payloadLimit = Number.POSITIVE_INFINITY;
230
+ const ranges = fenceRanges(safeBody);
231
+ if (!ranges.ok)
232
+ throw new Error(`${ranges.error.code}: artifact body: ${ranges.error.message} at offset ${ranges.error.offset}`);
118
233
  for (;;) {
119
234
  const probe = buildArtifactChunk(input.taskId, identity.stem, identity.title, input.agent, '', total, total, true, Boolean(input.backfill));
120
235
  const available = byteLimit - Buffer.byteLength(probe.body, 'utf8');
121
- pieces = chunkByUtf8(input.body, available);
122
- if (pieces.length === total)
123
- break;
124
- total = pieces.length;
236
+ const sourceLimit = Math.min(available, payloadLimit);
237
+ if (sourceLimit <= 0)
238
+ throw new Error('comment byte limit is too small for an artifact chunk');
239
+ const pieces = chunkByUtf8(safeBody, sourceLimit);
240
+ if (pieces.length !== total) {
241
+ total = pieces.length;
242
+ continue;
243
+ }
244
+ const chunks = pieces.map((piece, index) => buildArtifactChunk(input.taskId, identity.stem, identity.title, input.agent, piece.content, index + 1, total, true, Boolean(input.backfill), independentChunkContent(piece, ranges.value, sourceLimit)));
245
+ const overflow = Math.max(...chunks.map((chunk) => Buffer.byteLength(chunk.body, 'utf8') - byteLimit));
246
+ if (overflow <= 0)
247
+ return chunks;
248
+ payloadLimit = sourceLimit - overflow;
125
249
  }
126
- return pieces.map((content, index) => {
127
- const chunk = buildArtifactChunk(input.taskId, identity.stem, identity.title, input.agent, content, index + 1, total, true, Boolean(input.backfill));
128
- if (Buffer.byteLength(chunk.body, 'utf8') > byteLimit)
129
- throw new Error('rendered comment exceeds byte limit');
130
- return chunk;
131
- });
132
250
  }
133
251
  function findMarkerComments(comments, marker) {
134
252
  return comments.filter((comment) => normalizeCommentContent(String(comment.body || '')).split('\n', 1)[0] === marker);
@@ -137,7 +255,7 @@ function flattenComments(value) {
137
255
  if (!Array.isArray(value))
138
256
  return [];
139
257
  const flattened = value.flatMap((entry) => Array.isArray(entry) ? entry : [entry]);
140
- return flattened.filter((entry) => Boolean(entry && typeof entry === 'object' && typeof entry.id === 'number' && typeof entry.body === 'string'));
258
+ return flattened.filter((entry) => Boolean(entry && typeof entry === 'object' && (typeof entry.id === 'number' || typeof entry.id === 'string') && typeof entry.body === 'string'));
141
259
  }
142
260
  function issueNumberFromTask(content) {
143
261
  const value = parseTaskFrontmatter(content).issue_number;
@@ -161,7 +279,7 @@ function hasResolvedPlatformContext(context) {
161
279
  }
162
280
  function bodyEnvelope(marker, title, taskId, agent, body) {
163
281
  return normalizeCommentContent([
164
- marker, `## ${title}`, '', `> **${agent}** · ${taskId}`, '', body.replace(/\n+$/, ''), '', footer(agent, taskId)
282
+ marker, `## ${title}`, '', `> **${agent}** · ${taskId}`, '', sanitizeCommentBody(body, `${title} body`).replace(/\n+$/, ''), '', footer(agent, taskId)
165
283
  ].join('\n'));
166
284
  }
167
285
  function resolveArtifactBody(taskDir, artifact) {
@@ -246,7 +364,7 @@ function writeComment(client, repo, issue, cwd, body, id) {
246
364
  const endpoint = id ? `repos/${repo}/issues/comments/${id}` : `repos/${repo}/issues/${issue}/comments`;
247
365
  return client.json(['api', endpoint, '-X', id ? 'PATCH' : 'POST', '--input', '-'], { cwd, method: id ? 'PATCH' : 'POST', input: JSON.stringify({ body }) });
248
366
  }
249
- function syncPlatformComment(taskRef, options) {
367
+ async function syncPlatformComment(taskRef, options) {
250
368
  const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
251
369
  if (!resolved.ok) {
252
370
  return platformResult('failed', {
@@ -254,33 +372,50 @@ function syncPlatformComment(taskRef, options) {
254
372
  });
255
373
  }
256
374
  const taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
257
- const issue = issueNumberFromTask(taskContent);
258
- if (!issue) {
375
+ let issueIdentityFromTask;
376
+ try {
377
+ issueIdentityFromTask = taskIssueIdentity(parseTaskFrontmatter(taskContent), undefined, options.runtimeVersion);
378
+ }
379
+ catch (error) {
380
+ return platformResult('failed', { error: { ...taskIssueIdentityError(error), retryable: false } });
381
+ }
382
+ if (!issueIdentityFromTask) {
259
383
  return platformResult('no-op', {
260
384
  error: { code: 'ISSUE_NOT_LINKED', message: 'Task has no valid issue_number', retryable: false }
261
385
  });
262
386
  }
263
- const context = resolvePlatformContext({ cwd: resolved.repoRoot, client: options.client });
264
- if (!hasResolvedPlatformContext(context))
265
- return context;
266
- const client = options.client || createGitHubClient();
267
- const listed = listRemoteComments(client, context.platform.repository, issue, resolved.repoRoot);
268
- if (!listed.ok) {
269
- return platformResult(listed.error.retryable ? 'blocked' : 'failed', {
270
- ...contextFields(context), resource: { kind: 'issue', number: issue }, error: listed.error
271
- });
272
- }
273
387
  let desired;
274
388
  try {
275
389
  desired = expectedComments(resolved.taskId, taskContent, resolved.taskDir, options);
276
390
  }
277
391
  catch (error) {
278
392
  return platformResult('failed', {
279
- ...contextFields(context),
280
- resource: { kind: 'issue', number: issue },
393
+ resource: { kind: 'issue', number: resourceIdentityNumber(issueIdentityFromTask) },
281
394
  error: { code: 'COMMENT_PAYLOAD_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false }
282
395
  });
283
396
  }
397
+ const loaded = await resolvePlatformProviderContext({ cwd: resolved.repoRoot, client: options.client });
398
+ const context = loaded.ok ? loaded.value.context : loaded.context;
399
+ if (!hasResolvedPlatformContext(context) || !loaded.ok)
400
+ return context;
401
+ const issue = resourceIdentityNumber(issueIdentityFromTask);
402
+ const listed = loaded.value.provider.comments?.list
403
+ ? await loaded.value.provider.comments.list({
404
+ context: providerOperationContext(loaded.value),
405
+ parent: issueIdentityFromTask
406
+ }).then((response) => response.ok
407
+ ? { ok: true, value: response.value.map((comment) => ({
408
+ id: providerCommentId(comment.id, loaded.value.provider),
409
+ body: comment.body,
410
+ user: comment.author?.name ? { login: comment.author.name } : undefined
411
+ })) }
412
+ : response)
413
+ : unsupportedProviderOperation(loaded.value.provider, 'comments.list');
414
+ if (!listed.ok) {
415
+ return platformResult(listed.error.retryable ? 'blocked' : 'failed', {
416
+ ...contextFields(context), resource: { kind: 'issue', number: issue }, error: listed.error
417
+ });
418
+ }
284
419
  const existing = relatedComments(listed.value, markerPrefix(resolved.taskId, options));
285
420
  if (!validateRelatedMarkerSet(existing, markerPrefix(resolved.taskId, options)).ok) {
286
421
  return platformResult('failed', {
@@ -310,7 +445,6 @@ function syncPlatformComment(taskRef, options) {
310
445
  error: null
311
446
  });
312
447
  }
313
- const repo = context.platform.repository;
314
448
  const operations = [];
315
449
  const ids = [];
316
450
  for (const item of desired) {
@@ -320,16 +454,18 @@ function syncPlatformComment(taskRef, options) {
320
454
  operations.push({ name: `comment:${item.marker}`, status: 'no-op', reasonCode: null });
321
455
  continue;
322
456
  }
323
- const written = writeComment(client, repo, issue, resolved.repoRoot, item.body, current?.id);
457
+ const written = loaded.value.provider.comments?.write
458
+ ? await loaded.value.provider.comments.write({
459
+ context: providerOperationContext(loaded.value),
460
+ parent: issueIdentityFromTask,
461
+ body: item.body,
462
+ ...(current ? { existingComment: { kind: 'id', value: String(current.id) } } : {}),
463
+ mutation: { idempotencyKey: `comment:${resolved.taskId}:${item.marker}` }
464
+ })
465
+ : unsupportedProviderOperation(loaded.value.provider, 'comments.write');
324
466
  if (!written.ok) {
325
467
  if (!current && written.error.retryable) {
326
- const reconciled = listRemoteComments(client, repo, issue, resolved.repoRoot);
327
- const found = reconciled.ok ? findMarkerComments(reconciled.value, item.marker) : [];
328
- if (found.length === 1) {
329
- ids.push(found[0].id);
330
- operations.push({ name: `comment:${item.marker}`, status: 'applied', reasonCode: 'CREATE_RECONCILED' });
331
- continue;
332
- }
468
+ // A provider owns reconciliation because only it knows how to address the resource.
333
469
  }
334
470
  return platformResult(written.error.retryable ? 'blocked' : 'failed', {
335
471
  ...contextFields(context),
@@ -338,15 +474,20 @@ function syncPlatformComment(taskRef, options) {
338
474
  error: written.error
339
475
  });
340
476
  }
341
- ids.push(current?.id || written.value.id || 0);
477
+ ids.push(providerCommentId(written.value.remoteId, loaded.value.provider));
342
478
  operations.push({ name: `comment:${item.marker}`, status: 'applied', reasonCode: null });
343
479
  }
344
480
  const desiredMarkers = new Set(desired.map((item) => item.marker));
345
481
  const stale = existing.filter((comment) => !desiredMarkers.has(normalizeCommentContent(comment.body).split('\n', 1)[0]));
346
482
  for (const comment of stale) {
347
- const deleted = client.text(['api', `repos/${repo}/issues/comments/${comment.id}`, '-X', 'DELETE'], {
348
- cwd: resolved.repoRoot, method: 'DELETE'
349
- });
483
+ const deleted = loaded.value.provider.comments?.delete
484
+ ? await loaded.value.provider.comments.delete({
485
+ context: providerOperationContext(loaded.value),
486
+ parent: issueIdentityFromTask,
487
+ comment: { kind: 'id', value: String(comment.id) },
488
+ mutation: { idempotencyKey: `comment-delete:${resolved.taskId}:${comment.id}` }
489
+ })
490
+ : unsupportedProviderOperation(loaded.value.provider, 'comments.delete');
350
491
  if (!deleted.ok) {
351
492
  return platformResult(deleted.error.retryable ? 'blocked' : 'failed', {
352
493
  ...contextFields(context),
@@ -367,31 +508,55 @@ function syncPlatformComment(taskRef, options) {
367
508
  error: null
368
509
  });
369
510
  }
370
- function listPlatformComments(issue, cwd = process.cwd(), client = createGitHubClient()) {
371
- const context = resolvePlatformContext({ cwd, client });
372
- if (!hasResolvedPlatformContext(context))
511
+ async function listPlatformComments(issue, cwd = process.cwd(), client) {
512
+ const loaded = await resolvePlatformProviderContext({ cwd, client });
513
+ const context = loaded.ok ? loaded.value.context : loaded.context;
514
+ if (!hasResolvedPlatformContext(context) || !loaded.ok)
515
+ return context;
516
+ let identity;
517
+ try {
518
+ identity = loaded.ok ? providerResourceToken(loaded.value.provider, 'issue', String(issue)) : null;
519
+ }
520
+ catch (error) {
521
+ return platformResult('failed', { ...contextFields(context), error: { code: 'PLATFORM_IDENTITY_TOKEN_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false } });
522
+ }
523
+ if (!loaded.ok || !identity)
373
524
  return context;
374
- const result = listRemoteComments(client, context.platform.repository, issue, cwd);
375
- if (!result.ok) {
376
- return platformResult(result.error.retryable ? 'blocked' : 'failed', {
377
- ...contextFields(context), resource: { kind: 'issue', number: issue }, error: result.error
525
+ const listed = loaded.value.provider.comments?.list
526
+ ? await loaded.value.provider.comments.list({ context: providerOperationContext(loaded.value), parent: identity }).then((response) => response.ok
527
+ ? { ok: true, value: response.value.map((comment) => ({ id: comment.id, body: comment.body, user: comment.author?.name ? { login: comment.author.name } : undefined })) }
528
+ : response)
529
+ : unsupportedProviderOperation(loaded.value.provider, 'comments.list');
530
+ if (!listed.ok) {
531
+ return platformResult(providerStatus(listed.error), {
532
+ ...contextFields(context), resource: { kind: 'issue', number: resourceIdentityNumber(identity), identity }, error: providerError(listed.error, 'PLATFORM_PROVIDER_OPERATION_FAILED')
378
533
  });
379
534
  }
380
- return { ...platformResult('no-op', { ...contextFields(context), resource: { kind: 'issue', number: issue }, error: null }), comments: result.value };
535
+ return { ...platformResult('no-op', { ...contextFields(context), resource: { kind: 'issue', number: resourceIdentityNumber(identity), identity }, error: null }), comments: listed.value };
381
536
  }
382
- function checkPlatformCommentOwner(taskRef, options = {}) {
537
+ async function checkPlatformCommentOwner(taskRef, options = {}) {
383
538
  const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
384
539
  if (!resolved.ok)
385
540
  return platformResult('failed', { error: { code: resolved.code, message: resolved.message, retryable: false } });
386
541
  const content = fs.readFileSync(resolved.taskMdPath, 'utf8');
387
- const issue = issueNumberFromTask(content);
388
- if (!issue)
389
- return platformResult('no-op', { error: { code: 'ISSUE_NOT_LINKED', message: 'Task has no valid issue_number', retryable: false } });
390
- const client = options.client || createGitHubClient();
391
- const context = resolvePlatformContext({ cwd: resolved.repoRoot, client });
392
- if (!hasResolvedPlatformContext(context))
542
+ let issueIdentity;
543
+ try {
544
+ issueIdentity = taskIssueIdentity(parseTaskFrontmatter(content), undefined, options.runtimeVersion);
545
+ }
546
+ catch (error) {
547
+ return platformResult('failed', { error: { ...taskIssueIdentityError(error), retryable: false } });
548
+ }
549
+ if (!issueIdentity)
550
+ return platformResult('no-op', { error: { code: 'ISSUE_NOT_LINKED', message: 'Task has no valid platform issue identity', retryable: false } });
551
+ const loaded = await resolvePlatformProviderContext({ cwd: resolved.repoRoot, client: options.client });
552
+ const context = loaded.ok ? loaded.value.context : loaded.context;
553
+ if (!hasResolvedPlatformContext(context) || !loaded.ok)
393
554
  return context;
394
- const listed = listRemoteComments(client, context.platform.repository, issue, resolved.repoRoot);
555
+ const listed = loaded.value.provider.comments?.list
556
+ ? await loaded.value.provider.comments.list({ context: providerOperationContext(loaded.value), parent: issueIdentity }).then((response) => response.ok
557
+ ? { ok: true, value: response.value.map((comment) => ({ id: comment.id, body: comment.body, user: comment.author?.name ? { login: comment.author.name } : undefined })) }
558
+ : response)
559
+ : unsupportedProviderOperation(loaded.value.provider, 'comments.list');
395
560
  if (!listed.ok)
396
561
  return platformResult(listed.error.retryable ? 'blocked' : 'failed', { ...contextFields(context), error: listed.error });
397
562
  const matches = findMarkerComments(listed.value, MARKERS.task(resolved.taskId));
@@ -401,11 +566,11 @@ function checkPlatformCommentOwner(taskRef, options = {}) {
401
566
  if (owner && owner !== context.platform.currentUser && !context.capabilities.triage) {
402
567
  return platformResult('blocked', {
403
568
  ...contextFields(context),
404
- resource: { kind: 'issue', number: issue },
569
+ resource: { kind: 'issue', number: resourceIdentityNumber(issueIdentity), identity: issueIdentity },
405
570
  error: { code: 'COMMENT_OWNER_CONFLICT', message: `Task comment is owned by '${owner}'`, retryable: false }
406
571
  });
407
572
  }
408
- return platformResult('no-op', { ...contextFields(context), resource: { kind: 'issue', number: issue }, error: null });
573
+ return platformResult('no-op', { ...contextFields(context), resource: { kind: 'issue', number: resourceIdentityNumber(issueIdentity), identity: issueIdentity }, error: null });
409
574
  }
410
575
  export { MARKERS, chunkArtifactComment, findMarkerComments, listRemoteComments, checkPlatformCommentOwner, listPlatformComments, normalizeCommentContent, renderTaskComment, syncPlatformComment, validateRelatedMarkerSet, writeComment };
411
576
  //# sourceMappingURL=issue-comments.js.map