@fitlab-ai/agent-infra 0.11.15 → 0.12.1

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 (265) hide show
  1. package/bin/cli.ts +1 -1
  2. package/dist/bin/cli.js +1 -1
  3. package/dist/bin/internal-cli.js +6 -1
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +6 -6
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +8 -18
  6. package/dist/lib/agent-clients/adapters/codex-orchestration.js +1 -1
  7. package/dist/lib/decide.js +2 -7
  8. package/dist/lib/internal/cli-route-inventory.js +3 -0
  9. package/dist/lib/internal/manual-validation.js +19 -33
  10. package/dist/lib/internal/platform-comment.js +59 -9
  11. package/dist/lib/internal/task-activity.js +1 -12
  12. package/dist/lib/internal/task-event.js +1 -12
  13. package/dist/lib/internal/task-ledger.js +1 -12
  14. package/dist/lib/internal/task-operation-registry.js +2 -1
  15. package/dist/lib/internal/task-qualification.js +6 -15
  16. package/dist/lib/internal/task-review.js +3 -16
  17. package/dist/lib/internal/task-short-id.js +2 -1
  18. package/dist/lib/internal/task-verify.js +1 -11
  19. package/dist/lib/internal/task-warning.js +1 -12
  20. package/dist/lib/internal/task-workspace.js +43 -0
  21. package/dist/lib/merge.js +4 -3
  22. package/dist/lib/platform/issue-comments.js +373 -267
  23. package/dist/lib/platform/issue-summary-content.js +112 -0
  24. package/dist/lib/platform/pr-change-report.js +175 -26
  25. package/dist/lib/platform/pr-summary-content.js +54 -0
  26. package/dist/lib/platform/pr-summary.js +321 -296
  27. package/dist/lib/platform/pull-requests.js +3 -15
  28. package/dist/lib/platform/summary-content-pipeline.js +18 -0
  29. package/dist/lib/pr-review/evidence-grading.js +3 -2
  30. package/dist/lib/process-data/index.js +5 -4
  31. package/dist/lib/process-data/sources.js +6 -0
  32. package/dist/lib/run/index.js +3 -2
  33. package/dist/lib/sandbox/commands/create.js +5 -1
  34. package/dist/lib/sandbox/commands/enter.js +1 -1
  35. package/dist/lib/sandbox/commands/list-running.js +2 -1
  36. package/dist/lib/sandbox/control/client.js +8 -13
  37. package/dist/lib/sandbox/control/completed-reentry.js +2 -1
  38. package/dist/lib/sandbox/control/task-view.js +4 -2
  39. package/dist/lib/sandbox/mounts.js +4 -4
  40. package/dist/lib/sandbox/recovery.js +26 -16
  41. package/dist/lib/sandbox/removal.js +62 -12
  42. package/dist/lib/sandbox/task-resolver.js +9 -6
  43. package/dist/lib/sandbox/workspace-identity.js +6 -5
  44. package/dist/lib/sandbox/workspace-view.js +29 -22
  45. package/dist/lib/task/activity-intent.js +2 -12
  46. package/dist/lib/task/artifact-lifecycle.js +17 -11
  47. package/dist/lib/task/artifact-operations.js +41 -49
  48. package/dist/lib/task/capabilities.js +2 -2
  49. package/dist/lib/task/commands/ls.js +4 -3
  50. package/dist/lib/task/commands/status.js +1 -1
  51. package/dist/lib/task/commit-operation.js +6 -6
  52. package/dist/lib/task/control-authority.js +32 -32
  53. package/dist/lib/task/create-service.js +2 -4
  54. package/dist/lib/task/create.js +12 -65
  55. package/dist/lib/task/delegation-receipts.js +1 -1
  56. package/dist/lib/task/delivery.js +2 -8
  57. package/dist/lib/task/events.js +5 -31
  58. package/dist/lib/task/finalization.js +33 -29
  59. package/dist/lib/task/lifecycle.js +9 -48
  60. package/dist/lib/task/local-artifact-finalization.js +1 -2
  61. package/dist/lib/task/orchestration.js +24 -57
  62. package/dist/lib/task/platform-operation-recovery.js +87 -88
  63. package/dist/lib/task/resolve-ref.js +17 -12
  64. package/dist/lib/task/review-finalization.js +2 -35
  65. package/dist/lib/task/review-identity.js +3 -1
  66. package/dist/lib/task/short-id.js +10 -8
  67. package/dist/lib/task/verification-engine.js +19 -58
  68. package/dist/lib/task/workspace-root.js +59 -0
  69. package/dist/lib/task/workspace-snapshot.js +4 -3
  70. package/dist/lifecycle-build-manifest.json +3 -3
  71. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +6 -6
  72. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +8 -34
  73. package/lib/agent-clients/adapters/codex-orchestration.ts +1 -1
  74. package/lib/decide.ts +2 -7
  75. package/lib/internal/cli-route-inventory.ts +2 -0
  76. package/lib/internal/manual-validation.ts +20 -37
  77. package/lib/internal/platform-comment.ts +49 -6
  78. package/lib/internal/task-activity.ts +1 -10
  79. package/lib/internal/task-event.ts +1 -10
  80. package/lib/internal/task-ledger.ts +1 -10
  81. package/lib/internal/task-operation-registry.ts +2 -1
  82. package/lib/internal/task-qualification.ts +5 -11
  83. package/lib/internal/task-review.ts +3 -14
  84. package/lib/internal/task-short-id.ts +2 -1
  85. package/lib/internal/task-verify.ts +1 -9
  86. package/lib/internal/task-warning.ts +1 -10
  87. package/lib/internal/task-workspace.ts +39 -0
  88. package/lib/merge.ts +4 -3
  89. package/lib/platform/issue-comments.ts +478 -280
  90. package/lib/platform/issue-summary-content.ts +132 -0
  91. package/lib/platform/pr-change-report.ts +165 -20
  92. package/lib/platform/pr-summary-content.ts +60 -0
  93. package/lib/platform/pr-summary.ts +367 -269
  94. package/lib/platform/pull-requests.ts +3 -24
  95. package/lib/platform/summary-content-pipeline.ts +23 -0
  96. package/lib/pr-review/evidence-grading.ts +3 -2
  97. package/lib/process-data/index.ts +4 -3
  98. package/lib/process-data/sources.ts +6 -0
  99. package/lib/run/index.ts +3 -2
  100. package/lib/sandbox/commands/create.ts +5 -1
  101. package/lib/sandbox/commands/enter.ts +1 -1
  102. package/lib/sandbox/commands/list-running.ts +2 -1
  103. package/lib/sandbox/control/client.ts +7 -12
  104. package/lib/sandbox/control/completed-reentry.ts +2 -1
  105. package/lib/sandbox/control/task-view.ts +4 -2
  106. package/lib/sandbox/mounts.ts +4 -4
  107. package/lib/sandbox/recovery.ts +26 -18
  108. package/lib/sandbox/removal.ts +60 -27
  109. package/lib/sandbox/task-resolver.ts +9 -6
  110. package/lib/sandbox/workspace-identity.ts +6 -5
  111. package/lib/sandbox/workspace-view.ts +28 -20
  112. package/lib/task/activity-intent.ts +3 -17
  113. package/lib/task/artifact-lifecycle.ts +39 -11
  114. package/lib/task/artifact-operations.ts +28 -35
  115. package/lib/task/capabilities.ts +2 -2
  116. package/lib/task/commands/ls.ts +4 -3
  117. package/lib/task/commands/status.ts +1 -1
  118. package/lib/task/commit-operation.ts +5 -6
  119. package/lib/task/control-authority.ts +29 -35
  120. package/lib/task/create-service.ts +2 -4
  121. package/lib/task/create.ts +10 -51
  122. package/lib/task/delegation-receipts.ts +1 -1
  123. package/lib/task/delivery.ts +2 -10
  124. package/lib/task/events.ts +5 -32
  125. package/lib/task/finalization.ts +27 -30
  126. package/lib/task/lifecycle.ts +9 -50
  127. package/lib/task/local-artifact-finalization.ts +1 -4
  128. package/lib/task/orchestration.ts +31 -80
  129. package/lib/task/platform-operation-recovery.ts +70 -82
  130. package/lib/task/resolve-ref.ts +18 -12
  131. package/lib/task/review-finalization.ts +2 -52
  132. package/lib/task/review-identity.ts +3 -1
  133. package/lib/task/short-id.ts +10 -8
  134. package/lib/task/verification-engine.ts +20 -62
  135. package/lib/task/workspace-root.ts +78 -0
  136. package/lib/task/workspace-snapshot.ts +4 -3
  137. package/package.json +1 -1
  138. package/templates/.agents/QUICKSTART.en.md +19 -8
  139. package/templates/.agents/QUICKSTART.zh-CN.md +19 -8
  140. package/templates/.agents/README.en.md +10 -15
  141. package/templates/.agents/README.zh-CN.md +9 -13
  142. package/templates/.agents/rules/issue-sync.en.md +1 -1
  143. package/templates/.agents/rules/issue-sync.zh-CN.md +1 -1
  144. package/templates/.agents/rules/lifecycle-orchestration.en.md +1 -1
  145. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +1 -1
  146. package/templates/.agents/rules/next-step-output.en.md +2 -2
  147. package/templates/.agents/rules/next-step-output.zh-CN.md +2 -2
  148. package/templates/.agents/rules/pr-sync.en.md +5 -3
  149. package/templates/.agents/rules/pr-sync.zh-CN.md +7 -10
  150. package/templates/.agents/rules/release-commands.en.md +3 -0
  151. package/templates/.agents/rules/release-commands.zh-CN.md +3 -0
  152. package/templates/.agents/rules/task-management.en.md +0 -2
  153. package/templates/.agents/rules/task-management.zh-CN.md +0 -2
  154. package/templates/.agents/rules/task-short-id.en.md +2 -2
  155. package/templates/.agents/rules/task-short-id.zh-CN.md +2 -2
  156. package/templates/.agents/scripts/task-short-id.js +2 -8
  157. package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
  158. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
  159. package/templates/.agents/skills/analyze-task/config/verify.en.json +0 -1
  160. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +0 -1
  161. package/templates/.agents/skills/archive-tasks/SKILL.en.md +5 -5
  162. package/templates/.agents/skills/archive-tasks/SKILL.zh-CN.md +5 -5
  163. package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +10 -2
  164. package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
  165. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +5 -5
  166. package/templates/.agents/skills/block-task/config/verify.json +0 -1
  167. package/templates/.agents/skills/cancel-task/SKILL.en.md +7 -7
  168. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +7 -7
  169. package/templates/.agents/skills/cancel-task/config/verify.json +0 -1
  170. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  171. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
  172. package/templates/.agents/skills/code-task/config/verify.en.json +0 -1
  173. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +0 -1
  174. package/templates/.agents/skills/code-task/reference/output-template.en.md +1 -1
  175. package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +1 -1
  176. package/templates/.agents/skills/commit/SKILL.en.md +1 -1
  177. package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
  178. package/templates/.agents/skills/commit/config/verify.json +0 -1
  179. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +2 -2
  180. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +2 -2
  181. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +2 -2
  182. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +2 -2
  183. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
  184. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
  185. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +0 -1
  186. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +0 -1
  187. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +3 -3
  188. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +3 -3
  189. package/templates/.agents/skills/complete-task/SKILL.en.md +15 -30
  190. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +17 -32
  191. package/templates/.agents/skills/complete-task/config/verify.en.json +0 -1
  192. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +0 -1
  193. package/templates/.agents/skills/create-pr/SKILL.en.md +3 -3
  194. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +3 -3
  195. package/templates/.agents/skills/create-pr/config/verify.json +0 -1
  196. package/templates/.agents/skills/create-pr/reference/change-report.en.md +10 -3
  197. package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +10 -3
  198. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +3 -3
  199. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +3 -3
  200. package/templates/.agents/skills/create-release-note/SKILL.en.md +2 -2
  201. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +2 -2
  202. package/templates/.agents/skills/create-task/SKILL.en.md +4 -22
  203. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +4 -22
  204. package/templates/.agents/skills/create-task/config/verify.json +0 -1
  205. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  206. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  207. package/templates/.agents/skills/import-codescan/config/verify.json +0 -1
  208. package/templates/.agents/skills/import-dependabot/SKILL.en.md +3 -3
  209. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +3 -3
  210. package/templates/.agents/skills/import-dependabot/config/verify.json +0 -1
  211. package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
  212. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
  213. package/templates/.agents/skills/import-issue/config/verify.json +0 -1
  214. package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
  215. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
  216. package/templates/.agents/skills/plan-task/config/verify.en.json +0 -1
  217. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +0 -1
  218. package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
  219. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +3 -3
  220. package/templates/.agents/skills/restore-task/config/verify.json +0 -1
  221. package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
  222. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
  223. package/templates/.agents/skills/review-analysis/config/verify.en.json +0 -1
  224. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +0 -1
  225. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +5 -5
  226. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +5 -5
  227. package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
  228. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
  229. package/templates/.agents/skills/review-code/reference/output-templates.en.md +4 -4
  230. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +4 -4
  231. package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
  232. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
  233. package/templates/.agents/skills/review-plan/config/verify.en.json +0 -1
  234. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +0 -1
  235. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +5 -5
  236. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +5 -5
  237. package/templates/.agents/skills/review-pr/SKILL.en.md +2 -2
  238. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +2 -2
  239. package/templates/.agents/skills/review-pr/config/verify.en.json +0 -1
  240. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +0 -1
  241. package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +1 -1
  242. package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +1 -1
  243. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +0 -1
  244. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +0 -1
  245. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  246. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  247. package/templates/.agents/skills/test/SKILL.en.md +0 -3
  248. package/templates/.agents/skills/test/SKILL.zh-CN.md +0 -3
  249. package/templates/.agents/skills/test-integration/SKILL.en.md +0 -3
  250. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +0 -3
  251. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +13 -6
  252. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +0 -3
  253. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +0 -3
  254. package/templates/.agents/skills/watch-pr/SKILL.en.md +4 -4
  255. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +4 -4
  256. package/templates/.agents/skills/watch-pr/config/verify.json +0 -1
  257. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +3 -3
  258. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +3 -3
  259. package/templates/.agents/templates/handoff.en.md +1 -1
  260. package/templates/.agents/templates/handoff.zh-CN.md +1 -1
  261. package/templates/.agents/workflows/bug-fix.en.yaml +1 -1
  262. package/templates/.agents/workflows/feature-development.en.yaml +1 -1
  263. package/templates/.agents/workflows/refactoring.en.yaml +1 -1
  264. package/dist/lib/task/task-execution-lock.js +0 -250
  265. package/lib/task/task-execution-lock.ts +0 -320
