@fitlab-ai/agent-infra 0.9.12 → 0.9.13

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 (239) hide show
  1. package/README.md +11 -11
  2. package/README.zh-CN.md +11 -11
  3. package/dist/bin/internal-cli.js +24 -19
  4. package/dist/lib/agent-clients/next-steps.js +6 -1
  5. package/dist/lib/decide.js +3 -3
  6. package/dist/lib/internal/platform-checks.js +3 -3
  7. package/dist/lib/internal/platform-comment.js +9 -9
  8. package/dist/lib/internal/platform-context.js +2 -2
  9. package/dist/lib/internal/platform-issue.js +8 -9
  10. package/dist/lib/internal/platform-pr-review.js +10 -10
  11. package/dist/lib/internal/platform-pr.js +63 -24
  12. package/dist/lib/internal/platform-release-notes.js +3 -3
  13. package/dist/lib/internal/pr-review-grade.js +11 -9
  14. package/dist/lib/internal/release-workflow.js +4 -4
  15. package/dist/lib/internal/sandbox-control.js +30 -4
  16. package/dist/lib/internal/task-activity.js +3 -3
  17. package/dist/lib/internal/task-artifact.js +76 -24
  18. package/dist/lib/internal/task-create.js +2 -2
  19. package/dist/lib/internal/task-event.js +6 -3
  20. package/dist/lib/internal/task-finalization.js +2 -2
  21. package/dist/lib/internal/task-invalidation.js +60 -0
  22. package/dist/lib/internal/task-ledger.js +9 -6
  23. package/dist/lib/internal/task-lifecycle.js +2 -2
  24. package/dist/lib/internal/task-override.js +4 -4
  25. package/dist/lib/internal/task-review.js +3 -3
  26. package/dist/lib/internal/task-verify.js +6 -6
  27. package/dist/lib/internal/task-warning.js +3 -3
  28. package/dist/lib/platform/adapters.js +1 -3
  29. package/dist/lib/platform/change-request-git-evidence.js +27 -7
  30. package/dist/lib/platform/completion-backfill.js +6 -5
  31. package/dist/lib/platform/context.js +70 -130
  32. package/dist/lib/platform/github-client.js +1 -1
  33. package/dist/lib/platform/github-provider.js +903 -0
  34. package/dist/lib/platform/in-label-sync.js +143 -0
  35. package/dist/lib/platform/issue-comments.js +94 -41
  36. package/dist/lib/platform/issues.js +335 -189
  37. package/dist/lib/platform/metadata-labels.js +6 -2
  38. package/dist/lib/platform/none-provider.js +28 -0
  39. package/dist/lib/platform/pr-change-report.js +551 -0
  40. package/dist/lib/platform/pr-checks.js +119 -104
  41. package/dist/lib/platform/pr-review.js +88 -72
  42. package/dist/lib/platform/pr-summary.js +520 -50
  43. package/dist/lib/platform/provider-bridge.js +45 -0
  44. package/dist/lib/platform/provider-contract.d.ts +487 -0
  45. package/dist/lib/platform/provider-contract.js +95 -0
  46. package/dist/lib/platform/provider-loader.js +251 -0
  47. package/dist/lib/platform/provider-validation.js +504 -0
  48. package/dist/lib/platform/pull-request-metadata.js +8 -6
  49. package/dist/lib/platform/pull-requests.js +783 -537
  50. package/dist/lib/platform/release-notes.js +78 -55
  51. package/dist/lib/platform/releases.js +79 -50
  52. package/dist/lib/platform/resource-identity.d.ts +26 -0
  53. package/dist/lib/platform/resource-identity.js +92 -0
  54. package/dist/lib/platform/task-identities.js +31 -0
  55. package/dist/lib/platform/verification-required.js +11 -12
  56. package/dist/lib/platform/verification-sync.js +112 -238
  57. package/dist/lib/pr-review/evidence-grading.js +19 -6
  58. package/dist/lib/run/index.js +63 -23
  59. package/dist/lib/sandbox/control/client.js +96 -15
  60. package/dist/lib/sandbox/control/executor.js +3 -3
  61. package/dist/lib/sandbox/control/protocol.js +77 -0
  62. package/dist/lib/sandbox/control/server.js +398 -25
  63. package/dist/lib/sandbox/control/state.js +204 -1
  64. package/dist/lib/server/protocol.js +38 -1
  65. package/dist/lib/task/activity-log.js +8 -1
  66. package/dist/lib/task/artifact-lifecycle.js +33 -5
  67. package/dist/lib/task/capabilities.js +239 -0
  68. package/dist/lib/task/commands/status.js +32 -0
  69. package/dist/lib/task/commit-operation.js +12 -1
  70. package/dist/lib/task/control-authority.js +8 -5
  71. package/dist/lib/task/create-service.js +8 -8
  72. package/dist/lib/task/current-contract.js +5 -1
  73. package/dist/lib/task/decision-details.js +1 -1
  74. package/dist/lib/task/events.js +297 -17
  75. package/dist/lib/task/finalization.js +37 -13
  76. package/dist/lib/task/human-override.js +58 -36
  77. package/dist/lib/task/invalidation-command.js +86 -0
  78. package/dist/lib/task/invalidation.js +260 -0
  79. package/dist/lib/task/ledger-intents.js +37 -0
  80. package/dist/lib/task/local-artifact-finalization.js +374 -0
  81. package/dist/lib/task/orchestration.js +49 -93
  82. package/dist/lib/task/pr-delivery-fact.js +102 -101
  83. package/dist/lib/task/review-fingerprint.js +24 -2
  84. package/dist/lib/task/rework-intent.js +138 -0
  85. package/dist/lib/task/skill-scope.js +23 -0
  86. package/dist/lib/task/task-execution-lock.js +27 -17
  87. package/dist/lib/task/verification-engine.js +43 -16
  88. package/dist/lib/task/verification.js +3 -3
  89. package/dist/lib/task/workflow-warning-intents.js +1 -1
  90. package/dist/lib/task/write.js +11 -0
  91. package/dist/lib/version.js +11 -1
  92. package/dist/lifecycle-build-manifest.json +3 -3
  93. package/lib/agent-clients/next-steps.ts +6 -1
  94. package/lib/decide.ts +3 -3
  95. package/lib/init.ts +1 -1
  96. package/lib/internal/platform-checks.ts +3 -3
  97. package/lib/internal/platform-comment.ts +8 -8
  98. package/lib/internal/platform-context.ts +2 -2
  99. package/lib/internal/platform-issue.ts +7 -8
  100. package/lib/internal/platform-pr-review.ts +9 -9
  101. package/lib/internal/platform-pr.ts +47 -22
  102. package/lib/internal/platform-release-notes.ts +3 -3
  103. package/lib/internal/pr-review-grade.ts +10 -8
  104. package/lib/internal/release-workflow.ts +4 -4
  105. package/lib/internal/sandbox-control.ts +27 -3
  106. package/lib/internal/task-activity.ts +3 -3
  107. package/lib/internal/task-artifact.ts +65 -22
  108. package/lib/internal/task-create.ts +2 -2
  109. package/lib/internal/task-event.ts +6 -3
  110. package/lib/internal/task-finalization.ts +3 -3
  111. package/lib/internal/task-invalidation.ts +39 -0
  112. package/lib/internal/task-ledger.ts +11 -8
  113. package/lib/internal/task-lifecycle.ts +2 -2
  114. package/lib/internal/task-override.ts +6 -6
  115. package/lib/internal/task-review.ts +3 -3
  116. package/lib/internal/task-verify.ts +6 -6
  117. package/lib/internal/task-warning.ts +3 -3
  118. package/lib/platform/adapters.ts +3 -2
  119. package/lib/platform/change-request-git-evidence.ts +36 -9
  120. package/lib/platform/completion-backfill.ts +9 -8
  121. package/lib/platform/context.ts +97 -134
  122. package/lib/platform/github-client.ts +2 -2
  123. package/lib/platform/github-provider.ts +910 -0
  124. package/lib/platform/in-label-sync.ts +208 -0
  125. package/lib/platform/issue-comments.ts +97 -47
  126. package/lib/platform/issues.ts +344 -174
  127. package/lib/platform/metadata-labels.ts +5 -2
  128. package/lib/platform/none-provider.ts +35 -0
  129. package/lib/platform/pr-change-report.ts +645 -0
  130. package/lib/platform/pr-checks.ts +126 -106
  131. package/lib/platform/pr-review.ts +88 -85
  132. package/lib/platform/pr-summary.ts +560 -53
  133. package/lib/platform/provider-bridge.ts +82 -0
  134. package/lib/platform/provider-contract.ts +536 -0
  135. package/lib/platform/provider-loader.ts +307 -0
  136. package/lib/platform/provider-validation.ts +529 -0
  137. package/lib/platform/pull-request-metadata.ts +10 -7
  138. package/lib/platform/pull-requests.ts +826 -543
  139. package/lib/platform/release-notes.ts +76 -59
  140. package/lib/platform/releases.ts +77 -43
  141. package/lib/platform/resource-identity.ts +121 -0
  142. package/lib/platform/task-identities.ts +40 -0
  143. package/lib/platform/types.ts +4 -2
  144. package/lib/platform/verification-required.ts +11 -13
  145. package/lib/platform/verification-sync.ts +112 -292
  146. package/lib/pr-review/evidence-grading.ts +25 -8
  147. package/lib/run/index.ts +57 -22
  148. package/lib/sandbox/control/client.ts +105 -15
  149. package/lib/sandbox/control/executor.ts +4 -4
  150. package/lib/sandbox/control/protocol.ts +113 -0
  151. package/lib/sandbox/control/server.ts +431 -23
  152. package/lib/sandbox/control/state.ts +251 -1
  153. package/lib/server/protocol.ts +36 -1
  154. package/lib/task/activity-log.ts +8 -0
  155. package/lib/task/artifact-lifecycle.ts +32 -6
  156. package/lib/task/capabilities.ts +262 -0
  157. package/lib/task/commands/status.ts +38 -0
  158. package/lib/task/commit-operation.ts +12 -1
  159. package/lib/task/control-authority.ts +11 -8
  160. package/lib/task/create-service.ts +8 -8
  161. package/lib/task/current-contract.ts +6 -1
  162. package/lib/task/decision-details.ts +6 -2
  163. package/lib/task/events.ts +296 -16
  164. package/lib/task/finalization.ts +50 -14
  165. package/lib/task/human-override.ts +70 -46
  166. package/lib/task/invalidation-command.ts +86 -0
  167. package/lib/task/invalidation.ts +332 -0
  168. package/lib/task/ledger-intents.ts +35 -1
  169. package/lib/task/local-artifact-finalization.ts +563 -0
  170. package/lib/task/orchestration.ts +52 -85
  171. package/lib/task/pr-delivery-fact.ts +131 -148
  172. package/lib/task/review-fingerprint.ts +23 -2
  173. package/lib/task/rework-intent.ts +165 -0
  174. package/lib/task/skill-scope.ts +24 -0
  175. package/lib/task/task-execution-lock.ts +23 -14
  176. package/lib/task/verification-engine.ts +52 -17
  177. package/lib/task/verification.ts +4 -4
  178. package/lib/task/workflow-warning-intents.ts +1 -1
  179. package/lib/task/write.ts +19 -0
  180. package/lib/update.ts +1 -1
  181. package/lib/version.ts +15 -1
  182. package/package.json +7 -1
  183. package/templates/.agents/rules/README.en.md +1 -1
  184. package/templates/.agents/rules/README.zh-CN.md +1 -1
  185. package/templates/.agents/rules/issue-pr-commands.en.md +1 -1
  186. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
  187. package/templates/.agents/rules/issue-sync.en.md +2 -0
  188. package/templates/.agents/rules/issue-sync.zh-CN.md +2 -0
  189. package/templates/.agents/rules/local-artifact-repair.en.md +9 -2
  190. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +9 -2
  191. package/templates/.agents/rules/pr-sync.en.md +10 -4
  192. package/templates/.agents/rules/pr-sync.zh-CN.md +17 -4
  193. package/templates/.agents/rules/task-short-id.en.md +1 -4
  194. package/templates/.agents/rules/task-short-id.zh-CN.md +1 -3
  195. package/templates/.agents/skills/analyze-task/SKILL.en.md +13 -3
  196. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -3
  197. package/templates/.agents/skills/check-task/SKILL.en.md +16 -37
  198. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -37
  199. package/templates/.agents/skills/code-task/SKILL.en.md +27 -8
  200. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +29 -10
  201. package/templates/.agents/skills/code-task/config/verify.en.json +1 -0
  202. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -0
  203. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +2 -0
  204. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +2 -0
  205. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +3 -3
  206. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +3 -3
  207. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +5 -3
  208. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -3
  209. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +4 -3
  210. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +4 -3
  211. package/templates/.agents/skills/complete-task/SKILL.en.md +21 -3
  212. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +20 -3
  213. package/templates/.agents/skills/create-pr/SKILL.en.md +7 -6
  214. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -7
  215. package/templates/.agents/skills/create-pr/config/verify.json +1 -0
  216. package/templates/.agents/skills/create-pr/reference/change-report.en.md +16 -4
  217. package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +16 -4
  218. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +5 -3
  219. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +5 -3
  220. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
  221. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
  222. package/templates/.agents/skills/create-pr/scripts/change-report.mjs +3 -0
  223. package/templates/.agents/skills/plan-task/SKILL.en.md +13 -3
  224. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +10 -3
  225. package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
  226. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
  227. package/templates/.agents/skills/review-code/SKILL.en.md +4 -4
  228. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +4 -4
  229. package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
  230. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
  231. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +4 -2
  232. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +4 -2
  233. package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
  234. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
  235. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +6 -0
  236. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +6 -0
  237. package/templates/.agents/templates/task.en.md +18 -1
  238. package/templates/.agents/templates/task.zh-CN.md +18 -1
  239. package/templates/.github/workflows/pr-label.yml +7 -26
