@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
@@ -3,14 +3,16 @@ import path from "node:path";
3
3
  import process from "node:process";
4
4
  import { execFileSync } from "node:child_process";
5
5
 
6
- import { createGitHubClient } from "./github-client.ts";
7
- import { resolvePlatformContext } from "./context.ts";
6
+ import { resolvePlatformProviderContext } from "./context.ts";
8
7
  import { hasCheckedRequirement, resolveRequirementSection } from "./issue-metadata.ts";
9
- import { inspectGitHubIssueMetadata, requirementSectionAnchors } from "./issues.ts";
10
- import { listRemoteComments } from "./issue-comments.ts";
8
+ import { requirementSectionAnchors } from "./issues.ts";
11
9
  import { taskTypeLabel } from "./metadata-labels.ts";
10
+ import { planInLabelUpdate, validateInLabelMapping, validateRepositoryLabelPayload } from "./in-label-sync.ts";
12
11
  import { inspectGitHubPullRequest } from "./pull-requests.ts";
12
+ import { createGitHubClient } from "./github-client.ts";
13
13
  import { readPrDeliveryFact } from "../task/pr-delivery-fact.ts";
14
+ import { providerError, providerOperationContext, resourceIdentityNumber, unsupportedProviderOperation } from "./provider-bridge.ts";
15
+ import { taskIssueIdentity } from "./task-identities.ts";
14
16
 
15
17
  const CHECK_TYPE = "platform-sync";
16
18
  const VERSION_LINE_REGEX = /^[0-9]+\.[0-9]+\.x$/;