@@ -4,19 +4,21 @@ import { createHash } from 'node:crypto';
4
4
  import { enumerateArtifacts } from '../task/artifacts.js';
5
5
  import { parseTypedTaskFrontmatter } from '../task/frontmatter.js';
6
6
  import { resolveTaskRef } from '../task/resolve-ref.js';
7
- import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
8
7
  import { readPrDeliveryFact } from '../task/pr-delivery-fact.js';
9
8
  import { resolvePlatformProviderContext } from './context.js';
10
- import { normalizeCommentContent } from './issue-comments.js';
9
+ import { inspectPlatformPullRequest } from './pull-requests.js';
10
+ import { normalizeCommentContent, syncCommentPlan } from './issue-comments.js';
11
11
  import { CONTROL_MARKER_PATTERN, sanitizeMarkdownDocument, splitDocumentPlaceholder } from './comment-safety.js';
12
12
  import { platformResult } from './types.js';
13
- import { coordinatePlatformWrite } from './operation-coordinator.js';
14
- import { providerError, providerOperationContext, providerStatus, providerResourceToken, resourceIdentityNumber, unsupportedProviderOperation } from './provider-bridge.js';
15
- import { resourceIdentityEquals } from './resource-identity.js';
13
+ import { providerError, providerOperationContext, resourceIdentityNumber, unsupportedProviderOperation } from './provider-bridge.js';
14
+ import { parseResourceIdentity, resourceIdentityEquals } from './resource-identity.js';
16
15
  import { buildPrChangeReport, CANONICAL_REPORT_PLACEHOLDER, readPrChangeReport, replaceCanonicalReportPlaceholder, runMechanicalChangeReport, taskIntentDigest, validateMechanicalReport, validatePrecheckCandidate, writePrChangeReportAtomic } from './pr-change-report.js';
