@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
@@ -3,13 +3,15 @@ import path from 'node:path';
3
3
  import { createHash } from 'node:crypto';
4
4
  import { execFileSync } from 'node:child_process';
5
5
 
6
- import { inspectPlatformCommentOperation, normalizeCommentContent, syncPlatformComment } from '../platform/issue-comments.ts';
6
+ import { inspectPlatformCommentOperation, syncPlatformComment } from '../platform/issue-comments.ts';
7
+ import { parseDeliverySummaryContent, renderDeliverySummaryContent, parseCancelSummaryContent, renderCancelSummaryContent } from '../platform/issue-summary-content.ts';
8
+ import { enumerateArtifacts } from './artifacts.ts';
7
9
  import { inspectPlatformIssue, syncPlatformIssue } from '../platform/issues.ts';
8
- import { resolvePlatformProviderContext } from '../platform/context.ts';
9
- import { providerOperationContext, providerError, unsupportedProviderOperation } from '../platform/provider-bridge.ts';
10
+ import '../platform/context.ts';
11
+ import '../platform/provider-bridge.ts';
10
12
  import { inLabelMappingDigest } from '../platform/in-label-sync.ts';
11
- import { bindPlatformPullRequest, inspectPlatformPullRequest, recoverCreatedPullRequest, syncPlatformPullRequest } from '../platform/pull-requests.ts';
12
- import { syncPullRequestSummary } from '../platform/pr-summary.ts';
13
+ import { bindPlatformPullRequest, recoverCreatedPullRequest, syncPlatformPullRequest } from '../platform/pull-requests.ts';
14
+ import { replayPullRequestSummary } from '../platform/pr-summary.ts';
13
15
  import { publishPrReview, readReviewBodyFile } from '../platform/pr-review.ts';
14
16
  import type { PlatformClient } from '../platform/context.ts';
15
17
  import { parseTaskFrontmatter } from './frontmatter.ts';
@@ -19,11 +21,12 @@ import { operationId } from './platform-operation-journal.ts';
19
21
  import { canonicalizeSummaryBody } from '../platform/comment-safety.ts';
20
22
  import { platformResult } from '../platform/types.ts';
21
23
  import type { PlatformResult } from '../platform/types.ts';
24
+ import { mergeOperationWarnings, type OperationWarning } from './operation-outcome.ts';
22
25
  import { resolveTaskRef } from './resolve-ref.ts';
23
26
  import { readPlatformOperationJournal, recordPlatformOperation } from './platform-operation-journal.ts';
24
- import { providerErrorRetryable } from '../platform/provider-validation.ts';
27
+ import '../platform/provider-validation.ts';
25
28
 
26
- type RecoveryOptions = Readonly<{ agent: string; client?: PlatformClient; cwd?: string; limit?: number; attempts?: number; excludeId?: string }>;
29
+ type RecoveryOptions = Readonly<{ agent: string; client?: PlatformClient; cwd?: string; limit?: number; attempts?: number; excludeId?: string; beforeId?: string }>;
27
30
  const DEFAULT_RECOVERY_ATTEMPTS = 3;