@@ -29,7 +31,6 @@ const OPTION_LOCALIZATION: Record<string, string> = {
29
31
 
30
32
  let activeShared: any = null;
31
33
  let repoRoot = "";
32
- const githubClient = createGitHubClient();
33
34
 
34
35
  export function getDefaults(): any {
35
36
  return {
@@ -95,23 +96,15 @@ function safeStat(...args: any[]): any {
95
96
  return getShared().safeStat(...args);
96
97
  }
97
98
 
98
- function parseIssueNumber(...args: any[]): any {
99
- return getShared().parseIssueNumber(...args);
100
- }
101
-
102
- function parsePrNumber(...args: any[]): any {
103
- return getShared().parsePrNumber(...args);
104
- }
105
-
106
- export function check({ taskDir, config, artifactFile }: any, shared: any): any {
99
+ export async function check({ taskDir, config, artifactFile }: any, shared: any): Promise<any> {
107
100
  activeShared = shared;
108
101
  repoRoot = shared.repoRoot;
109
- const context = buildSyncContext({ taskDir, config, artifactFile });
102
+ const context = await buildSyncContext({ taskDir, config, artifactFile });
110
103
  if (context.earlyReturn) {
111
104
  return context.earlyReturn;
112
105
  }
113
106
 
114
- const remoteData = fetchRemoteData(context);
107
+ const remoteData = await fetchRemoteData(context);
115
108
  if (remoteData.earlyReturn) {
116
109
  return remoteData.earlyReturn;
117
110
  }
@@ -142,33 +135,36 @@ export function check({ taskDir, config, artifactFile }: any, shared: any): any
142
135
  }
143
136
  }
144
137
 
145
- return passResult(CHECK_TYPE, `GitHub sync checks passed for Issue #${context.issueNumber}`);
138
+ return passResult(CHECK_TYPE, `Platform sync checks passed for Issue ${context.issueNumber || "identity"}`);
146
139
  }
147
140
 
148
- function buildSyncContext({ taskDir, config, artifactFile }: any): any {
141
+ async function buildSyncContext({ taskDir, config, artifactFile }: any): Promise<any> {
149
142
  const task = loadTask(taskDir);
150
143
  if (!task.ok) {
151
144
  return { earlyReturn: failResult(CHECK_TYPE, task.message) };
152
145
  }
153
146
 
154
- const issueNumber = parseIssueNumber(task.metadata.issue_number);
147
+ const issueIdentity = taskIssueIdentity(task.metadata);
148
+ const issueNumber = resourceIdentityNumber(issueIdentity);
155
149
  const fact = readPrDeliveryFact(task.metadata);
156
150
  if (fact.status === "invalid") {
157
151
  return { earlyReturn: failResult(CHECK_TYPE, fact.error.message, "check_failed") };
158
152
  }
159
- const prNumber = fact.status === "valid" && fact.fact.state === "bound" ? fact.fact.identity.number : null;
160
- if (config.when === "issue_number_exists" && !issueNumber) {
153
+ const prIdentity = fact.status === "valid" && fact.fact.state === "bound" ? fact.fact.identity.resource : null;
154
+ const prNumber = resourceIdentityNumber(prIdentity);
155
+ if (config.when === "issue_number_exists" && !issueIdentity) {
161
156
  return { earlyReturn: passResult(CHECK_TYPE, "Skipped: task has no issue_number") };
162
157
  }
163
- if (config.when === "pr_fact_bound" && !prNumber) {
158
+ if (config.when === "pr_fact_bound" && !prIdentity) {
164
159
  return { earlyReturn: passResult(CHECK_TYPE, "Skipped: task has no verified bound pull request") };
165
160
  }
166
161
 
167
- if (!issueNumber) {
162
+ if (!issueIdentity) {
168
163
  return { earlyReturn: passResult(CHECK_TYPE, "Skipped: platform-sync not required for this task") };
169
164
  }
170
165
 
171
- const platformContext = resolvePlatformContext({ cwd: repoRoot, client: githubClient });
166
+ const loaded = await resolvePlatformProviderContext({ cwd: repoRoot });
167
+ const platformContext = loaded.ok ? loaded.value.context : loaded.context;
172
168
  if (platformContext.status === "failed") {
173
169
  return { earlyReturn: failResult(CHECK_TYPE, platformContext.error?.message || "Platform context failed", "check_failed") };
174
170
  }
@@ -181,7 +177,6 @@ function buildSyncContext({ taskDir, config, artifactFile }: any): any {
181
177
  }
182
178
  return { earlyReturn: passResult(CHECK_TYPE, `Skipped: ${platformContext.error?.message || "platform unavailable"}`) };
183
179
  }
184
- const repoOwnerType = detectRepoOwnerType(platformContext.platform.repository, taskDir);
185
180
  const expectedValues = resolveExpectedValues(config);
186
181
  if (!expectedValues.ok) {
187
182
  return { earlyReturn: failResult(CHECK_TYPE, expectedValues.message, "check_failed") };
@@ -201,15 +196,20 @@ function buildSyncContext({ taskDir, config, artifactFile }: any): any {
201
196
  config,
202
197
  artifactFile,
203
198
  artifactPath,
199
+ issueIdentity,
200
+ prIdentity,
204
201
  issueNumber,
205
202
  prNumber,
206
203
  upstreamRepo: platformContext.platform.repository,
207
- repoOwnerType,
204
+ repoOwnerType: String(loaded.ok ? loaded.value.snapshot.metadata?.ownerType || "unknown" : "unknown"),
208
205
  hasTriage: platformContext.capabilities.triage,
209
206
  hasPush: platformContext.capabilities.push,
210
207
  expectedStatusLabel: expectedValues.statusLabel,
211
208
  marker,
212
- prMarker
209
+ prMarker,
210
+ provider: loaded.ok ? loaded.value.provider : null,
211
+ providerType: loaded.ok ? loaded.value.providerType : null,
212
+ loadedContext: loaded.ok ? loaded.value : null
213
213
  };
214
214
  }
215
215
 
@@ -266,170 +266,68 @@ function resolveDefaultValue({ collection, key, value, configKey }: any): any {
266
266
  return { ok: true, value: resolvedValue };
267
267
  }
268
268
 
269
- function fetchRemoteData(context: any): any {
270
- const sharedIssue = inspectGitHubIssueMetadata(
271
- githubClient,
272
- context.upstreamRepo,
273
- context.issueNumber,
274
- context.taskDir
275
- );
276
- const issueResult = sharedIssue.ok
277
- ? {
278
- ok: true,
279
- value: {
280
- state: sharedIssue.value.state.toUpperCase(),
281
- labels: sharedIssue.value.labels.map((name) => ({ name })),
282
- body: sharedIssue.value.body,
283
- milestone: sharedIssue.value.milestone ? { title: sharedIssue.value.milestone } : null
284
- }
285
- }
286
- : {
287
- ok: false,
288
- type: sharedIssue.error.retryable ? "network_error" : "check_failed",
289
- message: sharedIssue.error.message
290
- };
291
- if (!issueResult.ok) {
269
+ async function fetchRemoteData(context: any): Promise<any> {
270
+ const provider = context.provider;
271
+ const operationContext = providerOperationContext(context.loadedContext, context.taskDir);
272
+ const facts = provider?.verification?.fetchRemoteFacts
273
+ ? await provider.verification.fetchRemoteFacts({
274
+ context: operationContext,
275
+ taskId: context.task?.metadata?.id || context.task?.id || "",
276
+ ...(context.issueIdentity ? { issue: context.issueIdentity } : {}),
277
+ ...(context.prIdentity ? { changeRequest: context.prIdentity } : {}),
278
+ includeComments: shouldFetchComments(context.config),
279
+ includeFields: Boolean(context.config.verify_issue_fields)
280
+ })
281
+ : unsupportedProviderOperation(provider, "verification.fetchRemoteFacts");
282
+ if (!facts.ok) {
292
283
  return {
293
- earlyReturn: issueResult.type === "check_failed"
294
- ? failResult(CHECK_TYPE, issueResult.message, issueResult.type)
295
- : blockedResult(CHECK_TYPE, issueResult.message, issueResult.type)
284
+ earlyReturn: facts.error.retryable
285
+ ? blockedResult(CHECK_TYPE, providerError(facts.error, "PLATFORM_PROVIDER_OPERATION_FAILED").message, "network_error")
286
+ : failResult(CHECK_TYPE, providerError(facts.error, "PLATFORM_PROVIDER_OPERATION_FAILED").message, "check_failed")
296
287
  };
297
288
  }
298
-
299
- const issue = issueResult.value;
300
- if (context.config.issue_must_exist !== false && !issue) {
301
- return {
302
- earlyReturn: failResult(CHECK_TYPE, `Issue #${context.issueNumber} not found`, "check_failed")
303
- };
304
- }
305
-
306
- let comments = null;
307
- if (shouldFetchComments(context.config)) {
308
- const commentsResult = withRetry(() => ghPaginatedJson([
309
- "api",
310
- "--paginate",
311
- "--slurp",
312
- `repos/${context.upstreamRepo}/issues/${context.issueNumber}/comments?per_page=100`
313
- ], context.taskDir));
314
-
315
- if (!commentsResult.ok) {
316
- return {
317
- earlyReturn: commentsResult.type === "check_failed"
318
- ? failResult(CHECK_TYPE, commentsResult.message, commentsResult.type)
319
- : blockedResult(CHECK_TYPE, commentsResult.message, commentsResult.type)
320
- };
289
+ const issueSnapshot = facts.value.issue;
290
+ const issue = issueSnapshot
291
+ ? {
292
+ state: issueSnapshot.state.toUpperCase(),
293
+ labels: issueSnapshot.labels.map((name: string) => ({ name })),
294
+ body: issueSnapshot.body,
295
+ milestone: issueSnapshot.milestone ? { title: issueSnapshot.milestone } : null
321
296
  }
322
-
323
- comments = flattenComments(commentsResult.value);
297
+ : null;
298
+ let issueFields: any;
299
+ if (context.config.verify_issue_fields && issueSnapshot?.issueType) {
300
+ const fieldKinds = new Map(issueSnapshot.issueType.fields.map((field: { name: string; kind: string }) => [field.name, field.kind]));
301
+ issueFields = {
302
+ pinnedNames: new Set(issueSnapshot.issueType.fields.map((field: { name: string }) => field.name)),
303
+ values: new Map(Object.entries(issueSnapshot.fields).map(([name, value]) => [name, { kind: fieldKinds.get(name) || (typeof value === "number" ? "number" : "single-select"), value }]))
304
+ };
324
305
  }
325
-
326
306
  let prComments = null;
327
- if (context.prMarker) {
328
- if (!context.prNumber) {
329
- return {
330
- earlyReturn: failResult(CHECK_TYPE, "Expected a verified bound pull request for PR comment verification", "check_failed")
331
- };
332
- }
333
-
334
- const prCommentsResult = listRemoteComments(
335
- githubClient,
336
- context.upstreamRepo,
337
- context.prNumber,
338
- context.taskDir
339
- );
340
-
341
- if (!prCommentsResult.ok) {
342
- return {
343
- earlyReturn: prCommentsResult.error.retryable
344
- ? blockedResult(CHECK_TYPE, prCommentsResult.error.message, "network_error")
345
- : failResult(CHECK_TYPE, prCommentsResult.error.message, "check_failed")
346
- };
347
- }
348
-
349
- prComments = prCommentsResult.value;
350
- }
351
-
352
- let issueType;
353
- if (context.config.verify_issue_type && context.hasPush) {
354
- const issueTypeResult = withRetry(() => ghText([
355
- "api",
356
- `repos/${context.upstreamRepo}/issues/${context.issueNumber}`,
357
- "--jq",
358
- ".type.name // empty"
359
- ], context.taskDir));
360
-
361
- if (issueTypeResult.ok) {
362
- issueType = issueTypeResult.value || null;
363
- }
364
- }
365
-
366
- let issueFields;
367
- if (context.config.verify_issue_fields && context.hasPush) {
368
- const [owner, name] = context.upstreamRepo.split("/");
369
- const issueFieldsResult = withRetry(() => ghJson([
370
- "api",
371
- "graphql",
372
- "-f",
373
- `query=${ISSUE_FIELDS_QUERY}`,
374
- "-F",
375
- `owner=${owner}`,
376
- "-F",
377
- `name=${name}`,
378
- "-F",
379
- `number=${context.issueNumber}`
380
- ], context.taskDir));
381
-
382
- if (issueFieldsResult.ok) {
383
- issueFields = normalizeIssueFields(issueFieldsResult.value);
384
- }
385
- }
386
-
387
- let prLabels = null;
388
- let prMilestone;
389
- let prAssignees;
390
- let prHeadSha;
391
- if (((context.config.verify_in_labels_match_pr && context.hasTriage)
392
- || (context.config.verify_pr_type_label && context.hasTriage)
393
- || (context.config.verify_milestone && context.hasTriage)
394
- || (context.config.verify_pr_assignee && context.hasPush)
395
- || context.config.verify_pr_comment_last_commit_matches_head) && context.prNumber) {
396
- const prResult = inspectGitHubPullRequest(
397
- githubClient,
398
- context.upstreamRepo,
399
- context.prNumber,
400
- context.taskDir
401
- );
402
-
403
- if (!prResult.ok) {
404
- return {
405
- earlyReturn: prResult.error.retryable
406
- ? blockedResult(CHECK_TYPE, prResult.error.message, "network_error")
407
- : failResult(CHECK_TYPE, prResult.error.message, "check_failed")
408
- };
409
- }
410
-
411
- prLabels = (context.config.verify_in_labels_match_pr || context.config.verify_pr_type_label)
412
- ? prResult.value.labels
413
- : null;
414
- prMilestone = context.config.verify_milestone
415
- ? prResult.value.milestone ? { title: prResult.value.milestone } : null
416
- : undefined;
417
- prAssignees = context.config.verify_pr_assignee
418
- ? prResult.value.assignees
419
- : undefined;
420
- prHeadSha = prResult.value.head.sha;
307
+ if (context.prMarker && context.prIdentity && shouldFetchComments(context.config) && provider?.comments?.list) {
308
+ const listed = await provider.comments.list({ context: operationContext, parent: context.prIdentity });
309
+ if (!listed.ok) return {
310
+ earlyReturn: listed.error.retryable
311
+ ? blockedResult(CHECK_TYPE, listed.error.message, "network_error")
312
+ : failResult(CHECK_TYPE, listed.error.message, "check_failed")
313
+ };
314
+ prComments = listed.value.map((comment: { id: string; body: string }) => ({ id: comment.id, body: comment.body }));
421
315
  }
422
-
316
+ const changeRequest = facts.value.changeRequest;
423
317
  return {
424
318
  issue,
425
- comments,
319
+ comments: facts.value.comments.map((comment: { id: string; body: string }) => ({ id: comment.id, body: comment.body })),
426
320
  prComments,
427
- prLabels,
428
- issueType,
321
+ prLabels: changeRequest?.labels || null,
322
+ issueType: issueSnapshot
323
+ ? (issueSnapshot.issueType ? issueSnapshot.issueType.name : null)
324
+ : undefined,
429
325
  issueFields,
430
- prMilestone,
431
- prAssignees,
432
- prHeadSha
326
+ prMilestone: changeRequest
327
+ ? (changeRequest.milestone ? { title: changeRequest.milestone } : null)
328
+ : undefined,
329
+ prAssignees: changeRequest?.assignees,
330
+ prHeadSha: changeRequest?.headSha
433
331
  };
434
332
  }
435
333
 
@@ -449,14 +347,6 @@ function shouldFetchComments(config: any): any {
449
347
  );
450
348
  }
451
349
 
452
- function flattenComments(value: any): any {
453
- if (!Array.isArray(value)) {
454
- return [];
455
- }
456
-
457
- return value.flatMap((page) => Array.isArray(page) ? page : []);
458
- }
459
-
460
350
  function checkStatusLabel(context: any, remoteData: any): any {
461
351
  if (!context.expectedStatusLabel || !context.hasTriage) {
462
352
  return null;
@@ -715,7 +605,7 @@ function checkInLabelsComputed(context: any, remoteData: any): any {
715
605
  return null;
716
606
  }
717
607
 
718
- const expectedInLabels = computeExpectedInLabels(context.taskDir);
608
+ const expectedInLabels = computeExpectedInLabels(context.taskDir, context.upstreamRepo);
719
609
  if (!expectedInLabels.ok) {
720
610
  return expectedInLabels.type === "check_failed"
721
611
  ? failResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type)
@@ -1052,8 +942,6 @@ function mapTaskTypeToIssueType(taskType: any): any {
1052
942
  return mapping[taskType] || "Task";
1053
943
  }
1054
944
 
1055
- 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}}}}}}}}`;
1056
-
1057
945
  function normalizeIssueFields(payload: any): any {
1058
946
  const issue = payload?.data?.repository?.issue;
1059
947
  const pinnedFields = Array.isArray(issue?.issueType?.pinnedFields)
@@ -1127,10 +1015,18 @@ function formatLabelList(labels: any): any {
1127
1015
  return labels.length > 0 ? labels.join(", ") : "none";
1128
1016
  }
1129
1017
 
1130
- function computeExpectedInLabels(taskDir: any): any {
1131
- const changedFilesResult = gitText(["diff", "main...HEAD", "--name-only"], taskDir);
1018
+ function computeExpectedInLabels(taskDir: any, repository: any): any {
1019
+ const task = loadTask(taskDir);
1020
+ if (!task.ok) {
1021
+ return task;
1022
+ }
1023
+ const baseRef = String(task.metadata?.delivery_base_ref || "").trim();
1024
+ if (!baseRef) {
1025
+ return { ok: false, type: "check_failed", message: "Task has no delivery_base_ref for in-label evidence" };
1026
+ }
1027
+ const changedFilesResult = gitText(["diff", `${baseRef}...HEAD`, "--name-only"], taskDir);
1132
1028
  if (!changedFilesResult.ok) {
1133
- return changedFilesResult;
1029
+ return { ...changedFilesResult, type: "network_error" };
1134
1030
  }
1135
1031
 
1136
1032
  const changedFiles = String(changedFilesResult.value || "")
@@ -1143,55 +1039,32 @@ function computeExpectedInLabels(taskDir: any): any {
1143
1039
  return mapping;
1144
1040
  }
1145
1041
 
1146
- if (Object.keys(mapping.value ?? {}).length > 0) {
1147
- const labels = new Set();
1148
-
1149
- for (const file of changedFiles) {
1150
- for (const [label, prefixes] of Object.entries(mapping.value ?? {}) as Array<[string, string[]]>) {
1151
- if (prefixes.some((prefix: any) => file.startsWith(prefix))) {
1152
- labels.add(`in: ${label}`);
1153
- }
1154
- }
1155
- }
1156
-
1157
- return { ok: true, labels: Array.from(labels).sort(), mode: "mapped" };
1042
+ if (Object.keys(mapping.value ?? {}).length === 0) {
1043
+ return { ok: true, labels: [], mode: "mapped" };
1158
1044
  }
1159
1045
 
1160
- const repoLabelsResult = withRetry(() => ghJson([
1161
- "label",
1162
- "list",
1163
- "--limit",
1164
- "200",
1165
- "--json",
1166
- "name"
1167
- ], taskDir));
1046
+ const client = createGitHubClient();
1047
+ const repoLabelsResult = withRetry(() => client.json([
1048
+ "api", "--paginate", "--slurp", `repos/${repository}/labels?per_page=100`
1049
+ ], { cwd: taskDir }));
1168
1050
  if (!repoLabelsResult.ok) {
1169
1051
  return repoLabelsResult;
1170
1052
  }
1171
1053
 
1172
- const repoInLabels = new Set(
1173
- extractLabelNames(repoLabelsResult.value)
1174
- .filter((label: any) => label.startsWith("in:"))
1175
- );
1176
-
1177
- if (repoInLabels.size === 0) {
1178
- return { ok: true, labels: [], mode: "fallback" };
1054
+ const repositoryLabels = validateRepositoryLabelPayload(repoLabelsResult.value);
1055
+ if (!repositoryLabels.ok) {
1056
+ return { ok: false, type: "check_failed", message: repositoryLabels.error.message };
1179
1057
  }
1180
-
1181
- const labels = new Set();
1182
- for (const file of changedFiles) {
1183
- const topLevel = file.split("/")[0];
1184
- if (!topLevel) {
1185
- continue;
1186
- }
1187
-
1188
- const candidate = `in: ${topLevel}`;
1189
- if (repoInLabels.has(candidate)) {
1190
- labels.add(candidate);
1191
- }
1058
+ const planned = planInLabelUpdate({
1059
+ changedFiles,
1060
+ currentLabels: [],
1061
+ mapping: mapping.value ?? {},
1062
+ repositoryLabels: new Set(repositoryLabels.value)
1063
+ });
1064
+ if (planned.error) {
1065
+ return { ok: false, type: "check_failed", message: planned.error.message };
1192
1066
  }
1193
-
1194
- return { ok: true, labels: Array.from(labels).sort(), mode: "fallback" };
1067
+ return { ok: true, labels: planned.target, mode: "mapped" };
1195
1068
  }
1196
1069
 
1197
1070
  function loadInLabelMapping(): any {
@@ -1202,26 +1075,10 @@ function loadInLabelMapping(): any {
1202
1075
 
1203
1076
  try {
1204
1077
  const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
1205
- const mapping = config?.labels?.in;
1206
- if (!mapping || typeof mapping !== "object" || Array.isArray(mapping)) {
1207
- return { ok: true, value: {} };
1208
- }
1209
-
1210
- const normalized: Record<string, string[]> = {};
1211
- for (const [label, prefixes] of Object.entries(mapping)) {
1212
- if (!Array.isArray(prefixes)) {
1213
- continue;
1214
- }
1215
-
1216
- const cleaned = prefixes
1217
- .map((value) => String(value || "").trim())
1218
- .filter(Boolean);
1219
- if (cleaned.length > 0) {
1220
- normalized[label] = cleaned;
1221
- }
1222
- }
1223
-
1224
- return { ok: true, value: normalized };
1078
+ const mapping = validateInLabelMapping(config?.labels?.in);
1079
+ return mapping.ok
1080
+ ? { ok: true, value: mapping.value }
1081
+ : { ok: false, type: "check_failed", message: mapping.error.message };
1225
1082
  } catch (error: any) {
1226
1083
  return { ok: false, type: "check_failed", message: `Unable to parse .agents/.airc.json: ${error.message}` };
1227
1084
  }
@@ -1229,43 +1086,6 @@ function loadInLabelMapping(): any {
1229
1086
 
1230
1087
  // === GitHub API ===
1231
1088
 
1232
- function detectRepoOwnerType(upstreamRepo: any, taskDir: any): any {
1233
- const ownerTypeResult = withRetry(() => ghText([
1234
- "api",
1235
- `repos/${upstreamRepo}`,
1236
- "--jq",
1237
- ".owner.type // empty"
1238
- ], taskDir));
1239
-
1240
- if (!ownerTypeResult.ok) {
1241
- return "unknown";
1242
- }
1243
-
1244
- return ownerTypeResult.value || "unknown";
1245
- }
1246
-
1247
- function ghJson(args: any, cwd: any): any {
1248
- return mapClientResult(githubClient.json(args, { cwd }));
1249
- }
1250
-
1251
- function ghText(args: any, cwd: any): any {
1252
- return mapClientResult(githubClient.text(args, { cwd }));
1253
- }
1254
-
1255
- function ghPaginatedJson(args: any, cwd: any): any {
1256
- return ghJson(args, cwd);
1257
- }
1258
-
1259
- function mapClientResult(result: any): any {
1260
- if (result.ok) return result;
1261
- const checkFailed = ["RESOURCE_NOT_FOUND", "PERMISSION_DENIED", "PLATFORM_REQUEST_INVALID"].includes(result.error.code);
1262
- return {
1263
- ok: false,
1264
- type: checkFailed ? "check_failed" : "network_error",
1265
- message: result.error.message
1266
- };
1267
- }
1268
-
1269
1089
  function gitText(args: any, cwd: any): any {
1270
1090
  try {
1271
1091
  const value = execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
@@ -3,6 +3,9 @@ import path from 'node:path';
3
3
 
4
4
  import { parseTypedTaskFrontmatter } from '../task/frontmatter.ts';
5
5
  import { readPrDeliveryFact } from '../task/pr-delivery-fact.ts';
6
+ import { resourceIdentityEquals, resourceIdentityNumber } from '../platform/resource-identity.ts';
7
+ import type { ResourceIdentity } from '../platform/resource-identity.ts';
8
+ import { taskIssueIdentity } from '../platform/task-identities.ts';
6
9
 
7
10
  // ---------------------------------------------------------------------------
8
11
  // Host resolution (PL-4): PR -> unique / ambiguous / none.
@@ -13,10 +16,12 @@ export type HostCandidate = {
13
16
  taskDir: string;
14
17
  issueNumber: number | null;
15
18
  prNumber: number | null;
19
+ issueIdentity?: ResourceIdentity;
20
+ prIdentity?: ResourceIdentity;
16
21
  };
17
22
 
18
23
  export type HostResolution =
19
- | { kind: 'unique'; taskId: string; taskDir: string; issueNumber: number; prNumber: number }
24
+ | { kind: 'unique'; taskId: string; taskDir: string; issueNumber: number; prNumber: number; issueIdentity?: ResourceIdentity; prIdentity?: ResourceIdentity }
20
25
  | { kind: 'ambiguous'; candidates: Array<{ taskId: string; issueNumber: number }> }
21
26
  | { kind: 'none' };
22
27
 
@@ -66,7 +71,8 @@ export function extractClosingIssueNumbers(body: string): number[] {
66
71
  * a verified fact identity hit takes priority over the issue-number reverse lookup.
67
72
  */
68
73
  export function collectHostCandidates(input: {
69
- prNumber: number;
74
+ prNumber?: number;
75
+ prIdentity?: ResourceIdentity;
70
76
  closingIssues: number[];
71
77
  workspaceRoot: string;
72
78
  }): HostCandidate[] {
@@ -90,13 +96,22 @@ export function collectHostCandidates(input: {
90
96
  }
91
97
  let frontmatter: ReturnType<typeof parseTypedTaskFrontmatter>;
92
98
  try { frontmatter = parseTypedTaskFrontmatter(content); } catch { continue; }
93
- const issueNumber = toPositiveNumber(frontmatter.issue_number);
99
+ const issueIdentity = taskIssueIdentity(frontmatter);
100
+ const issueNumber = resourceIdentityNumber(issueIdentity) ?? toPositiveNumber(frontmatter.issue_number);
94
101
  const fact = readPrDeliveryFact(frontmatter);
95
- const prNumber = fact.status === 'valid' && fact.fact.state === 'bound' ? fact.fact.identity.number : null;
96
- if (prNumber === input.prNumber) {
97
- candidates.push({ taskId: entry.name, taskDir, issueNumber, prNumber });
102
+ const prIdentity = fact.status === 'valid' && fact.fact.state === 'bound' ? fact.fact.identity.resource : null;
103
+ const prNumber = resourceIdentityNumber(prIdentity);
104
+ const matchesPr = input.prIdentity
105
+ ? Boolean(prIdentity && resourceIdentityEquals(prIdentity, input.prIdentity))
106
+ : input.prNumber !== undefined && prNumber === input.prNumber;
107
+ const identityDetails = {
108
+ ...(issueIdentity && issueIdentity.kind !== 'number' ? { issueIdentity } : {}),
109
+ ...(prIdentity && prIdentity.kind !== 'number' ? { prIdentity } : {})
110
+ };
111
+ if (matchesPr) {
112
+ candidates.push({ taskId: entry.name, taskDir, issueNumber, prNumber, ...identityDetails });
98
113
  } else if (issueNumber !== null && input.closingIssues.includes(issueNumber)) {
99
- candidates.push({ taskId: entry.name, taskDir, issueNumber, prNumber });
114
+ candidates.push({ taskId: entry.name, taskDir, issueNumber, prNumber, ...identityDetails });
100
115
  }
101
116
  }
102
117
  return candidates;
@@ -116,7 +131,9 @@ export function resolveHostFromCandidates(candidates: HostCandidate[]): HostReso
116
131
  taskId: candidate.taskId,
117
132
  taskDir: candidate.taskDir,
118
133
  issueNumber: candidate.issueNumber ?? 0,
119
- prNumber: candidate.prNumber ?? 0
134
+ prNumber: candidate.prNumber ?? 0,
135
+ ...(candidate.issueIdentity ? { issueIdentity: candidate.issueIdentity } : {}),
136
+ ...(candidate.prIdentity ? { prIdentity: candidate.prIdentity } : {})
120
137
  };
121
138
  }
122
139
  return {