17
16
  import { MANUAL_VALIDATION_RECEIPT_PLACEHOLDER, manualValidationFinalSummaryProjectionMatches } from '../task/manual-validation-receipt.js';
18
17
  import { readManualValidationCompletion } from '../task/manual-validation-completion.js';
19
18
  import { readManualValidationTransaction } from '../task/manual-validation-transaction.js';
19
+ import { parsePrSummaryContent, renderPrSummaryContent } from './pr-summary-content.js';
20
+ import { runSummaryContentPipeline } from './summary-content-pipeline.js';
21
+ import { parseLedgerDocument } from '../task/ledger.js';
20
22
  function summaryMarker(taskId) {
21
23
  return `<!-- sync-pr:${taskId}:summary -->`;
22
24
  }
@@ -59,17 +61,6 @@ function platformError(error) {
59
61
  function buildPullRequestSummary(taskId, body, headSha) {
60
62
  return normalizeCommentContent(`${summaryMarker(taskId)}\n<!-- last-commit: ${headSha} -->\n\n${body.replace(/\s+$/, '')}\n`);
61
63
  }
62
- function reconcileSummaryComment(comments, taskId, desired) {
63
- const marker = summaryMarker(taskId);
64
- const matches = comments.filter((comment) => comment.body.includes(marker));
65
- if (matches.length > 1)
66
- return { action: 'conflict', commentId: null };
67
- if (matches.length === 0)
68
- return { action: 'create', commentId: null };
69
- return normalizeCommentContent(matches[0].body) === normalizeCommentContent(desired)
70
- ? { action: 'no-op', commentId: matches[0].id }
71
- : { action: 'update', commentId: matches[0].id };
72
- }
73
64
  function canonicalArtifacts(taskDir) {
74
65
  const allowed = /^(plan|review-plan|code|review-code|manual-validation)(?:-r\d+)?\.md$/;
75
66
  const byFamily = new Map();
@@ -103,6 +94,247 @@ function isHardSummaryFailure(error) {
103
94
  function inspectionError(error) {
104
95
  return error.retryable ? 'blocked' : 'failed';
105
96
  }
97
+ function preparationError(code, message) {
98
+ return Object.assign(new Error(message), { code });
99
+ }
100
+ function validateManualSummaryPhase(manual, rendered) {
101
+ const hasFinal = /###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)/u.test(rendered);
102
+ if (hasFinal && (!manual || manual.phase !== 'final'))
103
+ throw preparationError('MANUAL_VALIDATION_TRANSACTION_REQUIRED', 'final manual-validation summary requires the transaction coordinator');
104
+ if (manual && (manual.phase === 'pending' ? hasFinal : !hasFinal))
105
+ throw preparationError('MANUAL_VALIDATION_SUMMARY_PHASE_INVALID', 'manual-validation summary phase does not match the requested writer phase');
106
+ }
107
+ function readPendingManualEvidence(resolved, manual, current, prNumber) {
108
+ const transaction = readManualValidationTransaction(resolved.taskDir, { transactionId: manual.transactionId, taskId: resolved.taskId, prNumber, prHeadSha: current.head.sha });
109
+ if (!transaction.ok)
110
+ throw preparationError(transaction.error.code, transaction.error.message);
111
+ return null;
112
+ }
113
+ function readFinalManualEvidence(resolved, manual, rendered, current, prNumber) {
114
+ if (!manual.receiptDigest || manual.prHeadSha !== current.head.sha || manual.authority !== 'coordinator')
115
+ throw preparationError('MANUAL_VALIDATION_TRANSACTION_REQUIRED', 'final manual-validation summary requires transaction, receipt, and current head identity');
116
+ const completion = readManualValidationCompletion(resolved.taskDir, { transactionId: manual.transactionId, taskId: resolved.taskId, prNumber, prHeadSha: current.head.sha, receiptDigest: manual.receiptDigest });
117
+ if (!completion.ok)
118
+ throw preparationError(completion.error.code, completion.error.message);
119
+ if (completion.value.transaction.phase !== 'final-promotion-in-progress' || !completion.value.transaction.eventAppended
120
+ || !manualValidationFinalSummaryProjectionMatches(rendered, completion.value.receipt))
121
+ throw preparationError('MANUAL_VALIDATION_SUMMARY_PROJECTION_INVALID', 'final summary does not match the receipt-backed promotion intent');
122
+ return completion.value.receipt.evidenceDigest;
123
+ }
124
+ async function manualValidationEvidence(resolved, manual, rendered, current) {
125
+ validateManualSummaryPhase(manual, rendered);
126
+ if (!manual)
127
+ return null;
128
+ const prNumber = current.number;
129
+ if (!Number.isSafeInteger(prNumber) || prNumber <= 0)
130
+ throw preparationError('MANUAL_VALIDATION_PR_NUMBER_REQUIRED', 'manual-validation summary requires a positive safe pull-request number from the authoritative snapshot');
131
+ if (!manual.transactionId)
132
+ throw preparationError('MANUAL_VALIDATION_TRANSACTION_REQUIRED', 'manual-validation summary requires a transaction ID');
133
+ return manual.phase === 'pending'
134
+ ? readPendingManualEvidence(resolved, manual, current, prNumber)
135
+ : readFinalManualEvidence(resolved, manual, rendered, current, prNumber);
136
+ }
137
+ function validatedSummaryReport(resolved, options, current, taskContent) {
138
+ if (!options.changeReportFile)
139
+ throw preparationError('PR_CHANGE_REPORT_MISSING', 'summary-sync requires --change-report-file');
140
+ const reportPath = taskReportPath(resolved.taskDir);
141
+ if (path.resolve(resolved.repoRoot, options.changeReportFile) !== reportPath)
142
+ throw preparationError('PR_CHANGE_REPORT_PATH_INVALID', 'summary-sync must consume the task-bound pr-change-report.json');
143
+ const report = readPrChangeReport(reportPath);
144
+ if (!report.ok)
145
+ throw preparationError(report.error.code, report.error.message);
146
+ const checked = currentReportCheck(report.value, current, taskContent, resolved.repoRoot);
147
+ if (!checked.ok)
148
+ throw preparationError(checked.error.code, checked.error.message);
149
+ return report.value;
150
+ }
151
+ function renderedSummaryBody(taskId, rendered, report, manual, evidenceDigest, headSha) {
152
+ const split = splitDocumentPlaceholder(rendered, CANONICAL_REPORT_PLACEHOLDER);
153
+ if (!split.ok)
154
+ throw preparationError('PR_SUMMARY_BODY_CONTRACT_INVALID', split.error.message);
155
+ const prefix = sanitizeMarkdownDocument(split.value.prefix, { reservedMarkers: [CONTROL_MARKER_PATTERN] });
156
+ const suffix = sanitizeMarkdownDocument(split.value.suffix, { reservedMarkers: [CONTROL_MARKER_PATTERN] });
157
+ if (!prefix.ok || !suffix.ok)
158
+ throw preparationError('PR_SUMMARY_BODY_CONTRACT_INVALID', 'PR summary body contract is invalid');
159
+ const replaced = replaceCanonicalReportPlaceholder(`${prefix.value}${CANONICAL_REPORT_PLACEHOLDER}${suffix.value}`, report);
160
+ if (!replaced.ok)
161
+ throw preparationError(replaced.error.code, replaced.error.message);
162
+ const withReceipt = manual?.phase === 'final' ? replaced.value.replace(MANUAL_VALIDATION_RECEIPT_PLACEHOLDER, `<!-- manual-validation-receipt: transaction=${manual.transactionId}; receipt=${manual.receiptDigest}; evidence=${evidenceDigest}; head=${manual.prHeadSha} -->`) : replaced.value;
163
+ const body = buildPullRequestSummary(taskId, withReceipt, headSha);
164
+ if (!isSafeSummaryEnvelope(body, taskId))
165
+ throw preparationError('PR_SUMMARY_RENDER_INVALID', 'Summary contains an invalid or duplicated reserved control marker');
166
+ return body;
167
+ }
168
+ function prepareBoundSummaryContent(body, taskContent) {
169
+ try {
170
+ return prepareAndRenderPrSummaryContent(JSON.parse(body), taskContent);
171
+ }
172
+ catch (error) {
173
+ throw preparationError('PR_SUMMARY_CONTENT_INVALID', error instanceof Error ? error.message : String(error));
174
+ }
175
+ }
176
+ async function prepareBoundPullRequestSummary(resolved, options, context, loaded, expectedIdentity) {
177
+ const taskSnapshot = readBoundTaskSnapshot(resolved.taskMdPath);
178
+ if (taskSnapshot.factError)
179
+ throw preparationError(taskSnapshot.factError.code, taskSnapshot.factError.message);
180
+ if (!taskSnapshot.boundFact || !resourceIdentityEquals(taskSnapshot.boundFact.identity.resource, expectedIdentity))
181
+ throw preparationError('PLATFORM_OPERATION_IDENTITY_MISMATCH', 'Bound pull request identity changed while the summary was queued');
182
+ const preparedContent = prepareBoundSummaryContent(options.body, taskSnapshot.taskContent);
183
+ const current = await inspectBoundPullRequest(context, resolved.repoRoot, expectedIdentity, loaded);
184
+ if (!current.ok)
185
+ throw preparationError(current.error?.code || 'PR_SNAPSHOT_UNAVAILABLE', current.error?.message || 'Pull-request snapshot is unavailable');
186
+ const evidenceDigest = await manualValidationEvidence(resolved, options.manualValidation, preparedContent.rendered, current.value);
187
+ const report = validatedSummaryReport(resolved, options, current.value, taskSnapshot.taskContent);
188
+ const body = renderedSummaryBody(resolved.taskId, preparedContent.rendered, report, options.manualValidation, evidenceDigest, current.value.head.sha);
189
+ return {
190
+ parent: expectedIdentity,
191
+ item: { marker: summaryMarker(resolved.taskId), body, content: options.body, part: 1, total: 1 },
192
+ snapshot: current.value,
193
+ rendered: body,
194
+ manual: options.manualValidation,
195
+ prNumber: current.value.number,
196
+ precheckVerdict: report.precheck.verdict,
197
+ nextAction: report.precheck.route
198
+ };
199
+ }
200
+ async function validateBoundPullRequestSummary(resolved, options, context, loaded, prepared) {
201
+ const latest = await inspectBoundPullRequest(context, resolved.repoRoot, prepared.parent, loaded);
202
+ if (!latest.ok)
203
+ return latest.error;
204
+ if (!sameIdentity(prepared.snapshot, latest.value))
205
+ return { code: 'PR_SUMMARY_HEAD_RACE', message: 'Pull-request identity or base/head snapshot changed during summary publication', retryable: true };
206
+ const frontmatter = parseTypedTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8'));
207
+ const bound = readPrDeliveryFact(frontmatter, options.runtimeVersion);
208
+ if (bound.status !== 'valid' || bound.fact.state !== 'bound' || !resourceIdentityEquals(bound.fact.identity.resource, prepared.parent))
209
+ return { code: 'PLATFORM_OPERATION_IDENTITY_MISMATCH', message: 'Bound pull request identity changed during summary publication', retryable: true };
210
+ try {
211
+ await manualValidationEvidence(resolved, prepared.manual, prepared.rendered, latest.value);
212
+ }
213
+ catch (error) {
214
+ const value = error;
215
+ return { code: value.code || 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: value.message || String(error), retryable: false };
216
+ }
217
+ return null;
218
+ }
219
+ async function syncBoundPullRequestSummary(resolved, options, context, loaded, expectedIdentity, preserveFailure) {
220
+ const state = { prepared: null };
221
+ const expectedDigest = createHash('sha256').update(options.body).digest('hex');
222
+ const operation = {
223
+ taskRef: resolved.taskId, cwd: resolved.repoRoot, kind: 'pull-request-summary',
224
+ target: JSON.stringify(expectedIdentity), expectedDigest, dependency: 'deferred',
225
+ pullRequestSummary: {
226
+ body: options.body, changeReportFile: options.changeReportFile ?? taskReportPath(resolved.taskDir),
227
+ ...(options.manualValidation?.transactionId ? { manualValidation: { ...options.manualValidation, transactionId: options.manualValidation.transactionId } } : {})
228
+ }
229
+ };
230
+ const strategy = {
231
+ taskId: resolved.taskId, cwd: resolved.repoRoot, agent: options.agent, client: options.client, resolved: { loaded, context },
232
+ parentKind: 'pull-request', kind: 'summary', markerPrefix: summaryMarker(resolved.taskId).replace(/ -->$/u, ''),
233
+ dryRun: options.dryRun, verifyReadback: true,
234
+ prepare: async () => {
235
+ const prepared = await prepareBoundPullRequestSummary(resolved, options, context, loaded, expectedIdentity);
236
+ state.prepared = prepared;
237
+ return { parent: prepared.parent, items: [prepared.item] };
238
+ },
239
+ validate: async () => state.prepared ? validateBoundPullRequestSummary(resolved, options, context, loaded, state.prepared) : null
240
+ };
241
+ const output = await syncCommentPlan(strategy, operation, options.queuedOperation);
242
+ if (output.error || output.status === 'failed' || output.status === 'blocked')
243
+ return preserveFailure({
244
+ ...output, resource: { kind: 'pull-request', number: state.prepared?.prNumber ?? resourceIdentityNumber(expectedIdentity), identity: expectedIdentity }
245
+ }, state.prepared?.prNumber ?? resourceIdentityNumber(expectedIdentity));
246
+ const info = state.prepared
247
+ ? { precheckVerdict: state.prepared.precheckVerdict, nextAction: state.prepared.nextAction }
248
+ : { precheckVerdict: 'needs-review', nextAction: 'review-code' };
249
+ return {
250
+ ...output,
251
+ resource: { kind: 'pull-request', number: state.prepared?.prNumber ?? resourceIdentityNumber(expectedIdentity), identity: expectedIdentity },
252
+ comment: output.comment ? { ...output.comment, kind: 'summary' } : null,
253
+ result: null, warnings: [], ...info
254
+ };
255
+ }
256
+ function validateUnboundPrSummaryInput(options, taskContent, knownPrNumber) {
257
+ try {
258
+ prepareAndRenderPrSummaryContent(JSON.parse(options.body), taskContent);
259
+ return null;
260
+ }
261
+ catch (error) {
262
+ return platformResult('failed', {
263
+ resource: { kind: 'pull-request', number: knownPrNumber },
264
+ error: { code: 'PR_SUMMARY_CONTENT_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false }
265
+ });
266
+ }
267
+ }
268
+ function boundSummaryPhase(raw) {
269
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
270
+ return null;
271
+ const input = raw;
272
+ const manual = input.manualValidation && typeof input.manualValidation === 'object' && !Array.isArray(input.manualValidation)
273
+ ? input.manualValidation : {};
274
+ const state = typeof manual.state === 'string' ? manual.state : null;
275
+ const overview = typeof input.overview === 'string' ? input.overview : '';
276
+ return {
277
+ passed: state === 'passed' || /###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)/u.test(overview),
278
+ known: state !== null || overview.length > 0
279
+ };
280
+ }
281
+ function manualSummaryPhaseFailure(options, phase) {
282
+ const manual = options.manualValidation;
283
+ if (phase.passed && (manual?.phase !== 'final' || manual.authority !== 'coordinator'))
284
+ return {
285
+ code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'final manual-validation summary requires the transaction coordinator'
286
+ };
287
+ if (!manual || !phase.known)
288
+ return null;
289
+ if ((manual.phase === 'pending' && phase.passed) || (manual.phase === 'final' && !phase.passed))
290
+ return {
291
+ code: 'MANUAL_VALIDATION_SUMMARY_PHASE_INVALID', message: 'manual-validation summary phase does not match the requested writer phase'
292
+ };
293
+ return null;
294
+ }
295
+ function boundSummaryPhaseError(options, raw, knownPrNumber) {
296
+ const phase = boundSummaryPhase(raw);
297
+ if (!phase)
298
+ return null;
299
+ const error = manualSummaryPhaseFailure(options, phase);
300
+ return error ? platformResult('failed', { resource: { kind: 'pull-request', number: knownPrNumber }, error: { ...error, retryable: false } }) : null;
301
+ }
302
+ function validateBoundPrSummaryInput(options, knownPrNumber) {
303
+ try {
304
+ return boundSummaryPhaseError(options, JSON.parse(options.body), knownPrNumber);
305
+ }
306
+ catch (error) {
307
+ return platformResult('failed', {
308
+ resource: { kind: 'pull-request', number: knownPrNumber },
309
+ error: { code: 'PR_SUMMARY_CONTENT_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false }
310
+ });
311
+ }
312
+ }
313
+ async function prepareSummarySync(taskRef, options) {
314
+ const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
315
+ if (!resolved.ok)
316
+ return { ok: false, knownPrNumber: null, error: platformResult('failed', { error: { code: resolved.code, message: resolved.message, retryable: false } }) };
317
+ const knownPrNumber = boundPrNumberForWarning(resolved.taskMdPath);
318
+ const taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
319
+ const fact = readPrDeliveryFact(parseTypedTaskFrontmatter(taskContent), options.runtimeVersion);
320
+ if (fact.status === 'invalid')
321
+ return { ok: false, knownPrNumber, error: platformResult('failed', { error: { code: fact.error.code, message: fact.error.message, retryable: false } }) };
322
+ const isBound = fact.status === 'valid' && fact.fact.state === 'bound';
323
+ const contentError = isBound
324
+ ? validateBoundPrSummaryInput(options, knownPrNumber)
325
+ : validateUnboundPrSummaryInput(options, taskContent, knownPrNumber);
326
+ if (contentError)
327
+ return { ok: false, knownPrNumber, error: contentError };
328
+ if (!options.changeReportFile && (options.dryRun || fact.status !== 'valid' || fact.fact.state !== 'bound'))
329
+ return {
330
+ ok: false, knownPrNumber, error: platformResult('failed', { error: { code: 'PR_CHANGE_REPORT_MISSING', message: 'summary-sync requires --change-report-file', retryable: false } })
331
+ };
332
+ const loaded = await resolvePlatformProviderContext({ cwd: resolved.repoRoot, client: options.client });
333
+ const context = loaded.ok ? loaded.value.context : loaded.context;
334
+ if (!loaded.ok || !context.platform.repository || !['no-op', 'degraded'].includes(context.status))
335
+ return { ok: false, knownPrNumber, error: context };
336
+ return { ok: true, resolved, fact, knownPrNumber, loaded: loaded.value, context };
337
+ }
106
338
  async function inspectBoundPullRequest(context, repoRoot, prIdentity, loadedContext) {
107
339
  if (!context.platform.repository) {
108
340
  return { ok: false, status: context.status, error: context.error };
@@ -348,7 +580,7 @@ async function reportWrite(taskRef, options) {
348
580
  return { ...context, report: null };
349
581
  let knownPrNumber = null;
350
582
  try {
351
- return await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, options.agent, async () => {
583
+ return await (async () => {
352
584
  const { taskContent, boundFact, factError } = readBoundTaskSnapshot(resolved.taskMdPath);
353
585
  if (factError)
354
586
  return {
@@ -433,22 +665,19 @@ async function reportWrite(taskRef, options) {
433
665
  operations: [{ name: 'change-report:write', status: 'applied', reasonCode: null }],
434
666
  report: { ...reportInfo, status: 'written' }
435
667
  };
436
- });
668
+ })();
437
669
  }
438
670
  catch (error) {
439
- const lockError = error instanceof TaskExecutionLockError
440
- ? { code: error.code, message: error.message, retryable: error.code === 'ORCHESTRATION_LOCK_BUSY' }
441
- : { code: 'PR_CHANGE_REPORT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false };
442
- return { ...basePlatformResult(lockError.retryable ? 'blocked' : 'failed', context, resolved.taskId, knownPrNumber, lockError), report: null };
671
+ return { ...basePlatformResult('failed', context, resolved.taskId, knownPrNumber, { code: 'PR_CHANGE_REPORT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false }), report: null };
443
672
  }
444
673
  }
445
674
  async function syncPullRequestSummary(taskRef, options) {
446
675
  const warningResult = warningResultForPrimary(options.primaryResult);
447
676
  let knownPrNumber = null;
448
- const softenFailure = (output) => {
677
+ const softenFailure = (output, fallbackNumber = knownPrNumber) => {
449
678
  const prNumber = output.resource.kind === 'pull-request' && output.resource.number
450
679
  ? output.resource.number
451
- : knownPrNumber;
680
+ : fallbackNumber;
452
681
  const warning = output.error && prNumber && output.error.code !== 'PR_NOT_LINKED'
453
682
  ? {
454
683
  code: output.error.code,
@@ -471,282 +700,78 @@ async function syncPullRequestSummary(taskRef, options) {
471
700
  }
472
701
  : { ...output, result: null, warnings: [] };
473
702
  };
474
- const preserveFailure = (output) => options.strict
703
+ const preserveFailure = (output, fallbackNumber = knownPrNumber) => options.strict
704
+ || isHardSummaryFailure(output.error)
475
705
  ? { ...output, result: null, warnings: [] }
476
- : softenFailure(output);
477
- const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
478
- if (!resolved.ok)
479
- return softenFailure(platformResult('failed', { error: { code: resolved.code, message: resolved.message, retryable: false } }));
480
- const fail = (status, context, error, prNumber = knownPrNumber) => {
481
- const output = basePlatformResult(status, context, resolved.taskId, prNumber, error);
482
- return options.strict || isHardSummaryFailure(error)
483
- ? { ...output, result: null, warnings: [] }
484
- : { ...softenFailure(output) };
485
- };
486
- const frontmatter = parseTypedTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8'));
487
- const fact = readPrDeliveryFact(frontmatter, options.runtimeVersion);
488
- if (fact.status === 'invalid')
489
- return preserveFailure(platformResult('failed', { error: { code: fact.error.code, message: fact.error.message, retryable: false } }));
490
- if (!options.changeReportFile)
491
- return preserveFailure(platformResult('failed', { error: { code: 'PR_CHANGE_REPORT_MISSING', message: 'summary-sync requires --change-report-file', retryable: false } }));
492
- const loaded = await resolvePlatformProviderContext({ cwd: resolved.repoRoot, client: options.client });
493
- const context = loaded.ok ? loaded.value.context : loaded.context;
494
- if (!loaded.ok || !context.platform.repository || !['no-op', 'degraded'].includes(context.status)) {
495
- // This diagnostic-only read preserves the known PR target when context resolution fails before the execution lock.
496
- knownPrNumber = boundPrNumberForWarning(resolved.taskMdPath);
497
- return preserveFailure(context);
706
+ : softenFailure(output, fallbackNumber);
707
+ const prepared = await prepareSummarySync(taskRef, options);
708
+ if (!prepared.ok)
709
+ return preserveFailure(prepared.error, prepared.knownPrNumber);
710
+ const { resolved, fact, loaded, context } = prepared;
711
+ knownPrNumber = prepared.knownPrNumber;
712
+ if (fact.status === 'valid' && fact.fact.state === 'bound') {
713
+ return syncBoundPullRequestSummary(resolved, options, context, loaded, fact.fact.identity.resource, preserveFailure);
498
714
  }
715
+ return preserveFailure(platformResult(options.dryRun ? 'blocked' : 'failed', { error: {
716
+ code: 'PR_NOT_LINKED', message: 'Task has no verified bound pull request', retryable: false
717
+ } }));
718
+ }
719
+ async function replayPullRequestSummary(taskId, operation, options) {
720
+ const intent = operation.pullRequestSummary;
721
+ if (!intent)
722
+ return platformResult('blocked', { error: { code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Queued pull-request summary content is missing', retryable: false } });
723
+ let target;
499
724
  try {
500
- const execute = async () => {
501
- const { taskContent, boundFact, factError } = readBoundTaskSnapshot(resolved.taskMdPath);
502
- if (factError)
503
- return {
504
- ...platformResult('failed', { resource: { kind: 'pull-request', number: null }, error: { code: factError.code, message: factError.message, retryable: false } }),
505
- result: null, warnings: []
506
- };
507
- if (!boundFact)
508
- return {
509
- ...platformResult('failed', { resource: { kind: 'pull-request', number: null }, error: { code: 'PR_NOT_LINKED', message: 'Task has no verified bound pull request', retryable: false } }),
510
- result: null, warnings: []
511
- };
512
- const prIdentity = boundFact.identity.resource;
513
- const boundPrNumber = resourceIdentityNumber(prIdentity);
514
- knownPrNumber = boundPrNumber;
515
- let prNumber = boundPrNumber;
516
- const initial = await inspectBoundPullRequest(context, resolved.repoRoot, prIdentity, loaded.value);
517
- if (!initial.ok)
518
- return fail(initial.status, context, initial.error);
519
- const manual = options.manualValidation;
520
- let manualEvidenceDigest = null;
521
- const hasFinalManualValidation = /###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)/u.test(options.body);
522
- if (hasFinalManualValidation && (!manual || manual.phase !== 'final'))
523
- return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'final manual-validation summary requires the transaction coordinator', retryable: false }, prNumber);
524
- if (manual && (manual.phase === 'pending' ? hasFinalManualValidation : !hasFinalManualValidation))
525
- return fail('failed', context, { code: 'MANUAL_VALIDATION_SUMMARY_PHASE_INVALID', message: 'manual-validation summary phase does not match the requested writer phase', retryable: false }, prNumber);
526
- if (manual) {
527
- if (!Number.isSafeInteger(initial.value.number) || initial.value.number <= 0)
528
- return fail('failed', context, { code: 'MANUAL_VALIDATION_PR_NUMBER_REQUIRED', message: 'manual-validation summary requires a positive safe pull-request number from the authoritative snapshot', retryable: false }, prNumber);
529
- prNumber = initial.value.number;
530
- knownPrNumber = prNumber;
531
- if (!manual.transactionId)
532
- return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'manual-validation summary requires a transaction ID', retryable: false }, prNumber);
533
- if (manual.phase === 'pending') {
534
- const transaction = readManualValidationTransaction(resolved.taskDir, {
535
- transactionId: manual.transactionId,
536
- taskId: resolved.taskId,
537
- prNumber,
538
- prHeadSha: initial.value.head.sha
539
- });
540
- if (!transaction.ok)
541
- return fail('failed', context, platformError(transaction.error), prNumber);
542
- }
543
- if (manual.phase === 'final' && (!manual.receiptDigest || !manual.prHeadSha || manual.prHeadSha !== initial.value.head.sha))
544
- return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'final manual-validation summary requires transaction, receipt, and current head identity', retryable: false }, prNumber);
545
- if (manual.phase === 'final') {
546
- if (manual.authority !== 'coordinator')
547
- return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'final manual-validation summary requires coordinator authority', retryable: false }, prNumber);
548
- const completion = readManualValidationCompletion(resolved.taskDir, {
549
- transactionId: manual.transactionId,
550
- taskId: resolved.taskId,
551
- prNumber,
552
- prHeadSha: initial.value.head.sha,
553
- receiptDigest: manual.receiptDigest
554
- });
555
- if (!completion.ok)
556
- return fail('failed', context, platformError(completion.error), prNumber);
557
- const { receipt, transaction } = completion.value;
558
- manualEvidenceDigest = receipt.evidenceDigest;
559
- if (transaction.phase !== 'final-promotion-in-progress' || !transaction.eventAppended)
560
- return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_PHASE_INVALID', message: 'final summary requires a receipt-backed promotion intent', retryable: false }, prNumber);
561
- if (!manualValidationFinalSummaryProjectionMatches(options.body, receipt))
562
- return fail('failed', context, { code: 'MANUAL_VALIDATION_SUMMARY_PROJECTION_INVALID', message: 'final summary body does not match the canonical receipt projection', retryable: false }, prNumber);
563
- }
564
- }
565
- const expectedReportPath = taskReportPath(resolved.taskDir);
566
- const suppliedReportPath = path.resolve(resolved.repoRoot, options.changeReportFile);
567
- if (suppliedReportPath !== expectedReportPath)
568
- return fail('failed', context, { code: 'PR_CHANGE_REPORT_PATH_INVALID', message: 'summary-sync must consume the task-bound pr-change-report.json', retryable: false });
569
- const report = readPrChangeReport(suppliedReportPath);
570
- if (!report.ok)
571
- return fail('failed', context, platformError(report.error));
572
- const checked = currentReportCheck(report.value, initial.value, taskContent, resolved.repoRoot);
573
- if (!checked.ok)
574
- return fail('failed', context, checked.error);
575
- const placeholder = CANONICAL_REPORT_PLACEHOLDER;
576
- const split = splitDocumentPlaceholder(options.body, placeholder);
577
- if (!split.ok)
578
- return fail('failed', context, {
579
- code: 'PR_SUMMARY_BODY_CONTRACT_INVALID',
580
- message: split.error.message,
581
- retryable: false
582
- });
583
- const prefix = sanitizeMarkdownDocument(split.value.prefix, { reservedMarkers: [CONTROL_MARKER_PATTERN] });
584
- const suffix = sanitizeMarkdownDocument(split.value.suffix, { reservedMarkers: [CONTROL_MARKER_PATTERN] });
585
- if (!prefix.ok) {
586
- return fail('failed', context, {
587
- code: 'PR_SUMMARY_BODY_CONTRACT_INVALID',
588
- message: `${prefix.error.message} at offset ${prefix.error.offset}`,
589
- retryable: false
590
- });
591
- }
592
- if (!suffix.ok) {
593
- return fail('failed', context, {
594
- code: 'PR_SUMMARY_BODY_CONTRACT_INVALID',
595
- message: `${suffix.error.message} at offset ${suffix.error.offset}`,
596
- retryable: false
597
- });
598
- }
599
- const replaced = replaceCanonicalReportPlaceholder(`${prefix.value}${placeholder}${suffix.value}`, report.value);
600
- if (!replaced.ok)
601
- return fail('failed', context, platformError(replaced.error));
602
- const renderedBody = manual?.phase === 'final'
603
- ? replaced.value.replace(MANUAL_VALIDATION_RECEIPT_PLACEHOLDER, `<!-- manual-validation-receipt: transaction=${manual.transactionId}; receipt=${manual.receiptDigest}; evidence=${manualEvidenceDigest}; head=${manual.prHeadSha} -->`)
604
- : replaced.value;
605
- const desired = buildPullRequestSummary(resolved.taskId, renderedBody, initial.value.head.sha);
606
- if (!isSafeSummaryEnvelope(desired, resolved.taskId))
607
- return fail('failed', context, {
608
- code: 'PR_SUMMARY_RENDER_INVALID',
609
- message: 'Summary contains an invalid or duplicated reserved control marker',
610
- retryable: false
611
- });
612
- const listed = loaded.value.provider.comments?.list
613
- ? await loaded.value.provider.comments.list({ context: providerOperationContext(loaded.value), parent: prIdentity }).then((response) => response.ok
614
- ? { ok: true, value: response.value.map((comment) => ({ id: /^\d+$/.test(comment.id) ? Number(comment.id) : comment.id, body: comment.body })) }
615
- : response)
616
- : unsupportedProviderOperation(loaded.value.provider, 'comments.list');
617
- if (!listed.ok)
618
- return fail(providerStatus(listed.error) === 'blocked' ? 'blocked' : 'failed', context, providerError(listed.error, 'PLATFORM_PROVIDER_OPERATION_FAILED'));
619
- const reconciliation = reconcileSummaryComment(listed.value, resolved.taskId, desired);
620
- if (reconciliation.action === 'conflict')
621
- return fail('failed', context, { code: 'PR_SUMMARY_MARKER_AMBIGUOUS', message: 'Multiple PR comments contain the summary marker', retryable: false });
622
- const previousBody = reconciliation.action === 'update'
623
- ? listed.value.find((comment) => comment.id === reconciliation.commentId)?.body ?? null
624
- : null;
625
- const info = { precheckVerdict: report.value.precheck.verdict, nextAction: report.value.precheck.route };
626
- const beforeWrite = await inspectBoundPullRequest(context, resolved.repoRoot, prIdentity, loaded.value);
627
- if (!beforeWrite.ok)
628
- return fail(beforeWrite.status, context, beforeWrite.error, prNumber);
629
- if (!sameIdentity(initial.value, beforeWrite.value))
630
- return fail('blocked', context, {
631
- code: 'PR_SUMMARY_HEAD_RACE',
632
- message: 'Pull request head changed before publishing the summary',
633
- retryable: true
634
- }, prNumber);
635
- if (reconciliation.action === 'no-op')
636
- return {
637
- ...basePlatformResult('no-op', context, resolved.taskId, prNumber),
638
- comment: { kind: 'summary', marker: summaryMarker(resolved.taskId), ids: [reconciliation.commentId], parts: 1 },
639
- result: null, warnings: [], ...info
640
- };
641
- if (options.dryRun)
642
- return {
643
- ...basePlatformResult('planned', context, resolved.taskId, prNumber),
644
- operations: [{ name: `summary:${reconciliation.action}`, status: 'planned', reasonCode: null }],
645
- result: null, warnings: [], ...info
646
- };
647
- const publish = async () => {
648
- const existingComment = reconciliation.commentId === null
649
- ? undefined
650
- : providerResourceToken(loaded.value.provider, 'comment', String(reconciliation.commentId));
651
- const written = loaded.value.provider.comments?.write
652
- ? await loaded.value.provider.comments.write({
653
- context: providerOperationContext(loaded.value),
654
- parent: prIdentity,
655
- body: desired,
656
- ...(existingComment ? { existingComment } : {}),
657
- mutation: { idempotencyKey: `pr-summary:${resolved.taskId}` }
658
- })
659
- : unsupportedProviderOperation(loaded.value.provider, 'comments.write');
660
- if (!written.ok)
661
- return fail(providerStatus(written.error) === 'blocked' ? 'blocked' : 'failed', context, providerError(written.error, 'PLATFORM_PROVIDER_OPERATION_FAILED'), prNumber);
662
- const after = await inspectBoundPullRequest(context, resolved.repoRoot, prIdentity, loaded.value);
663
- const id = /^\d+$/.test(written.value.remoteId) ? Number(written.value.remoteId) : written.value.remoteId;
664
- const compensateWrittenSummary = async () => reconciliation.action === 'create'
665
- ? written.value.remoteId && loaded.value.provider.comments?.delete
666
- ? await loaded.value.provider.comments.delete({
667
- context: providerOperationContext(loaded.value),
668
- parent: prIdentity,
669
- comment: providerResourceToken(loaded.value.provider, 'comment', written.value.remoteId),
670
- mutation: { idempotencyKey: `pr-summary-delete:${resolved.taskId}:${written.value.remoteId}` }
671
- })
672
- : { ok: false, error: { code: 'PR_SUMMARY_RACE_COMPENSATION_FAILED', message: 'Created summary comment has no valid id', retryable: true } }
673
- : reconciliation.commentId && previousBody !== null && loaded.value.provider.comments?.write
674
- ? await loaded.value.provider.comments.write({
675
- context: providerOperationContext(loaded.value),
676
- parent: prIdentity,
677
- body: previousBody,
678
- existingComment: providerResourceToken(loaded.value.provider, 'comment', String(reconciliation.commentId)),
679
- mutation: { idempotencyKey: `pr-summary-restore:${resolved.taskId}` }
680
- })
681
- : { ok: false, error: { code: 'PR_SUMMARY_RACE_COMPENSATION_FAILED', message: 'Previous summary body is unavailable for restoration', retryable: true } };
682
- if (!after.ok) {
683
- const compensated = await compensateWrittenSummary();
684
- if (!compensated.ok)
685
- return fail('blocked', context, {
686
- code: 'PR_SUMMARY_RACE_COMPENSATION_FAILED',
687
- message: `Summary post-write verification failed and compensation failed: ${compensated.error.message}`,
688
- retryable: true
689
- }, prNumber);
690
- return fail('blocked', context, {
691
- code: 'PR_SUMMARY_POSTWRITE_VERIFY_FAILED',
692
- message: `Summary was written but pull-request verification failed: ${after.error?.message || 'unknown verification error'}; the previous summary was restored or the new comment was deleted`,
693
- retryable: true
694
- }, prNumber);
695
- }
696
- if (!sameIdentity(initial.value, after.value)) {
697
- const compensated = await compensateWrittenSummary();
698
- if (!compensated.ok)
699
- return fail('blocked', context, {
700
- code: 'PR_SUMMARY_RACE_COMPENSATION_FAILED',
701
- message: `Pull-request head changed and summary compensation failed: ${compensated.error.message}`,
702
- retryable: true
703
- }, prNumber);
704
- return fail('blocked', context, {
705
- code: 'PR_SUMMARY_HEAD_RACE',
706
- message: 'Pull-request head changed while publishing; the previous summary was restored or the new comment was deleted; rerun report and summary sync',
707
- retryable: true
708
- }, prNumber);
709
- }
710
- return {
711
- ...basePlatformResult('applied', context, resolved.taskId, prNumber),
712
- comment: { kind: 'summary', marker: summaryMarker(resolved.taskId), ids: Number.isInteger(id) ? [id] : [], parts: 1 },
713
- operations: [{ name: `summary:${reconciliation.action}`, status: 'applied', reasonCode: null }],
714
- result: null, warnings: [], ...info
715
- };
716
- };
717
- const operation = {
718
- taskRef: resolved.taskId,
719
- cwd: resolved.repoRoot,
720
- kind: 'pull-request-summary',
721
- target: JSON.stringify(prIdentity),
722
- expectedDigest: createHash('sha256').update(desired).digest('hex'),
723
- dependency: 'deferred',
724
- pullRequestSummary: {
725
- body: options.body,
726
- changeReportFile: options.changeReportFile,
727
- ...(manual?.transactionId ? { manualValidation: { ...manual, transactionId: manual.transactionId } } : {})
728
- }
729
- };
730
- if (options.skipQueue)
731
- return publish();
732
- return coordinatePlatformWrite({
733
- operation,
734
- agent: options.agent,
735
- execute: publish,
736
- block: (error) => fail('blocked', context, error, prNumber),
737
- persistenceFailure: (error) => fail('failed', context, error, prNumber)
738
- });
739
- };
740
- return await (options.lockAlreadyHeld
741
- ? execute()
742
- : withTaskExecutionLock(resolved.repoRoot, resolved.taskId, options.agent, execute));
725
+ target = parseResourceIdentity(JSON.parse(operation.target), 'queued pull-request summary identity');
743
726
  }
744
- catch (error) {
745
- const lockError = error instanceof TaskExecutionLockError
746
- ? { code: error.code, message: error.message, retryable: error.code === 'ORCHESTRATION_LOCK_BUSY' }
747
- : { code: 'PR_SUMMARY_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false };
748
- return fail(lockError.retryable ? 'blocked' : 'failed', context, lockError);
727
+ catch {
728
+ return platformResult('failed', { error: { code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Queued pull-request summary identity is invalid', retryable: false } });
749
729
  }
730
+ const bound = await inspectPlatformPullRequest(taskId, { cwd: options.cwd, client: options.client });
731
+ if (!bound.pullRequest || !resourceIdentityEquals(bound.pullRequest.identity, target))
732
+ return platformResult('blocked', {
733
+ resource: bound.pullRequest ? { kind: 'pull-request', number: bound.pullRequest.number, identity: bound.pullRequest.identity } : undefined,
734
+ error: bound.error ?? { code: 'PLATFORM_OPERATION_IDENTITY_MISMATCH', message: 'Bound pull request differs from the queued summary target', retryable: false }
735
+ });
736
+ const replayed = await syncPullRequestSummary(taskId, {
737
+ agent: options.agent, cwd: options.cwd, client: options.client, body: intent.body,
738
+ changeReportFile: intent.changeReportFile, ...(intent.manualValidation ? { manualValidation: intent.manualValidation } : {}),
739
+ primaryResult: 'no_op', strict: true, queuedOperation: operation
740
+ });
741
+ return (replayed.status === 'applied' || replayed.status === 'no-op') && !replayed.error && replayed.warnings.length === 0
742
+ ? replayed
743
+ : platformResult('blocked', { resource: replayed.resource, error: replayed.error ?? {
744
+ code: 'PR_SUMMARY_RECOVERY_UNCONFIRMED', message: 'Pull-request summary preparation or recovery did not confirm the frozen operation', retryable: true
745
+ } });
746
+ }
747
+ function correctPrSummaryContent(value, taskContent) {
748
+ if (!value || typeof value !== 'object' || Array.isArray(value))
749
+ return parsePrSummaryContent(value);
750
+ const input = { ...value };
751
+ const ledger = parseLedgerDocument(taskContent);
752
+ if (ledger.present) {
753
+ const decisions = ledger.rows.filter((row) => row.id.startsWith('HD-'));
754
+ const open = decisions.filter((row) => row.status === 'needs-human-decision').length;
755
+ const resolved = decisions.filter((row) => row.status === 'human-decided').length;
756
+ const current = input.humanDecisions && typeof input.humanDecisions === 'object' && !Array.isArray(input.humanDecisions)
757
+ ? { ...input.humanDecisions }
758
+ : {};
759
+ if (!Number.isSafeInteger(current.open) || Number(current.open) < 0)
760
+ current.open = open;
761
+ if (!Number.isSafeInteger(current.resolved) || Number(current.resolved) < 0)
762
+ current.resolved = resolved;
763
+ if (typeof current.detail !== 'string' || !current.detail.trim())
764
+ current.detail = `Task decision ledger: ${open} open, ${resolved} resolved.`;
765
+ input.humanDecisions = current;
766
+ }
767
+ return input;
768
+ }
769
+ export function prepareAndRenderPrSummaryContent(value, taskContent) {
770
+ return runSummaryContentPipeline(value, {
771
+ validate: parsePrSummaryContent,
772
+ correct: (input) => correctPrSummaryContent(input, taskContent),
773
+ render: renderPrSummaryContent
774
+ });
750
775
  }
751
- export { buildPullRequestSummary, reconcileSummaryComment, reportWrite, summaryCommentState, summaryContext, summaryMarker, syncPullRequestSummary, warningResultForPrimary };
776
+ export { buildPullRequestSummary, reportWrite, summaryCommentState, summaryContext, summaryMarker, syncPullRequestSummary, replayPullRequestSummary, warningResultForPrimary };
752
777
  //# sourceMappingURL=pr-summary.js.map