@fitlab-ai/agent-infra 0.9.13 → 0.9.15

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 (428) hide show
  1. package/README.md +10 -0
  2. package/README.zh-CN.md +10 -0
  3. package/bin/cli.ts +26 -9
  4. package/bin/internal-cli.sh +45 -0
  5. package/dist/bin/cli.js +173 -157
  6. package/dist/bin/internal-cli.js +126 -42
  7. package/dist/lib/agent-client.js +5 -0
  8. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
  9. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -2
  10. package/dist/lib/defaults.json +103 -103
  11. package/dist/lib/fs/durable-write.js +37 -0
  12. package/dist/lib/fs/file-lock.js +111 -0
  13. package/dist/lib/host-control/audit.js +19 -0
  14. package/dist/lib/host-control/client.js +142 -0
  15. package/dist/lib/host-control/command.js +49 -0
  16. package/dist/lib/host-control/path.js +160 -0
  17. package/dist/lib/host-control/secure-fs.js +140 -0
  18. package/dist/lib/host-control/server.js +159 -0
  19. package/dist/lib/host-control/service.js +112 -0
  20. package/dist/lib/init.js +11 -2
  21. package/dist/lib/internal/agent-client.js +5 -2
  22. package/dist/lib/internal/cli-route-inventory.js +154 -0
  23. package/dist/lib/internal/codex-lifecycle.js +15 -12
  24. package/dist/lib/internal/codex-sandbox-controller.js +8 -2
  25. package/dist/lib/internal/demo-promotion.js +322 -0
  26. package/dist/lib/internal/demo-transcript.js +454 -0
  27. package/dist/lib/internal/git-workflow.js +10 -6
  28. package/dist/lib/internal/host-control.js +28 -0
  29. package/dist/lib/internal/manual-validation.js +322 -0
  30. package/dist/lib/internal/platform-checks.js +16 -4
  31. package/dist/lib/internal/platform-comment.js +16 -4
  32. package/dist/lib/internal/platform-context.js +4 -1
  33. package/dist/lib/internal/platform-issue.js +12 -4
  34. package/dist/lib/internal/platform-metadata.js +88 -0
  35. package/dist/lib/internal/platform-pr-review.js +16 -3
  36. package/dist/lib/internal/platform-pr.js +42 -14
  37. package/dist/lib/internal/platform-release-notes.js +6 -3
  38. package/dist/lib/internal/platform-security.js +93 -0
  39. package/dist/lib/internal/pr-review-grade.js +14 -3
  40. package/dist/lib/internal/release-workflow.js +82 -89
  41. package/dist/lib/internal/sandbox-control.js +171 -7
  42. package/dist/lib/internal/task-activity.js +13 -4
  43. package/dist/lib/internal/task-artifact.js +19 -107
  44. package/dist/lib/internal/task-context.js +4 -1
  45. package/dist/lib/internal/task-create.js +48 -14
  46. package/dist/lib/internal/task-delivery.js +4 -1
  47. package/dist/lib/internal/task-event.js +38 -35
  48. package/dist/lib/internal/task-finalization.js +4 -1
  49. package/dist/lib/internal/task-invalidation.js +4 -1
  50. package/dist/lib/internal/task-ledger.js +12 -1
  51. package/dist/lib/internal/task-lifecycle.js +4 -1
  52. package/dist/lib/internal/task-operation-registry.js +519 -0
  53. package/dist/lib/internal/task-orchestration.js +12 -0
  54. package/dist/lib/internal/task-override.js +10 -3
  55. package/dist/lib/internal/task-qualification.js +90 -0
  56. package/dist/lib/internal/task-review.js +10 -52
  57. package/dist/lib/internal/task-short-id.js +11 -1
  58. package/dist/lib/internal/task-snapshot.js +4 -1
  59. package/dist/lib/internal/task-validate.js +37 -12
  60. package/dist/lib/internal/task-verify.js +5 -1
  61. package/dist/lib/internal/task-warning.js +8 -1
  62. package/dist/lib/owned-path.js +33 -0
  63. package/dist/lib/platform/check-status.js +11 -0
  64. package/dist/lib/platform/comment-safety.js +229 -0
  65. package/dist/lib/platform/completion-backfill.js +1 -1
  66. package/dist/lib/platform/github-data.js +241 -0
  67. package/dist/lib/platform/github-provider.js +248 -26
  68. package/dist/lib/platform/issue-comments.js +146 -34
  69. package/dist/lib/platform/issues.js +1 -179
  70. package/dist/lib/platform/pr-change-report.js +6 -12
  71. package/dist/lib/platform/pr-checks.js +12 -65
  72. package/dist/lib/platform/pr-summary.js +83 -10
  73. package/dist/lib/platform/provider-contract.d.ts +67 -1
  74. package/dist/lib/platform/provider-contract.js +6 -2
  75. package/dist/lib/platform/provider-validation.js +76 -3
  76. package/dist/lib/platform/pull-requests.js +117 -275
  77. package/dist/lib/platform/repository-metadata.js +377 -0
  78. package/dist/lib/platform/security-alerts.js +155 -0
  79. package/dist/lib/platform/snapshots.js +2 -0
  80. package/dist/lib/platform/verification-sync.js +154 -162
  81. package/dist/lib/process-data/index.js +7 -0
  82. package/dist/lib/release/workflow.js +1 -1
  83. package/dist/lib/run/index.js +5 -0
  84. package/dist/lib/sandbox/commands/control-status.js +22 -0
  85. package/dist/lib/sandbox/commands/create.js +39 -8
  86. package/dist/lib/sandbox/commands/ls.js +5 -0
  87. package/dist/lib/sandbox/commands/rm.js +3 -807
  88. package/dist/lib/sandbox/commands/show.js +16 -0
  89. package/dist/lib/sandbox/control/audit.js +203 -0
  90. package/dist/lib/sandbox/control/client.js +125 -12
  91. package/dist/lib/sandbox/control/completed-reentry.js +85 -0
  92. package/dist/lib/sandbox/control/container-identity.js +28 -31
  93. package/dist/lib/sandbox/control/executor.js +233 -34
  94. package/dist/lib/sandbox/control/finalization-response.js +27 -0
  95. package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
  96. package/dist/lib/sandbox/control/lifecycle.js +656 -128
  97. package/dist/lib/sandbox/control/native-file-lock.js +85 -0
  98. package/dist/lib/sandbox/control/protocol.js +55 -4
  99. package/dist/lib/sandbox/control/server.js +720 -79
  100. package/dist/lib/sandbox/control/state.js +63 -3
  101. package/dist/lib/sandbox/control/task-view.js +183 -0
  102. package/dist/lib/sandbox/control/task-workflow.js +74 -0
  103. package/dist/lib/sandbox/control/workflow-executor.js +99 -0
  104. package/dist/lib/sandbox/engines/authority.js +289 -0
  105. package/dist/lib/sandbox/engines/docker-exact-discovery.js +97 -0
  106. package/dist/lib/sandbox/index.js +7 -2
  107. package/dist/lib/sandbox/recovery.js +49 -5
  108. package/dist/lib/sandbox/removal.js +1717 -0
  109. package/dist/lib/sandbox/task-cleanup.js +217 -0
  110. package/dist/lib/sandbox/workspace-view.js +15 -15
  111. package/dist/lib/sandbox/worktree-safety.js +4 -1
  112. package/dist/lib/server/index.js +8 -0
  113. package/dist/lib/task/activity-intent.js +2 -1
  114. package/dist/lib/task/activity-log.js +30 -24
  115. package/dist/lib/task/artifact-command.js +101 -0
  116. package/dist/lib/task/artifact-lifecycle.js +71 -40
  117. package/dist/lib/task/artifact-name.js +36 -0
  118. package/dist/lib/task/artifact-operations.js +474 -0
  119. package/dist/lib/task/artifact-receipts.js +3 -15
  120. package/dist/lib/task/artifact-repair-intent.js +130 -0
  121. package/dist/lib/task/artifact-schema.js +127 -0
  122. package/dist/lib/task/capabilities.js +83 -19
  123. package/dist/lib/task/codex-orchestration.js +71 -25
  124. package/dist/lib/task/commit-identity.js +28 -0
  125. package/dist/lib/task/commit-operation.js +2 -16
  126. package/dist/lib/task/control-authority.js +375 -5
  127. package/dist/lib/task/control-recovery.js +128 -0
  128. package/dist/lib/task/create-service.js +103 -4
  129. package/dist/lib/task/create.js +80 -2
  130. package/dist/lib/task/decision-details.js +2 -46
  131. package/dist/lib/task/events.js +306 -76
  132. package/dist/lib/task/finalization-state.js +9 -0
  133. package/dist/lib/task/human-override.js +3 -6
  134. package/dist/lib/task/implementation-inputs.js +3 -3
  135. package/dist/lib/task/index.js +11 -3
  136. package/dist/lib/task/intermediate-cleanup.js +383 -0
  137. package/dist/lib/task/invalidation.js +18 -65
  138. package/dist/lib/task/ledger-intents.js +7 -22
  139. package/dist/lib/task/ledger.js +1 -3
  140. package/dist/lib/task/lifecycle.js +19 -1
  141. package/dist/lib/task/local-artifact-finalization.js +190 -290
  142. package/dist/lib/task/manual-validation-completion.js +26 -0
  143. package/dist/lib/task/manual-validation-evidence.js +130 -0
  144. package/dist/lib/task/manual-validation-receipt.js +101 -0
  145. package/dist/lib/task/manual-validation-shared.js +40 -0
  146. package/dist/lib/task/manual-validation-transaction.js +243 -0
  147. package/dist/lib/task/markdown.js +47 -0
  148. package/dist/lib/task/orchestration.js +206 -69
  149. package/dist/lib/task/qualification-audit.js +476 -0
  150. package/dist/lib/task/qualification-confirmation.js +109 -0
  151. package/dist/lib/task/qualification-intents.js +148 -0
  152. package/dist/lib/task/review-artifacts.js +23 -40
  153. package/dist/lib/task/review-command.js +25 -0
  154. package/dist/lib/task/review-finalization.js +146 -97
  155. package/dist/lib/task/review-fingerprint.js +2 -2
  156. package/dist/lib/task/sections.js +34 -54
  157. package/dist/lib/task/verification-config.js +11 -0
  158. package/dist/lib/task/verification-engine.js +170 -327
  159. package/dist/lib/task/verification.js +1 -1
  160. package/dist/lib/task/workflow-command.js +17 -0
  161. package/dist/lib/task/workflow-warning-intents.js +0 -16
  162. package/dist/lib/task/workflow-warnings.js +28 -2
  163. package/dist/lib/update.js +2 -0
  164. package/dist/lifecycle-build-manifest.json +3 -3
  165. package/lib/agent-client.ts +9 -0
  166. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
  167. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -2
  168. package/lib/fs/durable-write.ts +29 -0
  169. package/lib/fs/file-lock.ts +113 -0
  170. package/lib/host-control/audit.ts +22 -0
  171. package/lib/host-control/client.ts +172 -0
  172. package/lib/host-control/command.ts +60 -0
  173. package/lib/host-control/path.ts +167 -0
  174. package/lib/host-control/secure-fs.ts +135 -0
  175. package/lib/host-control/server.ts +180 -0
  176. package/lib/host-control/service.ts +133 -0
  177. package/lib/init.ts +15 -2
  178. package/lib/internal/agent-client.ts +4 -2
  179. package/lib/internal/cli-route-inventory.ts +142 -0
  180. package/lib/internal/codex-lifecycle.ts +14 -12
  181. package/lib/internal/codex-sandbox-controller.ts +7 -2
  182. package/lib/internal/demo-promotion.ts +354 -0
  183. package/lib/internal/demo-transcript.ts +419 -0
  184. package/lib/internal/git-workflow.ts +9 -6
  185. package/lib/internal/host-control.ts +28 -0
  186. package/lib/internal/manual-validation.ts +306 -0
  187. package/lib/internal/platform-checks.ts +12 -4
  188. package/lib/internal/platform-comment.ts +12 -4
  189. package/lib/internal/platform-context.ts +3 -1
  190. package/lib/internal/platform-issue.ts +11 -4
  191. package/lib/internal/platform-metadata.ts +70 -0
  192. package/lib/internal/platform-pr-review.ts +14 -3
  193. package/lib/internal/platform-pr.ts +38 -14
  194. package/lib/internal/platform-release-notes.ts +5 -3
  195. package/lib/internal/platform-security.ts +83 -0
  196. package/lib/internal/pr-review-grade.ts +10 -3
  197. package/lib/internal/release-workflow.ts +91 -87
  198. package/lib/internal/sandbox-control.ts +181 -6
  199. package/lib/internal/task-activity.ts +12 -4
  200. package/lib/internal/task-artifact.ts +15 -79
  201. package/lib/internal/task-context.ts +3 -1
  202. package/lib/internal/task-create.ts +76 -15
  203. package/lib/internal/task-delivery.ts +3 -1
  204. package/lib/internal/task-event.ts +21 -10
  205. package/lib/internal/task-finalization.ts +3 -1
  206. package/lib/internal/task-invalidation.ts +3 -1
  207. package/lib/internal/task-ledger.ts +11 -1
  208. package/lib/internal/task-lifecycle.ts +3 -1
  209. package/lib/internal/task-operation-registry.ts +590 -0
  210. package/lib/internal/task-orchestration.ts +12 -0
  211. package/lib/internal/task-override.ts +9 -3
  212. package/lib/internal/task-qualification.ts +61 -0
  213. package/lib/internal/task-review.ts +7 -50
  214. package/lib/internal/task-short-id.ts +10 -1
  215. package/lib/internal/task-snapshot.ts +3 -1
  216. package/lib/internal/task-validate.ts +32 -12
  217. package/lib/internal/task-verify.ts +4 -1
  218. package/lib/internal/task-warning.ts +7 -1
  219. package/lib/owned-path.ts +26 -0
  220. package/lib/platform/check-status.ts +9 -0
  221. package/lib/platform/comment-safety.ts +255 -0
  222. package/lib/platform/completion-backfill.ts +1 -1
  223. package/lib/platform/github-data.ts +322 -0
  224. package/lib/platform/github-provider.ts +244 -27
  225. package/lib/platform/issue-comments.ts +159 -35
  226. package/lib/platform/issues.ts +4 -245
  227. package/lib/platform/pr-change-report.ts +6 -12
  228. package/lib/platform/pr-checks.ts +13 -77
  229. package/lib/platform/pr-summary.ts +88 -11
  230. package/lib/platform/provider-contract.ts +78 -3
  231. package/lib/platform/provider-validation.ts +85 -3
  232. package/lib/platform/pull-requests.ts +129 -348
  233. package/lib/platform/repository-metadata.ts +453 -0
  234. package/lib/platform/security-alerts.ts +191 -0
  235. package/lib/platform/snapshots.ts +53 -0
  236. package/lib/platform/verification-sync.ts +171 -177
  237. package/lib/process-data/index.ts +7 -0
  238. package/lib/release/workflow.ts +3 -3
  239. package/lib/run/index.ts +5 -0
  240. package/lib/sandbox/commands/control-status.ts +26 -0
  241. package/lib/sandbox/commands/create.ts +53 -12
  242. package/lib/sandbox/commands/ls.ts +7 -0
  243. package/lib/sandbox/commands/rm.ts +3 -996
  244. package/lib/sandbox/commands/show.ts +16 -0
  245. package/lib/sandbox/control/audit.ts +253 -0
  246. package/lib/sandbox/control/client.ts +150 -13
  247. package/lib/sandbox/control/completed-reentry.ts +98 -0
  248. package/lib/sandbox/control/container-identity.ts +27 -43
  249. package/lib/sandbox/control/executor.ts +262 -37
  250. package/lib/sandbox/control/finalization-response.ts +29 -0
  251. package/lib/sandbox/control/identity-sentinel.ts +137 -0
  252. package/lib/sandbox/control/lifecycle.ts +672 -18
  253. package/lib/sandbox/control/native-file-lock.ts +108 -0
  254. package/lib/sandbox/control/protocol.ts +80 -8
  255. package/lib/sandbox/control/server.ts +779 -82
  256. package/lib/sandbox/control/state.ts +84 -3
  257. package/lib/sandbox/control/task-view.ts +240 -0
  258. package/lib/sandbox/control/task-workflow.ts +95 -0
  259. package/lib/sandbox/control/workflow-executor.ts +102 -0
  260. package/lib/sandbox/engines/authority.ts +365 -0
  261. package/lib/sandbox/engines/docker-exact-discovery.ts +132 -0
  262. package/lib/sandbox/index.ts +9 -2
  263. package/lib/sandbox/recovery.ts +49 -6
  264. package/lib/sandbox/removal.ts +2144 -0
  265. package/lib/sandbox/task-cleanup.ts +209 -0
  266. package/lib/sandbox/workspace-view.ts +26 -18
  267. package/lib/sandbox/worktree-safety.ts +4 -1
  268. package/lib/server/index.ts +10 -0
  269. package/lib/task/activity-intent.ts +2 -2
  270. package/lib/task/activity-log.ts +27 -19
  271. package/lib/task/artifact-command.ts +104 -0
  272. package/lib/task/artifact-lifecycle.ts +66 -39
  273. package/lib/task/artifact-name.ts +55 -0
  274. package/lib/task/artifact-operations.ts +580 -0
  275. package/lib/task/artifact-receipts.ts +3 -14
  276. package/lib/task/artifact-repair-intent.ts +152 -0
  277. package/lib/task/artifact-schema.ts +163 -0
  278. package/lib/task/capabilities.ts +82 -22
  279. package/lib/task/codex-orchestration.ts +85 -24
  280. package/lib/task/commit-identity.ts +31 -0
  281. package/lib/task/commit-operation.ts +2 -16
  282. package/lib/task/control-authority.ts +513 -6
  283. package/lib/task/control-recovery.ts +174 -0
  284. package/lib/task/create-service.ts +131 -6
  285. package/lib/task/create.ts +83 -1
  286. package/lib/task/decision-details.ts +3 -71
  287. package/lib/task/events.ts +292 -75
  288. package/lib/task/finalization-state.ts +11 -0
  289. package/lib/task/human-override.ts +3 -8
  290. package/lib/task/implementation-inputs.ts +3 -3
  291. package/lib/task/index.ts +16 -3
  292. package/lib/task/intermediate-cleanup.ts +455 -0
  293. package/lib/task/invalidation.ts +18 -62
  294. package/lib/task/ledger-intents.ts +7 -24
  295. package/lib/task/ledger.ts +1 -3
  296. package/lib/task/lifecycle.ts +17 -1
  297. package/lib/task/local-artifact-finalization.ts +223 -388
  298. package/lib/task/manual-validation-completion.ts +40 -0
  299. package/lib/task/manual-validation-evidence.ts +209 -0
  300. package/lib/task/manual-validation-receipt.ts +152 -0
  301. package/lib/task/manual-validation-shared.ts +50 -0
  302. package/lib/task/manual-validation-transaction.ts +304 -0
  303. package/lib/task/markdown.ts +70 -0
  304. package/lib/task/orchestration.ts +191 -68
  305. package/lib/task/qualification-audit.ts +588 -0
  306. package/lib/task/qualification-confirmation.ts +117 -0
  307. package/lib/task/qualification-intents.ts +150 -0
  308. package/lib/task/review-artifacts.ts +21 -41
  309. package/lib/task/review-command.ts +23 -0
  310. package/lib/task/review-finalization.ts +154 -153
  311. package/lib/task/review-fingerprint.ts +2 -2
  312. package/lib/task/sections.ts +33 -52
  313. package/lib/task/verification-config.ts +12 -0
  314. package/lib/task/verification-engine.ts +165 -370
  315. package/lib/task/verification-types.ts +17 -0
  316. package/lib/task/verification.ts +1 -1
  317. package/lib/task/workflow-command.ts +18 -0
  318. package/lib/task/workflow-warning-intents.ts +0 -13
  319. package/lib/task/workflow-warnings.ts +21 -2
  320. package/lib/update.ts +6 -0
  321. package/package.json +7 -2
  322. package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
  323. package/templates/.agents/rules/README.en.md +2 -0
  324. package/templates/.agents/rules/README.zh-CN.md +2 -0
  325. package/templates/.agents/rules/decision-qualification.en.md +22 -0
  326. package/templates/.agents/rules/decision-qualification.zh-CN.md +22 -0
  327. package/templates/.agents/rules/evidence-reporting.en.md +28 -0
  328. package/templates/.agents/rules/evidence-reporting.zh-CN.md +28 -0
  329. package/templates/.agents/rules/label-milestone-setup.en.md +7 -7
  330. package/templates/.agents/rules/label-milestone-setup.zh-CN.md +7 -7
  331. package/templates/.agents/rules/local-artifact-repair.en.md +5 -1
  332. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +5 -1
  333. package/templates/.agents/rules/security-alerts.en.md +8 -8
  334. package/templates/.agents/rules/security-alerts.zh-CN.md +8 -8
  335. package/templates/.agents/rules/testing-discipline.en.md +2 -0
  336. package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
  337. package/templates/.agents/scripts/package.json +3 -0
  338. package/templates/.agents/skills/analyze-task/SKILL.en.md +15 -2
  339. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +19 -2
  340. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -12
  341. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -12
  342. package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
  343. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
  344. package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
  345. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
  346. package/templates/.agents/skills/code-task/SKILL.en.md +14 -1
  347. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +14 -1
  348. package/templates/.agents/skills/code-task/config/verify.en.json +5 -14
  349. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +5 -14
  350. package/templates/.agents/skills/code-task/reference/code-rules.en.md +5 -4
  351. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +5 -4
  352. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
  353. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
  354. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +3 -1
  355. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +3 -1
  356. package/templates/.agents/skills/code-task/reference/report-template.en.md +42 -15
  357. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +39 -12
  358. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +37 -21
  359. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +37 -21
  360. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
  361. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
  362. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +4 -0
  363. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +4 -0
  364. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
  365. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
  366. package/templates/.agents/skills/complete-task/SKILL.en.md +10 -23
  367. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +10 -23
  368. package/templates/.agents/skills/create-task/SKILL.en.md +8 -0
  369. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +7 -0
  370. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  371. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  372. package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
  373. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  374. package/templates/.agents/skills/init-labels/SKILL.en.md +8 -8
  375. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +8 -8
  376. package/templates/.agents/skills/init-milestones/SKILL.en.md +9 -11
  377. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +9 -11
  378. package/templates/.agents/skills/plan-task/SKILL.en.md +14 -1
  379. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +16 -1
  380. package/templates/.agents/skills/plan-task/config/verify.en.json +4 -14
  381. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +4 -14
  382. package/templates/.agents/skills/release/SKILL.en.md +7 -3
  383. package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
  384. package/templates/.agents/skills/review-analysis/SKILL.en.md +14 -1
  385. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -1
  386. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -18
  387. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -18
  388. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +50 -23
  389. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +45 -18
  390. package/templates/.agents/skills/review-code/SKILL.en.md +14 -1
  391. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +14 -1
  392. package/templates/.agents/skills/review-code/config/verify.en.json +1 -23
  393. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -23
  394. package/templates/.agents/skills/review-code/reference/report-template.en.md +49 -22
  395. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +45 -18
  396. package/templates/.agents/skills/review-plan/SKILL.en.md +14 -1
  397. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -1
  398. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -18
  399. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -18
  400. package/templates/.agents/skills/review-plan/reference/report-template.en.md +49 -22
  401. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +45 -18
  402. package/templates/.agents/skills/review-pr/SKILL.en.md +5 -1
  403. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +5 -1
  404. package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -3
  405. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -3
  406. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +25 -4
  407. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +25 -4
  408. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
  409. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
  410. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +5 -1
  411. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +5 -1
  412. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  413. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  414. package/templates/.agents/skills/test/SKILL.en.md +29 -7
  415. package/templates/.agents/skills/test/SKILL.zh-CN.md +29 -7
  416. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +14 -0
  417. package/templates/.agents/templates/task.en.md +6 -0
  418. package/templates/.agents/templates/task.zh-CN.md +6 -0
  419. package/templates/.claude/commands/run-manual-validation.en.md +1 -1
  420. package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
  421. package/dist/lib/platform/adapters.js +0 -56
  422. package/lib/platform/adapters.ts +0 -197
  423. package/templates/.agents/scripts/security-alerts.github.sh +0 -151
  424. package/templates/.agents/scripts/security-alerts.sh +0 -31
  425. package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +0 -146
  426. package/templates/.agents/skills/init-labels/scripts/init-labels.sh +0 -32
  427. package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +0 -294
  428. package/templates/.agents/skills/init-milestones/scripts/init-milestones.sh +0 -6
