@fitlab-ai/agent-infra 0.9.14 → 0.9.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/README.md +10 -0
  2. package/README.zh-CN.md +10 -0
  3. package/bin/internal-cli.sh +45 -0
  4. package/dist/bin/internal-cli.js +93 -30
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -2
  7. package/dist/lib/defaults.json +103 -103
  8. package/dist/lib/fs/durable-write.js +37 -0
  9. package/dist/lib/fs/file-lock.js +111 -0
  10. package/dist/lib/host-control/audit.js +19 -0
  11. package/dist/lib/host-control/client.js +142 -0
  12. package/dist/lib/host-control/command.js +49 -0
  13. package/dist/lib/host-control/path.js +160 -0
  14. package/dist/lib/host-control/secure-fs.js +140 -0
  15. package/dist/lib/host-control/server.js +159 -0
  16. package/dist/lib/host-control/service.js +112 -0
  17. package/dist/lib/init.js +11 -2
  18. package/dist/lib/internal/cli-route-inventory.js +3 -1
  19. package/dist/lib/internal/codex-lifecycle.js +6 -6
  20. package/dist/lib/internal/demo-promotion.js +322 -0
  21. package/dist/lib/internal/demo-transcript.js +454 -0
  22. package/dist/lib/internal/host-control.js +28 -0
  23. package/dist/lib/internal/manual-validation.js +322 -0
  24. package/dist/lib/internal/platform-pr.js +18 -4
  25. package/dist/lib/internal/release-workflow.js +63 -84
  26. package/dist/lib/internal/sandbox-control.js +149 -2
  27. package/dist/lib/internal/task-artifact.js +15 -262
  28. package/dist/lib/internal/task-create.js +6 -5
  29. package/dist/lib/internal/task-event.js +37 -37
  30. package/dist/lib/internal/task-operation-registry.js +101 -3
  31. package/dist/lib/internal/task-review.js +8 -53
  32. package/dist/lib/internal/task-validate.js +30 -10
  33. package/dist/lib/owned-path.js +33 -0
  34. package/dist/lib/platform/check-status.js +11 -0
  35. package/dist/lib/platform/comment-safety.js +4 -1
  36. package/dist/lib/platform/completion-backfill.js +1 -1
  37. package/dist/lib/platform/github-data.js +241 -0
  38. package/dist/lib/platform/github-provider.js +15 -20
  39. package/dist/lib/platform/issue-comments.js +2 -2
  40. package/dist/lib/platform/issues.js +1 -179
  41. package/dist/lib/platform/pr-checks.js +12 -65
  42. package/dist/lib/platform/pr-summary.js +55 -3
  43. package/dist/lib/platform/pull-requests.js +2 -105
  44. package/dist/lib/platform/snapshots.js +2 -0
  45. package/dist/lib/platform/verification-sync.js +127 -142
  46. package/dist/lib/release/workflow.js +1 -1
  47. package/dist/lib/sandbox/commands/create.js +7 -2
  48. package/dist/lib/sandbox/commands/rm.js +3 -1514
  49. package/dist/lib/sandbox/control/audit.js +203 -0
  50. package/dist/lib/sandbox/control/client.js +93 -54
  51. package/dist/lib/sandbox/control/executor.js +129 -65
  52. package/dist/lib/sandbox/control/finalization-response.js +27 -0
  53. package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
  54. package/dist/lib/sandbox/control/lifecycle.js +36 -2
  55. package/dist/lib/sandbox/control/native-file-lock.js +6 -98
  56. package/dist/lib/sandbox/control/protocol.js +55 -4
  57. package/dist/lib/sandbox/control/server.js +491 -120
  58. package/dist/lib/sandbox/control/state.js +58 -0
  59. package/dist/lib/sandbox/control/task-workflow.js +74 -0
  60. package/dist/lib/sandbox/control/workflow-executor.js +99 -0
  61. package/dist/lib/sandbox/index.js +2 -2
  62. package/dist/lib/sandbox/recovery.js +28 -4
  63. package/dist/lib/sandbox/removal.js +1717 -0
  64. package/dist/lib/sandbox/task-cleanup.js +217 -0
  65. package/dist/lib/sandbox/workspace-view.js +13 -14
  66. package/dist/lib/task/activity-intent.js +2 -1
  67. package/dist/lib/task/activity-log.js +30 -24
  68. package/dist/lib/task/artifact-command.js +101 -0
  69. package/dist/lib/task/artifact-lifecycle.js +30 -28
  70. package/dist/lib/task/artifact-name.js +36 -0
  71. package/dist/lib/task/artifact-operations.js +23 -69
  72. package/dist/lib/task/artifact-receipts.js +3 -15
  73. package/dist/lib/task/artifact-repair-intent.js +87 -16
  74. package/dist/lib/task/artifact-schema.js +6 -14
  75. package/dist/lib/task/capabilities.js +12 -19
  76. package/dist/lib/task/codex-orchestration.js +71 -25
  77. package/dist/lib/task/commit-identity.js +28 -0
  78. package/dist/lib/task/commit-operation.js +2 -16
  79. package/dist/lib/task/control-authority.js +371 -5
  80. package/dist/lib/task/control-recovery.js +128 -0
  81. package/dist/lib/task/create.js +62 -9
  82. package/dist/lib/task/decision-details.js +2 -46
  83. package/dist/lib/task/events.js +120 -9
  84. package/dist/lib/task/finalization-state.js +9 -0
  85. package/dist/lib/task/human-override.js +3 -6
  86. package/dist/lib/task/implementation-inputs.js +3 -3
  87. package/dist/lib/task/intermediate-cleanup.js +383 -0
  88. package/dist/lib/task/invalidation.js +18 -65
  89. package/dist/lib/task/ledger-intents.js +7 -22
  90. package/dist/lib/task/ledger.js +1 -3
  91. package/dist/lib/task/lifecycle.js +19 -1
  92. package/dist/lib/task/local-artifact-finalization.js +132 -157
  93. package/dist/lib/task/manual-validation-completion.js +26 -0
  94. package/dist/lib/task/manual-validation-evidence.js +130 -0
  95. package/dist/lib/task/manual-validation-receipt.js +101 -0
  96. package/dist/lib/task/manual-validation-shared.js +40 -0
  97. package/dist/lib/task/manual-validation-transaction.js +243 -0
  98. package/dist/lib/task/markdown.js +47 -0
  99. package/dist/lib/task/orchestration.js +14 -17
  100. package/dist/lib/task/qualification-audit.js +20 -34
  101. package/dist/lib/task/review-artifacts.js +2 -18
  102. package/dist/lib/task/review-command.js +25 -0
  103. package/dist/lib/task/review-finalization.js +125 -193
  104. package/dist/lib/task/review-fingerprint.js +2 -2
  105. package/dist/lib/task/sections.js +34 -56
  106. package/dist/lib/task/verification-engine.js +116 -323
  107. package/dist/lib/task/verification.js +1 -1
  108. package/dist/lib/task/workflow-command.js +17 -0
  109. package/dist/lib/task/workflow-warning-intents.js +0 -16
  110. package/dist/lib/task/workflow-warnings.js +28 -2
  111. package/dist/lib/update.js +2 -0
  112. package/dist/lifecycle-build-manifest.json +3 -3
  113. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
  114. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -2
  115. package/lib/fs/durable-write.ts +29 -0
  116. package/lib/fs/file-lock.ts +113 -0
  117. package/lib/host-control/audit.ts +22 -0
  118. package/lib/host-control/client.ts +172 -0
  119. package/lib/host-control/command.ts +60 -0
  120. package/lib/host-control/path.ts +167 -0
  121. package/lib/host-control/secure-fs.ts +135 -0
  122. package/lib/host-control/server.ts +180 -0
  123. package/lib/host-control/service.ts +133 -0
  124. package/lib/init.ts +15 -2
  125. package/lib/internal/cli-route-inventory.ts +3 -1
  126. package/lib/internal/codex-lifecycle.ts +6 -6
  127. package/lib/internal/demo-promotion.ts +354 -0
  128. package/lib/internal/demo-transcript.ts +419 -0
  129. package/lib/internal/host-control.ts +28 -0
  130. package/lib/internal/manual-validation.ts +306 -0
  131. package/lib/internal/platform-pr.ts +15 -4
  132. package/lib/internal/release-workflow.ts +74 -82
  133. package/lib/internal/sandbox-control.ts +162 -1
  134. package/lib/internal/task-artifact.ts +13 -216
  135. package/lib/internal/task-create.ts +5 -5
  136. package/lib/internal/task-event.ts +20 -11
  137. package/lib/internal/task-operation-registry.ts +111 -3
  138. package/lib/internal/task-review.ts +6 -51
  139. package/lib/internal/task-validate.ts +26 -10
  140. package/lib/owned-path.ts +26 -0
  141. package/lib/platform/check-status.ts +9 -0
  142. package/lib/platform/comment-safety.ts +5 -0
  143. package/lib/platform/completion-backfill.ts +1 -1
  144. package/lib/platform/github-data.ts +322 -0
  145. package/lib/platform/github-provider.ts +15 -20
  146. package/lib/platform/issue-comments.ts +3 -4
  147. package/lib/platform/issues.ts +4 -245
  148. package/lib/platform/pr-checks.ts +13 -77
  149. package/lib/platform/pr-summary.ts +61 -5
  150. package/lib/platform/pull-requests.ts +3 -170
  151. package/lib/platform/snapshots.ts +53 -0
  152. package/lib/platform/verification-sync.ts +145 -155
  153. package/lib/release/workflow.ts +3 -3
  154. package/lib/sandbox/commands/create.ts +7 -6
  155. package/lib/sandbox/commands/rm.ts +3 -1909
  156. package/lib/sandbox/control/audit.ts +253 -0
  157. package/lib/sandbox/control/client.ts +109 -66
  158. package/lib/sandbox/control/executor.ts +134 -74
  159. package/lib/sandbox/control/finalization-response.ts +29 -0
  160. package/lib/sandbox/control/identity-sentinel.ts +137 -0
  161. package/lib/sandbox/control/lifecycle.ts +45 -3
  162. package/lib/sandbox/control/native-file-lock.ts +6 -107
  163. package/lib/sandbox/control/protocol.ts +75 -6
  164. package/lib/sandbox/control/server.ts +538 -123
  165. package/lib/sandbox/control/state.ts +78 -0
  166. package/lib/sandbox/control/task-workflow.ts +95 -0
  167. package/lib/sandbox/control/workflow-executor.ts +102 -0
  168. package/lib/sandbox/index.ts +2 -2
  169. package/lib/sandbox/recovery.ts +28 -5
  170. package/lib/sandbox/removal.ts +2144 -0
  171. package/lib/sandbox/task-cleanup.ts +209 -0
  172. package/lib/sandbox/workspace-view.ts +17 -15
  173. package/lib/task/activity-intent.ts +2 -2
  174. package/lib/task/activity-log.ts +27 -19
  175. package/lib/task/artifact-command.ts +104 -0
  176. package/lib/task/artifact-lifecycle.ts +31 -26
  177. package/lib/task/artifact-name.ts +55 -0
  178. package/lib/task/artifact-operations.ts +22 -68
  179. package/lib/task/artifact-receipts.ts +3 -14
  180. package/lib/task/artifact-repair-intent.ts +99 -18
  181. package/lib/task/artifact-schema.ts +5 -34
  182. package/lib/task/capabilities.ts +13 -22
  183. package/lib/task/codex-orchestration.ts +85 -24
  184. package/lib/task/commit-identity.ts +31 -0
  185. package/lib/task/commit-operation.ts +2 -16
  186. package/lib/task/control-authority.ts +502 -5
  187. package/lib/task/control-recovery.ts +174 -0
  188. package/lib/task/create.ts +63 -8
  189. package/lib/task/decision-details.ts +3 -71
  190. package/lib/task/events.ts +131 -10
  191. package/lib/task/finalization-state.ts +11 -0
  192. package/lib/task/human-override.ts +3 -8
  193. package/lib/task/implementation-inputs.ts +3 -3
  194. package/lib/task/intermediate-cleanup.ts +455 -0
  195. package/lib/task/invalidation.ts +18 -62
  196. package/lib/task/ledger-intents.ts +7 -24
  197. package/lib/task/ledger.ts +1 -3
  198. package/lib/task/lifecycle.ts +17 -1
  199. package/lib/task/local-artifact-finalization.ts +148 -182
  200. package/lib/task/manual-validation-completion.ts +40 -0
  201. package/lib/task/manual-validation-evidence.ts +209 -0
  202. package/lib/task/manual-validation-receipt.ts +152 -0
  203. package/lib/task/manual-validation-shared.ts +50 -0
  204. package/lib/task/manual-validation-transaction.ts +304 -0
  205. package/lib/task/markdown.ts +70 -0
  206. package/lib/task/orchestration.ts +19 -19
  207. package/lib/task/qualification-audit.ts +22 -33
  208. package/lib/task/review-artifacts.ts +1 -20
  209. package/lib/task/review-command.ts +23 -0
  210. package/lib/task/review-finalization.ts +131 -259
  211. package/lib/task/review-fingerprint.ts +2 -2
  212. package/lib/task/sections.ts +33 -54
  213. package/lib/task/verification-engine.ts +110 -366
  214. package/lib/task/verification-types.ts +17 -0
  215. package/lib/task/verification.ts +1 -1
  216. package/lib/task/workflow-command.ts +18 -0
  217. package/lib/task/workflow-warning-intents.ts +0 -13
  218. package/lib/task/workflow-warnings.ts +21 -2
  219. package/lib/update.ts +6 -0
  220. package/package.json +6 -6
  221. package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
  222. package/templates/.agents/rules/testing-discipline.en.md +2 -0
  223. package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
  224. package/templates/.agents/scripts/package.json +3 -0
  225. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +32 -20
  226. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +32 -20
  227. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
  228. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
  229. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  230. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  231. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
  232. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
  233. package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
  234. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
  235. package/templates/.agents/skills/release/SKILL.en.md +7 -3
  236. package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
  237. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
  238. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
  239. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
  240. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
  241. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
  242. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
  243. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  244. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  245. package/templates/.agents/skills/test/SKILL.en.md +17 -6
  246. package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
  247. package/templates/.claude/commands/run-manual-validation.en.md +1 -1
  248. package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
  249. package/dist/lib/platform/adapters.js +0 -56
  250. package/lib/platform/adapters.ts +0 -197
