@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,4 @@
1
+ import { ISSUE_FIELDS_QUERY, graphState, inspectGitHubIssue, inspectGitHubPullRequest, inspectGitHubIssueClosingChangeRequests, inspectGitHubRequiredChecks, parseRunJobIdentity, fetchCheckLogText } from './github-data.js';
1
2
  import { execFileSync } from 'node:child_process';
2
3
  import fs from 'node:fs';
3
4
  import os from 'node:os';
@@ -8,7 +9,6 @@ import { resourceIdentityNumber, resourceIdentityString } from './resource-ident
8
9
  import { extractPullRequestFileNames, syncLabelDelta } from './in-label-sync.js';
9
10
  const CURRENT_USER_QUERY = 'query { viewer { login } }';
10
11
  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}}}}}}`;
11
- 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}}}}}}}}`;
12
12
  const CLOSING_ISSUES_QUERY = 'query($owner:String!,$name:String!,$number:Int!,$cursor:String){repository(owner:$owner,name:$name){pullRequest(number:$number){closingIssuesReferences(first:100,after:$cursor){nodes{number} pageInfo{hasNextPage endCursor}}}}}';
13
13
  function parseGitHubRemote(remote) {
14
14
  const trimmed = remote.trim().replace(/\.git$/, '');
@@ -399,11 +399,10 @@ function createGitHubOperations(client) {
399
399
  const number = resourceIdentityNumber(target);
400
400
  if (!number)
401
401
  return invalid('ISSUE_NUMBER_INVALID', 'Issue number must be positive');
402
- const module = await import('./issues.js');
403
- const fetched = module.inspectGitHubIssue(client, repository(context), number, context.workingDirectory);
402
+ const fetched = inspectGitHubIssue(client, repository(context), number, context.workingDirectory);
404
403
  if (!fetched.ok)
405
404
  return fetched;
406
- const graph = module.graphState(client, repository(context), number, context.workingDirectory);
405
+ const graph = graphState(client, repository(context), number, context.workingDirectory);
407
406
  return { ok: true, value: issueSnapshot(fetched.value, graph || undefined) };
408
407
  },
409
408
  async create({ context, desired }) {
@@ -606,8 +605,7 @@ function createGitHubOperations(client) {
606
605
  const number = resourceIdentityNumber(target);
607
606
  if (!number)
608
607
  return invalid('PR_NUMBER_INVALID', 'Pull request number must be positive');
609
- const module = await import('./pull-requests.js');
610
- const fetched = module.inspectGitHubPullRequest(client, repository(context), number, context.workingDirectory);
608
+ const fetched = inspectGitHubPullRequest(client, repository(context), number, context.workingDirectory);
611
609
  if (!fetched.ok)
612
610
  return fetched;
613
611
  return { ok: true, value: changeRequestSnapshot(fetched.value) };
@@ -671,8 +669,7 @@ function createGitHubOperations(client) {
671
669
  const number = resourceIdentityNumber(issue);
672
670
  if (!number)
673
671
  return invalid('ISSUE_NUMBER_INVALID', 'Issue number must be positive');
674
- const module = await import('./pull-requests.js');
675
- const fetched = module.inspectGitHubIssueClosingChangeRequests(client, repository(context), number, context.workingDirectory);
672
+ const fetched = inspectGitHubIssueClosingChangeRequests(client, repository(context), number, context.workingDirectory);
676
673
  if (!fetched.ok)
677
674
  return fetched;
678
675
  return { ok: true, value: fetched.value.map(changeRequestSnapshot) };
@@ -756,8 +753,7 @@ function createGitHubOperations(client) {
756
753
  const number = resourceIdentityNumber(target);
757
754
  if (!number)
758
755
  return invalid('PR_NUMBER_INVALID', 'Pull request number must be positive');
759
- const module = await import('./github-provider.js');
760
- return module.resolveGitHubChangeRequestGitEvidence({
756
+ return resolveGitHubChangeRequestGitEvidence({
761
757
  cwd: context.workingDirectory,
762
758
  repository: repository(context),
763
759
  number,
@@ -771,18 +767,13 @@ function createGitHubOperations(client) {
771
767
  const number = resourceIdentityNumber(changeRequest);
772
768
  if (!number)
773
769
  return invalid('PR_NUMBER_INVALID', 'Pull request number must be positive');
774
- const module = await import('./pr-checks.js');
775
- const fetched = module.inspectGitHubRequiredChecks(client, repository(context), number, context.workingDirectory);
776
- if (!fetched.ok)
777
- return fetched;
778
- return { ok: true, value: fetched.value.map((check) => ({ name: check.name, status: check.bucket, conclusion: check.conclusion, detailsUrl: check.detailsUrl })) };
770
+ return inspectGitHubRequiredChecks(client, repository(context), number, context.workingDirectory);
779
771
  },
780
772
  async resolveRun({ context, changeRequest, checkName, detailsUrl }) {
781
773
  const number = resourceIdentityNumber(changeRequest);
782
774
  if (!number)
783
775
  return invalid('PR_NUMBER_INVALID', 'Pull request number must be positive');
784
- const module = await import('./pr-checks.js');
785
- const direct = detailsUrl ? module.parseRunJobIdentity(detailsUrl) : null;
776
+ const direct = detailsUrl ? parseRunJobIdentity(detailsUrl) : null;
786
777
  if (direct) {
787
778
  const run = client.json(['api', `repos/${repository(context)}/actions/runs/${direct.runId}`], { cwd: context.workingDirectory });
788
779
  if (!run.ok)
@@ -798,11 +789,10 @@ function createGitHubOperations(client) {
798
789
  : invalid('CHECK_RUN_NOT_FOUND', 'Check run was not found');
799
790
  },
800
791
  async fetchLogs({ context, runId, jobId }) {
801
- const module = await import('./pr-checks.js');
802
792
  const args = jobId
803
793
  ? ['api', `repos/${repository(context)}/actions/jobs/${jobId}/logs`]
804
794
  : ['run', 'view', runId, '--repo', repository(context), '--log-failed'];
805
- const fetched = module.fetchCheckLogText(client, args, context.workingDirectory);
795
+ const fetched = fetchCheckLogText(client, args, context.workingDirectory);
806
796
  if (!fetched.ok)
807
797
  return fetched;
808
798
  return { ok: true, value: { runId, jobId, text: fetched.value || '' } };
@@ -883,7 +873,12 @@ function createGitHubOperations(client) {
883
873
  }
884
874
  continue;
885
875
  }
886
- const response = client.json(['api', '--method', 'POST', `repos/${repository(context)}/milestones`, '-f', `title=${item.title}`, '-f', `description=${item.description}`], { cwd: context.workingDirectory });
876
+ const response = client.json([
877
+ 'api', '--method', 'POST', `repos/${repository(context)}/milestones`,
878
+ '-f', `title=${item.title}`,
879
+ '-f', `description=${item.description}`,
880
+ '-f', `state=${item.state}`
881
+ ], { cwd: context.workingDirectory });
887
882
  if (!response.ok)
888
883
  return response;
889
884
  created.push(item.title);
@@ -7,7 +7,7 @@ import { platformResult } from './types.js';
7
7
  import { providerError, providerOperationContext, providerStatus, providerResourceToken, unsupportedProviderOperation } from './provider-bridge.js';
8
8
  import { resourceIdentityNumber } from './resource-identity.js';
9
9
  import { taskIssueIdentity, taskIssueIdentityError } from './task-identities.js';
10
- import { CONTROL_MARKER_PATTERN, escapeHtmlText, fenceRanges, renderSafeCodeFence, sanitizeMarkdownDocument } from './comment-safety.js';
10
+ import { canonicalizeCommentBody, escapeHtmlText, fenceRanges, renderSafeCodeFence } from './comment-safety.js';
11
11
  function providerCommentId(id, provider) {
12
12
  return provider.identity?.comment === 'number' && /^\d+$/.test(id) ? Number(id) : id;
13
13
  }
@@ -39,7 +39,7 @@ function splitFrontmatter(content) {
39
39
  return { frontmatter: match[1], body: content.slice(match[0].length).replace(/^\r?\n/, '') };
40
40
  }
41
41
  function sanitizeCommentBody(value, label) {
42
- const result = sanitizeMarkdownDocument(value, { reservedMarkers: [CONTROL_MARKER_PATTERN] });
42
+ const result = canonicalizeCommentBody(value);
43
43
  if (!result.ok)
44
44
  throw new Error(`${result.error.code}: ${label}: ${result.error.message} at offset ${result.error.offset}`);
45
45
  return result.value;
@@ -14,8 +14,6 @@ import { labelDelta, planInLabelUpdate, syncLabelDelta, validateInLabelMapping,
14
14
  import { providerError, providerOperationContext, providerStatus, providerResourceIdentity, providerResourceToken, unsupportedProviderOperation } from './provider-bridge.js';
15
15
  import { resourceIdentityEquals, resourceIdentityNumber, resourceIdentityString, serializeResourceIdentity } from './resource-identity.js';
16
16
  import { taskIssueIdentity, taskIssueIdentityError } from './task-identities.js';
17
- 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}}}}}}`;
18
- 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}}}}}}}}`;
19
17
  function result(status, taskId, issueNumber, overrides = {}) {
20
18
  return {
21
19
  ...platformResult(status),
@@ -133,115 +131,6 @@ async function inspectExternalIssue(base, issueIdentity) {
133
131
  error: null
134
132
  });
135
133
  }
136
- function normalizeIssue(remote, repository, fallbackNumber) {
137
- const number = Number.isInteger(remote.number) && Number(remote.number) > 0
138
- ? Number(remote.number)
139
- : fallbackNumber;
140
- if (!number || !Number.isSafeInteger(number) || remote.pull_request)
141
- return null;
142
- return {
143
- repository,
144
- number,
145
- databaseId: Number.isInteger(remote.id) ? Number(remote.id) : null,
146
- nodeId: remote.node_id || `issue-${number}`,
147
- url: remote.html_url || `https://github.com/${repository}/issues/${number}`,
148
- state: String(remote.state || '').toLowerCase() === 'closed' ? 'closed' : 'open',
149
- title: remote.title || '',
150
- body: remote.body || '',
151
- labels: (remote.labels || []).map((label) => typeof label === 'string' ? label : label.name || '').filter(Boolean).sort(),
152
- assignees: (remote.assignees || []).map((assignee) => assignee.login || '').filter(Boolean).sort(),
153
- milestone: remote.milestone?.title || null,
154
- issueType: remote.type?.name || null,
155
- fields: {}
156
- };
157
- }
158
- function fieldKind(value) {
159
- if (value.__typename === 'IssueFieldSingleSelect' || value.__typename === 'IssueFieldSingleSelectValue')
160
- return 'single-select';
161
- if (value.__typename === 'IssueFieldDate' || value.__typename === 'IssueFieldDateValue')
162
- return 'date';
163
- if (value.__typename === 'IssueFieldText' || value.__typename === 'IssueFieldTextValue')
164
- return 'text';
165
- if (value.__typename === 'IssueFieldNumber' || value.__typename === 'IssueFieldNumberValue')
166
- return 'number';
167
- return null;
168
- }
169
- function normalizeFieldSchemas(value) {
170
- if (!Array.isArray(value))
171
- return [];
172
- return value.flatMap((raw) => {
173
- const item = raw;
174
- const kind = fieldKind(item);
175
- return item.id && item.name && kind ? [{
176
- id: item.id,
177
- name: item.name,
178
- kind,
179
- options: (item.options || []).flatMap((option) => option.id && option.name ? [{ id: option.id, name: option.name }] : [])
180
- }] : [];
181
- });
182
- }
183
- function normalizeIssueTypes(value) {
184
- const nodes = value?.data?.organization?.issueTypes?.nodes;
185
- return (nodes || []).flatMap((raw) => {
186
- const item = raw;
187
- return item.id && item.name ? [{ id: item.id, name: item.name, fields: normalizeFieldSchemas(item.pinnedFields) }] : [];
188
- });
189
- }
190
- function normalizeCurrentFields(value) {
191
- const issue = value?.data?.repository?.issue;
192
- const type = issue?.issueType?.name ? {
193
- id: issue.issueType.id || issue.issueType.name,
194
- name: issue.issueType.name,
195
- fields: normalizeFieldSchemas(issue.issueType.pinnedFields)
196
- } : null;
197
- const values = (issue?.issueFieldValues?.nodes || []).flatMap((raw) => {
198
- const item = raw;
199
- const kind = fieldKind(item);
200
- const value = kind === 'single-select' ? item.name : item.value;
201
- return item.field?.name && kind && value !== undefined
202
- ? [{ id: item.field.id || item.field.name, name: item.field.name, kind, value: value ?? null }]
203
- : [];
204
- });
205
- return { issueId: issue?.id || null, type, values };
206
- }
207
- function graphState(client, repository, issue, cwd) {
208
- const [owner, name] = repository.split('/');
209
- if (!owner || !name)
210
- return null;
211
- const current = client.json([
212
- 'api', 'graphql', '-f', `query=${ISSUE_FIELDS_QUERY}`, '-F', `owner=${owner}`, '-F', `name=${name}`, '-F', `number=${issue}`
213
- ], { cwd });
214
- if (!current.ok)
215
- return null;
216
- return normalizeCurrentFields(current.value);
217
- }
218
- function fetchIssue(client, repository, issue, cwd) {
219
- return client.json(['api', `repos/${repository}/issues/${issue}`], { cwd });
220
- }
221
- function inspectGitHubIssue(client, repository, issue, cwd) {
222
- const fetched = fetchIssue(client, repository, issue, cwd);
223
- if (!fetched.ok)
224
- return fetched;
225
- const snapshot = normalizeIssue(fetched.value, repository, issue);
226
- return snapshot
227
- ? { ok: true, value: snapshot }
228
- : { ok: false, error: { code: 'ISSUE_IDENTITY_INVALID', message: 'Remote resource is not a valid Issue', retryable: false } };
229
- }
230
- function inspectGitHubIssueMetadata(client, repository, issue, cwd) {
231
- const fetched = fetchIssue(client, repository, issue, cwd);
232
- if (!fetched.ok)
233
- return fetched;
234
- const remote = fetched.value;
235
- return {
236
- ok: true,
237
- value: {
238
- state: String(remote.state || '').toLowerCase() === 'closed' ? 'closed' : 'open',
239
- labels: (remote.labels || []).map((label) => typeof label === 'string' ? label : label.name || '').filter(Boolean).sort(),
240
- body: remote.body || '',
241
- milestone: remote.milestone?.title || null
242
- }
243
- };
244
- }
245
134
  async function inspectPlatformIssue(taskRef, options = {}) {
246
135
  const base = await resolvedContext(taskRef, options);
247
136
  if (!base.ok)
@@ -450,12 +339,6 @@ function requirementsFromTask(content) {
450
339
  checked: match[1].toLowerCase() === 'x', text: match[2]
451
340
  }));
452
341
  }
453
- function flattenPages(value) {
454
- return Array.isArray(value) ? value.flatMap((item) => Array.isArray(item) ? item : [item]) : [];
455
- }
456
- function listNames(value) {
457
- return flattenPages(value).map((item) => typeof item === 'string' ? item : String(item.name || '')).filter(Boolean);
458
- }
459
342
  function milestoneBasis(repoRoot, current) {
460
343
  if (!current || !/^\d+\.\d+\.x$/.test(current))
461
344
  return current;
@@ -552,67 +435,6 @@ function desiredFieldValues(frontmatter, fields) {
552
435
  }
553
436
  return desired;
554
437
  }
555
- function planGraphMetadata(client, repository, issue, cwd, frontmatter, options, capabilities) {
556
- if (!options.issueType && !options.fields)
557
- return { operations: [], issueId: null };
558
- if (!capabilities.push)
559
- return {
560
- operations: [
561
- ...(options.issueType ? [{ name: 'issue-type', status: 'skipped', reasonCode: 'PUSH_REQUIRED' }] : []),
562
- ...(options.fields ? [{ name: 'fields', status: 'skipped', reasonCode: 'PUSH_REQUIRED' }] : [])
563
- ], issueId: null
564
- };
565
- const [owner] = repository.split('/');
566
- const typesResult = client.json(['api', 'graphql', '-f', `query=${ISSUE_TYPES_QUERY}`, '-F', `owner=${owner}`], { cwd });
567
- if (!typesResult.ok)
568
- return { operations: [{ name: 'issue-schema', status: 'failed', reasonCode: typesResult.error.code }], issueId: null };
569
- const types = normalizeIssueTypes(typesResult.value);
570
- if (types.length === 0)
571
- return {
572
- operations: [
573
- ...(options.issueType ? [{ name: 'issue-type', status: 'skipped', reasonCode: 'ISSUE_TYPES_UNSUPPORTED' }] : []),
574
- ...(options.fields ? [{ name: 'fields', status: 'skipped', reasonCode: 'ISSUE_TYPES_UNSUPPORTED' }] : [])
575
- ], issueId: null
576
- };
577
- const current = graphState(client, repository, issue, cwd);
578
- if (!current?.issueId)
579
- return { operations: [{ name: 'issue-schema', status: 'failed', reasonCode: 'ISSUE_GRAPH_IDENTITY_INVALID' }], issueId: null };
580
- const target = types.find((type) => type.name === desiredIssueType(frontmatter.type || ''));
581
- if (!target)
582
- return { operations: [{ name: 'issue-type', status: 'failed', reasonCode: 'ISSUE_TYPE_NOT_FOUND' }], issueId: current.issueId };
583
- const operations = [];
584
- if (options.issueType)
585
- operations.push(current.type?.id === target.id
586
- ? { name: 'issue-type', status: 'no-op', reasonCode: null }
587
- : { name: 'issue-type', status: 'planned', reasonCode: null, value: { issueTypeId: target.id } });
588
- if (options.fields) {
589
- const desired = desiredFieldValues(frontmatter, target.fields);
590
- const inputs = desired.filter((field) => current.values.find((value) => value.name === field.name)?.value !== field.value).map((field) => field.input);
591
- if (current.type?.id !== target.id) {
592
- const targetNames = new Set(target.fields.map((field) => field.name));
593
- for (const old of current.values)
594
- if (!targetNames.has(old.name))
595
- inputs.push({ fieldId: old.id, delete: true });
596
- }
597
- operations.push(inputs.length === 0
598
- ? { name: 'fields', status: 'no-op', reasonCode: null }
599
- : { name: 'fields', status: 'planned', reasonCode: null, value: inputs });
600
- }
601
- return { operations, issueId: current.issueId };
602
- }
603
- function executeGraphOperation(client, cwd, issueId, operation) {
604
- if (operation.name === 'issue-type') {
605
- const query = 'mutation($issueId:ID!,$issueTypeId:ID){updateIssueIssueType(input:{issueId:$issueId,issueTypeId:$issueTypeId}){issue{id}}}';
606
- const typeId = operation.value.issueTypeId;
607
- return client.json(['api', 'graphql', '--input', '-'], {
608
- cwd, method: 'POST', input: JSON.stringify({ query, variables: { issueId, issueTypeId: typeId } })
609
- });
610
- }
611
- const query = 'mutation($issueId:ID!,$issueFields:[IssueFieldCreateOrUpdateInput!]!){setIssueFieldValue(input:{issueId:$issueId,issueFields:$issueFields}){issue{id}}}';
612
- return client.json(['api', 'graphql', '--input', '-'], {
613
- cwd, method: 'POST', input: JSON.stringify({ query, variables: { issueId, issueFields: operation.value } })
614
- });
615
- }
616
438
  async function syncPlatformIssue(taskRef, options) {
617
439
  const base = await resolvedContext(taskRef, options);
618
440
  if (!base.ok)
@@ -761,5 +583,5 @@ async function syncPlatformIssue(taskRef, options) {
761
583
  operations: plan.operations.map((operation) => operation.status === 'planned' ? { ...operation, status: 'applied' } : operation), error: null
762
584
  });
763
585
  }
764
- export { bindPlatformIssue, createPlatformIssue, graphState, inspectGitHubIssue, inspectGitHubIssueMetadata, inspectPlatformIssue, normalizeIssue, requirementSectionAnchors, syncPlatformIssue };
586
+ export { bindPlatformIssue, createPlatformIssue, inspectPlatformIssue, requirementSectionAnchors, syncPlatformIssue };
765
587
  //# sourceMappingURL=issues.js.map
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import { parseTypedTaskFrontmatter } from '../task/frontmatter.js';
3
3
  import { resolveTaskRef } from '../task/resolve-ref.js';
4
+ import { checkStatusBucket } from './check-status.js';
4
5
  import { resolvePlatformProviderContext } from './context.js';
5
6
  import { inspectPlatformPullRequest } from './pull-requests.js';
6
7
  import { platformResult } from './types.js';
@@ -26,20 +27,6 @@ function classifyPullRequestReadiness(input) {
26
27
  return { state: 'pending', headSha: input.headSha };
27
28
  return { state: 'ready', headSha: input.headSha };
28
29
  }
29
- function parseRunJobIdentity(detailsUrl) {
30
- try {
31
- const url = new URL(detailsUrl);
32
- if (url.hostname !== 'github.com')
33
- return null;
34
- const match = url.pathname.match(/\/actions\/runs\/(\d+)(?:\/job\/(\d+))?/);
35
- if (!match)
36
- return null;
37
- return { runId: Number(match[1]), jobId: match[2] ? Number(match[2]) : null };
38
- }
39
- catch {
40
- return null;
41
- }
42
- }
43
30
  function resolveRunCandidate(candidates, headSha, checkName) {
44
31
  const matches = candidates.filter((candidate) => candidate.headSha === headSha && candidate.name === checkName);
45
32
  if (matches.length === 1)
@@ -54,42 +41,6 @@ function checksResult(status, overrides = {}) {
54
41
  ...overrides
55
42
  };
56
43
  }
57
- function normalizeBucket(value) {
58
- const raw = String(value.bucket || value.status || value.conclusion || value.state || '').toLowerCase();
59
- if (['pass', 'success', 'successful', 'neutral'].includes(raw))
60
- return 'pass';
61
- if (['fail', 'failure', 'failed', 'error', 'timed_out', 'action_required'].includes(raw))
62
- return 'fail';
63
- if (['cancel', 'cancelled', 'canceled', 'skipped', 'stale'].includes(raw))
64
- return 'cancel';
65
- return 'pending';
66
- }
67
- function normalizeChecks(value) {
68
- if (!Array.isArray(value))
69
- return [];
70
- return value.flatMap((raw) => {
71
- const item = raw;
72
- const name = String(item.name || item.context || '');
73
- return name ? [{
74
- name,
75
- bucket: normalizeBucket(item),
76
- workflow: item.workflow ? String(item.workflow) : null,
77
- conclusion: item.conclusion ? String(item.conclusion) : item.state ? String(item.state) : null,
78
- detailsUrl: item.link ? String(item.link) : item.detailsUrl ? String(item.detailsUrl) : null,
79
- startedAt: item.startedAt ? String(item.startedAt) : null,
80
- completedAt: item.completedAt ? String(item.completedAt) : null
81
- }] : [];
82
- });
83
- }
84
- function inspectGitHubRequiredChecks(client, repository, number, cwd) {
85
- const inspected = client.json([
86
- 'pr', 'checks', String(number), '--repo', repository,
87
- '--json', 'name,state,bucket,link,workflow,startedAt,completedAt'
88
- ], { cwd });
89
- return inspected.ok
90
- ? { ok: true, value: normalizeChecks(inspected.value) }
91
- : { ok: false, error: inspected.error };
92
- }
93
44
  async function resolvedTask(taskRef, options) {
94
45
  const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
95
46
  if (!resolved.ok)
@@ -117,10 +68,9 @@ async function inspectRequiredChecks(taskRef, options = {}) {
117
68
  const base = await resolvedTask(taskRef, options);
118
69
  if (!base.ok)
119
70
  return base.output;
120
- return inspectChecksForResolvedTask(base, options);
71
+ return inspectChecksForResolvedTask(base);
121
72
  }
122
- async function inspectChecksForResolvedTask(base, options) {
123
- const repository = base.context.platform.repository;
73
+ async function inspectChecksForResolvedTask(base) {
124
74
  {
125
75
  const inspected = base.provider.checks?.inspectRequired
126
76
  ? await base.provider.checks.inspectRequired({
@@ -137,7 +87,13 @@ async function inspectChecksForResolvedTask(base, options) {
137
87
  pullRequest: base.pullRequest,
138
88
  error: providerError(inspected.error, 'PLATFORM_PROVIDER_OPERATION_FAILED')
139
89
  });
140
- const required = normalizeChecks(inspected.value);
90
+ const required = inspected.value.map((check) => ({
91
+ name: check.name,
92
+ bucket: checkStatusBucket(check.status),
93
+ conclusion: check.conclusion || null,
94
+ detailsUrl: check.detailsUrl || null,
95
+ workflow: null, startedAt: null, completedAt: null
96
+ }));
141
97
  const classified = classifyRequiredChecks(required);
142
98
  const status = classified.state === 'passed' || classified.state === 'no-required'
143
99
  ? 'no-op'
@@ -157,7 +113,7 @@ async function inspectPullRequestReadiness(taskRef, options = {}) {
157
113
  const base = await resolvedTask(taskRef, options);
158
114
  if (!base.ok)
159
115
  return base.output;
160
- const checked = await inspectChecksForResolvedTask(base, options);
116
+ const checked = await inspectChecksForResolvedTask(base);
161
117
  const classifiedCodes = new Set(['REQUIRED_CHECKS_PENDING', 'REQUIRED_CHECKS_FAILED', 'REQUIRED_CHECKS_CANCELLED']);
162
118
  if (checked.error && !classifiedCodes.has(checked.error.code))
163
119
  return checked;
@@ -242,15 +198,6 @@ async function resolvePlatformCheckRun(taskRef, options) {
242
198
  });
243
199
  }
244
200
  }
245
- function fetchCheckLogText(client, args, cwd) {
246
- if (!client.text)
247
- return { ok: false, error: { code: 'PLATFORM_CLIENT_TEXT_UNAVAILABLE', message: 'Platform client does not support text responses', retryable: false } };
248
- const fetched = client.text(args, { cwd });
249
- if (fetched.ok || args[0] !== 'api' || !/response contains terminal escape sequences/i.test(fetched.error.message)) {
250
- return fetched;
251
- }
252
- return client.text([...args, '--allow-escape-sequences'], { cwd });
253
- }
254
201
  async function fetchPlatformCheckLogs(taskRef, options) {
255
202
  const base = await resolvedTask(taskRef, options);
256
203
  if (!base.ok)
@@ -281,5 +228,5 @@ async function fetchPlatformCheckLogs(taskRef, options) {
281
228
  });
282
229
  }
283
230
  }
284
- export { classifyPullRequestReadiness, classifyRequiredChecks, fetchCheckLogText, fetchPlatformCheckLogs, inspectGitHubRequiredChecks, inspectRequiredChecks, inspectPullRequestReadiness, parseRunJobIdentity, resolvePlatformCheckRun, resolveRunCandidate, watchPullRequestReadiness };
231
+ export { classifyPullRequestReadiness, classifyRequiredChecks, fetchPlatformCheckLogs, inspectRequiredChecks, inspectPullRequestReadiness, resolvePlatformCheckRun, resolveRunCandidate, watchPullRequestReadiness };
285
232
  //# sourceMappingURL=pr-checks.js.map
@@ -12,6 +12,8 @@ import { CONTROL_MARKER_PATTERN, escapeHtmlText, sanitizeMarkdownDocument, split
12
12
  import { platformResult } from './types.js';
13
13
  import { providerError, providerOperationContext, providerStatus, providerResourceToken, resourceIdentityNumber, unsupportedProviderOperation } from './provider-bridge.js';
14
14
  import { buildPrChangeReport, CANONICAL_REPORT_PLACEHOLDER, readPrChangeReport, replaceCanonicalReportPlaceholder, runMechanicalChangeReport, taskIntentDigest, validateMechanicalReport, validatePrecheckCandidate, writePrChangeReportAtomic } from './pr-change-report.js';
15
+ import { manualValidationFinalSummaryProjectionMatches } from '../task/manual-validation-receipt.js';
16
+ import { readManualValidationCompletion } from '../task/manual-validation-completion.js';
15
17
  function summaryMarker(taskId) {
16
18
  return `<!-- sync-pr:${taskId}:summary -->`;
17
19
  }
@@ -286,6 +288,25 @@ async function summaryContext(taskRef, options = {}) {
286
288
  artifacts: canonicalArtifacts(resolved.taskDir)
287
289
  };
288
290
  }
291
+ async function summaryCommentState(taskRef, options = {}) {
292
+ const context = await summaryContext(taskRef, options);
293
+ if (!context.task.prNumber || !context.pullRequest)
294
+ return { ...context, comment: null };
295
+ const loaded = await resolvePlatformProviderContext({ cwd: options.cwd, client: options.client });
296
+ if (!loaded.ok || !loaded.value.provider.comments?.list)
297
+ return { ...context, comment: null };
298
+ const listed = await loaded.value.provider.comments.list({
299
+ context: providerOperationContext(loaded.value),
300
+ parent: providerResourceToken(loaded.value.provider, 'pull-request', String(context.task.prNumber))
301
+ });
302
+ if (!listed.ok)
303
+ return { ...context, comment: null };
304
+ const matches = listed.value.filter((comment) => comment.body.includes(summaryMarker(context.task.id)));
305
+ return {
306
+ ...context,
307
+ comment: matches.length === 1 ? { id: /^\d+$/u.test(matches[0].id) ? Number(matches[0].id) : matches[0].id, body: matches[0].body } : null
308
+ };
309
+ }
289
310
  async function reportWrite(taskRef, options) {
290
311
  const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
291
312
  if (!resolved.ok)
@@ -444,7 +465,7 @@ async function syncPullRequestSummary(taskRef, options) {
444
465
  return preserveFailure(context);
445
466
  }
446
467
  try {
447
- return await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, options.agent, async () => {
468
+ const execute = async () => {
448
469
  const { taskContent, boundFact } = readBoundTaskSnapshot(resolved.taskMdPath);
449
470
  if (!boundFact)
450
471
  return {
@@ -463,6 +484,34 @@ async function syncPullRequestSummary(taskRef, options) {
463
484
  const initial = await inspectBoundPullRequest(context, resolved.repoRoot, boundPrNumber, loaded.value);
464
485
  if (!initial.ok)
465
486
  return fail(initial.status, context, initial.error);
487
+ const manual = options.manualValidation;
488
+ const hasFinalManualValidation = /###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)/u.test(options.body);
489
+ if (hasFinalManualValidation && (!manual || manual.phase !== 'final'))
490
+ return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'final manual-validation summary requires the transaction coordinator', retryable: false }, prNumber);
491
+ if (manual && (manual.phase === 'pending' ? hasFinalManualValidation : !hasFinalManualValidation))
492
+ 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);
493
+ if (manual) {
494
+ if (manual.phase === 'final' && (!manual.transactionId || !manual.receiptDigest || !manual.prHeadSha || manual.prHeadSha !== initial.value.head.sha))
495
+ return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'final manual-validation summary requires transaction, receipt, and current head identity', retryable: false }, prNumber);
496
+ if (manual.phase === 'final') {
497
+ if (manual.authority !== 'coordinator')
498
+ return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'final manual-validation summary requires coordinator authority', retryable: false }, prNumber);
499
+ const completion = readManualValidationCompletion(resolved.taskDir, {
500
+ transactionId: manual.transactionId,
501
+ taskId: resolved.taskId,
502
+ prNumber,
503
+ prHeadSha: initial.value.head.sha,
504
+ receiptDigest: manual.receiptDigest
505
+ });
506
+ if (!completion.ok)
507
+ return fail('failed', context, platformError(completion.error), prNumber);
508
+ const { receipt, transaction } = completion.value;
509
+ if (transaction.phase !== 'final-promotion-in-progress' || !transaction.eventAppended)
510
+ return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_PHASE_INVALID', message: 'final summary requires a receipt-backed promotion intent', retryable: false }, prNumber);
511
+ if (!manualValidationFinalSummaryProjectionMatches(options.body, receipt))
512
+ return fail('failed', context, { code: 'MANUAL_VALIDATION_SUMMARY_PROJECTION_INVALID', message: 'final summary body does not match the canonical receipt projection', retryable: false }, prNumber);
513
+ }
514
+ }
466
515
  const expectedReportPath = taskReportPath(resolved.taskDir);
467
516
  const suppliedReportPath = path.resolve(resolved.repoRoot, options.changeReportFile);
468
517
  if (suppliedReportPath !== expectedReportPath)
@@ -610,7 +659,10 @@ async function syncPullRequestSummary(taskRef, options) {
610
659
  operations: [{ name: `summary:${reconciliation.action}`, status: 'applied', reasonCode: null }],
611
660
  result: null, warnings: [], ...info
612
661
  };
613
- });
662
+ };
663
+ return await (options.lockAlreadyHeld
664
+ ? execute()
665
+ : withTaskExecutionLock(resolved.repoRoot, resolved.taskId, options.agent, execute));
614
666
  }
615
667
  catch (error) {
616
668
  const lockError = error instanceof TaskExecutionLockError
@@ -619,5 +671,5 @@ async function syncPullRequestSummary(taskRef, options) {
619
671
  return fail(lockError.retryable ? 'blocked' : 'failed', context, lockError);
620
672
  }
621
673
  }
622
- export { buildPullRequestSummary, reconcileSummaryComment, reportWrite, summaryContext, summaryMarker, syncPullRequestSummary, warningResultForPrimary };
674
+ export { buildPullRequestSummary, reconcileSummaryComment, reportWrite, summaryCommentState, summaryContext, summaryMarker, syncPullRequestSummary, warningResultForPrimary };
623
675
  //# sourceMappingURL=pr-summary.js.map