@fitlab-ai/agent-infra 0.12.0 → 0.12.2

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 (331) hide show
  1. package/README.md +0 -2
  2. package/README.zh-CN.md +0 -2
  3. package/dist/bin/internal-cli.js +2 -17
  4. package/dist/lib/agent-clients/adapter.js +0 -13
  5. package/dist/lib/agent-clients/adapters/antigravity-cli.js +0 -5
  6. package/dist/lib/agent-clients/adapters/claude-code-credentials.js +0 -17
  7. package/dist/lib/agent-clients/adapters/claude-code.js +0 -9
  8. package/dist/lib/agent-clients/adapters/codex.js +0 -52
  9. package/dist/lib/agent-clients/adapters/opencode.js +0 -5
  10. package/dist/lib/agent-clients/adapters/traecli.js +0 -5
  11. package/dist/lib/agent-clients/config.js +19 -54
  12. package/dist/lib/agent-clients/registry.js +1 -12
  13. package/dist/lib/agent-clients/schema.js +0 -18
  14. package/dist/lib/agent-clients/tokens.js +1 -2
  15. package/dist/lib/agent-clients/types.js +0 -1
  16. package/dist/lib/decide.js +2 -0
  17. package/dist/lib/git/worktree-identity.js +1 -9
  18. package/dist/lib/internal/agent-client.js +2 -62
  19. package/dist/lib/internal/cli-route-inventory.js +1 -5
  20. package/dist/lib/internal/manual-validation.js +29 -27
  21. package/dist/lib/internal/platform-comment.js +56 -7
  22. package/dist/lib/internal/task-create.js +1 -1
  23. package/dist/lib/internal/task-event.js +1 -8
  24. package/dist/lib/internal/task-finalization.js +0 -1
  25. package/dist/lib/internal/task-lifecycle.js +2 -2
  26. package/dist/lib/internal/task-operation-registry.js +4 -20
  27. package/dist/lib/internal/task-review.js +4 -4
  28. package/dist/lib/platform/comment-safety.js +1 -7
  29. package/dist/lib/platform/in-label-sync.js +1 -14
  30. package/dist/lib/platform/issue-comments.js +373 -282
  31. package/dist/lib/platform/issue-summary-content.js +112 -0
  32. package/dist/lib/platform/issues.js +1 -1
  33. package/dist/lib/platform/operation-coordinator.js +4 -2
  34. package/dist/lib/platform/pr-change-report.js +176 -30
  35. package/dist/lib/platform/pr-checks.js +2 -8
  36. package/dist/lib/platform/pr-summary-content.js +54 -0
  37. package/dist/lib/platform/pr-summary.js +318 -284
  38. package/dist/lib/platform/provider-validation.js +0 -20
  39. package/dist/lib/platform/pull-requests.js +73 -84
  40. package/dist/lib/platform/releases.js +1 -43
  41. package/dist/lib/platform/summary-content-pipeline.js +18 -0
  42. package/dist/lib/platform/verification-required.js +0 -2
  43. package/dist/lib/platform/verification-sync.js +1 -79
  44. package/dist/lib/process-data/normalize.js +1 -185
  45. package/dist/lib/process-data/privacy.js +1 -16
  46. package/dist/lib/process-data/sources.js +2 -279
  47. package/dist/lib/render.js +0 -12
  48. package/dist/lib/run/index.js +15 -10
  49. package/dist/lib/run/skills.js +0 -3
  50. package/dist/lib/sandbox/commands/create.js +40 -53
  51. package/dist/lib/sandbox/commands/list-running.js +0 -18
  52. package/dist/lib/sandbox/commands/start.js +7 -8
  53. package/dist/lib/sandbox/constants.js +0 -6
  54. package/dist/lib/sandbox/control/client.js +0 -1
  55. package/dist/lib/sandbox/control/container-identity.js +1 -16
  56. package/dist/lib/sandbox/control/executor.js +0 -4
  57. package/dist/lib/sandbox/control/identity-sentinel.js +0 -7
  58. package/dist/lib/sandbox/control/lifecycle.js +21 -37
  59. package/dist/lib/sandbox/control/native-file-lock.js +0 -3
  60. package/dist/lib/sandbox/control/server.js +18 -38
  61. package/dist/lib/sandbox/control/state.js +1 -9
  62. package/dist/lib/sandbox/dockerfile.js +0 -6
  63. package/dist/lib/sandbox/engine.js +0 -14
  64. package/dist/lib/sandbox/engines/docker-exact-discovery.js +1 -4
  65. package/dist/lib/sandbox/engines/wsl2-paths.js +0 -3
  66. package/dist/lib/sandbox/recovery.js +24 -26
  67. package/dist/lib/sandbox/removal.js +67 -518
  68. package/dist/lib/sandbox/task-resolver.js +0 -41
  69. package/dist/lib/sandbox/tools.js +1 -4
  70. package/dist/lib/sandbox/workspace-view.js +1 -2
  71. package/dist/lib/server/display.js +0 -6
  72. package/dist/lib/task/activity-intent.js +45 -40
  73. package/dist/lib/task/activity-log.js +9 -16
  74. package/dist/lib/task/artifact-command.js +1 -1
  75. package/dist/lib/task/artifact-lifecycle.js +61 -63
  76. package/dist/lib/task/artifact-name.js +0 -6
  77. package/dist/lib/task/artifact-operations.js +3 -3
  78. package/dist/lib/task/capabilities.js +4 -5
  79. package/dist/lib/task/commands/log.js +0 -6
  80. package/dist/lib/task/commands/status.js +17 -60
  81. package/dist/lib/task/commit-operation.js +28 -39
  82. package/dist/lib/task/completion-facts.js +56 -43
  83. package/dist/lib/task/control-authority.js +23 -256
  84. package/dist/lib/task/control-recovery.js +3 -5
  85. package/dist/lib/task/create.js +2 -4
  86. package/dist/lib/task/decision-intents.js +8 -9
  87. package/dist/lib/task/delivery-target.js +1 -40
  88. package/dist/lib/task/events.js +84 -107
  89. package/dist/lib/task/finalization.js +60 -15
  90. package/dist/lib/task/ledger-intents.js +1 -1
  91. package/dist/lib/task/lifecycle-execution.js +38 -33
  92. package/dist/lib/task/lifecycle.js +49 -41
  93. package/dist/lib/task/local-artifact-finalization.js +1 -4
  94. package/dist/lib/task/manual-validation-evidence.js +1 -37
  95. package/dist/lib/task/platform-operation-recovery.js +151 -108
  96. package/dist/lib/task/pr-delivery-fact.js +2 -5
  97. package/dist/lib/task/qualification-confirmation.js +5 -7
  98. package/dist/lib/task/resolve-ref.js +1 -25
  99. package/dist/lib/task/review-command.js +2 -2
  100. package/dist/lib/task/review-finalization.js +1 -21
  101. package/dist/lib/task/review-fingerprint.js +0 -4
  102. package/dist/lib/task/secure-fs.js +0 -56
  103. package/dist/lib/task/short-id.js +1 -39
  104. package/dist/lib/task/skill-scope.js +0 -1
  105. package/dist/lib/task/verification-engine.js +58 -282
  106. package/dist/lib/task/verification.js +6 -3
  107. package/dist/lib/task/write.js +11 -1
  108. package/lib/agent-clients/adapter.ts +0 -76
  109. package/lib/agent-clients/adapters/antigravity-cli.ts +0 -5
  110. package/lib/agent-clients/adapters/claude-code-credentials.ts +0 -19
  111. package/lib/agent-clients/adapters/claude-code.ts +0 -9
  112. package/lib/agent-clients/adapters/codex.ts +0 -56
  113. package/lib/agent-clients/adapters/opencode.ts +0 -5
  114. package/lib/agent-clients/adapters/traecli.ts +0 -5
  115. package/lib/agent-clients/config.ts +20 -61
  116. package/lib/agent-clients/registry.ts +1 -34
  117. package/lib/agent-clients/schema.ts +0 -18
  118. package/lib/agent-clients/tokens.ts +1 -2
  119. package/lib/agent-clients/types.ts +0 -15
  120. package/lib/decide.ts +1 -0
  121. package/lib/git/worktree-identity.ts +0 -10
  122. package/lib/internal/agent-client.ts +2 -70
  123. package/lib/internal/cli-route-inventory.ts +1 -4
  124. package/lib/internal/manual-validation.ts +28 -27
  125. package/lib/internal/platform-comment.ts +46 -4
  126. package/lib/internal/task-create.ts +0 -1
  127. package/lib/internal/task-event.ts +1 -5
  128. package/lib/internal/task-finalization.ts +0 -1
  129. package/lib/internal/task-lifecycle.ts +3 -5
  130. package/lib/internal/task-operation-registry.ts +3 -19
  131. package/lib/internal/task-review.ts +4 -4
  132. package/lib/platform/comment-safety.ts +0 -10
  133. package/lib/platform/github-provider.ts +0 -2
  134. package/lib/platform/in-label-sync.ts +0 -16
  135. package/lib/platform/issue-comments.ts +478 -301
  136. package/lib/platform/issue-summary-content.ts +132 -0
  137. package/lib/platform/issues.ts +0 -2
  138. package/lib/platform/operation-coordinator.ts +4 -2
  139. package/lib/platform/pr-change-report.ts +165 -25
  140. package/lib/platform/pr-checks.ts +1 -10
  141. package/lib/platform/pr-review.ts +1 -1
  142. package/lib/platform/pr-summary-content.ts +60 -0
  143. package/lib/platform/pr-summary.ts +364 -257
  144. package/lib/platform/provider-bridge.ts +0 -1
  145. package/lib/platform/provider-loader.ts +1 -5
  146. package/lib/platform/provider-validation.ts +1 -49
  147. package/lib/platform/pull-requests.ts +64 -99
  148. package/lib/platform/releases.ts +1 -40
  149. package/lib/platform/summary-content-pipeline.ts +23 -0
  150. package/lib/platform/verification-required.ts +0 -2
  151. package/lib/platform/verification-sync.ts +0 -94
  152. package/lib/process-data/normalize.ts +1 -186
  153. package/lib/process-data/privacy.ts +2 -19
  154. package/lib/process-data/sources.ts +3 -283
  155. package/lib/render.ts +0 -15
  156. package/lib/run/index.ts +13 -7
  157. package/lib/run/skills.ts +0 -4
  158. package/lib/sandbox/commands/create.ts +37 -74
  159. package/lib/sandbox/commands/list-running.ts +0 -19
  160. package/lib/sandbox/commands/start.ts +7 -10
  161. package/lib/sandbox/constants.ts +0 -8
  162. package/lib/sandbox/control/client.ts +0 -1
  163. package/lib/sandbox/control/container-identity.ts +1 -16
  164. package/lib/sandbox/control/executor.ts +1 -5
  165. package/lib/sandbox/control/identity-sentinel.ts +0 -8
  166. package/lib/sandbox/control/lifecycle.ts +18 -51
  167. package/lib/sandbox/control/native-file-lock.ts +0 -8
  168. package/lib/sandbox/control/protocol.ts +0 -2
  169. package/lib/sandbox/control/server.ts +13 -39
  170. package/lib/sandbox/control/state.ts +1 -10
  171. package/lib/sandbox/dockerfile.ts +0 -11
  172. package/lib/sandbox/engine.ts +0 -17
  173. package/lib/sandbox/engines/docker-exact-discovery.ts +1 -5
  174. package/lib/sandbox/engines/wsl2-paths.ts +0 -4
  175. package/lib/sandbox/recovery.ts +24 -26
  176. package/lib/sandbox/removal.ts +58 -687
  177. package/lib/sandbox/shell.ts +1 -1
  178. package/lib/sandbox/task-resolver.ts +0 -48
  179. package/lib/sandbox/tools.ts +1 -5
  180. package/lib/sandbox/workspace-view.ts +2 -4
  181. package/lib/server/display.ts +0 -14
  182. package/lib/server/process-control.ts +1 -1
  183. package/lib/task/activity-intent.ts +36 -44
  184. package/lib/task/activity-log.ts +9 -22
  185. package/lib/task/artifact-command.ts +1 -1
  186. package/lib/task/artifact-lifecycle.ts +58 -67
  187. package/lib/task/artifact-name.ts +0 -7
  188. package/lib/task/artifact-operations.ts +3 -3
  189. package/lib/task/capabilities.ts +5 -6
  190. package/lib/task/commands/log.ts +0 -7
  191. package/lib/task/commands/status.ts +14 -79
  192. package/lib/task/commit-operation.ts +29 -51
  193. package/lib/task/completion-facts.ts +45 -42
  194. package/lib/task/control-authority.ts +27 -317
  195. package/lib/task/control-recovery.ts +2 -5
  196. package/lib/task/create.ts +2 -5
  197. package/lib/task/decision-intents.ts +5 -5
  198. package/lib/task/delivery-target.ts +0 -40
  199. package/lib/task/events.ts +89 -123
  200. package/lib/task/finalization.ts +52 -15
  201. package/lib/task/ledger-intents.ts +1 -1
  202. package/lib/task/lifecycle-execution.ts +37 -89
  203. package/lib/task/lifecycle.ts +48 -44
  204. package/lib/task/local-artifact-finalization.ts +0 -7
  205. package/lib/task/manual-validation-evidence.ts +1 -47
  206. package/lib/task/platform-operation-recovery.ts +117 -104
  207. package/lib/task/pr-delivery-fact.ts +2 -11
  208. package/lib/task/qualification-confirmation.ts +6 -6
  209. package/lib/task/resolve-ref.ts +0 -23
  210. package/lib/task/review-command.ts +2 -2
  211. package/lib/task/review-finalization.ts +1 -27
  212. package/lib/task/review-fingerprint.ts +0 -8
  213. package/lib/task/secure-fs.ts +0 -42
  214. package/lib/task/short-id.ts +0 -43
  215. package/lib/task/skill-scope.ts +0 -1
  216. package/lib/task/verification-engine.ts +56 -287
  217. package/lib/task/verification.ts +5 -5
  218. package/lib/task/write.ts +12 -1
  219. package/package.json +2 -2
  220. package/templates/.agents/README.en.md +1 -3
  221. package/templates/.agents/README.zh-CN.md +0 -2
  222. package/templates/.agents/rules/README.en.md +0 -1
  223. package/templates/.agents/rules/README.zh-CN.md +0 -1
  224. package/templates/.agents/rules/commit-and-pr.en.md +1 -1
  225. package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -1
  226. package/templates/.agents/rules/no-mid-flow-questions.en.md +0 -10
  227. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +0 -10
  228. package/templates/.agents/rules/pr-sync.en.md +4 -2
  229. package/templates/.agents/rules/pr-sync.zh-CN.md +6 -9
  230. package/templates/.agents/rules/task-management.en.md +3 -3
  231. package/templates/.agents/rules/task-management.zh-CN.md +3 -3
  232. package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
  233. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
  234. package/templates/.agents/skills/cancel-task/SKILL.en.md +2 -2
  235. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
  236. package/templates/.agents/skills/code-task/SKILL.en.md +2 -2
  237. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +4 -4
  238. package/templates/.agents/skills/commit/SKILL.en.md +2 -2
  239. package/templates/.agents/skills/commit/SKILL.zh-CN.md +2 -2
  240. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +6 -6
  241. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +6 -6
  242. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
  243. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
  244. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +2 -2
  245. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +2 -2
  246. package/templates/.agents/skills/complete-task/SKILL.en.md +10 -25
  247. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +11 -26
  248. package/templates/.agents/skills/create-pr/SKILL.en.md +1 -1
  249. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
  250. package/templates/.agents/skills/create-pr/reference/change-report.en.md +9 -2
  251. package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +9 -2
  252. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +2 -2
  253. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +2 -2
  254. package/templates/.agents/skills/create-task/SKILL.en.md +0 -18
  255. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +0 -18
  256. package/templates/.agents/skills/plan-task/SKILL.en.md +2 -2
  257. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -2
  258. package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
  259. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
  260. package/templates/.agents/skills/review-code/SKILL.en.md +5 -5
  261. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -5
  262. package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
  263. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
  264. package/templates/.agents/skills/review-pr/SKILL.en.md +4 -4
  265. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +4 -4
  266. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
  267. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
  268. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +3 -75
  269. package/templates/.agents/skills/watch-pr/SKILL.en.md +1 -1
  270. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +1 -1
  271. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +3 -3
  272. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +3 -3
  273. package/templates/.agents/templates/task.en.md +1 -0
  274. package/templates/.agents/templates/task.zh-CN.md +1 -0
  275. package/templates/.claude/settings.json +0 -24
  276. package/templates/.codex/hooks.json +0 -50
  277. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +0 -582
  278. package/dist/lib/agent-clients/adapters/codex-lifecycle/binding.js +0 -114
  279. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +0 -237
  280. package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +0 -443
  281. package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +0 -236
  282. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +0 -292
  283. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +0 -267
  284. package/dist/lib/agent-clients/adapters/codex-orchestration.js +0 -466
  285. package/dist/lib/internal/task-orchestration.js +0 -71
  286. package/dist/lib/platform/verification.js +0 -6
  287. package/dist/lib/run/prompt.js +0 -2
  288. package/dist/lib/sandbox/engines/authority.js +0 -289
  289. package/dist/lib/task/delegation-receipts.js +0 -410
  290. package/dist/lib/task/orchestration.js +0 -1233
  291. package/dist/lib/task/verification-checks.js +0 -9
  292. package/dist/lib/task/workspace-snapshot.js +0 -121
  293. package/dist/lifecycle-build-manifest.json +0 -6
  294. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +0 -688
  295. package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +0 -147
  296. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +0 -349
  297. package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +0 -506
  298. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +0 -385
  299. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +0 -41
  300. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +0 -418
  301. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +0 -354
  302. package/lib/agent-clients/adapters/codex-orchestration.ts +0 -630
  303. package/lib/internal/task-orchestration.ts +0 -74
  304. package/lib/platform/verification.ts +0 -11
  305. package/lib/run/prompt.ts +0 -1
  306. package/lib/sandbox/engines/authority.ts +0 -365
  307. package/lib/task/delegation-receipts.ts +0 -547
  308. package/lib/task/orchestration.ts +0 -1651
  309. package/lib/task/verification-checks.ts +0 -10
  310. package/lib/task/workspace-snapshot.ts +0 -144
  311. package/templates/.agents/hooks/lifecycle-delegation.js +0 -140
  312. package/templates/.agents/rules/lifecycle-orchestration.en.md +0 -44
  313. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +0 -44
  314. package/templates/.agents/skills/run-task/SKILL.en.md +0 -29
  315. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +0 -29
  316. package/templates/.agents/skills/run-task/config/verify.en.json +0 -10
  317. package/templates/.agents/skills/run-task/config/verify.zh-CN.json +0 -10
  318. package/templates/.agents/skills/run-task/reference/host-validation.en.md +0 -24
  319. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +0 -24
  320. package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +0 -6
  321. package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +0 -6
  322. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +0 -6
  323. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +0 -6
  324. package/templates/.claude/commands/run-task.en.md +0 -8
  325. package/templates/.claude/commands/run-task.zh-CN.md +0 -8
  326. package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +0 -3
  327. package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +0 -3
  328. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +0 -3
  329. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +0 -3
  330. package/templates/.opencode/commands/run-task.en.md +0 -11
  331. package/templates/.opencode/commands/run-task.zh-CN.md +0 -11