@@ -16,6 +16,13 @@ import {
16
16
  } from './provider-bridge.ts';
17
17
  import { resourceIdentityNumber } from './resource-identity.ts';
18
18
  import { taskIssueIdentity, taskIssueIdentityError } from './task-identities.ts';
19
+ import {
20
+ canonicalizeCommentBody,
21
+ escapeHtmlText,
22
+ fenceRanges,
23
+ renderSafeCodeFence
24
+ } from './comment-safety.ts';
25
+ import type { FenceRange } from './comment-safety.ts';
19
26
 
20
27
  type RemoteComment = { id: number | string; body: string; user?: { login?: string } };
21
28
  type RenderedChunk = { marker: string; body: string; content: string; part: number; total: number };
@@ -66,15 +73,22 @@ function splitFrontmatter(content: string): { frontmatter: string | null; body:
66
73
  return { frontmatter: match[1]!, body: content.slice(match[0].length).replace(/^\r?\n/, '') };
67
74
  }
68
75
 
76
+ function sanitizeCommentBody(value: string, label: string): string {
77
+ const result = canonicalizeCommentBody(value);
78
+ if (!result.ok) throw new Error(`${result.error.code}: ${label}: ${result.error.message} at offset ${result.error.offset}`);
79
+ return result.value;
80
+ }
81
+
69
82
  function footer(agent: string, taskId: string): string {
70
83
  return `---\n*由 ${agent} 自动生成 · 内部追踪:${taskId}*`;
71
84
  }