@@ -1,3 +1,5 @@
1
+ import type { PlatformIssueSnapshot as IssueSnapshot } from './snapshots.ts';
2
+ import type { IssueFieldSchema } from './github-data.ts';
1
3
  import fs from 'node:fs';
2
4
  import path from 'node:path';
3
5
  import { execFileSync } from 'node:child_process';
@@ -38,26 +40,7 @@ import {
38
40
  import { resourceIdentityEquals, resourceIdentityNumber, resourceIdentityString, serializeResourceIdentity } from './resource-identity.ts';
39
41
  import { taskIssueIdentity, taskIssueIdentityError } from './task-identities.ts';
40
42
  import type { ResourceIdentity } from './resource-identity.ts';
41
-
42
- type GitHubClient = PlatformClient;
43
43
  import type { IssueSnapshot as ProviderIssueSnapshot, RepositoryMetadataSnapshot } from './provider-contract.ts';
44
-
45
- type IssueSnapshot = {
46
- repository: string;
47
- number: number;
48
- identity?: ResourceIdentity;
49
- databaseId: number | null;
50
- nodeId: string;
51
- url: string;
52
- state: 'open' | 'closed';
53
- title: string;
54
- body: string;
55
- labels: string[];
56
- assignees: string[];
57
- milestone: string | null;
58
- issueType: string | null;
59
- fields: Record<string, string | number | null>;
60
- };
61
44
  type IssueResult = PlatformResult & {
62
45
  task: { id: string | null; issueNumber: number | null };
63
46
  issue: IssueSnapshot | null;
@@ -80,33 +63,6 @@ type SyncOptions = SharedOptions & {
80
63
  dryRun?: boolean;
81
64
  };
82
65
 
83
- type RemoteIssue = {
84
- number?: number;
85
- id?: number;
86
- node_id?: string;
87
- html_url?: string;
88
- state?: string;
89
- title?: string;
90
- body?: string | null;
91
- labels?: Array<string | { name?: string }>;
92
- assignees?: Array<{ login?: string }>;
93
- milestone?: { title?: string } | null;
94
- type?: { name?: string } | null;
95
- pull_request?: unknown;
96
- };
97
-
98
- type IssueFieldSchema = {
99
- id: string;
100
- name: string;
101
- kind: 'single-select' | 'date' | 'text' | 'number';
102
- options: Array<{ id: string; name: string }>;
103
- };
104
- type IssueTypeSchema = { id: string; name: string; fields: IssueFieldSchema[] };
105
- type CurrentField = { id: string; name: string; kind: IssueFieldSchema['kind']; value: string | number | null };
106
-
107
- const ISSUE_TYPES_QUERY = `query($owner:String!){organization(login:$owner){issueTypes(first:20){nodes{id name pinnedFields{__typename ... on IssueFieldSingleSelect{id name options{id name}} ... on IssueFieldDate{id name} ... on IssueFieldText{id name} ... on IssueFieldNumber{id name}}}}}}`;
108
- const ISSUE_FIELDS_QUERY = `query($owner:String!,$name:String!,$number:Int!){repository(owner:$owner,name:$name){issue(number:$number){id issueType{id name pinnedFields{__typename ... on IssueFieldSingleSelect{id name options{id name}} ... on IssueFieldDate{id name} ... on IssueFieldText{id name} ... on IssueFieldNumber{id name}}} issueFieldValues(first:50){nodes{__typename ... on IssueFieldSingleSelectValue{name optionId field{... on IssueFieldSingleSelect{id name}}} ... on IssueFieldDateValue{value field{... on IssueFieldDate{id name}}} ... on IssueFieldTextValue{value field{... on IssueFieldText{id name}}} ... on IssueFieldNumberValue{value field{... on IssueFieldNumber{id name}}}}}}}}`;
109
-
110
66
  function result(
111
67
  status: PlatformResult['status'],
112
68
  taskId: string | null,
@@ -235,118 +191,6 @@ async function inspectExternalIssue(
235
191
  });
236
192
  }
237
193
 
238
- function normalizeIssue(remote: RemoteIssue, repository: string, fallbackNumber?: number): IssueSnapshot | null {
239
- const number = Number.isInteger(remote.number) && Number(remote.number) > 0
240
- ? Number(remote.number)
241
- : fallbackNumber;
242
- if (!number || !Number.isSafeInteger(number) || remote.pull_request) return null;
243
- return {
244
- repository,
245
- number,
246
- databaseId: Number.isInteger(remote.id) ? Number(remote.id) : null,
247
- nodeId: remote.node_id || `issue-${number}`,
248
- url: remote.html_url || `https://github.com/${repository}/issues/${number}`,
249
- state: String(remote.state || '').toLowerCase() === 'closed' ? 'closed' : 'open',
250
- title: remote.title || '',
251
- body: remote.body || '',
252
- labels: (remote.labels || []).map((label) => typeof label === 'string' ? label : label.name || '').filter(Boolean).sort(),
253
- assignees: (remote.assignees || []).map((assignee) => assignee.login || '').filter(Boolean).sort(),
254
- milestone: remote.milestone?.title || null,
255
- issueType: remote.type?.name || null,
256
- fields: {}
257
- };
258
- }
259
-
260
- function fieldKind(value: { __typename?: string }): IssueFieldSchema['kind'] | null {
261
- if (value.__typename === 'IssueFieldSingleSelect' || value.__typename === 'IssueFieldSingleSelectValue') return 'single-select';
262
- if (value.__typename === 'IssueFieldDate' || value.__typename === 'IssueFieldDateValue') return 'date';
263
- if (value.__typename === 'IssueFieldText' || value.__typename === 'IssueFieldTextValue') return 'text';
264
- if (value.__typename === 'IssueFieldNumber' || value.__typename === 'IssueFieldNumberValue') return 'number';
265
- return null;
266
- }
267
-
268
- function normalizeFieldSchemas(value: unknown): IssueFieldSchema[] {
269
- if (!Array.isArray(value)) return [];
270
- return value.flatMap((raw) => {
271
- const item = raw as { id?: string; name?: string; __typename?: string; options?: Array<{ id?: string; name?: string }> };
272
- const kind = fieldKind(item);
273
- return item.id && item.name && kind ? [{
274
- id: item.id,
275
- name: item.name,
276
- kind,
277
- options: (item.options || []).flatMap((option) => option.id && option.name ? [{ id: option.id, name: option.name }] : [])
278
- }] : [];
279
- });
280
- }
281
-
282
- function normalizeIssueTypes(value: unknown): IssueTypeSchema[] {
283
- const nodes = (value as { data?: { organization?: { issueTypes?: { nodes?: unknown[] } } } })?.data?.organization?.issueTypes?.nodes;
284
- return (nodes || []).flatMap((raw) => {
285
- const item = raw as { id?: string; name?: string; pinnedFields?: unknown[] };
286
- return item.id && item.name ? [{ id: item.id, name: item.name, fields: normalizeFieldSchemas(item.pinnedFields) }] : [];
287
- });
288
- }
289
-
290
- function normalizeCurrentFields(value: unknown): { issueId: string | null; type: IssueTypeSchema | null; values: CurrentField[] } {
291
- const issue = (value as { data?: { repository?: { issue?: {
292
- id?: string;
293
- issueType?: { id?: string; name?: string; pinnedFields?: unknown[] } | null;
294
- issueFieldValues?: { nodes?: unknown[] };
295
- } } } })?.data?.repository?.issue;
296
- const type = issue?.issueType?.name ? {
297
- id: issue.issueType.id || issue.issueType.name,
298
- name: issue.issueType.name,
299
- fields: normalizeFieldSchemas(issue.issueType.pinnedFields)
300
- } : null;
301
- const values = (issue?.issueFieldValues?.nodes || []).flatMap((raw) => {
302
- const item = raw as { __typename?: string; name?: string; value?: string | number; field?: { id?: string; name?: string } };
303
- const kind = fieldKind(item);
304
- const value = kind === 'single-select' ? item.name : item.value;
305
- return item.field?.name && kind && value !== undefined
306
- ? [{ id: item.field.id || item.field.name, name: item.field.name, kind, value: value ?? null }]
307
- : [];
308
- });
309
- return { issueId: issue?.id || null, type, values };
310
- }
311
-
312
- function graphState(client: GitHubClient, repository: string, issue: number, cwd: string) {
313
- const [owner, name] = repository.split('/');
314
- if (!owner || !name) return null;
315
- const current = client.json<unknown>([
316
- 'api', 'graphql', '-f', `query=${ISSUE_FIELDS_QUERY}`, '-F', `owner=${owner}`, '-F', `name=${name}`, '-F', `number=${issue}`
317
- ], { cwd });
318
- if (!current.ok) return null;
319
- return normalizeCurrentFields(current.value);
320
- }
321
-
322
- function fetchIssue(client: GitHubClient, repository: string, issue: number, cwd: string) {
323
- return client.json<RemoteIssue>(['api', `repos/${repository}/issues/${issue}`], { cwd });
324
- }
325
-
326
- function inspectGitHubIssue(client: GitHubClient, repository: string, issue: number, cwd: string) {
327
- const fetched = fetchIssue(client, repository, issue, cwd);
328
- if (!fetched.ok) return fetched;
329
- const snapshot = normalizeIssue(fetched.value, repository, issue);
330
- return snapshot
331
- ? { ok: true as const, value: snapshot }
332
- : { ok: false as const, error: { code: 'ISSUE_IDENTITY_INVALID', message: 'Remote resource is not a valid Issue', retryable: false } };
333
- }
334
-
335
- function inspectGitHubIssueMetadata(client: GitHubClient, repository: string, issue: number, cwd: string) {
336
- const fetched = fetchIssue(client, repository, issue, cwd);
337
- if (!fetched.ok) return fetched;
338
- const remote = fetched.value;
339
- return {
340
- ok: true as const,
341
- value: {
342
- state: String(remote.state || '').toLowerCase() === 'closed' ? 'closed' as const : 'open' as const,
343
- labels: (remote.labels || []).map((label: string | { name?: string }) => typeof label === 'string' ? label : label.name || '').filter(Boolean).sort(),
344
- body: remote.body || '',
345
- milestone: remote.milestone?.title || null
346
- }
347
- };
348
- }
349
-
350
194
  async function inspectPlatformIssue(taskRef: string, options: SharedOptions = {}): Promise<IssueResult> {
351
195
  const base = await resolvedContext(taskRef, options);
352
196
  if (!base.ok) return base.output;
@@ -543,14 +387,6 @@ function requirementsFromTask(content: string): Requirement[] {
543
387
  }));