@@ -0,0 +1,132 @@
1
+ import { runSummaryContentPipeline } from './summary-content-pipeline.ts';
2
+
3
+ export type DeliverySummaryContent = Readonly<{
4
+ taskId: string; title: string; summary: string; artifacts: readonly string[];
5
+ verification: Readonly<{ state: string; detail: string }>;
6
+ }>;
7
+ export type CancelSummaryContent = Readonly<{
8
+ taskId: string; title: string; outcome: 'cancelled'; reason: string; cancelledAt: string;
9
+ lastCompletedArtifact: string | null;
10
+ }>;
11
+
12
+ export type DeliverySummarySources = Readonly<{
13
+ taskId: string;
14
+ title: string;
15
+ artifacts: readonly string[];
16
+ verificationFallback?: Readonly<{ state: string; detail: string }>;
17
+ }>;
18
+
19
+ function text(value: unknown, name: string): string {
20
+ if (typeof value !== 'string' || !value.trim()) throw new Error(`${name} must be a non-empty string`);
21
+ return value.trim();
22
+ }
23
+
24
+ function isMissingText(value: unknown): boolean {
25
+ return typeof value !== 'string' || !value.trim();
26
+ }
27
+
28
+ function fillText(input: Record<string, unknown>, key: string, fallback: string): void {
29
+ if (isMissingText(input[key])) input[key] = fallback;
30
+ }
31
+
32
+ function fillDeliveryVerification(
33
+ input: Record<string, unknown>, fallback: Readonly<{ state: string; detail: string }>
34
+ ): void {
35
+ if (!input.verification || typeof input.verification !== 'object' || Array.isArray(input.verification)) {
36
+ input.verification = fallback;
37
+ return;
38
+ }
39
+ const verification = { ...(input.verification as Record<string, unknown>) };
40
+ fillText(verification, 'state', fallback.state);
41
+ fillText(verification, 'detail', fallback.detail);
42
+ input.verification = verification;
43
+ }
44
+
45
+ function isStringArray(value: unknown): value is string[] {
46
+ return Array.isArray(value) && value.every((item) => typeof item === 'string' && item.trim());
47
+ }
48
+
49
+ export function parseDeliverySummaryContent(value: unknown): DeliverySummaryContent {
50
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('delivery summary must be an object');
51
+ const input = value as Record<string, unknown>;
52
+ if (!Array.isArray(input.artifacts) || input.artifacts.some((item) => typeof item !== 'string')) throw new Error('artifacts must be a string array');
53
+ if (!input.verification || typeof input.verification !== 'object' || Array.isArray(input.verification)) throw new Error('verification must be an object');
54
+ const verification = input.verification as Record<string, unknown>;
55
+ return {
56
+ taskId: text(input.taskId, 'taskId'), title: text(input.title, 'title'), summary: text(input.summary, 'summary'),
57
+ artifacts: input.artifacts.map((item) => text(item, 'artifacts[]')),
58
+ verification: { state: text(verification.state, 'verification.state'), detail: text(verification.detail, 'verification.detail') }
59
+ };
60
+ }
61
+
62
+ function correctDeliverySummaryContent(value: unknown, sources: DeliverySummarySources): unknown {
63
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('delivery summary must be an object');
64
+ const input = { ...(value as Record<string, unknown>) };
65
+ fillText(input, 'taskId', sources.taskId);
66
+ if (input.taskId !== sources.taskId) throw new Error('taskId does not match the active task');
67
+ fillText(input, 'title', sources.title);
68
+ if (!isStringArray(input.artifacts)) input.artifacts = [...sources.artifacts];
69
+ if (sources.verificationFallback) fillDeliveryVerification(input, sources.verificationFallback);
70
+ return input;
71
+ }
72
+
73
+ export function prepareAndRenderDeliverySummaryContent(value: unknown, sources: DeliverySummarySources) {
74
+ return runSummaryContentPipeline(value, {
75
+ validate: parseDeliverySummaryContent,
76
+ correct: (input) => correctDeliverySummaryContent(input, sources),
77
+ render: renderDeliverySummaryContent
78
+ });
79
+ }
80
+
81
+ export function renderDeliverySummaryContent(value: DeliverySummaryContent): string {
82
+ const content = parseDeliverySummaryContent(value);
83
+ return [
84
+ `# ${content.title}`, '', content.summary, '', '## 交付产物', '',
85
+ ...(content.artifacts.length ? content.artifacts.map((item) => `- ${item}`) : ['- 无']), '',
86
+ '## 验证', '', `状态:${content.verification.state}`, '', content.verification.detail, ''
87
+ ].join('\n');
88
+ }
89
+
90
+ export function parseCancelSummaryContent(value: unknown): CancelSummaryContent {
91
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('cancel summary must be an object');
92
+ const input = value as Record<string, unknown>;
93
+ if (input.outcome !== 'cancelled') throw new Error('outcome must be cancelled');
94
+ if (input.lastCompletedArtifact !== null && typeof input.lastCompletedArtifact !== 'string') throw new Error('lastCompletedArtifact must be a string or null');
95
+ return {
96
+ taskId: text(input.taskId, 'taskId'), title: text(input.title, 'title'), outcome: 'cancelled',
97
+ reason: text(input.reason, 'reason'), cancelledAt: text(input.cancelledAt, 'cancelledAt'),
98
+ lastCompletedArtifact: input.lastCompletedArtifact === null ? null : text(input.lastCompletedArtifact, 'lastCompletedArtifact')
99
+ };
100
+ }
101
+
102
+ function correctCancelSummaryContent(value: unknown, sources: Readonly<{
103
+ taskId: string; title: string; reason: string; cancelledAt: string; lastCompletedArtifact: string | null;
104
+ }>): unknown {
105
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('cancel summary must be an object');
106
+ const input = { ...(value as Record<string, unknown>) };
107
+ fillText(input, 'taskId', sources.taskId);
108
+ if (input.taskId !== sources.taskId) throw new Error('taskId does not match the task');
109
+ fillText(input, 'title', sources.title);
110
+ fillText(input, 'reason', sources.reason);
111
+ fillText(input, 'cancelledAt', sources.cancelledAt);
112
+ if (input.lastCompletedArtifact === undefined
113
+ || (input.lastCompletedArtifact !== null && (typeof input.lastCompletedArtifact !== 'string' || !input.lastCompletedArtifact.trim()))) {
114
+ input.lastCompletedArtifact = sources.lastCompletedArtifact;
115
+ }
116
+ return input;
117
+ }
118
+
119
+ export function prepareAndRenderCancelSummaryContent(value: unknown, sources: Readonly<{
120
+ taskId: string; title: string; reason: string; cancelledAt: string; lastCompletedArtifact: string | null;
121
+ }>) {
122
+ return runSummaryContentPipeline(value, {
123
+ validate: parseCancelSummaryContent,
124
+ correct: (input) => correctCancelSummaryContent(input, sources),
125
+ render: renderCancelSummaryContent
126
+ });
127
+ }
128
+
129
+ export function renderCancelSummaryContent(value: CancelSummaryContent): string {
130
+ const content = parseCancelSummaryContent(value);
131
+ return [`# ${content.title}`, '', `结果:${content.outcome}`, '', `取消时间:${content.cancelledAt}`, '', `原因:${content.reason}`, '', `最近完成产物:${content.lastCompletedArtifact ?? '无'}`, ''].join('\n');
132
+ }
@@ -27,9 +27,7 @@ import {
27
27
  labelDelta,
28
28
  inLabelMappingDigest,
29
29
  planInLabelUpdate,
30
- syncLabelDelta,
31
30
  validateInLabelMapping,
32
- validateRepositoryLabelPayload
33
31
  } from './in-label-sync.ts';