72
85
 
73
86
  function renderTaskComment(content: string, taskId: string, agent: string): string {
74
87
  const split = splitFrontmatter(content);
88
+ const safeBody = sanitizeCommentBody(split.body, 'task body');
75
89
  const taskBody = split.frontmatter === null
76
- ? split.body
77
- : `<details><summary>元数据 (frontmatter)</summary>\n\n\`\`\`yaml\n---\n${split.frontmatter}\n---\n\`\`\`\n\n</details>\n\n${split.body}`;
90
+ ? safeBody
91
+ : `<details><summary>元数据 (frontmatter)</summary>\n\n${renderSafeCodeFence(`---\n${split.frontmatter}\n---`, 'yaml')}\n\n</details>\n\n${safeBody}`;
78
92
  return normalizeCommentContent([
79
93
  MARKERS.task(taskId),
80
94
  '## 任务文件',
@@ -96,13 +110,16 @@ function artifactIdentity(artifact: string): { stem: string; title: string } {
96
110
  return { stem, title: `${base}(Round ${round})` };
97
111
  }
98
112
 
99
- function chunkByUtf8(content: string, maxBytes: number): string[] {
113
+ type SourceChunk = { content: string; start: number; end: number };
114
+
115
+ function chunkByUtf8(content: string, maxBytes: number): SourceChunk[] {
100
116
  if (maxBytes <= 0) throw new Error('comment byte limit is too small');
101
- const chunks: string[] = [];
102
- let remaining = content;
103
- while (remaining) {
117
+ const chunks: SourceChunk[] = [];
118
+ let start = 0;
119
+ while (start < content.length) {
120
+ const remaining = content.slice(start);
104
121
  if (Buffer.byteLength(remaining, 'utf8') <= maxBytes) {
105
- chunks.push(remaining);
122
+ chunks.push({ content: remaining, start, end: content.length });
106
123
  break;
107
124
  }
108
125
  let bytes = 0;
@@ -117,10 +134,102 @@ function chunkByUtf8(content: string, maxBytes: number): string[] {
117
134
  }
118
135
  const cut = newlineIndex > 0 ? newlineIndex : index;
119
136
  if (cut === 0) throw new Error('comment byte limit cannot fit one Unicode code point');
120
- chunks.push(remaining.slice(0, cut));
121
- remaining = remaining.slice(cut);
137
+ chunks.push({ content: remaining.slice(0, cut), start, end: start + cut });
138
+ start += cut;
122
139
  }
123
- return chunks.length > 0 ? chunks : [''];
140
+ return chunks.length > 0 ? chunks : [{ content: '', start: 0, end: 0 }];
141
+ }
142
+
143
+ function longestFenceLineRun(content: string, character: '`' | '~'): number {
144
+ const pattern = new RegExp(`^ {0,3}(${character}+)[ \\t]*(?:\\n|$)`, 'gm');
145
+ let longest = 0;
146
+ for (const match of content.matchAll(pattern)) longest = Math.max(longest, match[1]!.length);
147
+ return longest;
148
+ }
149
+
150
+ function boundedFence(content: string, sourceCharacter: '`' | '~', maxBytes: number): { opening: string; closing: string } | null {
151
+ const candidates: Array<'`' | '~'> = sourceCharacter === '`' ? ['~', '`'] : ['`', '~'];
152
+ for (const character of candidates) {
153
+ const length = Math.max(3, longestFenceLineRun(content, character) + 1);
154
+ const delimiter = character.repeat(length);
155
+ if (Buffer.byteLength(delimiter, 'utf8') * 2 + 2 <= maxBytes) return { opening: `${delimiter}\n`, closing: `${delimiter}\n` };
156
+ }
157
+ return null;
158
+ }
159
+
160
+ function sourceSlice(piece: SourceChunk, start: number, end: number): string {
161
+ return piece.content.slice(Math.max(0, start - piece.start), Math.max(0, end - piece.start));
162
+ }
163
+
164
+ function renderOversizedFenceChunk(piece: SourceChunk, fences: readonly FenceRange[], maxBytes: number): string {
165
+ const insertions = new Map<number, Array<{ value: string; ensureLineStart: boolean }>>();
166
+ const omitted: Array<{ start: number; end: number }> = [];
167
+ const insert = (position: number, value: string, ensureLineStart = false) => {
168
+ const values = insertions.get(position) || [];
169
+ values.push({ value, ensureLineStart });
170
+ insertions.set(position, values);
171
+ };
172
+
173
+ for (const fence of fences) {
174
+ if (piece.end <= fence.start || piece.start >= fence.end) continue;
175
+ const oversized = Buffer.byteLength(fence.opening, 'utf8') > maxBytes
176
+ || Buffer.byteLength(fence.closing, 'utf8') > maxBytes;
177
+ if (!oversized) {
178
+ if (piece.start > fence.start && piece.start < fence.end) insert(piece.start, fence.opening);
179
+ if (piece.end > fence.start && piece.end < fence.end) insert(piece.end, fence.closing, true);
180
+ continue;
181
+ }
182
+
183
+ const code = sourceSlice(piece, Math.max(piece.start, fence.openingEnd), Math.min(piece.end, fence.closingStart));
184
+ const delimiter = boundedFence(code, fence.character, maxBytes);
185
+ if (!delimiter) return escapeHtmlText(piece.content);
186
+ insert(Math.max(piece.start, fence.start), delimiter.opening);
187
+ insert(Math.min(piece.end, fence.end), delimiter.closing, true);
188
+ const openingStart = Math.max(piece.start, fence.start);
189
+ const openingEnd = Math.min(piece.end, fence.openingEnd);
190
+ if (openingStart < openingEnd) omitted.push({ start: openingStart, end: openingEnd });
191
+ const closingStart = Math.max(piece.start, fence.closingStart);
192
+ const closingEnd = Math.min(piece.end, fence.end);
193
+ if (closingStart < closingEnd) omitted.push({ start: closingStart, end: closingEnd });
194
+ }
195
+
196
+ const boundaries = new Set<number>([piece.start, piece.end]);
197
+ for (const position of insertions.keys()) boundaries.add(position);
198
+ for (const range of omitted) {
199
+ boundaries.add(range.start);
200
+ boundaries.add(range.end);
201
+ }
202
+ const points = [...boundaries].sort((left, right) => left - right);
203
+ let content = '';
204
+ for (let index = 0; index < points.length; index += 1) {
205
+ const point = points[index]!;
206
+ for (const insertion of insertions.get(point) || []) {
207
+ if (insertion.ensureLineStart && content.length > 0 && !content.endsWith('\n')) content += '\n';
208
+ content += insertion.value;
209
+ }
210
+ const next = points[index + 1];
211
+ if (next === undefined) continue;
212
+ const skipped = omitted.some((range) => range.start <= point && next <= range.end);
213
+ if (!skipped) content += sourceSlice(piece, point, next);
214
+ }
215
+ return content;
216
+ }
217
+
218
+ function independentChunkContent(piece: SourceChunk, fences: readonly FenceRange[], maxBytes: number): string {
219
+ const oversizedFence = fences.some((fence) =>
220
+ piece.end > fence.start && piece.start < fence.end
221
+ && (Buffer.byteLength(fence.opening, 'utf8') > maxBytes || Buffer.byteLength(fence.closing, 'utf8') > maxBytes)
222
+ );
223
+ if (oversizedFence) return renderOversizedFenceChunk(piece, fences, maxBytes);
224
+ const openingFence = fences.find((fence) => piece.start > fence.start && piece.start < fence.end);
225
+ const closingFence = fences.find((fence) => piece.end > fence.start && piece.end < fence.end);
226
+ let content = piece.content;
227
+ if (openingFence) content = openingFence.opening + content;
228
+ if (closingFence) {
229
+ if (!content.endsWith('\n')) content += '\n';
230
+ content += closingFence.closing;
231
+ }
232
+ return content;
124
233
  }
125
234
 
126
235
  function buildArtifactChunk(
@@ -132,7 +241,8 @@ function buildArtifactChunk(
132
241
  part: number,
133
242
  total: number,
134
243
  chunked: boolean,
135
- backfill: boolean
244
+ backfill: boolean,
245
+ renderedContent = content
136
246
  ): RenderedChunk {
137
247
  const marker = chunked ? MARKERS.artifactChunk(taskId, stem, part, total) : MARKERS.artifact(taskId, stem);
138
248
  const heading = chunked ? `## ${title}(${part}/${total})` : `## ${title}`;
@@ -144,7 +254,7 @@ function buildArtifactChunk(
144
254
  '',
145
255
  `> **${agent}** · ${taskId}`,
146
256
  '',
147
- content,
257
+ renderedContent,
148
258
  '',
149
259
  footer(agent, taskId)
150
260
  ].join('\n'));
@@ -161,29 +271,44 @@ function chunkArtifactComment(input: {
161
271
  }): RenderedChunk[] {
162
272
  const byteLimit = input.byteLimit || 60_000;
163
273
  const identity = artifactIdentity(input.artifact);
274
+ const safeBody = sanitizeCommentBody(input.body, 'artifact body');
164
275
  const single = buildArtifactChunk(
165
- input.taskId, identity.stem, identity.title, input.agent, input.body, 1, 1, false, Boolean(input.backfill)
276
+ input.taskId, identity.stem, identity.title, input.agent, safeBody, 1, 1, false, Boolean(input.backfill)
166
277
  );
167
278
  if (Buffer.byteLength(single.body, 'utf8') <= byteLimit) return [single];
168
279
 
169
280
  let total = 2;
170
- let pieces: string[] = [];
281
+ let payloadLimit = Number.POSITIVE_INFINITY;
282
+ const ranges = fenceRanges(safeBody);
283
+ if (!ranges.ok) throw new Error(`${ranges.error.code}: artifact body: ${ranges.error.message} at offset ${ranges.error.offset}`);
171
284
  for (;;) {
172
285
  const probe = buildArtifactChunk(
173
286
  input.taskId, identity.stem, identity.title, input.agent, '', total, total, true, Boolean(input.backfill)
174
287
  );
175
288
  const available = byteLimit - Buffer.byteLength(probe.body, 'utf8');
176
- pieces = chunkByUtf8(input.body, available);
177
- if (pieces.length === total) break;
178
- total = pieces.length;
289
+ const sourceLimit = Math.min(available, payloadLimit);
290
+ if (sourceLimit <= 0) throw new Error('comment byte limit is too small for an artifact chunk');
291
+ const pieces = chunkByUtf8(safeBody, sourceLimit);
292
+ if (pieces.length !== total) {
293
+ total = pieces.length;
294
+ continue;
295
+ }
296
+ const chunks = pieces.map((piece, index) => buildArtifactChunk(
297
+ input.taskId,
298
+ identity.stem,
299
+ identity.title,
300
+ input.agent,
301
+ piece.content,
302
+ index + 1,
303
+ total,
304
+ true,
305
+ Boolean(input.backfill),
306
+ independentChunkContent(piece, ranges.value, sourceLimit)
307
+ ));
308
+ const overflow = Math.max(...chunks.map((chunk) => Buffer.byteLength(chunk.body, 'utf8') - byteLimit));
309
+ if (overflow <= 0) return chunks;
310
+ payloadLimit = sourceLimit - overflow;
179
311
  }
180
- return pieces.map((content, index) => {
181
- const chunk = buildArtifactChunk(
182
- input.taskId, identity.stem, identity.title, input.agent, content, index + 1, total, true, Boolean(input.backfill)
183
- );
184
- if (Buffer.byteLength(chunk.body, 'utf8') > byteLimit) throw new Error('rendered comment exceeds byte limit');
185
- return chunk;
186
- });
187
312
  }
188
313
 
189
314
  function findMarkerComments(comments: RemoteComment[], marker: string): RemoteComment[] {
@@ -224,7 +349,7 @@ function hasResolvedPlatformContext(context: PlatformResult): boolean {
224
349
 
225
350
  function bodyEnvelope(marker: string, title: string, taskId: string, agent: string, body: string): string {
226
351
  return normalizeCommentContent([
227
- marker, `## ${title}`, '', `> **${agent}** · ${taskId}`, '', body.replace(/\n+$/, ''), '', footer(agent, taskId)
352
+ marker, `## ${title}`, '', `> **${agent}** · ${taskId}`, '', sanitizeCommentBody(body, `${title} body`).replace(/\n+$/, ''), '', footer(agent, taskId)
228
353
  ].join('\n'));
229
354
  }
230
355
 
@@ -334,6 +459,15 @@ async function syncPlatformComment(taskRef: string, options: SyncOptions): Promi
334
459
  error: { code: 'ISSUE_NOT_LINKED', message: 'Task has no valid issue_number', retryable: false }
335
460
  });
336
461
  }
462
+ let desired: RenderedChunk[];
463
+ try {
464
+ desired = expectedComments(resolved.taskId, taskContent, resolved.taskDir, options);
465
+ } catch (error) {
466
+ return platformResult('failed', {
467
+ resource: { kind: 'issue', number: resourceIdentityNumber(issueIdentityFromTask) },
468
+ error: { code: 'COMMENT_PAYLOAD_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false }
469
+ });
470
+ }
337
471
  const loaded = await resolvePlatformProviderContext({ cwd: resolved.repoRoot, client: options.client });
338
472
  const context = loaded.ok ? loaded.value.context : loaded.context;
339
473
  if (!hasResolvedPlatformContext(context) || !loaded.ok) return context;
@@ -356,16 +490,6 @@ async function syncPlatformComment(taskRef: string, options: SyncOptions): Promi
356
490
  });
357
491
  }
358
492
 
359
- let desired: RenderedChunk[];
360
- try {
361
- desired = expectedComments(resolved.taskId, taskContent, resolved.taskDir, options);
362
- } catch (error) {
363
- return platformResult('failed', {
364
- ...contextFields(context),
365
- resource: { kind: 'issue', number: issue },
366
- error: { code: 'COMMENT_PAYLOAD_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false }
367
- });
368
- }
369
493
  const existing = relatedComments(listed.value, markerPrefix(resolved.taskId, options));
370
494
  if (!validateRelatedMarkerSet(existing, markerPrefix(resolved.taskId, options)).ok) {
371
495
  return platformResult('failed', {
@@ -1,3 +1,5 @@
1
+ import type { PlatformIssueSnapshot as IssueSnapshot } from './snapshots.ts';
2
+ import type { IssueFieldSchema } from './github-data.ts';
1
3
  import fs from 'node:fs';
2
4
  import path from 'node:path';
3
5
  import { execFileSync } from 'node:child_process';
@@ -38,26 +40,7 @@ import {
38
40
  import { resourceIdentityEquals, resourceIdentityNumber, resourceIdentityString, serializeResourceIdentity } from './resource-identity.ts';
39
41
  import { taskIssueIdentity, taskIssueIdentityError } from './task-identities.ts';
40
42
  import type { ResourceIdentity } from './resource-identity.ts';
41
-
42
- type GitHubClient = PlatformClient;
43
43
  import type { IssueSnapshot as ProviderIssueSnapshot, RepositoryMetadataSnapshot } from './provider-contract.ts';
44
-
45
- type IssueSnapshot = {
46
- repository: string;
47
- number: number;
48
- identity?: ResourceIdentity;
49
- databaseId: number | null;
50
- nodeId: string;
51
- url: string;
52
- state: 'open' | 'closed';
53
- title: string;
54
- body: string;
55
- labels: string[];
56
- assignees: string[];
57
- milestone: string | null;
58
- issueType: string | null;
59
- fields: Record<string, string | number | null>;
60
- };
61
44
  type IssueResult = PlatformResult & {
62
45
  task: { id: string | null; issueNumber: number | null };
63
46
  issue: IssueSnapshot | null;
@@ -80,33 +63,6 @@ type SyncOptions = SharedOptions & {
80
63
  dryRun?: boolean;
81
64
  };
82
65
 
83
- type RemoteIssue = {
84
- number?: number;
85
- id?: number;
86
- node_id?: string;
87
- html_url?: string;
88
- state?: string;
89
- title?: string;
90
- body?: string | null;
91
- labels?: Array<string | { name?: string }>;
92
- assignees?: Array<{ login?: string }>;
93
- milestone?: { title?: string } | null;
94
- type?: { name?: string } | null;
95
- pull_request?: unknown;
96
- };
97
-
98
- type IssueFieldSchema = {
99
- id: string;
100
- name: string;
101
- kind: 'single-select' | 'date' | 'text' | 'number';
102
- options: Array<{ id: string; name: string }>;
103
- };
104
- type IssueTypeSchema = { id: string; name: string; fields: IssueFieldSchema[] };
105
- type CurrentField = { id: string; name: string; kind: IssueFieldSchema['kind']; value: string | number | null };
106
-
107
- 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}}}}}}`;
108
- 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}}}}}}}}`;
109
-
110
66
  function result(
111
67
  status: PlatformResult['status'],
112
68
  taskId: string | null,
@@ -235,118 +191,6 @@ async function inspectExternalIssue(
235
191
  });
236
192
  }
237
193
 
238
- function normalizeIssue(remote: RemoteIssue, repository: string, fallbackNumber?: number): IssueSnapshot | null {
239
- const number = Number.isInteger(remote.number) && Number(remote.number) > 0
240
- ? Number(remote.number)
241
- : fallbackNumber;
242
- if (!number || !Number.isSafeInteger(number) || remote.pull_request) return null;
243
- return {
244
- repository,
245
- number,
246
- databaseId: Number.isInteger(remote.id) ? Number(remote.id) : null,
247
- nodeId: remote.node_id || `issue-${number}`,
248
- url: remote.html_url || `https://github.com/${repository}/issues/${number}`,
249
- state: String(remote.state || '').toLowerCase() === 'closed' ? 'closed' : 'open',
250
- title: remote.title || '',
251
- body: remote.body || '',
252
- labels: (remote.labels || []).map((label) => typeof label === 'string' ? label : label.name || '').filter(Boolean).sort(),
253
- assignees: (remote.assignees || []).map((assignee) => assignee.login || '').filter(Boolean).sort(),
254
- milestone: remote.milestone?.title || null,
255
- issueType: remote.type?.name || null,
256
- fields: {}
257
- };
258
- }
259
-
260
- function fieldKind(value: { __typename?: string }): IssueFieldSchema['kind'] | null {
261
- if (value.__typename === 'IssueFieldSingleSelect' || value.__typename === 'IssueFieldSingleSelectValue') return 'single-select';
262
- if (value.__typename === 'IssueFieldDate' || value.__typename === 'IssueFieldDateValue') return 'date';
263
- if (value.__typename === 'IssueFieldText' || value.__typename === 'IssueFieldTextValue') return 'text';
264
- if (value.__typename === 'IssueFieldNumber' || value.__typename === 'IssueFieldNumberValue') return 'number';
265
- return null;
266
- }
267
-
268
- function normalizeFieldSchemas(value: unknown): IssueFieldSchema[] {
269
- if (!Array.isArray(value)) return [];
270
- return value.flatMap((raw) => {
271
- const item = raw as { id?: string; name?: string; __typename?: string; options?: Array<{ id?: string; name?: string }> };
272
- const kind = fieldKind(item);
273
- return item.id && item.name && kind ? [{
274
- id: item.id,
275
- name: item.name,
276
- kind,
277
- options: (item.options || []).flatMap((option) => option.id && option.name ? [{ id: option.id, name: option.name }] : [])
278
- }] : [];
279
- });
280
- }
281
-
282
- function normalizeIssueTypes(value: unknown): IssueTypeSchema[] {
283
- const nodes = (value as { data?: { organization?: { issueTypes?: { nodes?: unknown[] } } } })?.data?.organization?.issueTypes?.nodes;
284
- return (nodes || []).flatMap((raw) => {
285
- const item = raw as { id?: string; name?: string; pinnedFields?: unknown[] };
286
- return item.id && item.name ? [{ id: item.id, name: item.name, fields: normalizeFieldSchemas(item.pinnedFields) }] : [];
287
- });
288
- }
289
-
290
- function normalizeCurrentFields(value: unknown): { issueId: string | null; type: IssueTypeSchema | null; values: CurrentField[] } {
291
- const issue = (value as { data?: { repository?: { issue?: {
292
- id?: string;
293
- issueType?: { id?: string; name?: string; pinnedFields?: unknown[] } | null;
294
- issueFieldValues?: { nodes?: unknown[] };
295
- } } } })?.data?.repository?.issue;
296
- const type = issue?.issueType?.name ? {
297
- id: issue.issueType.id || issue.issueType.name,
298
- name: issue.issueType.name,
299
- fields: normalizeFieldSchemas(issue.issueType.pinnedFields)
300
- } : null;
301
- const values = (issue?.issueFieldValues?.nodes || []).flatMap((raw) => {
302
- const item = raw as { __typename?: string; name?: string; value?: string | number; field?: { id?: string; name?: string } };
303
- const kind = fieldKind(item);
304
- const value = kind === 'single-select' ? item.name : item.value;
305
- return item.field?.name && kind && value !== undefined
306
- ? [{ id: item.field.id || item.field.name, name: item.field.name, kind, value: value ?? null }]
307
- : [];
308
- });
309
- return { issueId: issue?.id || null, type, values };
310
- }
311
-
312
- function graphState(client: GitHubClient, repository: string, issue: number, cwd: string) {
313
- const [owner, name] = repository.split('/');
314
- if (!owner || !name) return null;
315
- const current = client.json<unknown>([
316
- 'api', 'graphql', '-f', `query=${ISSUE_FIELDS_QUERY}`, '-F', `owner=${owner}`, '-F', `name=${name}`, '-F', `number=${issue}`
317
- ], { cwd });
318
- if (!current.ok) return null;
319
- return normalizeCurrentFields(current.value);
320
- }
321
-
322
- function fetchIssue(client: GitHubClient, repository: string, issue: number, cwd: string) {
323
- return client.json<RemoteIssue>(['api', `repos/${repository}/issues/${issue}`], { cwd });
324
- }
325
-
326
- function inspectGitHubIssue(client: GitHubClient, repository: string, issue: number, cwd: string) {
327
- const fetched = fetchIssue(client, repository, issue, cwd);
328
- if (!fetched.ok) return fetched;
329
- const snapshot = normalizeIssue(fetched.value, repository, issue);
330
- return snapshot
331
- ? { ok: true as const, value: snapshot }
332
- : { ok: false as const, error: { code: 'ISSUE_IDENTITY_INVALID', message: 'Remote resource is not a valid Issue', retryable: false } };
333
- }
334
-
335
- function inspectGitHubIssueMetadata(client: GitHubClient, repository: string, issue: number, cwd: string) {
336
- const fetched = fetchIssue(client, repository, issue, cwd);
337
- if (!fetched.ok) return fetched;
338
- const remote = fetched.value;
339
- return {
340
- ok: true as const,
341
- value: {
342
- state: String(remote.state || '').toLowerCase() === 'closed' ? 'closed' as const : 'open' as const,
343
- labels: (remote.labels || []).map((label: string | { name?: string }) => typeof label === 'string' ? label : label.name || '').filter(Boolean).sort(),
344
- body: remote.body || '',
345
- milestone: remote.milestone?.title || null
346
- }
347
- };
348
- }
349
-
350
194
  async function inspectPlatformIssue(taskRef: string, options: SharedOptions = {}): Promise<IssueResult> {
351
195
  const base = await resolvedContext(taskRef, options);
352
196
  if (!base.ok) return base.output;
@@ -543,14 +387,6 @@ function requirementsFromTask(content: string): Requirement[] {
543
387
  }));
544
388
  }