@@ -2,14 +2,16 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import process from "node:process";
4
4
  import { execFileSync } from "node:child_process";
5
- import { createGitHubClient } from "./github-client.js";
6
- import { resolvePlatformContext } from "./context.js";
5
+ import { resolvePlatformProviderContext } from "./context.js";
7
6
  import { hasCheckedRequirement, resolveRequirementSection } from "./issue-metadata.js";
8
- import { inspectGitHubIssueMetadata, requirementSectionAnchors } from "./issues.js";
9
- import { listRemoteComments } from "./issue-comments.js";
7
+ import { requirementSectionAnchors } from "./issues.js";
10
8
  import { taskTypeLabel } from "./metadata-labels.js";
9
+ import { planInLabelUpdate, validateInLabelMapping, validateRepositoryLabelPayload } from "./in-label-sync.js";
11
10
  import { inspectGitHubPullRequest } from "./pull-requests.js";
11
+ import { createGitHubClient } from "./github-client.js";
12
12
  import { readPrDeliveryFact } from "../task/pr-delivery-fact.js";
13
+ import { providerError, providerOperationContext, resourceIdentityNumber, unsupportedProviderOperation } from "./provider-bridge.js";
14
+ import { taskIssueIdentity } from "./task-identities.js";
13
15
  const CHECK_TYPE = "platform-sync";