544
388
  }
545
389
 
546
- function flattenPages(value: unknown): unknown[] {
547
- return Array.isArray(value) ? value.flatMap((item) => Array.isArray(item) ? item : [item]) : [];
548
- }
549
-
550
- function listNames(value: unknown): string[] {
551
- return flattenPages(value).map((item) => typeof item === 'string' ? item : String((item as { name?: string }).name || '')).filter(Boolean);
552
- }
553
-
554
390
  function milestoneBasis(repoRoot: string, current: string | null): string | null {
555
391
  if (!current || !/^\d+\.\d+\.x$/.test(current)) return current;
556
392
  try {
@@ -642,73 +478,6 @@ function desiredFieldValues(frontmatter: Record<string, string>, fields: IssueFi
642
478
  return desired;
643
479
  }
644
480
 
645
- function planGraphMetadata(
646
- client: GitHubClient,
647
- repository: string,
648
- issue: number,
649
- cwd: string,
650
- frontmatter: Record<string, string>,
651
- options: Pick<SyncOptions, 'issueType' | 'fields'>,
652
- capabilities: PlatformResult['capabilities']
653
- ): { operations: PlannedOperation[]; issueId: string | null } {
654
- if (!options.issueType && !options.fields) return { operations: [], issueId: null };
655
- if (!capabilities.push) return {
656
- operations: [
657
- ...(options.issueType ? [{ name: 'issue-type', status: 'skipped' as const, reasonCode: 'PUSH_REQUIRED' }] : []),
658
- ...(options.fields ? [{ name: 'fields', status: 'skipped' as const, reasonCode: 'PUSH_REQUIRED' }] : [])
659
- ], issueId: null
660
- };
661
- const [owner] = repository.split('/');
662
- const typesResult = client.json<unknown>(['api', 'graphql', '-f', `query=${ISSUE_TYPES_QUERY}`, '-F', `owner=${owner}`], { cwd });
663
- if (!typesResult.ok) return { operations: [{ name: 'issue-schema', status: 'failed', reasonCode: typesResult.error.code }], issueId: null };
664
- const types = normalizeIssueTypes(typesResult.value);
665
- if (types.length === 0) return {
666
- operations: [
667
- ...(options.issueType ? [{ name: 'issue-type', status: 'skipped' as const, reasonCode: 'ISSUE_TYPES_UNSUPPORTED' }] : []),
668
- ...(options.fields ? [{ name: 'fields', status: 'skipped' as const, reasonCode: 'ISSUE_TYPES_UNSUPPORTED' }] : [])
669
- ], issueId: null
670
- };
671
- const current = graphState(client, repository, issue, cwd);
672
- if (!current?.issueId) return { operations: [{ name: 'issue-schema', status: 'failed', reasonCode: 'ISSUE_GRAPH_IDENTITY_INVALID' }], issueId: null };
673
- const target = types.find((type) => type.name === desiredIssueType(frontmatter.type || ''));
674
- if (!target) return { operations: [{ name: 'issue-type', status: 'failed', reasonCode: 'ISSUE_TYPE_NOT_FOUND' }], issueId: current.issueId };
675
- const operations: PlannedOperation[] = [];
676
- if (options.issueType) operations.push(current.type?.id === target.id
677
- ? { name: 'issue-type', status: 'no-op', reasonCode: null }
678
- : { name: 'issue-type', status: 'planned', reasonCode: null, value: { issueTypeId: target.id } });
679
- if (options.fields) {
680
- const desired = desiredFieldValues(frontmatter, target.fields);
681
- const inputs = desired.filter((field) => current.values.find((value) => value.name === field.name)?.value !== field.value).map((field) => field.input);
682
- if (current.type?.id !== target.id) {
683
- const targetNames = new Set(target.fields.map((field) => field.name));
684
- for (const old of current.values) if (!targetNames.has(old.name)) inputs.push({ fieldId: old.id, delete: true });
685
- }
686
- operations.push(inputs.length === 0
687
- ? { name: 'fields', status: 'no-op', reasonCode: null }
688
- : { name: 'fields', status: 'planned', reasonCode: null, value: inputs });
689
- }
690
- return { operations, issueId: current.issueId };
691
- }
692
-
693
- function executeGraphOperation(
694
- client: GitHubClient,
695
- cwd: string,
696
- issueId: string,
697
- operation: PlannedOperation
698
- ) {
699
- if (operation.name === 'issue-type') {
700
- const query = 'mutation($issueId:ID!,$issueTypeId:ID){updateIssueIssueType(input:{issueId:$issueId,issueTypeId:$issueTypeId}){issue{id}}}';
701
- const typeId = (operation.value as { issueTypeId: string }).issueTypeId;
702
- return client.json(['api', 'graphql', '--input', '-'], {
703
- cwd, method: 'POST', input: JSON.stringify({ query, variables: { issueId, issueTypeId: typeId } })
704
- });
705
- }
706
- const query = 'mutation($issueId:ID!,$issueFields:[IssueFieldCreateOrUpdateInput!]!){setIssueFieldValue(input:{issueId:$issueId,issueFields:$issueFields}){issue{id}}}';
707
- return client.json(['api', 'graphql', '--input', '-'], {
708
- cwd, method: 'POST', input: JSON.stringify({ query, variables: { issueId, issueFields: operation.value } })
709
- });
710
- }
711
-
712
481
  async function syncPlatformIssue(taskRef: string, options: SyncOptions): Promise<IssueResult> {
713
482
  const base = await resolvedContext(taskRef, options);
714
483
  if (!base.ok) return base.output;
@@ -849,15 +618,5 @@ async function syncPlatformIssue(taskRef: string, options: SyncOptions): Promise
849
618
  });
850
619
  }