34
32
  import {
35
33
  providerError,
@@ -37,7 +37,8 @@ async function coordinatePlatformWrite<T extends OperationResult>(input: Coordin
37
37
  });
38
38
  }
39
39
 
40
- const selection = input.operation.kind === 'issue-metadata' && input.operation.issueMetadata?.state === 'closed'
40
+ const boundedPr = input.operation.kind === 'pull-request' && input.operation.dependency === 'required';
41
+ const selection = boundedPr || input.operation.kind === 'issue-metadata' && input.operation.issueMetadata?.state === 'closed'
41
42
  ? 'all' : input.operation.dependency;
42
43
  const drained = input.drain
43
44
  ? await input.drain(input.operation.taskRef, operationId, input.agent, input.operation.cwd)
@@ -45,7 +46,8 @@ async function coordinatePlatformWrite<T extends OperationResult>(input: Coordin
45
46
  agent: input.agent,
46
47
  client: input.client,
47
48
  cwd: input.operation.cwd,
48
- excludeId: operationId
49
+ excludeId: operationId,
50
+ ...(boundedPr ? { beforeId: operationId } : {})
49
51
  });
50
52
  if (drained.status !== 'applied' && drained.status !== 'no-op') {
51
53
  return input.block(drained.error ? { ...drained.error, retryable: true } : {
@@ -7,7 +7,7 @@ import { isResourceIdentity } from './resource-identity.ts';
7
7
  import type { ResourceIdentity } from './resource-identity.ts';
8
8
 
9
9
  const REPORT_FILE_NAME = 'pr-change-report.json';
10
- const REPORT_VERSION = 2 as const;
10
+ const REPORT_VERSION = 3 as const;
11
11
  const CANONICAL_REPORT_PLACEHOLDER = '<!-- canonical-pr-change-report -->';
12
12
  const CANONICAL_REPORT_HEADING = '### PR 代码增减';
13
13
  const PRECHECK_IDS = [
@@ -71,11 +71,21 @@ type Evidence = {
71
71
  detail: string;
72
72
  };
73
73
 
74
+ type FunctionalGroup = {
75
+ id: string;
76
+ target: string;
77
+ behaviorChange: string;
78
+ necessity: string;
79
+ files: string[];
80
+ tests: { coverage: string; cost: string };
81
+ };
82
+
74
83
  type Precheck = {
75
84
  id: PrecheckId;
76
85
  verdict: CheckVerdict;
77
86
  evidence: Evidence[];
78
87
  rationale: string;
88
+ groups?: FunctionalGroup[];
79
89
  };
80
90
 
81
91
  type PrecheckCandidate = {
@@ -213,25 +223,136 @@ function parseEvidence(value: unknown, checkId: string, index: number): Validati
213
223
  return { ok: true, value: { path: value.path, startLine, endLine, detail: value.detail } };
214
224
  }
215
225
 
216
- function parseChecks(value: unknown, prefix = 'precheck.checks'): ValidationResult<Precheck[]> {
226
+ function parseFunctionalGroupFiles(value: unknown, prefix: string): ValidationResult<string[]> {
227
+ if (!Array.isArray(value) || value.length === 0) return invalid(`${prefix} must be a non-empty array`);
228
+ const files: string[] = [];
229
+ for (let index = 0; index < value.length; index += 1) {
230
+ const file = value[index];
231
+ if (!isSafeRenderedText(file)) return invalid(`${prefix}[${index}] must be a non-empty string`);
232
+ files.push(file.trim());
233
+ }
234
+ return { ok: true, value: files };
235
+ }
236
+
237
+ function parseFunctionalGroupTests(value: unknown, prefix: string): ValidationResult<FunctionalGroup['tests']> {
238
+ if (!isRecord(value)) return invalid(`${prefix} must be an object`);
239
+ if (!isSafeRenderedText(value.coverage)) return invalid(`${prefix}.coverage is required`);
240
+ if (!isSafeRenderedText(value.cost)) return invalid(`${prefix}.cost is required`);
241
+ return { ok: true, value: { coverage: value.coverage.trim(), cost: value.cost.trim() } };
242
+ }
243
+
244
+ function parseFunctionalGroup(value: unknown, prefix: string, ids: Set<string>): ValidationResult<FunctionalGroup> {
245
+ if (!isRecord(value)) return invalid(`${prefix} must be an object`);
246
+ if (Object.hasOwn(value, 'additions') || Object.hasOwn(value, 'deletions') || Object.hasOwn(value, 'net') || Object.hasOwn(value, 'counts')) {
247
+ return invalid(`${prefix} line counts are derived from diff files and must not be supplied`);
248
+ }
249
+ if (!isSafeRenderedText(value.id) || ids.has(value.id.trim())) return invalid(`${prefix}.id must be non-empty and unique`);
250
+ if (!isSafeRenderedText(value.target)) return invalid(`${prefix}.target is required`);
251
+ if (!isSafeRenderedText(value.behaviorChange)) return invalid(`${prefix}.behaviorChange is required`);
252
+ if (!isSafeRenderedText(value.necessity)) return invalid(`${prefix}.necessity is required`);
253
+ const files = parseFunctionalGroupFiles(value.files, `${prefix}.files`);
254
+ if (!files.ok) return files;
255
+ const tests = parseFunctionalGroupTests(value.tests, `${prefix}.tests`);
256
+ if (!tests.ok) return tests;
257
+ const id = value.id.trim();
258
+ ids.add(id);
259
+ return {
260
+ ok: true,
261
+ value: {
262
+ id, target: value.target.trim(), behaviorChange: value.behaviorChange.trim(),
263
+ necessity: value.necessity.trim(), files: files.value, tests: tests.value
264
+ }
265
+ };
266
+ }
267
+
268
+ function parseFunctionalGroups(value: unknown, prefix: string): ValidationResult<FunctionalGroup[]> {
269
+ if (!Array.isArray(value) || value.length === 0) return invalid(`${prefix} must be a non-empty array`);
270
+ const groups: FunctionalGroup[] = [];
271
+ const ids = new Set<string>();
272
+ for (let index = 0; index < value.length; index += 1) {
273
+ const group = parseFunctionalGroup(value[index], `${prefix}[${index}]`, ids);
274
+ if (!group.ok) return group;
275
+ groups.push(group.value);
276
+ }
277
+ return { ok: true, value: groups };
278
+ }
279
+
280
+ function indexDiffFiles(files: ChangeFile[]): ValidationResult<Map<string, ChangeFile>> {
281
+ const fileByPath = new Map<string, ChangeFile>();
282
+ for (const file of files) {
283
+ const key = file.oldPath ?? file.newPath;
284
+ if (!key || fileByPath.has(key)) return invalid(`diff file path '${key ?? ''}' is missing or duplicated`);
285
+ fileByPath.set(key, file);
286
+ }
287
+ return { ok: true, value: fileByPath };
288
+ }
289
+
290
+ function collectGroupMembership(
291
+ groups: FunctionalGroup[], fileByPath: Map<string, ChangeFile>
292
+ ): ValidationResult<Set<string>> {
293
+ const assigned = new Set<string>();
294
+ for (const group of groups) {
295
+ for (const member of group.files) {
296
+ const file = fileByPath.get(member);
297
+ if (!file) return invalid(`change-composition group '${group.id}' references unknown diff file '${member}'`);
298
+ if (assigned.has(member)) return invalid(`diff file '${member}' belongs to more than one functional group`);
299
+ assigned.add(member);
300
+ }
301
+ }
302
+ return { ok: true, value: assigned };
303
+ }
304
+
305
+ function validateGroupMembership(checks: Precheck[], files: ChangeFile[]): ValidationResult<Precheck[]> {
306
+ const composition = checks.find((check) => check.id === 'change-composition');
307
+ if (!composition?.groups) return invalid('precheck.checks.change-composition.groups is required');
308
+ const indexed = indexDiffFiles(files);
309
+ if (!indexed.ok) return indexed;
310
+ const assigned = collectGroupMembership(composition.groups, indexed.value);
311
+ if (!assigned.ok) return assigned;
312
+ const missing = [...indexed.value.keys()].find((key) => !assigned.value.has(key));
313
+ if (missing) return invalid(`diff file '${missing}' has no functional group`);
314
+ return { ok: true, value: checks };
315
+ }
316
+
317
+ function parseEvidenceList(value: unknown, checkId: PrecheckId, prefix: string): ValidationResult<Evidence[]> {
318
+ if (!Array.isArray(value) || value.length === 0) return invalid(`${prefix}.${checkId}.evidence must not be empty`);
319
+ const evidence: Evidence[] = [];
320
+ for (let index = 0; index < value.length; index += 1) {
321
+ const parsed = parseEvidence(value[index], checkId, index);
322
+ if (!parsed.ok) return parsed;
323
+ evidence.push(parsed.value);
324
+ }
325
+ return { ok: true, value: evidence };
326
+ }
327
+
328
+ function parseCheck(value: unknown, index: number, prefix: string): ValidationResult<Precheck> {
329
+ const expectedId = PRECHECK_IDS[index]!;
330
+ const entryPrefix = `${prefix}[${index}]`;
331
+ const at = `${prefix}.${expectedId}`;
332
+ if (!isRecord(value)) return invalid(`${entryPrefix} must be an object`);
333
+ if (value.id !== expectedId) return invalid(`${entryPrefix}.id must be '${expectedId}'`);
334
+ if (value.verdict !== 'pass' && value.verdict !== 'needs-review') return invalid(`${at}.verdict is invalid`);
335
+ const evidence = parseEvidenceList(value.evidence, expectedId, prefix);
336
+ if (!evidence.ok) return evidence;
337
+ if (!isSafeRenderedText(value.rationale)) return invalid(`${at}.rationale is required`);
338
+ if (expectedId !== 'change-composition') {
339
+ if (Object.hasOwn(value, 'groups')) return invalid(`${at}.groups is not allowed`);
340
+ return { ok: true, value: { id: expectedId, verdict: value.verdict, evidence: evidence.value, rationale: value.rationale } };
341
+ }
342
+ const groups = parseFunctionalGroups(value.groups, `${at}.groups`);
343
+ if (!groups.ok) return groups;
344
+ return { ok: true, value: { id: expectedId, verdict: value.verdict, evidence: evidence.value, rationale: value.rationale, groups: groups.value } };
345
+ }
346
+
347
+ function parseChecks(value: unknown, prefix = 'precheck.checks', diffFiles?: ChangeFile[]): ValidationResult<Precheck[]> {
217
348
  if (!Array.isArray(value) || value.length !== PRECHECK_IDS.length) return invalid(`${prefix} must contain exactly six checks`);
218
349
  const checks: Precheck[] = [];
219
350
  for (let index = 0; index < value.length; index += 1) {
220
- const raw = value[index];
221
- if (!isRecord(raw)) return invalid(`${prefix}[${index}] must be an object`);
222
- const expectedId = PRECHECK_IDS[index]!;
223
- if (raw.id !== expectedId) return invalid(`${prefix}[${index}].id must be '${expectedId}'`);
224
- if (raw.verdict !== 'pass' && raw.verdict !== 'needs-review') return invalid(`${prefix}.${expectedId}.verdict is invalid`);
225
- if (!Array.isArray(raw.evidence) || raw.evidence.length === 0) return invalid(`${prefix}.${expectedId}.evidence must not be empty`);
226
- const evidence: Evidence[] = [];
227
- for (let evidenceIndex = 0; evidenceIndex < raw.evidence.length; evidenceIndex += 1) {
228
- const parsed = parseEvidence(raw.evidence[evidenceIndex], expectedId, evidenceIndex);
229
- if (!parsed.ok) return parsed;
230
- evidence.push(parsed.value);
231
- }
232
- if (!isSafeRenderedText(raw.rationale)) return invalid(`${prefix}.${expectedId}.rationale is required`);
233
- checks.push({ id: expectedId, verdict: raw.verdict, evidence, rationale: raw.rationale });
351
+ const parsed = parseCheck(value[index], index, prefix);
352
+ if (!parsed.ok) return parsed;
353
+ checks.push(parsed.value);
234
354
  }
355
+ if (diffFiles) return validateGroupMembership(checks, diffFiles);
235
356
  return { ok: true, value: checks };
236
357
  }
237
358
 
@@ -306,7 +427,7 @@ function validatePrChangeReport(value: unknown): ValidationResult<PrChangeReport
306
427
  if (!totalsMatchFiles(totals.value, files)) return invalid('change report totals do not match files');
307
428
  if (value.precheck.formalReview !== false || (value.precheck.verdict !== 'clear' && value.precheck.verdict !== 'needs-review') ||
308
429
  (value.precheck.route !== 'watch-pr' && value.precheck.route !== 'review-code')) return invalid('change report precheck envelope is invalid');
309
- const checks = parseChecks(value.precheck.checks);
430
+ const checks = parseChecks(value.precheck.checks, 'precheck.checks', files);
310
431
  if (!checks.ok) return checks;
311
432
  const derivedVerdict: PrecheckVerdict = checks.value.every((check) => check.verdict === 'pass') ? 'clear' : 'needs-review';
312
433
  const derivedRoute: PrecheckRoute = derivedVerdict === 'clear' ? 'watch-pr' : 'review-code';
@@ -372,10 +493,6 @@ function buildPrChangeReport(
372
493
  });
373
494
  }
374
495
 
375
- function reportFilePath(taskDir: string): string {
376
- return path.join(taskDir, REPORT_FILE_NAME);
377
- }
378
-
379
496
  function readPrChangeReport(file: string): ValidationResult<PrChangeReport> {
380
497
  try {
381
498
  const stat = fs.lstatSync(file);
@@ -556,10 +673,34 @@ function renderEvidence(evidence: Evidence): string {
556
673
  return ` - 证据:<code>${escapeReportText(evidence.path)}</code>${location}:${escapeReportText(evidence.detail)}`;
557
674
  }
558
675
 
559
- function renderPrechecks(checks: Precheck[]): string[] {
676
+ function renderFunctionalGroups(groups: FunctionalGroup[], files: ChangeFile[]): string[] {
677
+ const filesByPath = new Map(files.map((file) => [file.oldPath ?? file.newPath!, file]));
678
+ return groups.flatMap((group) => {
679
+ const members = group.files.map((member) => filesByPath.get(member)!);
680
+ const additions = members.reduce((total, file) => total + (file.additions ?? 0), 0);
681
+ const deletions = members.reduce((total, file) => total + (file.deletions ?? 0), 0);
682
+ const unknownFiles = members.filter((file) => file.additions === null).length;
683
+ return [
684
+ ` - **功能组 ${escapeReportText(group.id)}**:${escapeReportText(group.target)}`,
685
+ ` - 行为变化:${escapeReportText(group.behaviorChange)}`,
686
+ ` - 必要性:${escapeReportText(group.necessity)}`,
687
+ ` - 文件:${group.files.map((member) => {
688
+ const file = filesByPath.get(member)!;
689
+ return file.oldPath && file.newPath && file.oldPath !== file.newPath
690
+ ? `<code>${escapeReportText(file.oldPath)}</code> → <code>${escapeReportText(file.newPath)}</code>`
691
+ : `<code>${escapeReportText(member)}</code>`;
692
+ }).join('、')}`,
693
+ ` - 可计数行数(由正式 diff 逐文件推导):新增 ${additions}、删除 ${deletions}、净增 ${additions - deletions}${unknownFiles ? `;另有 ${unknownFiles} 个文件行数不可计` : ''}。`,
694
+ ` - 测试覆盖:${escapeReportText(group.tests.coverage)};成本:${escapeReportText(group.tests.cost)}`
695
+ ];
696
+ });
697
+ }
698
+
699
+ function renderPrechecks(checks: Precheck[], files: ChangeFile[]): string[] {
560
700
  return checks.flatMap((check) => [
561
701
  `- **${PRECHECK_LABELS[check.id]}(${check.id})**:${check.verdict === 'pass' ? '通过' : '需复核'}。${escapeReportText(check.rationale)}`,
562
- ...check.evidence.map(renderEvidence)
702
+ ...check.evidence.map(renderEvidence),
703
+ ...(check.groups ? renderFunctionalGroups(check.groups, files) : [])
563
704
  ]);
564
705
  }
565
706
 
@@ -593,7 +734,7 @@ function renderCanonicalChangeReport(report: PrChangeReport): string {
593
734
  '',
594
735
  '#### 适宜性预检',
595
736
  `- 结论:${precheckSummary};${route};正式审查:否。`,
596
- ...renderPrechecks(report.precheck.checks)
737
+ ...renderPrechecks(report.precheck.checks, report.diff.files)
597
738
  ].join('\n');
598
739
  }
599
740
 
@@ -620,7 +761,6 @@ export {
620
761
  readPrChangeReport,
621
762
  renderCanonicalChangeReport,
622
763
  replaceCanonicalReportPlaceholder,
623
- reportFilePath,
624
764
  runMechanicalChangeReport,
625
765
  taskIntentDigest,
626
766
  taskIntentProjection,
@@ -15,7 +15,6 @@ import {
15
15
  providerError,
16
16
  providerOperationContext,
17
17
  providerStatus,
18
- resourceIdentity,
19
18
  resourceIdentityNumber,
20
19
  unsupportedProviderOperation
21
20
  } from './provider-bridge.ts';
@@ -56,14 +55,6 @@ function classifyPullRequestReadiness(input: {
56
55
  return { state: 'ready', headSha: input.headSha };
57
56
  }
58
57
 
59
- function resolveRunCandidate(candidates: RunCandidate[], headSha: string, checkName: string):
60
- | { status: 'resolved'; runId: number; jobId: number | null }
61
- | { status: 'missing' | 'ambiguous'; runId: null; jobId: null } {
62
- const matches = candidates.filter((candidate) => candidate.headSha === headSha && candidate.name === checkName);
63
- if (matches.length === 1) return { status: 'resolved', runId: matches[0]!.id, jobId: matches[0]!.jobId || null };
64
- return { status: matches.length === 0 ? 'missing' : 'ambiguous', runId: null, jobId: null };
65
- }
66
-
67
58
  function checksResult(status: PlatformResult['status'], overrides: Partial<ChecksResult> = {}): ChecksResult {
68
59
  return {
69
60
  ...platformResult(status),
@@ -256,5 +247,5 @@ async function fetchPlatformCheckLogs(taskRef: string, options: SharedOptions &
256
247
  }
257
248
  }
258
249
 
259
- export { classifyPullRequestReadiness, classifyRequiredChecks, fetchPlatformCheckLogs, inspectRequiredChecks, inspectPullRequestReadiness, resolvePlatformCheckRun, resolveRunCandidate, watchPullRequestReadiness };
250
+ export { classifyPullRequestReadiness, classifyRequiredChecks, fetchPlatformCheckLogs, inspectRequiredChecks, inspectPullRequestReadiness, resolvePlatformCheckRun, watchPullRequestReadiness };
260
251
  export type { CheckBucket, CheckSnapshot, ChecksResult, ChecksSnapshot, CheckState, ReadinessSnapshot, ReadinessState, RunCandidate };
@@ -7,7 +7,7 @@ import { resolvePlatformProviderContext } from './context.ts';
7
7
  import type { PlatformClient } from './context.ts';
8
8
  import { providerErrorRetryable } from './provider-validation.ts';
9
9
  import { platformResult } from './types.ts';
10
- import type { PlatformOperation, PlatformResult } from './types.ts';
10
+ import type { PlatformResult } from './types.ts';
11
11
  import {
12
12
  providerError,
13
13
  providerOperationContext,
@@ -0,0 +1,60 @@
1
+ import { CANONICAL_REPORT_PLACEHOLDER } from './pr-change-report.ts';
2
+ import { MANUAL_VALIDATION_RECEIPT_PLACEHOLDER } from '../task/manual-validation-receipt.ts';
3
+
4
+ export type PrSummaryContent = Readonly<{
5
+ overview: string;
6
+ tests: Readonly<{ state: string; detail: string }>;
7
+ review: Readonly<{ state: string; detail: string }>;
8
+ manualValidation: Readonly<{ state: string; detail: string }>;
9
+ humanDecisions: Readonly<{ open: number; resolved: number; detail: string }>;
10
+ risks: string;
11
+ }>;
12
+
13
+ function nonEmpty(value: unknown, field: string): string {
14
+ if (typeof value !== 'string' || !value.trim()) throw new Error(`${field} must be a non-empty string`);
15
+ return value.trim();
16
+ }
17
+
18
+ export function parsePrSummaryContent(value: unknown): PrSummaryContent {
19
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('PR summary must be an object');
20
+ const input = value as Record<string, unknown>;
21
+ const pair = (key: 'tests' | 'review' | 'manualValidation') => {
22
+ const item = input[key];
23
+ if (!item || typeof item !== 'object' || Array.isArray(item)) throw new Error(`${key} must be an object`);
24
+ const fields = item as Record<string, unknown>;
25
+ return { state: nonEmpty(fields.state, `${key}.state`), detail: nonEmpty(fields.detail, `${key}.detail`) };
26
+ };
27
+ const decisions = input.humanDecisions;
28
+ if (!decisions || typeof decisions !== 'object' || Array.isArray(decisions)) throw new Error('humanDecisions must be an object');
29
+ const counts = decisions as Record<string, unknown>;
30
+ for (const key of ['open', 'resolved'] as const) {
31
+ if (!Number.isSafeInteger(counts[key]) || Number(counts[key]) < 0) throw new Error(`humanDecisions.${key} must be a non-negative integer`);
32
+ }
33
+ return {
34
+ overview: nonEmpty(input.overview, 'overview'),
35
+ tests: pair('tests'), review: pair('review'), manualValidation: pair('manualValidation'),
36
+ humanDecisions: { open: counts.open as number, resolved: counts.resolved as number, detail: nonEmpty(counts.detail, 'humanDecisions.detail') },
37
+ risks: nonEmpty(input.risks, 'risks')
38
+ };
39
+ }
40
+
41
+ export function renderPrSummaryContent(value: PrSummaryContent): string {
42
+ const content = parsePrSummaryContent(value);
43
+ const manualValidationHeading = content.manualValidation.state === 'required' ? '⚠️ 需人工校验'
44
+ : content.manualValidation.state === 'pending' ? '⏳ 人工验证待收尾'
45
+ : content.manualValidation.state === 'passed' ? '✅ 人工验证已通过'
46
+ : ['none', 'not-required'].includes(content.manualValidation.state) ? '✅ 无需人工校验'
47
+ : content.manualValidation.state === 'unavailable' ? '⚠️ 人工校验状态不可用'
48
+ : content.manualValidation.state === 'not-observed' ? 'ℹ️ 尚未读取人工校验状态'
49
+ : `人工校验状态:${content.manualValidation.state}`;
50
+ return [
51
+ '## 审查摘要', '', content.overview, '',
52
+ '### 关键技术决策', '', '(见审查历程)', '',
53
+ '### 审查历程', '', `状态:${content.review.state}`, '', content.review.detail, '',
54
+ '### 测试结果', '', `状态:${content.tests.state}`, '', content.tests.detail, '',
55
+ `### ${manualValidationHeading}`, '', `状态:${content.manualValidation.state}`, '', content.manualValidation.detail,
56
+ ...(content.manualValidation.state === 'passed' ? ['', MANUAL_VALIDATION_RECEIPT_PLACEHOLDER] : []), '',
57
+ '### 人工决策', '', `未解决:${content.humanDecisions.open};已解决:${content.humanDecisions.resolved}`, '', content.humanDecisions.detail, '',
58
+ '### 风险', '', content.risks, '', CANONICAL_REPORT_PLACEHOLDER, ''
59
+ ].join('\n');
60
+ }