545
389
 
546
- function flattenPages(value: unknown): unknown[] {
547
- return Array.isArray(value) ? value.flatMap((item) => Array.isArray(item) ? item : [item]) : [];
548
- }
549
-
550
- function listNames(value: unknown): string[] {
551
- return flattenPages(value).map((item) => typeof item === 'string' ? item : String((item as { name?: string }).name || '')).filter(Boolean);
552
- }
553
-
554
390
  function milestoneBasis(repoRoot: string, current: string | null): string | null {
555
391
  if (!current || !/^\d+\.\d+\.x$/.test(current)) return current;
556
392
  try {
@@ -642,73 +478,6 @@ function desiredFieldValues(frontmatter: Record<string, string>, fields: IssueFi
642
478
  return desired;
643
479
  }
644
480
 
645
- function planGraphMetadata(
646
- client: GitHubClient,
647
- repository: string,
648
- issue: number,
649
- cwd: string,
650
- frontmatter: Record<string, string>,
651
- options: Pick<SyncOptions, 'issueType' | 'fields'>,
652
- capabilities: PlatformResult['capabilities']
653
- ): { operations: PlannedOperation[]; issueId: string | null } {
654
- if (!options.issueType && !options.fields) return { operations: [], issueId: null };
655
- if (!capabilities.push) return {
656
- operations: [
657
- ...(options.issueType ? [{ name: 'issue-type', status: 'skipped' as const, reasonCode: 'PUSH_REQUIRED' }] : []),
658
- ...(options.fields ? [{ name: 'fields', status: 'skipped' as const, reasonCode: 'PUSH_REQUIRED' }] : [])
659
- ], issueId: null
660
- };
661
- const [owner] = repository.split('/');
662
- const typesResult = client.json<unknown>(['api', 'graphql', '-f', `query=${ISSUE_TYPES_QUERY}`, '-F', `owner=${owner}`], { cwd });
663
- if (!typesResult.ok) return { operations: [{ name: 'issue-schema', status: 'failed', reasonCode: typesResult.error.code }], issueId: null };
664
- const types = normalizeIssueTypes(typesResult.value);
665
- if (types.length === 0) return {
666
- operations: [
667
- ...(options.issueType ? [{ name: 'issue-type', status: 'skipped' as const, reasonCode: 'ISSUE_TYPES_UNSUPPORTED' }] : []),
668
- ...(options.fields ? [{ name: 'fields', status: 'skipped' as const, reasonCode: 'ISSUE_TYPES_UNSUPPORTED' }] : [])
669
- ], issueId: null
670
- };
671
- const current = graphState(client, repository, issue, cwd);
672
- if (!current?.issueId) return { operations: [{ name: 'issue-schema', status: 'failed', reasonCode: 'ISSUE_GRAPH_IDENTITY_INVALID' }], issueId: null };
673
- const target = types.find((type) => type.name === desiredIssueType(frontmatter.type || ''));
674
- if (!target) return { operations: [{ name: 'issue-type', status: 'failed', reasonCode: 'ISSUE_TYPE_NOT_FOUND' }], issueId: current.issueId };
675
- const operations: PlannedOperation[] = [];
676
- if (options.issueType) operations.push(current.type?.id === target.id
677
- ? { name: 'issue-type', status: 'no-op', reasonCode: null }
678
- : { name: 'issue-type', status: 'planned', reasonCode: null, value: { issueTypeId: target.id } });
679
- if (options.fields) {
680
- const desired = desiredFieldValues(frontmatter, target.fields);
681
- const inputs = desired.filter((field) => current.values.find((value) => value.name === field.name)?.value !== field.value).map((field) => field.input);
682
- if (current.type?.id !== target.id) {
683
- const targetNames = new Set(target.fields.map((field) => field.name));
684
- for (const old of current.values) if (!targetNames.has(old.name)) inputs.push({ fieldId: old.id, delete: true });
685
- }
686
- operations.push(inputs.length === 0
687
- ? { name: 'fields', status: 'no-op', reasonCode: null }
688
- : { name: 'fields', status: 'planned', reasonCode: null, value: inputs });
689
- }
690
- return { operations, issueId: current.issueId };
691
- }
692
-
693
- function executeGraphOperation(
694
- client: GitHubClient,
695
- cwd: string,
696
- issueId: string,
697
- operation: PlannedOperation
698
- ) {
699
- if (operation.name === 'issue-type') {
700
- const query = 'mutation($issueId:ID!,$issueTypeId:ID){updateIssueIssueType(input:{issueId:$issueId,issueTypeId:$issueTypeId}){issue{id}}}';
701
- const typeId = (operation.value as { issueTypeId: string }).issueTypeId;
702
- return client.json(['api', 'graphql', '--input', '-'], {
703
- cwd, method: 'POST', input: JSON.stringify({ query, variables: { issueId, issueTypeId: typeId } })
704
- });
705
- }
706
- const query = 'mutation($issueId:ID!,$issueFields:[IssueFieldCreateOrUpdateInput!]!){setIssueFieldValue(input:{issueId:$issueId,issueFields:$issueFields}){issue{id}}}';
707
- return client.json(['api', 'graphql', '--input', '-'], {
708
- cwd, method: 'POST', input: JSON.stringify({ query, variables: { issueId, issueFields: operation.value } })
709
- });
710
- }
711
-
712
481
  async function syncPlatformIssue(taskRef: string, options: SyncOptions): Promise<IssueResult> {
713
482
  const base = await resolvedContext(taskRef, options);
714
483
  if (!base.ok) return base.output;
@@ -849,15 +618,5 @@ async function syncPlatformIssue(taskRef: string, options: SyncOptions): Promise
849
618
  });
850
619
  }