851
620
 
852
- export {
853
- bindPlatformIssue,
854
- createPlatformIssue,
855
- graphState,
856
- inspectGitHubIssue,
857
- inspectGitHubIssueMetadata,
858
- inspectPlatformIssue,
859
- normalizeIssue,
860
- requirementSectionAnchors,
861
- syncPlatformIssue
862
- };
863
- export type { BindOptions, CreateOptions, IssueResult, IssueSnapshot, SyncOptions };
621
+ export { bindPlatformIssue, createPlatformIssue, inspectPlatformIssue, requirementSectionAnchors, syncPlatformIssue };
622
+ export type { BindOptions, CreateOptions, IssueResult, SyncOptions };
@@ -2,7 +2,8 @@ import fs from 'node:fs';
2
2
 
3
3
  import { parseTypedTaskFrontmatter } from '../task/frontmatter.ts';
4
4
  import { resolveTaskRef } from '../task/resolve-ref.ts';
5
- import type { PlatformCheckSnapshot } from './adapters.ts';
5
+ import type { PlatformCheckSnapshot } from './snapshots.ts';
6
+ import { checkStatusBucket } from './check-status.ts';
6
7
  import { resolvePlatformProviderContext } from './context.ts';
7
8
  import type { PlatformClient } from './context.ts';