28
31
  type RecoveryResult = Readonly<{
29
32
  status: 'applied' | 'no-op' | 'blocked' | 'failed';
@@ -31,6 +34,8 @@ type RecoveryResult = Readonly<{
31
34
  recovered: readonly string[];
32
35
  pending: readonly string[];
33
36
  error: { code: string; message: string } | null;
37
+ degraded?: readonly { id: string; reasonCode: string }[];
38
+ warnings?: readonly OperationWarning[];
34
39
  }>;
35
40
  type RecoveryError = { code: string; message: string; retryable?: boolean };
36
41
 
@@ -46,40 +51,64 @@ function fieldsMatchExpected(
46
51
  return Object.entries(expected).every(([name, value]) => actual[name] === value);
47
52
  }
48
53
 
49
- function result(status: RecoveryResult['status'], recovered: string[], pending: string[], error: RecoveryError | null = null): RecoveryResult {
50
- return { status, changed: recovered.length > 0, recovered, pending, error: error ? { code: error.code, message: error.message } : null };
54
+ function result(status: RecoveryResult['status'], recovered: string[], pending: string[], error: RecoveryError | null = null, warnings: readonly OperationWarning[] = []): RecoveryResult {
55
+ return { status, changed: recovered.length > 0, recovered, pending, error: error ? { code: error.code, message: error.message } : null, ...(warnings.length ? { warnings } : {}) };
51
56
  }
52
57
 
53
58
  function summaryPayload(taskDir: string, taskId: string): { body: string; sha256: string } | null {
54
59
  const file = path.join(taskDir, '.delivery-summary.json');
55
60
  if (!fs.existsSync(file)) return null;
56
- const data = JSON.parse(fs.readFileSync(file, 'utf8')) as { taskId?: unknown; body?: unknown; sha256?: unknown };
57
- const canonical = typeof data.body === 'string' ? canonicalizeSummaryBody(data.body) : null;
58
- if (data.taskId !== taskId || typeof data.body !== 'string' || typeof data.sha256 !== 'string'
59
- || !canonical?.ok || canonical.value !== data.body
60
- || createHash('sha256').update(data.body).digest('hex') !== data.sha256) {
61
+ const data = JSON.parse(fs.readFileSync(file, 'utf8')) as { taskId?: unknown; content?: unknown; contentSha256?: unknown };
62
+ let content;
63
+ try { content = parseDeliverySummaryContent(data.content); }
64
+ catch { content = null; }
65
+ if (data.taskId !== taskId || !content || content.taskId !== taskId || typeof data.contentSha256 !== 'string'
66
+ || createHash('sha256').update(JSON.stringify(content)).digest('hex') !== data.contentSha256) {
61
67
  throw Object.assign(new Error('delivery summary staging record is invalid'), { code: 'SUMMARY_STAGING_INVALID' });
62
68
  }
63
- return { body: data.body, sha256: data.sha256 };
69
+ const canonical = canonicalizeSummaryBody(renderDeliverySummaryContent(content));
70
+ if (!canonical?.ok) throw Object.assign(new Error('delivery summary staging body is not canonical'), { code: 'SUMMARY_STAGING_INVALID' });
71
+ return { body: canonical.value, sha256: createHash('sha256').update(canonical.value).digest('hex') };
72
+ }
73
+
74
+ function cancellationPayload(taskDir: string, taskId: string): string | null {
75
+ const taskPath = path.join(taskDir, 'task.md');
76
+ if (!fs.existsSync(taskPath)) return null;
77
+ const taskContent = fs.readFileSync(taskPath, 'utf8');
78
+ const frontmatter = parseTaskFrontmatter(taskContent);
79
+ const title = /^#\s+(.+)$/mu.exec(taskContent)?.[1]?.trim() || taskId;
80
+ const artifacts = enumerateArtifacts(taskDir).filter((artifact) => artifact.name !== 'task.md').map((artifact) => artifact.name);
81
+ const content = parseCancelSummaryContent({
82
+ taskId,
83
+ title,
84
+ outcome: 'cancelled',
85
+ reason: frontmatter.cancel_reason,
86
+ cancelledAt: frontmatter.cancelled_at,
87
+ lastCompletedArtifact: artifacts.at(-1) ?? null
88
+ });
89
+ return renderCancelSummaryContent(content);
64
90
  }
65
91
 
66
92
  async function replayComment(taskId: string, taskDir: string, operation: ReturnType<typeof readPlatformOperationJournal>['operations'][number], agent: string, cwd: string, client?: PlatformClient): Promise<PlatformResult> {
67
93
  if (operation.kind === 'task-comment') {
68
- return syncPlatformComment(taskId, { kind: 'task', agent, cwd, client, dependency: operation.dependency, skipQueue: true });
94
+ return syncPlatformComment(taskId, { kind: 'task', agent, cwd, client, dependency: operation.dependency, queuedOperation: operation });
69
95
  }
70
96
  if (operation.kind === 'artifact-comment') {
71
- return syncPlatformComment(taskId, { kind: 'artifact', artifact: operation.target, agent, cwd, client, dependency: operation.dependency, skipQueue: true });
97
+ return syncPlatformComment(taskId, { kind: 'artifact', artifact: operation.target, agent, cwd, client, dependency: operation.dependency, queuedOperation: operation });
72
98
  }
73
99
  if (operation.kind === 'summary-comment') {
74
- const summary = summaryPayload(taskDir, taskId);
75
- if (!summary) return platformResult('failed', { error: { code: 'SUMMARY_STAGING_MISSING', message: 'delivery summary staging record is unavailable', retryable: false } });
76
- return syncPlatformComment(taskId, {
77
- kind: 'summary', body: summary.body, agent, cwd,
78
- summaryAuthorization: { sha256: summary.sha256 }, dependency: operation.dependency, client, skipQueue: true
79
- });
100
+ let summary;
101
+ try { summary = summaryPayload(taskDir, taskId); }
102
+ catch (error) { return platformResult('failed', { error: { code: 'SUMMARY_STAGING_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false } }); }
103
+ if (!summary) return platformResult('blocked', { error: { code: 'SUMMARY_STAGING_MISSING', message: 'delivery summary staging record is unavailable', retryable: true } });
104
+ return syncPlatformComment(taskId, { kind: 'summary', body: summary.body, agent, cwd, client, summaryAuthorization: { sha256: summary.sha256 }, dependency: operation.dependency, queuedOperation: operation });
80
105
  }
81
106
  if (operation.kind === 'cancel-comment') {
82
- return syncPlatformComment(taskId, { kind: 'cancel', agent, cwd, client, dependency: operation.dependency, skipQueue: true });
107
+ let body: string | null;
108
+ try { body = cancellationPayload(taskDir, taskId); }
109
+ catch (error) { return platformResult('failed', { error: { code: 'CANCEL_SUMMARY_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false } }); }
110
+ if (!body) return platformResult('blocked', { error: { code: 'CANCEL_SUMMARY_INVALID', message: 'Cancellation facts are unavailable for recovery', retryable: true } });
111
+ return syncPlatformComment(taskId, { kind: 'cancel', body, agent, cwd, client, dependency: operation.dependency, queuedOperation: operation });
83
112
  }
84
113
  return platformResult('failed', { error: { code: 'PLATFORM_OPERATION_UNSUPPORTED', message: `operation kind '${operation.kind}' has no registered recovery handler`, retryable: false } });
85
114
  }
@@ -138,7 +167,8 @@ async function replayIssueMetadata(taskId: string, operation: ReturnType<typeof
138
167
  }
139
168
  const issue = after.issue;
140
169
  const metadata = operation.issueMetadata;
141
- const failedOperation = sync.operations.find((item) => item.status === 'failed' || item.status === 'skipped');
170
+ const failedOperation = sync.operations.find((item) => item.status === 'failed' || (item.status === 'skipped'
171
+ && !(sync.capabilities.push === false && ['fields', 'issue-type'].includes(item.name) && item.reasonCode === 'PUSH_REQUIRED')));
142
172
  if (failedOperation) return platformResult('blocked', { error: {
143
173
  code: failedOperation.reasonCode || 'PLATFORM_OPERATION_METADATA_UNCONFIRMED',
144
174
  message: `Issue metadata recovery did not confirm operation '${failedOperation.name}'`, retryable: true
@@ -163,7 +193,7 @@ async function replayIssueMetadata(taskId: string, operation: ReturnType<typeof
163
193
  code: 'PLATFORM_OPERATION_METADATA_UNCONFIRMED',
164
194
  message: 'Issue metadata differs from the requested recovery target after reread', retryable: true
165
195
  } });
166
- return platformResult('no-op', { changed: sync.changed || after.changed });
196
+ return platformResult('no-op', { changed: sync.changed || after.changed, operations: sync.operations });
167
197
  }
168
198
 
169
199
  async function verifyIssueCreate(taskId: string, operation: ReturnType<typeof readPlatformOperationJournal>['operations'][number], cwd: string, client?: PlatformClient): Promise<PlatformResult> {
@@ -183,55 +213,6 @@ async function verifyIssueCreate(taskId: string, operation: ReturnType<typeof re
183
213
  return platformResult('no-op');
184
214
  }
185
215
 
186
- async function replayPullRequestSummary(taskId: string, operation: ReturnType<typeof readPlatformOperationJournal>['operations'][number], agent: string, cwd: string, client?: PlatformClient): Promise<PlatformResult> {
187
- let identity;
188
- try { identity = parseResourceIdentity(JSON.parse(operation.target), 'queued pull-request summary identity'); }
189
- catch { return platformResult('failed', { error: { code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Queued pull-request summary identity is invalid', retryable: false } }); }
190
- const summary = operation.pullRequestSummary;
191
- if (!summary) return platformResult('blocked', { error: {
192
- code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Pull-request summary recovery content is missing', retryable: false
193
- } });
194
- const manualValidationHeading = /^###\s+(?:⏳\s+(?:Manual Validation Pending|人工验证待收尾)|✅\s+(?:Manual Validation Passed|人工验证已通过))\s*$/mu.test(summary.body);
195
- if (manualValidationHeading && !summary.manualValidation) return platformResult('blocked', { error: {
196
- code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'Queued manual-validation summary has no persisted transaction context', retryable: false
197
- } });
198
- const resolved = await resolvePlatformProviderContext({ cwd, client });
199
- if (!resolved.ok) return resolved.context;
200
- const listed = resolved.value.provider.comments?.list
201
- ? await resolved.value.provider.comments.list({ context: providerOperationContext(resolved.value), parent: identity })
202
- : unsupportedProviderOperation(resolved.value.provider, 'comments.list');
203
- if (!listed.ok) return platformResult(providerErrorRetryable(listed.error.code) ? 'blocked' : 'failed', { error: providerError(listed.error, 'PLATFORM_PROVIDER_OPERATION_FAILED') });
204
- const marker = `<!-- sync-pr:${taskId}:summary -->`;
205
- const summaries = listed.value.filter((comment) => normalizeCommentContent(comment.body).split('\n', 1)[0] === marker);
206
- if (summaries.length > 1) return platformResult('blocked', { error: {
207
- code: 'PR_SUMMARY_MARKER_AMBIGUOUS', message: 'Multiple pull-request comments contain the queued summary marker', retryable: false
208
- } });
209
- if (!summary.manualValidation && summaries.length === 1 && createHash('sha256').update(normalizeCommentContent(summaries[0]!.body)).digest('hex') === operation.expectedDigest) {
210
- return platformResult('no-op');
211
- }
212
- const bound = await inspectPlatformPullRequest(taskId, { cwd, client });
213
- if (!bound.pullRequest || !resourceIdentityEquals(bound.pullRequest.identity, identity)) return platformResult('blocked', { error: bound.error ?? {
214
- code: 'PLATFORM_OPERATION_IDENTITY_MISMATCH', message: 'Bound pull request differs from the queued summary target', retryable: false
215
- } });
216
- const replayed = await syncPullRequestSummary(taskId, {
217
- agent,
218
- cwd,
219
- client,
220
- body: summary.body,
221
- changeReportFile: summary.changeReportFile,
222
- ...(summary.manualValidation ? { manualValidation: summary.manualValidation } : {}),
223
- primaryResult: 'no_op',
224
- strict: true,
225
- skipQueue: true
226
- });
227
- if ((replayed.status === 'applied' || replayed.status === 'no-op') && !replayed.error && replayed.warnings.length === 0) {
228
- return platformResult(replayed.status, { changed: replayed.changed });
229
- }
230
- return platformResult('blocked', { error: replayed.error ?? {
231
- code: 'PR_SUMMARY_RECOVERY_UNCONFIRMED', message: 'Pull-request summary replay did not confirm the requested remote state', retryable: true
232
- } });
233
- }
234
-
235
216
  async function replayPullRequestReview(operation: ReturnType<typeof readPlatformOperationJournal>['operations'][number], agent: string, cwd: string, client?: PlatformClient): Promise<PlatformResult> {
236
217
  const intent = operation.pullRequestReview;
237
218
  if (!intent) return platformResult('failed', { error: { code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Pull-request review recovery parameters are missing', retryable: false } });
@@ -280,6 +261,20 @@ async function replayPullRequest(taskId: string, operation: ReturnType<typeof re
280
261
  return recoverCreatedPullRequest(taskId, { agent, cwd, base: intent.baseRef || '', head: intent.headRef || '', skipQueue: true });
281
262
  }
282
263
 
264
+ function isRecoveryEligible(operation: ReturnType<typeof readPlatformOperationJournal>['operations'][number], excludeId?: string): boolean {
265
+ if (operation.id === excludeId) return false;
266
+ if (operation.state === 'queued' || operation.state === 'pending' || operation.state === 'unknown') return true;
267
+ return operation.state === 'failed'
268
+ && operation.lastCode !== 'PLATFORM_OPERATION_SUPERSEDED'
269
+ ;
270
+ }
271
+
272
+ function recoveryCandidates(journal: ReturnType<typeof readPlatformOperationJournal>, options: RecoveryOptions) {
273
+ const boundary = options.beforeId ? journal.operations.findIndex((item) => item.id === options.beforeId) : journal.operations.length;
274
+ if (boundary < 0) throw Object.assign(new Error('Current platform operation boundary is unavailable'), { code: 'PLATFORM_OPERATION_BOUNDARY_MISSING' });
275
+ return journal.operations.slice(0, boundary).filter((item) => isRecoveryEligible(item, options.excludeId));
276
+ }
277
+
283
278
  async function recoverPlatformOperationsOnce(
284
279
  taskRef: string,
285
280
  selection: 'required' | 'deferred' | 'all',
@@ -293,9 +288,9 @@ async function recoverPlatformOperationsOnce(
293
288
  const value = error as { code?: string; message?: string };
294
289
  return result('failed', [], [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false });
295
290
  }
296
- const candidates = journal.operations.filter((operation) => operation.id !== options.excludeId
297
- && (operation.state === 'queued' || operation.state === 'pending' || operation.state === 'unknown'
298
- || (operation.state === 'failed' && operation.lastCode !== 'PLATFORM_OPERATION_SUPERSEDED')));
291
+ let candidates;
292
+ try { candidates = recoveryCandidates(journal, options); }
293
+ catch (error) { const value = error as { code: string; message: string }; return result('blocked', [], [], value); }
299
294
  if (!candidates.length) return result('no-op', [], []);
300
295
  const head = candidates[0]!;
301
296
  if (selection !== 'all' && head.dependency !== selection) {
@@ -308,8 +303,9 @@ async function recoverPlatformOperationsOnce(
308
303
  const limit = options.limit === undefined ? candidates.length : Math.max(1, options.limit);
309
304
  const recovered: string[] = [];
310
305
  const pending: string[] = [];
306
+ let warnings: readonly OperationWarning[] = [];
311
307
  const selected = candidates.slice(0, limit);
312
- for (const [index, operation] of selected.entries()) {
308
+ for (const operation of selected) {
313
309
  {
314
310
  try {
315
311
  recordPlatformOperation({
@@ -357,11 +353,18 @@ async function recoverPlatformOperationsOnce(
357
353
  const commentOptions = operation.kind === 'task-comment'
358
354
  ? { kind: 'task' as const, agent: options.agent, cwd: resolved.repoRoot, dependency: operation.dependency }
359
355
  : operation.kind === 'artifact-comment'
360
- ? { kind: 'artifact' as const, artifact: operation.target, agent: options.agent, cwd: resolved.repoRoot, dependency: operation.dependency }
361
- : operation.kind === 'summary-comment' ? (() => {
362
- const summary = summaryPayload(resolved.taskDir, resolved.taskId);
363
- return summary ? { kind: 'summary' as const, body: summary.body, agent: options.agent, cwd: resolved.repoRoot, summaryAuthorization: { sha256: summary.sha256 }, dependency: operation.dependency } : null;
364
- })() : { kind: 'cancel' as const, agent: options.agent, cwd: resolved.repoRoot, dependency: operation.dependency };
356
+ ? { kind: 'artifact' as const, artifact: operation.target, agent: options.agent, cwd: resolved.repoRoot, dependency: operation.dependency }
357
+ : operation.kind === 'summary-comment' ? (() => {
358
+ try {
359
+ const summary = summaryPayload(resolved.taskDir, resolved.taskId);
360
+ return summary ? { kind: 'summary' as const, body: summary.body, agent: options.agent, cwd: resolved.repoRoot, summaryAuthorization: { sha256: summary.sha256 }, dependency: operation.dependency } : null;
361
+ } catch { return null; }
362
+ })() : (() => {
363
+ try {
364
+ const body = cancellationPayload(resolved.taskDir, resolved.taskId);
365
+ return body ? { kind: 'cancel' as const, body, agent: options.agent, cwd: resolved.repoRoot, dependency: operation.dependency } : null;
366
+ } catch { return null; }
367
+ })();
365
368
  const current = commentOptions ? inspectPlatformCommentOperation(resolved.taskId, commentOptions) : null;
366
369
  if (!current) {
367
370
  pending.push(operation.id);
@@ -378,25 +381,26 @@ async function recoverPlatformOperationsOnce(
378
381
  }
379
382
  }
380
383
  }
381
- let remote: PlatformResult;
384
+ let remote: PlatformResult & { warnings?: readonly OperationWarning[] };
382
385
  try {
383
386
  remote = operation.kind === 'issue-create'
384
387
  ? await verifyIssueCreate(resolved.taskId, operation, resolved.repoRoot)
385
388
  : operation.kind === 'pull-request-summary'
386
- ? await replayPullRequestSummary(resolved.taskId, operation, options.agent, resolved.repoRoot, options.client)
389
+ ? await replayPullRequestSummary(resolved.taskId, targetOperation, { agent: options.agent, cwd: resolved.repoRoot, client: options.client })
387
390
  : operation.kind === 'pull-request-review'
388
391
  ? await replayPullRequestReview(operation, options.agent, resolved.repoRoot, options.client)
389
392
  : operation.kind === 'issue-metadata'
390
393
  ? await replayIssueMetadata(resolved.taskId, targetOperation, options.agent, resolved.repoRoot)
391
394
  : operation.kind === 'pull-request'
392
395
  ? await replayPullRequest(resolved.taskId, targetOperation, options.agent, resolved.repoRoot)
393
- : await replayComment(resolved.taskId, resolved.taskDir, operation, options.agent, resolved.repoRoot);
396
+ : await replayComment(resolved.taskId, resolved.taskDir, targetOperation, options.agent, resolved.repoRoot, options.client);
394
397
  }
395
398
  catch (error) {
396
399
  const value = error as { code?: string; message?: string };
397
400
  remote = platformResult('blocked', { error: { code: value.code || 'PLATFORM_OPERATION_RECOVERY_FAILED', message: value.message || String(error), retryable: true } });
398
401
  }
399
402
  const succeeded = (remote.status === 'applied' || remote.status === 'no-op') && !remote.error;
403
+ const latestOperation = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot).operations.find((item) => item.id === operation.id);
400
404
  try {
401
405
  recordPlatformOperation({
402
406
  taskRef: resolved.taskId,
@@ -411,7 +415,7 @@ async function recoverPlatformOperationsOnce(
411
415
  ...(operation.pullRequestReview ? { pullRequestReview: operation.pullRequestReview } : {}),
412
416
  dependency: operation.dependency,
413
417
  state: succeeded ? 'succeeded' : 'unknown',
414
- lastCode: remote.error?.code ?? null
418
+ lastCode: remote.error?.code ?? remote.operations.find((item) => item.status === 'skipped' && item.reasonCode === 'PUSH_REQUIRED')?.reasonCode ?? (succeeded ? null : latestOperation?.lastCode) ?? null
415
419
  });
416
420
  } catch (error) {
417
421
  const value = error as { code?: string; message?: string };
@@ -421,11 +425,12 @@ async function recoverPlatformOperationsOnce(
421
425
  pending.push(targetOperation.id);
422
426
  return result(remote.status === 'failed' ? 'failed' : 'blocked', recovered, pending, remote.error ?? { code: 'PLATFORM_OPERATION_RECOVERY_UNKNOWN', message: 'Platform operation outcome remains unknown', retryable: true });
423
427
  }
428
+ warnings = mergeOperationWarnings(warnings, remote.warnings ?? []);
424
429
  recovered.push(targetOperation.id);
425
430
  }
426
431
  const remaining = candidates.slice(limit).map((operation) => operation.id);
427
432
  pending.push(...remaining);
428
- return result(pending.length ? 'blocked' : recovered.length ? 'applied' : 'no-op', recovered, pending, pending.length ? { code: 'PLATFORM_OPERATION_RECOVERY_PENDING', message: 'Platform operations remain pending after the recovery budget', retryable: true } : null);
433
+ return result(pending.length ? 'blocked' : recovered.length ? 'applied' : 'no-op', recovered, pending, pending.length ? { code: 'PLATFORM_OPERATION_RECOVERY_PENDING', message: 'Platform operations remain pending after the recovery budget', retryable: true } : null, warnings);
429
434
  }
430
435
 
431
436
  async function recoverPlatformOperations(
@@ -441,10 +446,9 @@ async function recoverPlatformOperations(
441
446
  const value = error as { code?: string; message?: string };
442
447
  return result('failed', [], [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false });
443
448
  }
444
- const eligible = (operation: typeof initial.operations[number]) => operation.id !== options.excludeId
445
- && (operation.state === 'queued' || operation.state === 'pending' || operation.state === 'unknown'
446
- || (operation.state === 'failed' && operation.lastCode !== 'PLATFORM_OPERATION_SUPERSEDED'));
447
- const first = initial.operations.filter(eligible);
449
+ let first;
450
+ try { first = recoveryCandidates(initial, options); }
451
+ catch (error) { const value = error as { code: string; message: string }; return result('blocked', [], [], value); }
448
452
  if (!first.length) return result('no-op', [], []);
449
453
  const operationLimit = options.limit === undefined ? first.length : Math.max(1, options.limit);
450
454
  const attemptLimit = options.attempts ?? DEFAULT_RECOVERY_ATTEMPTS;
@@ -452,47 +456,56 @@ async function recoverPlatformOperations(
452
456
  return result('failed', [], [], { code: 'PLATFORM_OPERATION_RECOVERY_PAYLOAD_INVALID', message: 'Recovery attempts must be a positive safe integer', retryable: false });
453
457
  }
454
458
  const recovered: string[] = [];
459
+ let warnings: readonly OperationWarning[] = [];
455
460
 
456
461
  for (let operationIndex = 0; operationIndex < operationLimit; operationIndex += 1) {
457
462
  let latest;
458
463
  try { latest = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot); }
459
464
  catch (error) {
460
465
  const value = error as { code?: string; message?: string };
461
- return result('failed', recovered, [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false });
466
+ return result('failed', recovered, [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false }, warnings);
462
467
  }
463
- const head = latest.operations.find(eligible);
468
+ let candidates;
469
+ try { candidates = recoveryCandidates(latest, options); }
470
+ catch (error) { const value = error as { code: string; message: string }; return result('blocked', recovered, [], value, warnings); }
471
+ const head = candidates[0];
464
472
  if (!head) break;
465
473
  if (selection !== 'all' && head.dependency !== selection) {
466
- const pending = latest.operations.filter(eligible).map((operation) => operation.id);
474
+ const pending = candidates.map((operation) => operation.id);
467
475
  return result('blocked', recovered, pending, {
468
476
  code: 'PLATFORM_OPERATION_RECOVERY_PENDING',
469
477
  message: 'The first unresolved platform operation belongs to another recovery selection',
470
478
  retryable: true
471
- });
479
+ }, warnings);
472
480
  }
473
481
  let last: RecoveryResult | null = null;
474
482
  for (let attempt = 0; attempt < attemptLimit; attempt += 1) {
475
483
  last = await recoverPlatformOperationsOnce(taskRef, selection, { ...options, limit: 1 });
476
484
  recovered.push(...last.recovered.filter((id) => !recovered.includes(id)));
485
+ warnings = mergeOperationWarnings(warnings, last.warnings ?? []);
477
486
  if (last.recovered.length > 0 || last.status === 'applied' || last.status === 'no-op') break;
478
487
  }
479
488
  if (!last || (last.recovered.length === 0 && last.status !== 'applied' && last.status !== 'no-op')) {
480
489
  let current;
481
490
  try { current = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot); }
482
- catch { return result('blocked', recovered, [head.id], last?.error ?? null); }
483
- const pending = current.operations.filter(eligible).map((operation) => operation.id);
491
+ catch { return result('blocked', recovered, [head.id], last?.error ?? null, warnings); }
492
+ let pending: string[];
493
+ try { pending = recoveryCandidates(current, options).map((operation) => operation.id); }
494
+ catch (error) { const value = error as { code: string; message: string }; return result('blocked', recovered, [], value, warnings); }
484
495
  return result(last?.status === 'failed' ? 'failed' : 'blocked', recovered, pending, last?.error ?? {
485
496
  code: 'PLATFORM_OPERATION_RECOVERY_PENDING',
486
497
  message: 'The first queued platform operation remains unresolved after this recovery run',
487
498
  retryable: true
488
- });
499
+ }, warnings);
489
500
  }
490
501
  }
491
502
  let remaining: string[];
492
- try { remaining = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot).operations.filter(eligible).map((operation) => operation.id); }
493
- catch { remaining = []; }
494
- return result(remaining.length ? 'blocked' : recovered.length ? 'applied' : 'no-op', recovered, remaining,
495
- remaining.length ? { code: 'PLATFORM_OPERATION_RECOVERY_PENDING', message: 'Platform operations remain queued after this recovery run', retryable: true } : null);
503
+ try { remaining = recoveryCandidates(readPlatformOperationJournal(resolved.taskId, resolved.repoRoot), options).map((operation) => operation.id); }
504
+ catch (error) { const value = error as { code?: string; message?: string }; return result('blocked', recovered, [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || 'Unable to read platform operations' }, warnings); }
505
+ const output = result(remaining.length ? 'blocked' : recovered.length ? 'applied' : 'no-op', recovered, remaining,
506
+ remaining.length ? { code: 'PLATFORM_OPERATION_RECOVERY_PENDING', message: 'Platform operations remain queued after this recovery run', retryable: true } : null, warnings);
507
+ const degraded = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot).operations.filter((item) => recovered.includes(item.id) && item.lastCode === 'PUSH_REQUIRED').map((item) => ({ id: item.id, reasonCode: item.lastCode! }));
508
+ return degraded.length ? { ...output, degraded } : output;
496
509
  }
497
510
 
498
511
  export { fieldsMatchExpected, labelsMatchOwnedPrefix, recoverPlatformOperations };
@@ -1,10 +1,5 @@
1
1
  import type { ResourceIdentity } from '../platform/resource-identity.ts';
2
- import {
3
- isResourceIdentity,
4
- parseResourceIdentity,
5
- resourceIdentityNumber,
6
- serializeResourceIdentity
7
- } from '../platform/resource-identity.ts';
2
+ import { parseResourceIdentity, serializeResourceIdentity } from '../platform/resource-identity.ts';
8
3
 
9
4
  const PR_DELIVERY_FACT_KEY = 'pr_delivery_fact';
10
5
 
@@ -182,9 +177,5 @@ function buildBoundFact(input: {
182
177
  }, provenance: { establishedBy: provenanceForSource(input.source) } });
183
178
  }
184
179
  function factFrontmatterMutation(fact: PrDeliveryFact): { set: { [PR_DELIVERY_FACT_KEY]: string } } { return { set: { [PR_DELIVERY_FACT_KEY]: encodePrDeliveryFact(fact) } }; }
185
- function identityFromPullRequest(pullRequest: PrDeliveryIdentityInput): PrDeliveryIdentity {
186
- return canonicalIdentity(pullRequest);
187
- }
188
-
189
- export { PR_DELIVERY_FACT_KEY, buildBoundFact, buildSkippedFact, buildUnboundFact, decodePrDeliveryFact, encodePrDeliveryFact, factFrontmatterMutation, identityFromPullRequest, readPrDeliveryFact, serializeResourceIdentity };
180
+ export { PR_DELIVERY_FACT_KEY, buildBoundFact, buildSkippedFact, buildUnboundFact, decodePrDeliveryFact, encodePrDeliveryFact, factFrontmatterMutation, readPrDeliveryFact, serializeResourceIdentity };
190
181
  export type { CreationOutcome, PrDeliveryBindingSource, PrDeliveryFact, PrDeliveryFactError, PrDeliveryFactReadResult, PrDeliveryIdentity, PrDeliveryIdentityInput, PrDeliveryProvenance };
@@ -1,7 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import fs from 'node:fs';
3
3
 
4
- import { appendActivityEntry, locateActivityLog } from './activity-log.ts';
4
+ import { activityWarnings, appendActivityEntry, locateActivityLog } from './activity-log.ts';
5
5
  import { CONFIRMATION_COLUMNS, CONFIRMATION_HEADINGS, constraintDigest, parseQualificationConfirmations, parseTaskQualification } from './qualification-audit.ts';
6
6
  import type { QualificationConfirmation } from './qualification-audit.ts';
7
7
  import { findSectionHeading } from './sections.ts';
@@ -25,6 +25,7 @@ type QualificationConfirmationResult = {
25
25
  requestId: string | null;
26
26
  operations: readonly TaskOperationSummary[];
27
27
  error: { code: string; message: string } | null;
28
+ warnings?: readonly { code: string; message: string }[];
28
29
  };
29
30
 
30
31
  function failed(code: string, message: string, taskId: string | null = null): QualificationConfirmationResult {
@@ -73,13 +74,13 @@ function applyQualificationConfirmation(request: QualificationConfirmationReques
73
74
  const previous = existing.confirmations.find((row) => row.qcrId === constraint.approvalEvidence);
74
75
  if (operation === 'confirm' && constraint.status === 'confirmed') {
75
76
  if (previous?.constraintId === constraint.constraintId && previous.approvedDigest === constraint.digest && previous.rationale === request.rationale.trim()) {
76
- return { status: 'no-op', changed: false, taskId: resolved.taskId, qcrId: previous.qcrId, requestId: previous.requestId, operations: [], error: null };
77
+ return { status: 'no-op', changed: false, taskId: resolved.taskId, qcrId: previous.qcrId, requestId: previous.requestId, operations: [], error: null, warnings: activityWarnings(content) };
77
78
  }
78
79
  return failed('QUALIFICATION_CONFIRMATION_CONFLICT', `constraint '${request.constraintId}' is already confirmed with different evidence`, resolved.taskId);
79
80
  }
80
81
  const transitionStatus = operation === 'supersede' ? 'superseded' : 'open';
81
82
  if (operation !== 'confirm' && constraint.status === transitionStatus && !constraint.approvalEvidence) {
82
- return { status: 'no-op', changed: false, taskId: resolved.taskId, qcrId: null, requestId: null, operations: [], error: null };
83
+ return { status: 'no-op', changed: false, taskId: resolved.taskId, qcrId: null, requestId: null, operations: [], error: null, warnings: activityWarnings(content) };
83
84
  }
84
85
  if (operation !== 'confirm' && constraint.status !== 'confirmed') return failed('QUALIFICATION_CONFIRMATION_REQUIRED', `constraint '${request.constraintId}' is not confirmed`, resolved.taskId);
85
86
  if (parsed.qualification.constraintDigest !== request.expectedDigest) return failed('QUALIFICATION_DIGEST_CONFLICT', 'constraint digest changed before human confirmation', resolved.taskId);
@@ -99,18 +100,17 @@ function applyQualificationConfirmation(request: QualificationConfirmationReques
99
100
  } : null;
100
101
  const confirmations = confirmation ? [...existing.confirmations, confirmation] : existing.confirmations;
101
102
  const activity = locateActivityLog(content);
102
- if (!activity) return failed('QUALIFICATION_ACTIVITY_MISSING', 'task has no unique Activity Log section', resolved.taskId);
103
103
  const mutations: Parameters<typeof writeTask>[0]['mutations'][number][] = [
104
104
  {
105
105
  kind: 'table-row', action: 'upsert', sectionAliases: ['约束', 'Constraints'], columns: ['constraint_id', 'statement', 'status', 'authority', 'source', 'evidence', 'derived_from', 'approval_evidence'], keyColumn: 'constraint_id', key: constraint.constraintId,
106
106
  values: { statement: constraint.statement, status: operation === 'confirm' ? 'confirmed' : transitionStatus, authority: operation === 'confirm' ? 'human-declared' : constraint.authority, source: constraint.source, evidence: constraint.evidence, derived_from: constraint.derivedFrom.join(','), approval_evidence: qcrId ?? '' }
107
107
  },
108
108
  ...(operation === 'confirm' ? [{ kind: 'section' as const, aliases: CONFIRMATION_HEADINGS, heading: findSectionHeading(content, [...CONFIRMATION_HEADINGS]), body: renderConfirmations(confirmations) }] : []),
109
- { kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: activity.heading, body: appendActivityEntry(activity, { time: metadata.timestamp, step: 'Qualification Confirmation', agent: 'human', note: operation === 'confirm' ? `${request.constraintId} confirmed → ${qcrId}` : `${request.constraintId} ${operation}d` }) }
109
+ ...(activity ? [{ kind: 'section' as const, aliases: ['活动日志', 'Activity Log'], heading: activity.heading, body: appendActivityEntry(activity, { time: metadata.timestamp, step: 'Qualification Confirmation', agent: 'human', note: operation === 'confirm' ? `${request.constraintId} confirmed → ${qcrId}` : `${request.constraintId} ${operation}d` }) }] : [])
110
110
  ];
111
111
  const result = writeTask({ taskRef: resolved.taskId, expectedState: 'active', mutations, dryRun: request.dryRun }, { ...options, taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state }, metadataProvider: () => metadata });
112
112
  if (result.status === 'failed') return failed(result.error.code, result.error.message, result.taskId);
113
- return { status: result.status, changed: result.changed, taskId: result.taskId, qcrId, requestId, operations: result.operations, error: null };
113
+ return { status: result.status, changed: result.changed, taskId: result.taskId, qcrId, requestId, operations: result.operations, error: null, warnings: activityWarnings(content) };
114
114
  }
115
115
 
116
116
  export { applyQualificationConfirmation };
@@ -114,28 +114,6 @@ function findTaskMd(
114
114
  return archived ? { taskMdPath: archived, state: 'archive' } : null;
115
115
  }
116
116
 
117
- /**
118
- * Enumerate every task directory under the flat workspace states
119
- * (active / blocked / completed) — archive is intentionally excluded so a
120
- * full-tree scan never pulls in cold data. Ordered by state, then task id
121
- * ascending, giving callers a deterministic traversal.
122
- */
123
- function enumerateTaskDirs(repoRoot: string): { taskId: string; taskDir: string }[] {
124
- const out: { taskId: string; taskDir: string }[] = [];
125
- const workspaceRoot = taskRoot(repoRoot);
126
- for (const sub of FLAT_WORKSPACE_DIRS) {
127
- const base = path.join(workspaceRoot, sub);
128
- if (!fs.existsSync(base)) continue;
129
- for (const entry of fs.readdirSync(base).sort()) {
130
- if (!TASK_ID_RE.test(entry)) continue;
131
- const taskDir = path.join(base, entry);
132
- if (!fs.existsSync(path.join(taskDir, 'task.md'))) continue;
133
- out.push({ taskId: entry, taskDir });
134
- }
135
- }
136
- return out;
137
- }
138
-
139
117
  /**
140
118
  * Enumerate every task directory, including the cold YYYY/MM/DD archive.
141
119
  * Invalid directory names, symlinks and entries without task.md are ignored;
@@ -364,7 +342,6 @@ export {
364
342
  resolveTaskRef,
365
343
  resolveTaskContext,
366
344
  detectRepoRoot,
367
- enumerateTaskDirs,
368
345
  enumerateAllTaskDirs,
369
346
  locateHotTaskDirs,
370
347
  TASK_ID_RE
@@ -4,14 +4,14 @@ export function parseReviewCommand(args: readonly string[]): ReviewFinalizationR
4
4
  if (!args[0] || !['preflight', 'finalize-summary'].includes(args[1] ?? '')) throw new Error('task ref and a supported review operation are required');
5
5
  const values: Record<string, string | boolean> = {};
6
6
  const flags = {
7
- '--stage': 'stage', '--artifact': 'artifact', '--orchestrated': 'orchestrated', '--dry-run': 'dryRun'
7
+ '--stage': 'stage', '--artifact': 'artifact', '--dry-run': 'dryRun'
8
8
  } as const;
9
9
  for (let index = 2; index < args.length; index += 1) {
10
10
  const flag = args[index]!;
11
11
  if (!Object.hasOwn(flags, flag)) throw new Error(`unknown option '${flag}'`);
12
12
  const key = flags[flag as keyof typeof flags];
13
13
  if (Object.hasOwn(values, key)) throw new Error(`duplicate option '${flag}'`);
14
- const value = flag === '--orchestrated' || flag === '--dry-run' ? true : args[++index];
14
+ const value = flag === '--dry-run' ? true : args[++index];
15
15
  if (!value || typeof value === 'string' && value.startsWith('--')) throw new Error(`option '${flag}' requires a value`);
16
16
  values[key] = value;
17
17
  }
@@ -9,7 +9,6 @@ import type { LedgerStageStatus, ReviewStage } from './ledger.ts';
9
9
  import { finalizeReviewSummaryContent } from './review-artifacts.ts';
10
10
  import { inspectDecisionDetailDuplicates } from './decision-details.ts';
11
11
  import { resolveTaskRef } from './resolve-ref.ts';
12
- import { validateLifecycleExecution } from './lifecycle-execution.ts';
13
12
  import type { ResolveTaskRefErrorCode } from './resolve-ref.ts';
14
13
  import { getArtifactSchema } from './artifact-schema.ts';
15
14
  import { validateQualificationAudit } from './qualification-audit.ts';
@@ -36,7 +35,6 @@ type ReviewFinalizationRequest = {
36
35
  taskRef: string;
37
36
  stage: string;
38
37
  artifact: string;
39
- orchestrated?: boolean;
40
38
  dryRun?: boolean;
41
39
  };
42
40
  type ReviewFinalizationResult = {
@@ -144,13 +142,6 @@ function preflightReviewSummary(
144
142
  content = fs.readFileSync(validated.artifact.path, 'utf8');
145
143
  } catch (error) { return preflightFailed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId); }
146
144
 
147
- const execution = validateLifecycleExecution(request.taskRef, {
148
- mode: request.orchestrated ? 'orchestrated' : 'standalone',
149
- identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
150
- dryRun: request.dryRun
151
- }, { repoRoot: options.repoRoot });
152
- if (!execution.ok) return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', `${execution.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH'}: ${execution.error?.message ?? 'orchestration provenance validation failed'}`, resolved.taskId);
153
-
154
145
  const artifactSha256 = sha256Content(content);
155
146
  const semanticDigest = canonicalSemanticDigest(content);
156
147
  const detail = inspectDecisionDetailDuplicates(content);
@@ -211,15 +202,6 @@ function prepareReviewSummaryCandidate(
211
202
  catch (error) { return reject('REVIEW_ARTIFACT_NOT_REGULAR', String(error)); }
212
203
  const artifactSha256 = sha256Content(artifactContent);
213
204
  const semanticDigest = canonicalSemanticDigest(artifactContent);
214
- const digests = { artifactSha256, semanticDigest };
215
- const execution = validateLifecycleExecution(request.taskRef, {
216
- mode: request.orchestrated ? 'orchestrated' : 'standalone',
217
- identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
218
- dryRun: request.dryRun
219
- }, { repoRoot: options.repoRoot });
220
- if (!execution.ok) {
221
- return reject('REVIEW_PROVENANCE_INVALID', `${execution.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH'}: ${execution.error?.message ?? 'orchestration provenance validation failed'}`);
222
- }
223
205
  try {
224
206
  const ledger = parseLedgerDocument(taskContent);
225
207
  if (!ledger.present) return reject('REVIEW_LEDGER_INVALID', `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}`);
@@ -259,14 +241,6 @@ function prepareReviewSummaryCandidate(
259
241
  };
260
242
  }
261
243
 
262
- function commitReviewSummaryProvenance(
263
- prepared: ReviewSummaryCandidatePreparation,
264
- repoRoot: string,
265
- options: Readonly<{ afterPublish?: () => void }> = {}
266
- ): ReviewFinalizationResult {
267
- return prepared.result;
268
- }
269
-
270
244
  function replaceCurrentArtifactAtomically(file: string, content: string): void {
271
245
  const temporary = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${randomUUID()}.tmp`);
272
246
  try {
@@ -316,7 +290,7 @@ function finalizeReviewSummary(
316
290
  }
317
291
  }
318
292
 
319
- export { finalizeReviewSummary, preflightReviewSummary, prepareReviewSummaryCandidate, commitReviewSummaryProvenance };
293
+ export { finalizeReviewSummary, preflightReviewSummary, prepareReviewSummaryCandidate };
320
294
  export type {
321
295
  ReviewFinalizationError,
322
296
  ReviewFinalizationErrorCode,