851
620
 
852
- export {
853
- bindPlatformIssue,
854
- createPlatformIssue,
855
- graphState,
856
- inspectGitHubIssue,
857
- inspectGitHubIssueMetadata,
858
- inspectPlatformIssue,
859
- normalizeIssue,
860
- requirementSectionAnchors,
861
- syncPlatformIssue
862
- };
863
- export type { BindOptions, CreateOptions, IssueResult, IssueSnapshot, SyncOptions };
621
+ export { bindPlatformIssue, createPlatformIssue, inspectPlatformIssue, requirementSectionAnchors, syncPlatformIssue };
622
+ export type { BindOptions, CreateOptions, IssueResult, SyncOptions };
@@ -2,6 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { createHash, randomUUID } from 'node:crypto';
4
4
  import { execFileSync } from 'node:child_process';
5
+ import { escapeHtmlText } from './comment-safety.ts';
5
6
 
6
7
  const REPORT_FILE_NAME = 'pr-change-report.json';
7
8
  const REPORT_VERSION = 1 as const;
@@ -483,22 +484,15 @@ function representativePath(file: ChangeFile): string {
483
484
  return file.newPath || file.oldPath || '';
484
485
  }
485
486
 
486
- function escapeHtml(value: string): string {
487
- return value
488
- .replaceAll('&', '&amp;')
489
- .replaceAll('<', '&lt;')
490
- .replaceAll('>', '&gt;')
491
- .replaceAll('"', '&quot;')
492
- .replaceAll("'", '&#39;')
493
- .replaceAll('\r', '\\r')
494
- .replaceAll('\n', '\\n');
487
+ function escapeReportText(value: string): string {
488
+ return escapeHtmlText(value).replaceAll('\r', '\\r').replaceAll('\n', '\\n');
495
489
  }
496
490
 
497
491
  function renderRepresentativePath(file: ChangeFile): string {
498
492
  const paths = file.oldPath && file.newPath && file.oldPath !== file.newPath
499
493
  ? [file.oldPath, file.newPath]
500
494
  : [representativePath(file)];
501
- return paths.map((filePath) => `<code>${escapeHtml(filePath)}</code>`).join(' → ');
495
+ return paths.map((filePath) => `<code>${escapeReportText(filePath)}</code>`).join(' → ');
502
496
  }
503
497
 
504
498
  function representativeScore(file: ChangeFile, metric: 'lines' | 'bytes'): number {
@@ -551,12 +545,12 @@ function renderEvidence(evidence: Evidence): string {
551
545
  ? `:${evidence.startLine}`
552
546
  : `:${evidence.startLine}-${evidence.endLine}`
553
547
  : `:${evidence.startLine ?? evidence.endLine}`;
554
- return ` - 证据:<code>${escapeHtml(evidence.path)}</code>${location}:${escapeHtml(evidence.detail)}`;
548
+ return ` - 证据:<code>${escapeReportText(evidence.path)}</code>${location}:${escapeReportText(evidence.detail)}`;
555
549
  }
556
550
 
557
551
  function renderPrechecks(checks: Precheck[]): string[] {
558
552
  return checks.flatMap((check) => [
559
- `- **${PRECHECK_LABELS[check.id]}(${check.id})**:${check.verdict === 'pass' ? '通过' : '需复核'}。${escapeHtml(check.rationale)}`,
553
+ `- **${PRECHECK_LABELS[check.id]}(${check.id})**:${check.verdict === 'pass' ? '通过' : '需复核'}。${escapeReportText(check.rationale)}`,
560
554
  ...check.evidence.slice(0, 1).map(renderEvidence)
561
555
  ]);
562
556
  }