8
9
  import { inspectPlatformPullRequest } from './pull-requests.ts';
@@ -33,7 +34,6 @@ type ChecksResult = PlatformResult & {
33
34
  resolution?: { status: 'resolved' | 'missing' | 'ambiguous'; runId: number | null; jobId: number | null };
34
35
  logs?: { runId: number; jobId?: number; text: string };
35
36
  };
36
- type GitHubClient = PlatformClient;
37
37
  type InspectionOptions = { cwd?: string; client?: PlatformClient; runtimeVersion?: string };
38
38
  type SharedOptions = { cwd?: string; client?: PlatformClient };
39
39
 
@@ -56,18 +56,6 @@ function classifyPullRequestReadiness(input: {
56
56
  return { state: 'ready', headSha: input.headSha };
57
57
  }
58
58
 
59
- function parseRunJobIdentity(detailsUrl: string): { runId: number; jobId: number | null } | null {
60
- try {
61
- const url = new URL(detailsUrl);
62
- if (url.hostname !== 'github.com') return null;
63
- const match = url.pathname.match(/\/actions\/runs\/(\d+)(?:\/job\/(\d+))?/);
64
- if (!match) return null;
65
- return { runId: Number(match[1]), jobId: match[2] ? Number(match[2]) : null };
66
- } catch {
67
- return null;
68
- }
69
- }
70
-
71
59
  function resolveRunCandidate(candidates: RunCandidate[], headSha: string, checkName: string):
72
60
  | { status: 'resolved'; runId: number; jobId: number | null }
73
61
  | { status: 'missing' | 'ambiguous'; runId: null; jobId: null } {
@@ -85,41 +73,6 @@ function checksResult(status: PlatformResult['status'], overrides: Partial<Check
85
73
  };
86
74
  }
87
75
 
88
- function normalizeBucket(value: { bucket?: string; status?: string; state?: string; conclusion?: string }): CheckBucket {
89
- const raw = String(value.bucket || value.status || value.conclusion || value.state || '').toLowerCase();
90
- if (['pass', 'success', 'successful', 'neutral'].includes(raw)) return 'pass';
91
- if (['fail', 'failure', 'failed', 'error', 'timed_out', 'action_required'].includes(raw)) return 'fail';
92
- if (['cancel', 'cancelled', 'canceled', 'skipped', 'stale'].includes(raw)) return 'cancel';
93
- return 'pending';
94
- }
95
-
96
- function normalizeChecks(value: unknown): CheckSnapshot[] {
97
- if (!Array.isArray(value)) return [];
98
- return value.flatMap((raw) => {
99
- const item = raw as Record<string, unknown>;
100
- const name = String(item.name || item.context || '');
101
- return name ? [{
102
- name,
103
- bucket: normalizeBucket(item as { bucket?: string; state?: string; conclusion?: string }),
104
- workflow: item.workflow ? String(item.workflow) : null,
105
- conclusion: item.conclusion ? String(item.conclusion) : item.state ? String(item.state) : null,
106
- detailsUrl: item.link ? String(item.link) : item.detailsUrl ? String(item.detailsUrl) : null,
107
- startedAt: item.startedAt ? String(item.startedAt) : null,
108
- completedAt: item.completedAt ? String(item.completedAt) : null
109
- }] : [];
110
- });
111
- }
112
-
113
- function inspectGitHubRequiredChecks(client: GitHubClient, repository: string, number: number, cwd: string) {
114
- const inspected = client.json<unknown>([
115
- 'pr', 'checks', String(number), '--repo', repository,
116
- '--json', 'name,state,bucket,link,workflow,startedAt,completedAt'
117
- ], { cwd });
118
- return inspected.ok
119
- ? { ok: true as const, value: normalizeChecks(inspected.value) }
120
- : { ok: false as const, error: inspected.error };
121
- }
122
-
123
76
  async function resolvedTask(taskRef: string, options: InspectionOptions) {
124
77
  const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
125
78
  if (!resolved.ok) return { ok: false as const, output: checksResult('failed', { error: { code: resolved.code, message: resolved.message, retryable: false } }) };
@@ -141,14 +94,12 @@ async function resolvedTask(taskRef: string, options: InspectionOptions) {
141
94
  async function inspectRequiredChecks(taskRef: string, options: InspectionOptions = {}): Promise<ChecksResult> {
142
95
  const base = await resolvedTask(taskRef, options);
143
96
  if (!base.ok) return base.output;
144
- return inspectChecksForResolvedTask(base, options);
97
+ return inspectChecksForResolvedTask(base);
145
98
  }
146
99
 
147
100
  async function inspectChecksForResolvedTask(
148
- base: Extract<Awaited<ReturnType<typeof resolvedTask>>, { ok: true }>,
149
- options: InspectionOptions
101
+ base: Extract<Awaited<ReturnType<typeof resolvedTask>>, { ok: true }>
150
102
  ): Promise<ChecksResult> {
151
- const repository = base.context.platform.repository!;
152
103
  {
153
104
  const inspected = base.provider.checks?.inspectRequired
154
105
  ? await base.provider.checks.inspectRequired({
@@ -164,7 +115,13 @@ async function inspectChecksForResolvedTask(
164
115
  pullRequest: base.pullRequest,
165
116
  error: providerError(inspected.error, 'PLATFORM_PROVIDER_OPERATION_FAILED')
166
117
  });
167
- const required = normalizeChecks(inspected.value);
118
+ const required = inspected.value.map((check): CheckSnapshot => ({
119
+ name: check.name,
120
+ bucket: checkStatusBucket(check.status),
121
+ conclusion: check.conclusion || null,
122
+ detailsUrl: check.detailsUrl || null,
123
+ workflow: null, startedAt: null, completedAt: null
124
+ }));
168
125
  const classified = classifyRequiredChecks(required);
169
126
  const status = classified.state === 'passed' || classified.state === 'no-required'
170
127
  ? 'no-op'
@@ -184,7 +141,7 @@ async function inspectChecksForResolvedTask(
184
141
  async function inspectPullRequestReadiness(taskRef: string, options: InspectionOptions = {}): Promise<ChecksResult> {
185
142
  const base = await resolvedTask(taskRef, options);
186
143
  if (!base.ok) return base.output;
187
- const checked = await inspectChecksForResolvedTask(base, options);
144
+ const checked = await inspectChecksForResolvedTask(base);
188
145
  const classifiedCodes = new Set(['REQUIRED_CHECKS_PENDING', 'REQUIRED_CHECKS_FAILED', 'REQUIRED_CHECKS_CANCELLED']);
189
146
  if (checked.error && !classifiedCodes.has(checked.error.code)) return checked;
190
147
  const readiness = classifyPullRequestReadiness({
@@ -271,15 +228,6 @@ async function resolvePlatformCheckRun(taskRef: string, options: SharedOptions &
271
228
  }
272
229
  }
273
230
 
274
- function fetchCheckLogText(client: GitHubClient, args: string[], cwd: string) {
275
- if (!client.text) return { ok: false as const, error: { code: 'PLATFORM_CLIENT_TEXT_UNAVAILABLE', message: 'Platform client does not support text responses', retryable: false } };
276
- const fetched = client.text(args, { cwd });
277
- if (fetched.ok || args[0] !== 'api' || !/response contains terminal escape sequences/i.test(fetched.error.message)) {
278
- return fetched;
279
- }
280
- return client.text([...args, '--allow-escape-sequences'], { cwd });
281
- }
282
-
283
231
  async function fetchPlatformCheckLogs(taskRef: string, options: SharedOptions & { run: number; job?: number }): Promise<ChecksResult> {
284
232
  const base = await resolvedTask(taskRef, options);
285
233
  if (!base.ok) return base.output;
@@ -308,17 +256,5 @@ async function fetchPlatformCheckLogs(taskRef: string, options: SharedOptions &
308
256
  }
309
257
  }
310
258
 
311
- export {
312
- classifyPullRequestReadiness,
313
- classifyRequiredChecks,
314
- fetchCheckLogText,
315
- fetchPlatformCheckLogs,
316
- inspectGitHubRequiredChecks,
317
- inspectRequiredChecks,
318
- inspectPullRequestReadiness,
319
- parseRunJobIdentity,
320
- resolvePlatformCheckRun,
321
- resolveRunCandidate,
322
- watchPullRequestReadiness
323
- };
259
+ export { classifyPullRequestReadiness, classifyRequiredChecks, fetchPlatformCheckLogs, inspectRequiredChecks, inspectPullRequestReadiness, resolvePlatformCheckRun, resolveRunCandidate, watchPullRequestReadiness };
324
260
  export type { CheckBucket, CheckSnapshot, ChecksResult, ChecksSnapshot, CheckState, ReadinessSnapshot, ReadinessState, RunCandidate };
@@ -16,7 +16,7 @@ import { platformResult } from './types.ts';
16
16
  import type { PlatformResult } from './types.ts';
17
17
  import type { OperationWarning } from '../task/operation-outcome.ts';
18
18
  import { providerError, providerOperationContext, providerStatus, providerResourceToken, resourceIdentityNumber, unsupportedProviderOperation } from './provider-bridge.ts';
19
- import type { PlatformChangeRequestSnapshot } from './adapters.ts';
19
+ import type { PlatformChangeRequestSnapshot } from './snapshots.ts';
20
20
  import type { ChangeRequestSnapshot } from './provider-contract.ts';
21
21
  import {
22
22
  buildPrChangeReport,
@@ -33,6 +33,8 @@ import type {
33
33
  MechanicalChangeReport,
34
34
  PrChangeReport
35
35
  } from './pr-change-report.ts';
36
+ import { manualValidationFinalSummaryProjectionMatches } from '../task/manual-validation-receipt.ts';
37
+ import { readManualValidationCompletion } from '../task/manual-validation-completion.ts';
36
38
 
37
39
  type SummaryComment = { id: number | string; body: string };
38
40
  type ChangeReportState = 'ready' | 'missing' | 'stale' | 'invalid';
@@ -50,9 +52,21 @@ type PullRequestSummaryResult = PlatformResult & {
50
52
  };
51
53
  type PullRequestPrimaryResult = 'pr_created' | 'pr_reused' | 'no_op';
52
54
  type SummaryOptions = { cwd?: string; client?: PlatformClient; runtimeVersion?: string };
55
+ type ManualValidationSummaryOptions = {
56
+ phase: 'pending' | 'final';
57
+ transactionId?: string;
58
+ receiptDigest?: string;
59
+ prHeadSha?: string;
60
+ authority?: 'coordinator';
61
+ };
53
62
  type ReportWriteResult = PlatformResult & {
54
63
  report: { path: string; status: 'written' | 'no-op' | 'planned'; precheckVerdict: 'clear' | 'needs-review'; nextAction: 'watch-pr' | 'review-code' } | null;
55
64
  };
65
+ type SummaryCommentStateResult = Omit<PlatformResult, 'comment'> & {
66
+ task: { id: string | null; prNumber: number | null };
67
+ pullRequest: PlatformChangeRequestSnapshot | null;
68
+ comment: SummaryComment | null;
69
+ };
56
70
 
57
71
  function summaryMarker(taskId: string): string {
58
72
  return `<!-- sync-pr:${taskId}:summary -->`;
@@ -354,6 +368,23 @@ async function summaryContext(taskRef: string, options: SummaryOptions = {}): Pr
354
368
  };
355
369
  }
356
370
 
371
+ async function summaryCommentState(taskRef: string, options: SummaryOptions = {}): Promise<SummaryCommentStateResult> {
372
+ const context = await summaryContext(taskRef, options);
373
+ if (!context.task.prNumber || !context.pullRequest) return { ...context, comment: null };
374
+ const loaded = await resolvePlatformProviderContext({ cwd: options.cwd, client: options.client });
375
+ if (!loaded.ok || !loaded.value.provider.comments?.list) return { ...context, comment: null };
376
+ const listed = await loaded.value.provider.comments.list({
377
+ context: providerOperationContext(loaded.value),
378
+ parent: providerResourceToken(loaded.value.provider, 'pull-request', String(context.task.prNumber))
379
+ });
380
+ if (!listed.ok) return { ...context, comment: null };
381
+ const matches = listed.value.filter((comment) => comment.body.includes(summaryMarker(context.task.id!)));
382
+ return {
383
+ ...context,
384
+ comment: matches.length === 1 ? { id: /^\d+$/u.test(matches[0]!.id) ? Number(matches[0]!.id) : matches[0]!.id, body: matches[0]!.body } : null
385
+ };
386
+ }
387
+
357
388
  type ReportWriteOptions = {
358
389
  agent: string;
359
390
  mechanicalFile: string;
@@ -450,7 +481,7 @@ async function reportWrite(taskRef: string, options: ReportWriteOptions): Promis
450
481
 
451
482
  async function syncPullRequestSummary(
452
483
  taskRef: string,
453
- options: { agent: string; body: string; changeReportFile?: string; cwd?: string; client?: PlatformClient; dryRun?: boolean; strict?: boolean; primaryResult: PullRequestPrimaryResult; runtimeVersion?: string }
484
+ options: { agent: string; body: string; changeReportFile?: string; cwd?: string; client?: PlatformClient; dryRun?: boolean; strict?: boolean; primaryResult: PullRequestPrimaryResult; runtimeVersion?: string; manualValidation?: ManualValidationSummaryOptions; lockAlreadyHeld?: boolean }
454
485
  ): Promise<PullRequestSummaryResult> {
455
486
  const warningResult = warningResultForPrimary(options.primaryResult);
456
487
  let knownPrNumber: number | null = null;
@@ -503,7 +534,7 @@ async function syncPullRequestSummary(
503
534
  return preserveFailure(context);
504
535
  }
505
536
  try {
506
- return await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, options.agent, async () => {
537
+ const execute = async (): Promise<PullRequestSummaryResult> => {
507
538
  const { taskContent, boundFact } = readBoundTaskSnapshot(resolved.taskMdPath);
508
539
  if (!boundFact) return {
509
540
  ...platformResult('failed', { resource: { kind: 'pull-request', number: null }, error: { code: 'PR_NOT_LINKED', message: 'Task has no verified bound pull request', retryable: false } }),
@@ -519,6 +550,27 @@ async function syncPullRequestSummary(
519
550
  const prNumber = boundPrNumber;
520
551
  const initial = await inspectBoundPullRequest(context, resolved.repoRoot, boundPrNumber, loaded.value);
521
552
  if (!initial.ok) return fail(initial.status, context, initial.error);
553
+ const manual = options.manualValidation;
554
+ const hasFinalManualValidation = /###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)/u.test(options.body);
555
+ if (hasFinalManualValidation && (!manual || manual.phase !== 'final')) return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'final manual-validation summary requires the transaction coordinator', retryable: false }, prNumber);
556
+ if (manual && (manual.phase === 'pending' ? hasFinalManualValidation : !hasFinalManualValidation)) 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);
557
+ if (manual) {
558
+ if (manual.phase === 'final' && (!manual.transactionId || !manual.receiptDigest || !manual.prHeadSha || manual.prHeadSha !== initial.value.head.sha)) return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'final manual-validation summary requires transaction, receipt, and current head identity', retryable: false }, prNumber);
559
+ if (manual.phase === 'final') {
560
+ if (manual.authority !== 'coordinator') return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'final manual-validation summary requires coordinator authority', retryable: false }, prNumber);
561
+ const completion = readManualValidationCompletion(resolved.taskDir, {
562
+ transactionId: manual.transactionId,
563
+ taskId: resolved.taskId,
564
+ prNumber,
565
+ prHeadSha: initial.value.head.sha,
566
+ receiptDigest: manual.receiptDigest
567
+ });
568
+ if (!completion.ok) return fail('failed', context, platformError(completion.error), prNumber);
569
+ const { receipt, transaction } = completion.value;
570
+ if (transaction.phase !== 'final-promotion-in-progress' || !transaction.eventAppended) return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_PHASE_INVALID', message: 'final summary requires a receipt-backed promotion intent', retryable: false }, prNumber);
571
+ if (!manualValidationFinalSummaryProjectionMatches(options.body, receipt)) return fail('failed', context, { code: 'MANUAL_VALIDATION_SUMMARY_PROJECTION_INVALID', message: 'final summary body does not match the canonical receipt projection', retryable: false }, prNumber);
572
+ }
573
+ }
522
574
  const expectedReportPath = taskReportPath(resolved.taskDir);
523
575
  const suppliedReportPath = path.resolve(resolved.repoRoot, options.changeReportFile!);
524
576
  if (suppliedReportPath !== expectedReportPath) return fail('failed', context, { code: 'PR_CHANGE_REPORT_PATH_INVALID', message: 'summary-sync must consume the task-bound pr-change-report.json', retryable: false });
@@ -651,7 +703,10 @@ async function syncPullRequestSummary(
651
703
  operations: [{ name: `summary:${reconciliation.action}`, status: 'applied', reasonCode: null }],
652
704
  result: null, warnings: [], ...info
653
705
  };
654
- });
706
+ };
707
+ return await (options.lockAlreadyHeld
708
+ ? execute()
709
+ : withTaskExecutionLock(resolved.repoRoot, resolved.taskId, options.agent, execute));
655
710
  } catch (error) {
656
711
  const lockError = error instanceof TaskExecutionLockError
657
712
  ? { code: error.code, message: error.message, retryable: error.code === 'ORCHESTRATION_LOCK_BUSY' }
@@ -664,9 +719,10 @@ export {
664
719
  buildPullRequestSummary,
665
720
  reconcileSummaryComment,
666
721
  reportWrite,
722
+ summaryCommentState,
667
723
  summaryContext,
668
724
  summaryMarker,
669
725
  syncPullRequestSummary,
670
726
  warningResultForPrimary
671
727
  };
672
- export type { PullRequestSummaryResult, ReportWriteOptions, ReportWriteResult, SummaryContextResult };
728
+ export type { ManualValidationSummaryOptions, PullRequestSummaryResult, ReportWriteOptions, ReportWriteResult, SummaryCommentStateResult, SummaryContextResult };