@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
@@ -10,6 +10,7 @@ import { planInLabelUpdate, validateInLabelMapping } from "./in-label-sync.js";
10
10
  import { readPrDeliveryFact } from "../task/pr-delivery-fact.js";
11
11
  import { providerError, providerOperationContext, resourceIdentityNumber, unsupportedProviderOperation } from "./provider-bridge.js";
12
12
  import { taskIssueIdentity } from "./task-identities.js";
13
+ import { CONTROL_MARKER_PATTERN, canonicalizeCommentBody, renderSafeCodeFence, sanitizeMarkdownDocument } from "./comment-safety.js";
13
14
  const CHECK_TYPE = "platform-sync";
14
15
  const VERSION_LINE_REGEX = /^[0-9]+\.[0-9]+\.x$/;
15
16
  const FRONTMATTER_FIELD_MAP = {
@@ -24,8 +25,6 @@ const OPTION_LOCALIZATION = {
24
25
  "中": "Medium",
25
26
  "低": "Low"
26
27
  };
27
- let activeShared = null;
28
- let repoRoot = "";
29
28
  export function getDefaults() {
30
29
  return {
31
30
  statusLabels: {
@@ -45,47 +44,16 @@ export function getDefaults() {
45
44
  }
46
45
  };
47
46
  }
48
- function getShared() {
49
- if (!activeShared) {
50
- throw new Error("platform-sync adapter shared utilities are unavailable");
51
- }
52
- return activeShared;
53
- }
54
- function loadTask(...args) {
55
- return getShared().loadTask(...args);
56
- }
57
- function getCheckedRequirements(...args) {
58
- return getShared().getCheckedRequirements(...args);
59
- }
60
- function normalizeContent(...args) {
61
- return getShared().normalizeContent(...args);
62
- }
63
- function isBlank(...args) {
64
- return getShared().isBlank(...args);
65
- }
66
- function escapeRegExp(...args) {
67
- return getShared().escapeRegExp(...args);
68
- }
69
- function passResult(...args) {
70
- return getShared().passResult(...args);
71
- }
72
- function failResult(...args) {
73
- return getShared().failResult(...args);
74
- }
75
- function blockedResult(...args) {
76
- return getShared().blockedResult(...args);
77
- }
78
- function safeStat(...args) {
79
- return getShared().safeStat(...args);
47
+ function sanitizeCommentContent(content) {
48
+ const result = sanitizeMarkdownDocument(content, { reservedMarkers: [CONTROL_MARKER_PATTERN] });
49
+ return result.ok ? result.value : null;
80
50
  }
81
51
  export async function check({ taskDir, config, artifactFile }, shared) {
82
- activeShared = shared;
83
- repoRoot = shared.repoRoot;
84
- const context = await buildSyncContext({ taskDir, config, artifactFile });
52
+ const context = await buildSyncContext({ taskDir, config, artifactFile }, shared);
85
53
  if (context.earlyReturn) {
86
54
  return context.earlyReturn;
87
55
  }
88
- const remoteData = await fetchRemoteData(context);
56
+ const remoteData = await fetchRemoteData(context, shared);
89
57
  if (remoteData.earlyReturn) {
90
58
  return remoteData.earlyReturn;
91
59
  }
@@ -108,52 +76,52 @@ export async function check({ taskDir, config, artifactFile }, shared) {
108
76
  checkMilestone
109
77
  ];
110
78
  for (const subCheck of subChecks) {
111
- const result = subCheck(context, remoteData);
79
+ const result = subCheck(context, remoteData, shared);
112
80
  if (result) {
113
81
  return result;
114
82
  }
115
83
  }
116
- return passResult(CHECK_TYPE, `Platform sync checks passed for Issue ${context.issueNumber || "identity"}`);
84
+ return shared.passResult(CHECK_TYPE, `Platform sync checks passed for Issue ${context.issueNumber || "identity"}`);
117
85
  }
118
- async function buildSyncContext({ taskDir, config, artifactFile }) {
119
- const task = loadTask(taskDir);
86
+ async function buildSyncContext({ taskDir, config, artifactFile }, shared) {
87
+ const task = shared.loadTask(taskDir);
120
88
  if (!task.ok) {
121
- return { earlyReturn: failResult(CHECK_TYPE, task.message) };
89
+ return { earlyReturn: shared.failResult(CHECK_TYPE, task.message) };
122
90
  }
123
91
  const issueIdentity = taskIssueIdentity(task.metadata);
124
92
  const issueNumber = resourceIdentityNumber(issueIdentity);
125
93
  const fact = readPrDeliveryFact(task.metadata);
126
94
  if (fact.status === "invalid") {
127
- return { earlyReturn: failResult(CHECK_TYPE, fact.error.message, "check_failed") };
95
+ return { earlyReturn: shared.failResult(CHECK_TYPE, fact.error.message, "check_failed") };
128
96
  }
129
97
  const prIdentity = fact.status === "valid" && fact.fact.state === "bound" ? fact.fact.identity.resource : null;
130
98
  const prNumber = resourceIdentityNumber(prIdentity);
131
99
  if (config.when === "issue_number_exists" && !issueIdentity) {
132
- return { earlyReturn: passResult(CHECK_TYPE, "Skipped: task has no issue_number") };
100
+ return { earlyReturn: shared.passResult(CHECK_TYPE, "Skipped: task has no issue_number") };
133
101
  }
134
102
  if (config.when === "pr_fact_bound" && !prIdentity) {
135
- return { earlyReturn: passResult(CHECK_TYPE, "Skipped: task has no verified bound pull request") };
103
+ return { earlyReturn: shared.passResult(CHECK_TYPE, "Skipped: task has no verified bound pull request") };
136
104
  }
137
105
  if (!issueIdentity) {
138
- return { earlyReturn: passResult(CHECK_TYPE, "Skipped: platform-sync not required for this task") };
106
+ return { earlyReturn: shared.passResult(CHECK_TYPE, "Skipped: platform-sync not required for this task") };
139
107
  }
140
- const loaded = await resolvePlatformProviderContext({ cwd: repoRoot });
108
+ const loaded = await resolvePlatformProviderContext({ cwd: shared.repoRoot });
141
109
  const platformContext = loaded.ok ? loaded.value.context : loaded.context;
142
110
  if (platformContext.status === "failed") {
143
- return { earlyReturn: failResult(CHECK_TYPE, platformContext.error?.message || "Platform context failed", "check_failed") };
111
+ return { earlyReturn: shared.failResult(CHECK_TYPE, platformContext.error?.message || "Platform context failed", "check_failed") };
144
112
  }
145
113
  if (platformContext.status === "blocked") {
146
- return { earlyReturn: blockedResult(CHECK_TYPE, platformContext.error?.message || "Platform context blocked", "network_error") };
114
+ return { earlyReturn: shared.blockedResult(CHECK_TYPE, platformContext.error?.message || "Platform context blocked", "network_error") };
147
115
  }
148
116
  if (!platformContext.platform.repository) {
149
117
  if (platformContext.error?.code === "REMOTE_MISSING" || platformContext.error?.code === "REMOTE_INVALID") {
150
- return { earlyReturn: blockedResult(CHECK_TYPE, platformContext.error.message, "network_error") };
118
+ return { earlyReturn: shared.blockedResult(CHECK_TYPE, platformContext.error.message, "network_error") };
151
119
  }
152
- return { earlyReturn: passResult(CHECK_TYPE, `Skipped: ${platformContext.error?.message || "platform unavailable"}`) };
120
+ return { earlyReturn: shared.passResult(CHECK_TYPE, `Skipped: ${platformContext.error?.message || "platform unavailable"}`) };
153
121
  }
154
122
  const expectedValues = resolveExpectedValues(config);
155
123
  if (!expectedValues.ok) {
156
- return { earlyReturn: failResult(CHECK_TYPE, expectedValues.message, "check_failed") };
124
+ return { earlyReturn: shared.failResult(CHECK_TYPE, expectedValues.message, "check_failed") };
157
125
  }
158
126
  const marker = expectedValues.commentMarker
159
127
  ? interpolate(expectedValues.commentMarker, taskDir, artifactFile)
@@ -229,7 +197,7 @@ function resolveDefaultValue({ collection, key, value, configKey }) {
229
197
  }
230
198
  return { ok: true, value: resolvedValue };
231
199
  }
232
- async function fetchRemoteData(context) {
200
+ async function fetchRemoteData(context, shared) {
233
201
  const provider = context.provider;
234
202
  const operationContext = providerOperationContext(context.loadedContext, context.taskDir);
235
203
  const facts = provider?.verification?.fetchRemoteFacts
@@ -245,8 +213,8 @@ async function fetchRemoteData(context) {
245
213
  if (!facts.ok) {
246
214
  return {
247
215
  earlyReturn: facts.error.retryable
248
- ? blockedResult(CHECK_TYPE, providerError(facts.error, "PLATFORM_PROVIDER_OPERATION_FAILED").message, "network_error")
249
- : failResult(CHECK_TYPE, providerError(facts.error, "PLATFORM_PROVIDER_OPERATION_FAILED").message, "check_failed")
216
+ ? shared.blockedResult(CHECK_TYPE, providerError(facts.error, "PLATFORM_PROVIDER_OPERATION_FAILED").message, "network_error")
217
+ : shared.failResult(CHECK_TYPE, providerError(facts.error, "PLATFORM_PROVIDER_OPERATION_FAILED").message, "check_failed")
250
218
  };
251
219
  }
252
220
  const issueSnapshot = facts.value.issue;
@@ -272,8 +240,8 @@ async function fetchRemoteData(context) {
272
240
  if (!listed.ok)
273
241
  return {
274
242
  earlyReturn: listed.error.retryable
275
- ? blockedResult(CHECK_TYPE, listed.error.message, "network_error")
276
- : failResult(CHECK_TYPE, listed.error.message, "check_failed")
243
+ ? shared.blockedResult(CHECK_TYPE, listed.error.message, "network_error")
244
+ : shared.failResult(CHECK_TYPE, listed.error.message, "check_failed")
277
245
  };
278
246
  prComments = listed.value.map((comment) => ({ id: comment.id, body: comment.body }));
279
247
  }
@@ -281,9 +249,9 @@ async function fetchRemoteData(context) {
281
249
  let inLabelMapping = {};
282
250
  let repositoryLabels = [];
283
251
  if (context.config.verify_in_labels_computed && context.hasTriage) {
284
- const mapping = loadInLabelMapping();
252
+ const mapping = loadInLabelMapping(shared);
285
253
  if (!mapping.ok)
286
- return { earlyReturn: failResult(CHECK_TYPE, mapping.message, "check_failed") };
254
+ return { earlyReturn: shared.failResult(CHECK_TYPE, mapping.message, "check_failed") };
287
255
  inLabelMapping = mapping.value;
288
256
  if (Object.keys(inLabelMapping).length > 0) {
289
257
  const labels = provider?.issues?.listLabels
@@ -293,8 +261,8 @@ async function fetchRemoteData(context) {
293
261
  const error = providerError(labels.error, "PLATFORM_PROVIDER_OPERATION_FAILED");
294
262
  return {
295
263
  earlyReturn: error.retryable
296
- ? blockedResult(CHECK_TYPE, `${error.code}: ${error.message}`, "network_error")
297
- : failResult(CHECK_TYPE, `${error.code}: ${error.message}`, "check_failed")
264
+ ? shared.blockedResult(CHECK_TYPE, `${error.code}: ${error.message}`, "network_error")
265
+ : shared.failResult(CHECK_TYPE, `${error.code}: ${error.message}`, "check_failed")
298
266
  };
299
267
  }
300
268
  repositoryLabels = labels.value;
@@ -330,7 +298,7 @@ function shouldFetchComments(config) {
330
298
  || config.verify_comment_content
331
299
  || config.verify_task_comment_content);
332
300
  }
333
- function checkStatusLabel(context, remoteData) {
301
+ function checkStatusLabel(context, remoteData, shared) {
334
302
  if (!context.expectedStatusLabel || !context.hasTriage) {
335
303
  return null;
336
304
  }
@@ -341,9 +309,9 @@ function checkStatusLabel(context, remoteData) {
341
309
  if (labels.includes(context.expectedStatusLabel)) {
342
310
  return null;
343
311
  }
344
- return failResult(CHECK_TYPE, `Expected label '${context.expectedStatusLabel}' not found on Issue #${context.issueNumber}`, "check_failed");
312
+ return shared.failResult(CHECK_TYPE, `Expected label '${context.expectedStatusLabel}' not found on Issue #${context.issueNumber}`, "check_failed");
345
313
  }
346
- function checkClosedIssueStatusLabels(context, remoteData) {
314
+ function checkClosedIssueStatusLabels(context, remoteData, shared) {
347
315
  if (!context.config.verify_closed_issue_has_no_status_labels) {
348
316
  return null;
349
317
  }
@@ -355,9 +323,9 @@ function checkClosedIssueStatusLabels(context, remoteData) {
355
323
  if (statusLabels.length === 0) {
356
324
  return null;
357
325
  }
358
- return failResult(CHECK_TYPE, `Closed Issue #${context.issueNumber} retains status labels: ${statusLabels.join(", ")}`, "check_failed");
326
+ return shared.failResult(CHECK_TYPE, `Closed Issue #${context.issueNumber} retains status labels: ${statusLabels.join(", ")}`, "check_failed");
359
327
  }
360
- function checkCommentMarker(context, remoteData) {
328
+ function checkCommentMarker(context, remoteData, shared) {
361
329
  if (!context.marker) {
362
330
  return null;
363
331
  }
@@ -365,9 +333,9 @@ function checkCommentMarker(context, remoteData) {
365
333
  if (comment) {
366
334
  return null;
367
335
  }
368
- return failResult(CHECK_TYPE, `Expected comment marker '${context.marker}' not found on Issue #${context.issueNumber}`, "check_failed");
336
+ return shared.failResult(CHECK_TYPE, `Expected comment marker '${context.marker}' not found on Issue #${context.issueNumber}`, "check_failed");
369
337
  }
370
- function checkPrCommentMarker(context, remoteData) {
338
+ function checkPrCommentMarker(context, remoteData, shared) {
371
339
  if (!context.prMarker) {
372
340
  return null;
373
341
  }
@@ -375,88 +343,107 @@ function checkPrCommentMarker(context, remoteData) {
375
343
  if (comment) {
376
344
  return null;
377
345
  }
378
- return failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed");
346
+ return shared.failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed");
379
347
  }
380
- function checkPrCommentLastCommit(context, remoteData) {
348
+ function checkPrCommentLastCommit(context, remoteData, shared) {
381
349
  if (!context.config.verify_pr_comment_last_commit_matches_head) {
382
350
  return null;
383
351
  }
384
352
  if (!context.prMarker) {
385
- return failResult(CHECK_TYPE, "verify_pr_comment_last_commit_matches_head requires expected_pr_comment_marker", "check_failed");
353
+ return shared.failResult(CHECK_TYPE, "verify_pr_comment_last_commit_matches_head requires expected_pr_comment_marker", "check_failed");
386
354
  }
387
355
  const comment = findCommentByMarker(remoteData.prComments, context.prMarker);
388
356
  if (!comment) {
389
- return failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed");
357
+ return shared.failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed");
390
358
  }
391
359
  const match = String(comment.body || "").match(/<!--\s*last-commit:\s*([0-9a-f]{7,40})\s*-->/i);
392
360
  if (!match) {
393
- return failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment is missing '<!-- last-commit: <sha> -->' metadata`, "check_failed");
361
+ return shared.failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment is missing '<!-- last-commit: <sha> -->' metadata`, "check_failed");
394
362
  }
395
363
  const expectedHead = String(remoteData.prHeadSha || "").trim();
396
364
  if (!expectedHead)
397
- return blockedResult(CHECK_TYPE, "Unable to resolve the PR head SHA", "network_error");
365
+ return shared.blockedResult(CHECK_TYPE, "Unable to resolve the PR head SHA", "network_error");
398
366
  const actualHead = match[1].trim();
399
367
  if (expectedHead === actualHead) {
400
368
  return null;
401
369
  }
402
- return failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment last-commit metadata mismatch: expected ${expectedHead}, got ${actualHead}`, "check_failed");
370
+ return shared.failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment last-commit metadata mismatch: expected ${expectedHead}, got ${actualHead}`, "check_failed");
403
371
  }
404
- function checkPrCommentRequiredPatterns(context, remoteData) {
372
+ function checkPrCommentRequiredPatterns(context, remoteData, shared) {
405
373
  const patterns = context.config.expected_pr_comment_required_patterns || [];
406
374
  if (!Array.isArray(patterns) || patterns.length === 0) {
407
375
  return null;
408
376
  }
409
377
  if (!context.prMarker) {
410
- return failResult(CHECK_TYPE, "expected_pr_comment_required_patterns requires expected_pr_comment_marker", "check_failed");
378
+ return shared.failResult(CHECK_TYPE, "expected_pr_comment_required_patterns requires expected_pr_comment_marker", "check_failed");
411
379
  }
412
380
  const comment = findCommentByMarker(remoteData.prComments, context.prMarker);
413
381
  if (!comment) {
414
- return failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed");
382
+ return shared.failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed");
415
383
  }
416
384
  const body = String(comment.body || "");
417
385
  for (const pattern of patterns) {
418
386
  const regex = new RegExp(pattern, "m");
419
387
  if (!regex.test(body)) {
420
- return failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment is missing required pattern: ${pattern}`, "check_failed");
388
+ return shared.failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment is missing required pattern: ${pattern}`, "check_failed");
421
389
  }
422
390
  }
423
391
  return null;
424
392
  }
425
- function checkCommentContent(context, remoteData) {
393
+ function checkCommentContent(context, remoteData, shared) {
426
394
  if (!context.config.verify_comment_content) {
427
395
  return null;
428
396
  }
429
397
  if (!context.marker) {
430
- return failResult(CHECK_TYPE, "verify_comment_content requires expected_comment_marker", "check_failed");
398
+ return shared.failResult(CHECK_TYPE, "verify_comment_content requires expected_comment_marker", "check_failed");
431
399
  }
432
- if (!context.artifactPath || !safeStat(context.artifactPath)) {
433
- return failResult(CHECK_TYPE, `Artifact not found for comment verification: ${context.artifactFile || "(missing artifactFile)"}`, "check_failed");
400
+ if (!context.artifactPath || !shared.safeStat(context.artifactPath)) {
401
+ return shared.failResult(CHECK_TYPE, `Artifact not found for comment verification: ${context.artifactFile || "(missing artifactFile)"}`, "check_failed");
434
402
  }
435
403
  const comment = findCommentByMarker(remoteData.comments, context.marker);
436
- const localContent = normalizeContent(fs.readFileSync(context.artifactPath, "utf8"));
437
- const commentContent = normalizeContent(extractCommentBody(comment?.body || ""));
404
+ let localContent;
405
+ let commentContent;
406
+ try {
407
+ const localCanonical = canonicalizeCommentBody(fs.readFileSync(context.artifactPath, "utf8"));
408
+ const commentCanonical = canonicalizeCommentBody(extractCommentBody(comment?.body || ""));
409
+ if (!localCanonical.ok) {
410
+ return shared.failResult(CHECK_TYPE, `Comment content cannot be canonicalized for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${localCanonical.error.message}`, "check_failed");
411
+ }
412
+ if (!commentCanonical.ok) {
413
+ return shared.failResult(CHECK_TYPE, `Comment content cannot be canonicalized for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${commentCanonical.error.message}`, "check_failed");
414
+ }
415
+ localContent = shared.normalizeContent(localCanonical.value);
416
+ commentContent = shared.normalizeContent(commentCanonical.value);
417
+ }
418
+ catch (error) {
419
+ return shared.failResult(CHECK_TYPE, `Comment content cannot be read for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${error instanceof Error ? error.message : String(error)}`, "check_failed");
420
+ }
438
421
  if (localContent === commentContent) {
439
422
  return null;
440
423
  }
441
- return failResult(CHECK_TYPE, buildCommentContentMismatchMessage(path.basename(context.artifactPath, path.extname(context.artifactPath)), context.issueNumber, localContent, commentContent), "check_failed");
424
+ return shared.failResult(CHECK_TYPE, buildCommentContentMismatchMessage(path.basename(context.artifactPath, path.extname(context.artifactPath)), context.issueNumber, localContent, commentContent), "check_failed");
442
425
  }
443
- function checkTaskCommentContent(context, remoteData) {
426
+ function checkTaskCommentContent(context, remoteData, shared) {
444
427
  if (!context.config.verify_task_comment_content) {
445
428
  return null;
446
429
  }
447
430
  const taskMarker = `<!-- sync-issue:${context.task.metadata.id}:task -->`;
448
431
  const comment = findCommentByMarker(remoteData.comments, taskMarker);
449
432
  if (!comment) {
450
- return failResult(CHECK_TYPE, `Expected comment marker '${taskMarker}' not found on Issue #${context.issueNumber}`, "check_failed");
433
+ return shared.failResult(CHECK_TYPE, `Expected comment marker '${taskMarker}' not found on Issue #${context.issueNumber}`, "check_failed");
434
+ }
435
+ const expectedTaskBody = buildExpectedTaskBody(context.task.content, shared);
436
+ if (expectedTaskBody === null) {
437
+ return shared.failResult(CHECK_TYPE, "Task content cannot be rendered safely for comment verification", "check_failed");
451
438
  }
452
- const expectedBody = normalizeContent(buildExpectedTaskBody(context.task.content));
453
- const commentBody = normalizeContent(extractCommentBody(comment.body || ""));
439
+ const expectedBody = shared.normalizeContent(expectedTaskBody);
440
+ const commentBody = shared.normalizeContent(extractCommentBody(comment.body || ""));
454
441
  if (expectedBody === commentBody) {
455
442
  return null;
456
443
  }
457
- return failResult(CHECK_TYPE, buildCommentContentMismatchMessage("task", context.issueNumber, expectedBody, commentBody), "check_failed");
444
+ return shared.failResult(CHECK_TYPE, buildCommentContentMismatchMessage("task", context.issueNumber, expectedBody, commentBody), "check_failed");
458
445
  }
459
- function checkPrTypeLabel(context, remoteData) {
446
+ function checkPrTypeLabel(context, remoteData, shared) {
460
447
  if (!context.config.verify_pr_type_label || !context.hasTriage || !context.prNumber || !remoteData.prLabels) {
461
448
  return null;
462
449
  }
@@ -467,9 +454,9 @@ function checkPrTypeLabel(context, remoteData) {
467
454
  if (remoteData.prLabels.includes(expectedLabel)) {
468
455
  return null;
469
456
  }
470
- return failResult(CHECK_TYPE, `Expected type label '${expectedLabel}' not found on PR #${context.prNumber}`, "check_failed");
457
+ return shared.failResult(CHECK_TYPE, `Expected type label '${expectedLabel}' not found on PR #${context.prNumber}`, "check_failed");
471
458
  }
472
- function checkInLabelsMatchPr(context, remoteData) {
459
+ function checkInLabelsMatchPr(context, remoteData, shared) {
473
460
  if (!context.config.verify_in_labels_match_pr || !context.hasTriage || !context.prNumber || !remoteData.prLabels) {
474
461
  return null;
475
462
  }
@@ -482,17 +469,17 @@ function checkInLabelsMatchPr(context, remoteData) {
482
469
  if (arraysEqual(issueInLabels, prInLabels)) {
483
470
  return null;
484
471
  }
485
- return failResult(CHECK_TYPE, `in: labels mismatch — PR #${context.prNumber} has [${formatLabelList(prInLabels)}], Issue #${context.issueNumber} has [${formatLabelList(issueInLabels)}]`, "check_failed");
472
+ return shared.failResult(CHECK_TYPE, `in: labels mismatch — PR #${context.prNumber} has [${formatLabelList(prInLabels)}], Issue #${context.issueNumber} has [${formatLabelList(issueInLabels)}]`, "check_failed");
486
473
  }
487
- function checkInLabelsComputed(context, remoteData) {
474
+ function checkInLabelsComputed(context, remoteData, shared) {
488
475
  if (!context.config.verify_in_labels_computed || !context.hasTriage) {
489
476
  return null;
490
477
  }
491
- const expectedInLabels = computeExpectedInLabels(context.taskDir, remoteData.repositoryLabels, remoteData.inLabelMapping);
478
+ const expectedInLabels = computeExpectedInLabels(context.taskDir, remoteData.repositoryLabels, remoteData.inLabelMapping, shared);
492
479
  if (!expectedInLabels.ok) {
493
480
  return expectedInLabels.type === "check_failed"
494
- ? failResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type)
495
- : blockedResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type);
481
+ ? shared.failResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type)
482
+ : shared.blockedResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type);
496
483
  }
497
484
  if (expectedInLabels.mode === "skipped") {
498
485
  return null;
@@ -503,32 +490,32 @@ function checkInLabelsComputed(context, remoteData) {
503
490
  if (arraysEqual(expectedInLabels.labels, actualInLabels)) {
504
491
  return null;
505
492
  }
506
- return failResult(CHECK_TYPE, `Issue #${context.issueNumber} in: labels do not match committed changes: expected [${formatLabelList(expectedInLabels.labels)}], got [${formatLabelList(actualInLabels)}]`, "check_failed");
493
+ return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} in: labels do not match committed changes: expected [${formatLabelList(expectedInLabels.labels)}], got [${formatLabelList(actualInLabels)}]`, "check_failed");
507
494
  }
508
- function checkSyncedRequirements(context, remoteData) {
495
+ function checkSyncedRequirements(context, remoteData, shared) {
509
496
  if (!context.config.sync_checked_requirements || !context.hasTriage) {
510
497
  return null;
511
498
  }
512
- const checkedRequirements = getCheckedRequirements(context.task.content);
499
+ const checkedRequirements = shared.getCheckedRequirements(context.task.content);
513
500
  if (checkedRequirements.length === 0) {
514
501
  return null;
515
502
  }
516
503
  const issueBody = remoteData.issue.body || "";
517
- const resolution = resolveRequirementSection(issueBody, requirementSectionAnchors(repoRoot, context.task.metadata.type || "task"));
504
+ const resolution = resolveRequirementSection(issueBody, requirementSectionAnchors(shared.repoRoot, context.task.metadata.type || "task"));
518
505
  if (resolution.status === "missing") {
519
506
  return null;
520
507
  }
521
508
  if (resolution.status === "ambiguous") {
522
- return failResult(CHECK_TYPE, `Issue #${context.issueNumber} requirements section is ambiguous`, "check_failed");
509
+ return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} requirements section is ambiguous`, "check_failed");
523
510
  }
524
511
  const requirementBody = issueBody.slice(resolution.bodyStart, resolution.bodyEnd);
525
512
  const missingRequirements = checkedRequirements.filter((item) => !hasCheckedRequirement(requirementBody, item));
526
513
  if (missingRequirements.length === 0) {
527
514
  return null;
528
515
  }
529
- return failResult(CHECK_TYPE, `Issue body is missing checked requirements: ${missingRequirements.join(", ")}`, "check_failed");
516
+ return shared.failResult(CHECK_TYPE, `Issue body is missing checked requirements: ${missingRequirements.join(", ")}`, "check_failed");
530
517
  }
531
- function checkIssueType(context, remoteData) {
518
+ function checkIssueType(context, remoteData, shared) {
532
519
  if (!context.config.verify_issue_type || !context.hasPush) {
533
520
  return null;
534
521
  }
@@ -539,15 +526,15 @@ function checkIssueType(context, remoteData) {
539
526
  if (context.repoOwnerType === "User") {
540
527
  return null;
541
528
  }
542
- return failResult(CHECK_TYPE, `Issue #${context.issueNumber} has no Issue Type set`, "check_failed");
529
+ return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} has no Issue Type set`, "check_failed");
543
530
  }
544
531
  const expectedType = mapTaskTypeToIssueType(context.task.metadata.type);
545
532
  if (expectedType && remoteData.issueType !== expectedType) {
546
- return failResult(CHECK_TYPE, `Issue #${context.issueNumber} has type '${remoteData.issueType}', expected '${expectedType}' (from task type '${context.task.metadata.type}')`, "check_failed");
533
+ return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} has type '${remoteData.issueType}', expected '${expectedType}' (from task type '${context.task.metadata.type}')`, "check_failed");
547
534
  }
548
535
  return null;
549
536
  }
550
- function checkIssueFields(context, remoteData) {
537
+ function checkIssueFields(context, remoteData, shared) {
551
538
  if (!context.config.verify_issue_fields || !context.hasPush) {
552
539
  return null;
553
540
  }
@@ -556,7 +543,7 @@ function checkIssueFields(context, remoteData) {
556
543
  }
557
544
  for (const [metadataKey, fieldName] of Object.entries(FRONTMATTER_FIELD_MAP)) {
558
545
  const expectedRaw = context.task.metadata[metadataKey];
559
- if (isBlank(expectedRaw) || !remoteData.issueFields.pinnedNames.has(fieldName)) {
546
+ if (shared.isBlank(expectedRaw) || !remoteData.issueFields.pinnedNames.has(fieldName)) {
560
547
  continue;
561
548
  }
562
549
  const actual = remoteData.issueFields.values.get(fieldName);
@@ -565,40 +552,40 @@ function checkIssueFields(context, remoteData) {
565
552
  continue;
566
553
  }
567
554
  if (!actual) {
568
- return failResult(CHECK_TYPE, `Issue #${context.issueNumber} field '${fieldName}' is missing, expected '${expected.value}'`, "check_failed");
555
+ return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} field '${fieldName}' is missing, expected '${expected.value}'`, "check_failed");
569
556
  }
570
557
  if (actual.kind !== expected.kind || actual.value !== expected.value) {
571
- return failResult(CHECK_TYPE, `Issue #${context.issueNumber} field '${fieldName}' is '${actual.value}', expected '${expected.value}'`, "check_failed");
558
+ return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} field '${fieldName}' is '${actual.value}', expected '${expected.value}'`, "check_failed");
572
559
  }
573
560
  }
574
561
  return null;
575
562
  }
576
- function checkPrAssignee(context, remoteData) {
563
+ function checkPrAssignee(context, remoteData, shared) {
577
564
  if (!context.config.verify_pr_assignee || !context.hasPush || !context.prNumber) {
578
565
  return null;
579
566
  }
580
567
  if (!remoteData.prAssignees || remoteData.prAssignees.length === 0) {
581
- return failResult(CHECK_TYPE, `PR #${context.prNumber} has no assignee`, "check_failed");
568
+ return shared.failResult(CHECK_TYPE, `PR #${context.prNumber} has no assignee`, "check_failed");
582
569
  }
583
570
  return null;
584
571
  }
585
- function checkMilestone(context, remoteData) {
572
+ function checkMilestone(context, remoteData, shared) {
586
573
  if (!context.config.verify_milestone || !context.hasTriage) {
587
574
  return null;
588
575
  }
589
576
  if (!remoteData.issue?.milestone?.title) {
590
- return failResult(CHECK_TYPE, `Issue #${context.issueNumber} has no milestone set`, "check_failed");
577
+ return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} has no milestone set`, "check_failed");
591
578
  }
592
579
  if (context.prNumber && remoteData.prMilestone !== undefined && !remoteData.prMilestone?.title) {
593
- return failResult(CHECK_TYPE, `PR #${context.prNumber} has no milestone set`, "check_failed");
580
+ return shared.failResult(CHECK_TYPE, `PR #${context.prNumber} has no milestone set`, "check_failed");
594
581
  }
595
582
  if (context.config.verify_milestone_specific) {
596
583
  const issueTitle = remoteData.issue.milestone.title;
597
584
  if (VERSION_LINE_REGEX.test(issueTitle)) {
598
- return failResult(CHECK_TYPE, `Issue #${context.issueNumber} milestone '${issueTitle}' is a release line; narrow to a specific version (e.g. ${issueTitle.replace(/\.x$/, ".N")}) before continuing`, "check_failed");
585
+ return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} milestone '${issueTitle}' is a release line; narrow to a specific version (e.g. ${issueTitle.replace(/\.x$/, ".N")}) before continuing`, "check_failed");
599
586
  }
600
587
  if (context.prNumber && remoteData.prMilestone?.title && VERSION_LINE_REGEX.test(remoteData.prMilestone.title)) {
601
- return failResult(CHECK_TYPE, `PR #${context.prNumber} milestone '${remoteData.prMilestone.title}' is a release line; narrow to a specific version before continuing`, "check_failed");
588
+ return shared.failResult(CHECK_TYPE, `PR #${context.prNumber} milestone '${remoteData.prMilestone.title}' is a release line; narrow to a specific version before continuing`, "check_failed");
602
589
  }
603
590
  }
604
591
  return null;
@@ -643,37 +630,38 @@ function extractCommentBody(commentBody) {
643
630
  }
644
631
  return lines.slice(start, end).join("\n");
645
632
  }
646
- function buildExpectedTaskBody(taskContent) {
633
+ function buildExpectedTaskBody(taskContent, shared) {
647
634
  const frontmatterMatch = taskContent.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
648
635
  if (!frontmatterMatch) {
649
- return taskContent.trim();
636
+ return sanitizeCommentContent(taskContent.trim());
637
+ }
638
+ const body = sanitizeCommentContent(taskContent.slice(frontmatterMatch[0].length).trim());
639
+ if (body === null) {
640
+ return null;
650
641
  }
651
- const body = taskContent.slice(frontmatterMatch[0].length).trim();
652
642
  return [
653
- buildTaskFrontmatterSummary(),
643
+ buildTaskFrontmatterSummary(shared),
654
644
  "",
655
- "```yaml",
656
- frontmatterMatch[0].trim(),
657
- "```",
645
+ renderSafeCodeFence(frontmatterMatch[0].trim(), "yaml"),
658
646
  "",
659
647
  "</details>",
660
648
  "",
661
649
  body
662
650
  ].join("\n").trim();
663
651
  }
664
- function buildTaskFrontmatterSummary() {
665
- const language = loadProjectLanguage();
652
+ function buildTaskFrontmatterSummary(shared) {
653
+ const language = loadProjectLanguage(shared);
666
654
  if (language === "en" || language === "en-US") {
667
655
  return "<details><summary>Metadata (frontmatter)</summary>";
668
656
  }
669
657
  return "<details><summary>元数据 (frontmatter)</summary>";
670
658
  }
671
- function loadProjectLanguage() {
659
+ function loadProjectLanguage(shared) {
672
660
  const override = process.env.VALIDATE_ARTIFACT_LANGUAGE;
673
- if (!isBlank(override)) {
661
+ if (!shared.isBlank(override)) {
674
662
  return String(override).trim();
675
663
  }
676
- const configPath = path.join(repoRoot, ".agents", ".airc.json");
664
+ const configPath = path.join(shared.repoRoot, ".agents", ".airc.json");
677
665
  if (!fs.existsSync(configPath)) {
678
666
  return "";
679
667
  }
@@ -788,8 +776,8 @@ function arraysEqual(left, right) {
788
776
  function formatLabelList(labels) {
789
777
  return labels.length > 0 ? labels.join(", ") : "none";
790
778
  }
791
- function computeExpectedInLabels(taskDir, repositoryLabels = [], mappingOverride) {
792
- const task = loadTask(taskDir);
779
+ function computeExpectedInLabels(taskDir, repositoryLabels, mappingOverride, shared) {
780
+ const task = shared.loadTask(taskDir);
793
781
  if (!task.ok) {
794
782
  return task;
795
783
  }
@@ -820,8 +808,8 @@ function computeExpectedInLabels(taskDir, repositoryLabels = [], mappingOverride
820
808
  }
821
809
  return { ok: true, labels: planned.target, mode: "mapped" };
822
810
  }
823
- function loadInLabelMapping() {
824
- const configPath = path.join(repoRoot, ".agents", ".airc.json");
811
+ function loadInLabelMapping(shared) {
812
+ const configPath = path.join(shared.repoRoot, ".agents", ".airc.json");
825
813
  if (!fs.existsSync(configPath)) {
826
814
  return { ok: true, value: {} };
827
815
  }
@@ -852,12 +840,12 @@ function gitText(args, cwd) {
852
840
  }
853
841
  }
854
842
  function resolvePrHeadSha(context) {
855
- const fallback = () => withRetry(() => gitText(["rev-parse", "HEAD"], context.taskDir));
843
+ const fallback = () => gitText(["rev-parse", "HEAD"], context.taskDir);
856
844
  const branch = String(context.task?.metadata?.branch || "").trim();
857
845
  if (!branch) {
858
846
  return fallback();
859
847
  }
860
- const worktreeList = withRetry(() => gitText(["worktree", "list", "--porcelain"], context.taskDir));
848
+ const worktreeList = gitText(["worktree", "list", "--porcelain"], context.taskDir);
861
849
  if (!worktreeList.ok) {
862
850
  return fallback();
863
851
  }
@@ -865,7 +853,7 @@ function resolvePrHeadSha(context) {
865
853
  if (!matchedWorktree) {
866
854
  return fallback();
867
855
  }
868
- const headInWorktree = withRetry(() => gitText(["rev-parse", "HEAD"], matchedWorktree));
856
+ const headInWorktree = gitText(["rev-parse", "HEAD"], matchedWorktree);
869
857
  if (!headInWorktree.ok) {
870
858
  return fallback();
871
859
  }
@@ -888,9 +876,6 @@ function findWorktreeForBranch(porcelainOutput, branch) {
888
876
  }
889
877
  return null;
890
878
  }
891
- function withRetry(operation) {
892
- return operation();
893
- }
894
879
  function interpolate(template, taskDir, artifactFile) {
895
880
  const artifactStem = artifactFile ? path.basename(artifactFile, path.extname(artifactFile)) : "";
896
881
  return template
@@ -26,7 +26,7 @@ function createPostConfirmation(version, post) {
26
26
  remoteHead: post.remoteHead,
27
27
  newVersion: post.newVersion,
28
28
  changedPaths: byteSort(post.changedPaths),
29
- demoInputSha256: post.demoInputSha256,
29
+ demoTranscriptSha256: post.demoTranscriptSha256,
30
30
  worktree: byteSort(post.worktree),
31
31
  staged: byteSort(post.staged)
32
32
  };