14
16
  const VERSION_LINE_REGEX = /^[0-9]+\.[0-9]+\.x$/;
15
17
  const FRONTMATTER_FIELD_MAP = {
@@ -26,7 +28,6 @@ const OPTION_LOCALIZATION = {
26
28
  };
27
29
  let activeShared = null;
28
30
  let repoRoot = "";
29
- const githubClient = createGitHubClient();
30
31
  export function getDefaults() {
31
32
  return {
32
33
  statusLabels: {
@@ -79,20 +80,14 @@ function blockedResult(...args) {
79
80
  function safeStat(...args) {
80
81
  return getShared().safeStat(...args);
81
82
  }
82
- function parseIssueNumber(...args) {
83
- return getShared().parseIssueNumber(...args);
84
- }
85
- function parsePrNumber(...args) {
86
- return getShared().parsePrNumber(...args);
87
- }
88
- export function check({ taskDir, config, artifactFile }, shared) {
83
+ export async function check({ taskDir, config, artifactFile }, shared) {
89
84
  activeShared = shared;
90
85
  repoRoot = shared.repoRoot;
91
- const context = buildSyncContext({ taskDir, config, artifactFile });
86
+ const context = await buildSyncContext({ taskDir, config, artifactFile });
92
87
  if (context.earlyReturn) {
93
88
  return context.earlyReturn;
94
89
  }
95
- const remoteData = fetchRemoteData(context);
90
+ const remoteData = await fetchRemoteData(context);
96
91
  if (remoteData.earlyReturn) {
97
92
  return remoteData.earlyReturn;
98
93
  }
@@ -120,29 +115,32 @@ export function check({ taskDir, config, artifactFile }, shared) {
120
115
  return result;
121
116
  }
122
117
  }
123
- return passResult(CHECK_TYPE, `GitHub sync checks passed for Issue #${context.issueNumber}`);
118
+ return passResult(CHECK_TYPE, `Platform sync checks passed for Issue ${context.issueNumber || "identity"}`);
124
119
  }
125
- function buildSyncContext({ taskDir, config, artifactFile }) {
120
+ async function buildSyncContext({ taskDir, config, artifactFile }) {
126
121
  const task = loadTask(taskDir);
127
122
  if (!task.ok) {
128
123
  return { earlyReturn: failResult(CHECK_TYPE, task.message) };
129
124
  }
130
- const issueNumber = parseIssueNumber(task.metadata.issue_number);
125
+ const issueIdentity = taskIssueIdentity(task.metadata);
126
+ const issueNumber = resourceIdentityNumber(issueIdentity);
131
127
  const fact = readPrDeliveryFact(task.metadata);
132
128
  if (fact.status === "invalid") {
133
129
  return { earlyReturn: failResult(CHECK_TYPE, fact.error.message, "check_failed") };
134
130
  }
135
- const prNumber = fact.status === "valid" && fact.fact.state === "bound" ? fact.fact.identity.number : null;
136
- if (config.when === "issue_number_exists" && !issueNumber) {
131
+ const prIdentity = fact.status === "valid" && fact.fact.state === "bound" ? fact.fact.identity.resource : null;
132
+ const prNumber = resourceIdentityNumber(prIdentity);
133
+ if (config.when === "issue_number_exists" && !issueIdentity) {
137
134
  return { earlyReturn: passResult(CHECK_TYPE, "Skipped: task has no issue_number") };
138
135
  }
139
- if (config.when === "pr_fact_bound" && !prNumber) {
136
+ if (config.when === "pr_fact_bound" && !prIdentity) {
140
137
  return { earlyReturn: passResult(CHECK_TYPE, "Skipped: task has no verified bound pull request") };
141
138
  }
142
- if (!issueNumber) {
139
+ if (!issueIdentity) {
143
140
  return { earlyReturn: passResult(CHECK_TYPE, "Skipped: platform-sync not required for this task") };
144
141
  }
145
- const platformContext = resolvePlatformContext({ cwd: repoRoot, client: githubClient });
142
+ const loaded = await resolvePlatformProviderContext({ cwd: repoRoot });
143
+ const platformContext = loaded.ok ? loaded.value.context : loaded.context;
146
144
  if (platformContext.status === "failed") {
147
145
  return { earlyReturn: failResult(CHECK_TYPE, platformContext.error?.message || "Platform context failed", "check_failed") };
148
146
  }
@@ -155,7 +153,6 @@ function buildSyncContext({ taskDir, config, artifactFile }) {
155
153
  }
156
154
  return { earlyReturn: passResult(CHECK_TYPE, `Skipped: ${platformContext.error?.message || "platform unavailable"}`) };
157
155
  }
158
- const repoOwnerType = detectRepoOwnerType(platformContext.platform.repository, taskDir);
159
156
  const expectedValues = resolveExpectedValues(config);
160
157
  if (!expectedValues.ok) {
161
158
  return { earlyReturn: failResult(CHECK_TYPE, expectedValues.message, "check_failed") };
@@ -173,15 +170,20 @@ function buildSyncContext({ taskDir, config, artifactFile }) {
173
170
  config,
174
171
  artifactFile,
175
172
  artifactPath,
173
+ issueIdentity,
174
+ prIdentity,
176
175
  issueNumber,
177
176
  prNumber,
178
177
  upstreamRepo: platformContext.platform.repository,
179
- repoOwnerType,
178
+ repoOwnerType: String(loaded.ok ? loaded.value.snapshot.metadata?.ownerType || "unknown" : "unknown"),
180
179
  hasTriage: platformContext.capabilities.triage,
181
180
  hasPush: platformContext.capabilities.push,
182
181
  expectedStatusLabel: expectedValues.statusLabel,
183
182
  marker,
184
- prMarker
183
+ prMarker,
184
+ provider: loaded.ok ? loaded.value.provider : null,
185
+ providerType: loaded.ok ? loaded.value.providerType : null,
186
+ loadedContext: loaded.ok ? loaded.value : null
185
187
  };
186
188
  }
187
189
  function resolveExpectedValues(config) {
@@ -230,139 +232,69 @@ function resolveDefaultValue({ collection, key, value, configKey }) {
230
232
  }
231
233
  return { ok: true, value: resolvedValue };
232
234
  }
233
- function fetchRemoteData(context) {
234
- const sharedIssue = inspectGitHubIssueMetadata(githubClient, context.upstreamRepo, context.issueNumber, context.taskDir);
235
- const issueResult = sharedIssue.ok
236
- ? {
237
- ok: true,
238
- value: {
239
- state: sharedIssue.value.state.toUpperCase(),
240
- labels: sharedIssue.value.labels.map((name) => ({ name })),
241
- body: sharedIssue.value.body,
242
- milestone: sharedIssue.value.milestone ? { title: sharedIssue.value.milestone } : null
243
- }
244
- }
245
- : {
246
- ok: false,
247
- type: sharedIssue.error.retryable ? "network_error" : "check_failed",
248
- message: sharedIssue.error.message
249
- };
250
- if (!issueResult.ok) {
251
- return {
252
- earlyReturn: issueResult.type === "check_failed"
253
- ? failResult(CHECK_TYPE, issueResult.message, issueResult.type)
254
- : blockedResult(CHECK_TYPE, issueResult.message, issueResult.type)
255
- };
256
- }
257
- const issue = issueResult.value;
258
- if (context.config.issue_must_exist !== false && !issue) {
235
+ async function fetchRemoteData(context) {
236
+ const provider = context.provider;
237
+ const operationContext = providerOperationContext(context.loadedContext, context.taskDir);
238
+ const facts = provider?.verification?.fetchRemoteFacts
239
+ ? await provider.verification.fetchRemoteFacts({
240
+ context: operationContext,
241
+ taskId: context.task?.metadata?.id || context.task?.id || "",
242
+ ...(context.issueIdentity ? { issue: context.issueIdentity } : {}),
243
+ ...(context.prIdentity ? { changeRequest: context.prIdentity } : {}),
244
+ includeComments: shouldFetchComments(context.config),
245
+ includeFields: Boolean(context.config.verify_issue_fields)
246
+ })
247
+ : unsupportedProviderOperation(provider, "verification.fetchRemoteFacts");
248
+ if (!facts.ok) {
259
249
  return {
260
- earlyReturn: failResult(CHECK_TYPE, `Issue #${context.issueNumber} not found`, "check_failed")
250
+ earlyReturn: facts.error.retryable
251
+ ? blockedResult(CHECK_TYPE, providerError(facts.error, "PLATFORM_PROVIDER_OPERATION_FAILED").message, "network_error")
252
+ : failResult(CHECK_TYPE, providerError(facts.error, "PLATFORM_PROVIDER_OPERATION_FAILED").message, "check_failed")
261
253
  };
262
254
  }
263
- let comments = null;
264
- if (shouldFetchComments(context.config)) {
265
- const commentsResult = withRetry(() => ghPaginatedJson([
266
- "api",
267
- "--paginate",
268
- "--slurp",
269
- `repos/${context.upstreamRepo}/issues/${context.issueNumber}/comments?per_page=100`
270
- ], context.taskDir));
271
- if (!commentsResult.ok) {
272
- return {
273
- earlyReturn: commentsResult.type === "check_failed"
274
- ? failResult(CHECK_TYPE, commentsResult.message, commentsResult.type)
275
- : blockedResult(CHECK_TYPE, commentsResult.message, commentsResult.type)
276
- };
277
- }
278
- comments = flattenComments(commentsResult.value);
279
- }
280
- let prComments = null;
281
- if (context.prMarker) {
282
- if (!context.prNumber) {
283
- return {
284
- earlyReturn: failResult(CHECK_TYPE, "Expected a verified bound pull request for PR comment verification", "check_failed")
285
- };
286
- }
287
- const prCommentsResult = listRemoteComments(githubClient, context.upstreamRepo, context.prNumber, context.taskDir);
288
- if (!prCommentsResult.ok) {
289
- return {
290
- earlyReturn: prCommentsResult.error.retryable
291
- ? blockedResult(CHECK_TYPE, prCommentsResult.error.message, "network_error")
292
- : failResult(CHECK_TYPE, prCommentsResult.error.message, "check_failed")
293
- };
294
- }
295
- prComments = prCommentsResult.value;
296
- }
297
- let issueType;
298
- if (context.config.verify_issue_type && context.hasPush) {
299
- const issueTypeResult = withRetry(() => ghText([
300
- "api",
301
- `repos/${context.upstreamRepo}/issues/${context.issueNumber}`,
302
- "--jq",
303
- ".type.name // empty"
304
- ], context.taskDir));
305
- if (issueTypeResult.ok) {
306
- issueType = issueTypeResult.value || null;
255
+ const issueSnapshot = facts.value.issue;
256
+ const issue = issueSnapshot
257
+ ? {
258
+ state: issueSnapshot.state.toUpperCase(),
259
+ labels: issueSnapshot.labels.map((name) => ({ name })),
260
+ body: issueSnapshot.body,
261
+ milestone: issueSnapshot.milestone ? { title: issueSnapshot.milestone } : null
307
262
  }
308
- }
263
+ : null;
309
264
  let issueFields;
310
- if (context.config.verify_issue_fields && context.hasPush) {
311
- const [owner, name] = context.upstreamRepo.split("/");
312
- const issueFieldsResult = withRetry(() => ghJson([
313
- "api",
314
- "graphql",
315
- "-f",
316
- `query=${ISSUE_FIELDS_QUERY}`,
317
- "-F",
318
- `owner=${owner}`,
319
- "-F",
320
- `name=${name}`,
321
- "-F",
322
- `number=${context.issueNumber}`
323
- ], context.taskDir));
324
- if (issueFieldsResult.ok) {
325
- issueFields = normalizeIssueFields(issueFieldsResult.value);
326
- }
265
+ if (context.config.verify_issue_fields && issueSnapshot?.issueType) {
266
+ const fieldKinds = new Map(issueSnapshot.issueType.fields.map((field) => [field.name, field.kind]));
267
+ issueFields = {
268
+ pinnedNames: new Set(issueSnapshot.issueType.fields.map((field) => field.name)),
269
+ values: new Map(Object.entries(issueSnapshot.fields).map(([name, value]) => [name, { kind: fieldKinds.get(name) || (typeof value === "number" ? "number" : "single-select"), value }]))
270
+ };
327
271
  }
328
- let prLabels = null;
329
- let prMilestone;
330
- let prAssignees;
331
- let prHeadSha;
332
- if (((context.config.verify_in_labels_match_pr && context.hasTriage)
333
- || (context.config.verify_pr_type_label && context.hasTriage)
334
- || (context.config.verify_milestone && context.hasTriage)
335
- || (context.config.verify_pr_assignee && context.hasPush)
336
- || context.config.verify_pr_comment_last_commit_matches_head) && context.prNumber) {
337
- const prResult = inspectGitHubPullRequest(githubClient, context.upstreamRepo, context.prNumber, context.taskDir);
338
- if (!prResult.ok) {
272
+ let prComments = null;
273
+ if (context.prMarker && context.prIdentity && shouldFetchComments(context.config) && provider?.comments?.list) {
274
+ const listed = await provider.comments.list({ context: operationContext, parent: context.prIdentity });
275
+ if (!listed.ok)
339
276
  return {
340
- earlyReturn: prResult.error.retryable
341
- ? blockedResult(CHECK_TYPE, prResult.error.message, "network_error")
342
- : failResult(CHECK_TYPE, prResult.error.message, "check_failed")
277
+ earlyReturn: listed.error.retryable
278
+ ? blockedResult(CHECK_TYPE, listed.error.message, "network_error")
279
+ : failResult(CHECK_TYPE, listed.error.message, "check_failed")
343
280
  };
344
- }
345
- prLabels = (context.config.verify_in_labels_match_pr || context.config.verify_pr_type_label)
346
- ? prResult.value.labels
347
- : null;
348
- prMilestone = context.config.verify_milestone
349
- ? prResult.value.milestone ? { title: prResult.value.milestone } : null
350
- : undefined;
351
- prAssignees = context.config.verify_pr_assignee
352
- ? prResult.value.assignees
353
- : undefined;
354
- prHeadSha = prResult.value.head.sha;
281
+ prComments = listed.value.map((comment) => ({ id: comment.id, body: comment.body }));
355
282
  }
283
+ const changeRequest = facts.value.changeRequest;
356
284
  return {
357
285
  issue,
358
- comments,
286
+ comments: facts.value.comments.map((comment) => ({ id: comment.id, body: comment.body })),
359
287
  prComments,
360
- prLabels,
361
- issueType,
288
+ prLabels: changeRequest?.labels || null,
289
+ issueType: issueSnapshot
290
+ ? (issueSnapshot.issueType ? issueSnapshot.issueType.name : null)
291
+ : undefined,
362
292
  issueFields,
363
- prMilestone,
364
- prAssignees,
365
- prHeadSha
293
+ prMilestone: changeRequest
294
+ ? (changeRequest.milestone ? { title: changeRequest.milestone } : null)
295
+ : undefined,
296
+ prAssignees: changeRequest?.assignees,
297
+ prHeadSha: changeRequest?.headSha
366
298
  };
367
299
  }
368
300
  function mapTaskTypeToLabel(taskType) {
@@ -377,12 +309,6 @@ function shouldFetchComments(config) {
377
309
  || config.verify_comment_content
378
310
  || config.verify_task_comment_content);
379
311
  }
380
- function flattenComments(value) {
381
- if (!Array.isArray(value)) {
382
- return [];
383
- }
384
- return value.flatMap((page) => Array.isArray(page) ? page : []);
385
- }
386
312
  function checkStatusLabel(context, remoteData) {
387
313
  if (!context.expectedStatusLabel || !context.hasTriage) {
388
314
  return null;
@@ -541,7 +467,7 @@ function checkInLabelsComputed(context, remoteData) {
541
467
  if (!context.config.verify_in_labels_computed || !context.hasTriage) {
542
468
  return null;
543
469
  }
544
- const expectedInLabels = computeExpectedInLabels(context.taskDir);
470
+ const expectedInLabels = computeExpectedInLabels(context.taskDir, context.upstreamRepo);
545
471
  if (!expectedInLabels.ok) {
546
472
  return expectedInLabels.type === "check_failed"
547
473
  ? failResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type)
@@ -781,7 +707,6 @@ function mapTaskTypeToIssueType(taskType) {
781
707
  };
782
708
  return mapping[taskType] || "Task";
783
709
  }
784
- const ISSUE_FIELDS_QUERY = `query($owner:String!,$name:String!,$number:Int!){repository(owner:$owner,name:$name){issue(number:$number){issueType{name pinnedFields{__typename ... on IssueFieldSingleSelect{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{name}}} ... on IssueFieldDateValue{value field{... on IssueFieldDate{name}}} ... on IssueFieldTextValue{value field{... on IssueFieldText{name}}} ... on IssueFieldNumberValue{value field{... on IssueFieldNumber{name}}}}}}}}`;
785
710
  function normalizeIssueFields(payload) {
786
711
  const issue = payload?.data?.repository?.issue;
787
712
  const pinnedFields = Array.isArray(issue?.issueType?.pinnedFields)
@@ -842,10 +767,18 @@ function arraysEqual(left, right) {
842
767
  function formatLabelList(labels) {
843
768
  return labels.length > 0 ? labels.join(", ") : "none";
844
769
  }
845
- function computeExpectedInLabels(taskDir) {
846
- const changedFilesResult = gitText(["diff", "main...HEAD", "--name-only"], taskDir);
770
+ function computeExpectedInLabels(taskDir, repository) {
771
+ const task = loadTask(taskDir);
772
+ if (!task.ok) {
773
+ return task;
774
+ }
775
+ const baseRef = String(task.metadata?.delivery_base_ref || "").trim();
776
+ if (!baseRef) {
777
+ return { ok: false, type: "check_failed", message: "Task has no delivery_base_ref for in-label evidence" };
778
+ }
779
+ const changedFilesResult = gitText(["diff", `${baseRef}...HEAD`, "--name-only"], taskDir);
847
780
  if (!changedFilesResult.ok) {
848
- return changedFilesResult;
781
+ return { ...changedFilesResult, type: "network_error" };
849
782
  }
850
783
  const changedFiles = String(changedFilesResult.value || "")
851
784
  .split(/\r?\n/)
@@ -855,45 +788,30 @@ function computeExpectedInLabels(taskDir) {
855
788
  if (!mapping.ok) {
856
789
  return mapping;
857
790
  }
858
- if (Object.keys(mapping.value ?? {}).length > 0) {
859
- const labels = new Set();
860
- for (const file of changedFiles) {
861
- for (const [label, prefixes] of Object.entries(mapping.value ?? {})) {
862
- if (prefixes.some((prefix) => file.startsWith(prefix))) {
863
- labels.add(`in: ${label}`);
864
- }
865
- }
866
- }
867
- return { ok: true, labels: Array.from(labels).sort(), mode: "mapped" };
868
- }
869
- const repoLabelsResult = withRetry(() => ghJson([
870
- "label",
871
- "list",
872
- "--limit",
873
- "200",
874
- "--json",
875
- "name"
876
- ], taskDir));
791
+ if (Object.keys(mapping.value ?? {}).length === 0) {
792
+ return { ok: true, labels: [], mode: "mapped" };
793
+ }
794
+ const client = createGitHubClient();
795
+ const repoLabelsResult = withRetry(() => client.json([
796
+ "api", "--paginate", "--slurp", `repos/${repository}/labels?per_page=100`
797
+ ], { cwd: taskDir }));
877
798
  if (!repoLabelsResult.ok) {
878
799
  return repoLabelsResult;
879
800
  }
880
- const repoInLabels = new Set(extractLabelNames(repoLabelsResult.value)
881
- .filter((label) => label.startsWith("in:")));
882
- if (repoInLabels.size === 0) {
883
- return { ok: true, labels: [], mode: "fallback" };
801
+ const repositoryLabels = validateRepositoryLabelPayload(repoLabelsResult.value);
802
+ if (!repositoryLabels.ok) {
803
+ return { ok: false, type: "check_failed", message: repositoryLabels.error.message };
884
804
  }
885
- const labels = new Set();
886
- for (const file of changedFiles) {
887
- const topLevel = file.split("/")[0];
888
- if (!topLevel) {
889
- continue;
890
- }
891
- const candidate = `in: ${topLevel}`;
892
- if (repoInLabels.has(candidate)) {
893
- labels.add(candidate);
894
- }
805
+ const planned = planInLabelUpdate({
806
+ changedFiles,
807
+ currentLabels: [],
808
+ mapping: mapping.value ?? {},
809
+ repositoryLabels: new Set(repositoryLabels.value)
810
+ });
811
+ if (planned.error) {
812
+ return { ok: false, type: "check_failed", message: planned.error.message };
895
813
  }
896
- return { ok: true, labels: Array.from(labels).sort(), mode: "fallback" };
814
+ return { ok: true, labels: planned.target, mode: "mapped" };
897
815
  }
898
816
  function loadInLabelMapping() {
899
817
  const configPath = path.join(repoRoot, ".agents", ".airc.json");
@@ -902,60 +820,16 @@ function loadInLabelMapping() {
902
820
  }
903
821
  try {
904
822
  const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
905
- const mapping = config?.labels?.in;
906
- if (!mapping || typeof mapping !== "object" || Array.isArray(mapping)) {
907
- return { ok: true, value: {} };
908
- }
909
- const normalized = {};
910
- for (const [label, prefixes] of Object.entries(mapping)) {
911
- if (!Array.isArray(prefixes)) {
912
- continue;
913
- }
914
- const cleaned = prefixes
915
- .map((value) => String(value || "").trim())
916
- .filter(Boolean);
917
- if (cleaned.length > 0) {
918
- normalized[label] = cleaned;
919
- }
920
- }
921
- return { ok: true, value: normalized };
823
+ const mapping = validateInLabelMapping(config?.labels?.in);
824
+ return mapping.ok
825
+ ? { ok: true, value: mapping.value }
826
+ : { ok: false, type: "check_failed", message: mapping.error.message };
922
827
  }
923
828
  catch (error) {
924
829
  return { ok: false, type: "check_failed", message: `Unable to parse .agents/.airc.json: ${error.message}` };
925
830
  }
926
831
  }
927
832
  // === GitHub API ===
928
- function detectRepoOwnerType(upstreamRepo, taskDir) {
929
- const ownerTypeResult = withRetry(() => ghText([
930
- "api",
931
- `repos/${upstreamRepo}`,
932
- "--jq",
933
- ".owner.type // empty"
934
- ], taskDir));
935
- if (!ownerTypeResult.ok) {
936
- return "unknown";
937
- }
938
- return ownerTypeResult.value || "unknown";
939
- }
940
- function ghJson(args, cwd) {
941
- return mapClientResult(githubClient.json(args, { cwd }));
942
- }
943
- function ghText(args, cwd) {
944
- return mapClientResult(githubClient.text(args, { cwd }));
945
- }
946
- function ghPaginatedJson(args, cwd) {
947
- return ghJson(args, cwd);
948
- }
949
- function mapClientResult(result) {
950
- if (result.ok)
951
- return result;
952
- const checkFailed = ["RESOURCE_NOT_FOUND", "PERMISSION_DENIED", "PLATFORM_REQUEST_INVALID"].includes(result.error.code);
953
- return {
954
- ok: false,
955
- type: checkFailed ? "check_failed" : "network_error",
956
- message: result.error.message
957
- };
958
- }
959
833
  function gitText(args, cwd) {
960
834
  try {
961
835
  const value = execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
@@ -2,6 +2,8 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { parseTypedTaskFrontmatter } from '../task/frontmatter.js';
4
4
  import { readPrDeliveryFact } from '../task/pr-delivery-fact.js';
5
+ import { resourceIdentityEquals, resourceIdentityNumber } from '../platform/resource-identity.js';
6
+ import { taskIssueIdentity } from '../platform/task-identities.js';
5
7
  export class HostResolutionError extends Error {
6
8
  code;
7
9
  constructor(code, message) {
@@ -70,14 +72,23 @@ export function collectHostCandidates(input) {
70
72
  catch {
71
73
  continue;
72
74
  }
73
- const issueNumber = toPositiveNumber(frontmatter.issue_number);
75
+ const issueIdentity = taskIssueIdentity(frontmatter);
76
+ const issueNumber = resourceIdentityNumber(issueIdentity) ?? toPositiveNumber(frontmatter.issue_number);
74
77
  const fact = readPrDeliveryFact(frontmatter);
75
- const prNumber = fact.status === 'valid' && fact.fact.state === 'bound' ? fact.fact.identity.number : null;
76
- if (prNumber === input.prNumber) {
77
- candidates.push({ taskId: entry.name, taskDir, issueNumber, prNumber });
78
+ const prIdentity = fact.status === 'valid' && fact.fact.state === 'bound' ? fact.fact.identity.resource : null;
79
+ const prNumber = resourceIdentityNumber(prIdentity);
80
+ const matchesPr = input.prIdentity
81
+ ? Boolean(prIdentity && resourceIdentityEquals(prIdentity, input.prIdentity))
82
+ : input.prNumber !== undefined && prNumber === input.prNumber;
83
+ const identityDetails = {
84
+ ...(issueIdentity && issueIdentity.kind !== 'number' ? { issueIdentity } : {}),
85
+ ...(prIdentity && prIdentity.kind !== 'number' ? { prIdentity } : {})
86
+ };
87
+ if (matchesPr) {
88
+ candidates.push({ taskId: entry.name, taskDir, issueNumber, prNumber, ...identityDetails });
78
89
  }
79
90
  else if (issueNumber !== null && input.closingIssues.includes(issueNumber)) {
80
- candidates.push({ taskId: entry.name, taskDir, issueNumber, prNumber });
91
+ candidates.push({ taskId: entry.name, taskDir, issueNumber, prNumber, ...identityDetails });
81
92
  }
82
93
  }
83
94
  return candidates;
@@ -97,7 +108,9 @@ export function resolveHostFromCandidates(candidates) {
97
108
  taskId: candidate.taskId,
98
109
  taskDir: candidate.taskDir,
99
110
  issueNumber: candidate.issueNumber ?? 0,
100
- prNumber: candidate.prNumber ?? 0
111
+ prNumber: candidate.prNumber ?? 0,
112
+ ...(candidate.issueIdentity ? { issueIdentity: candidate.issueIdentity } : {}),
113
+ ...(candidate.prIdentity ? { prIdentity: candidate.prIdentity } : {})
101
114
  };
102
115
  }
103
116
  return {