@fitlab-ai/agent-infra 0.9.13 → 0.9.14

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 (280) hide show
  1. package/bin/cli.ts +26 -9
  2. package/dist/bin/cli.js +173 -157
  3. package/dist/bin/internal-cli.js +42 -21
  4. package/dist/lib/agent-client.js +5 -0
  5. package/dist/lib/internal/agent-client.js +5 -2
  6. package/dist/lib/internal/cli-route-inventory.js +152 -0
  7. package/dist/lib/internal/codex-lifecycle.js +9 -6
  8. package/dist/lib/internal/codex-sandbox-controller.js +8 -2
  9. package/dist/lib/internal/git-workflow.js +10 -6
  10. package/dist/lib/internal/platform-checks.js +16 -4
  11. package/dist/lib/internal/platform-comment.js +16 -4
  12. package/dist/lib/internal/platform-context.js +4 -1
  13. package/dist/lib/internal/platform-issue.js +12 -4
  14. package/dist/lib/internal/platform-metadata.js +88 -0
  15. package/dist/lib/internal/platform-pr-review.js +16 -3
  16. package/dist/lib/internal/platform-pr.js +24 -10
  17. package/dist/lib/internal/platform-release-notes.js +6 -3
  18. package/dist/lib/internal/platform-security.js +93 -0
  19. package/dist/lib/internal/pr-review-grade.js +14 -3
  20. package/dist/lib/internal/release-workflow.js +19 -5
  21. package/dist/lib/internal/sandbox-control.js +22 -5
  22. package/dist/lib/internal/task-activity.js +13 -4
  23. package/dist/lib/internal/task-artifact.js +186 -27
  24. package/dist/lib/internal/task-context.js +4 -1
  25. package/dist/lib/internal/task-create.js +48 -15
  26. package/dist/lib/internal/task-delivery.js +4 -1
  27. package/dist/lib/internal/task-event.js +4 -1
  28. package/dist/lib/internal/task-finalization.js +4 -1
  29. package/dist/lib/internal/task-invalidation.js +4 -1
  30. package/dist/lib/internal/task-ledger.js +12 -1
  31. package/dist/lib/internal/task-lifecycle.js +4 -1
  32. package/dist/lib/internal/task-operation-registry.js +421 -0
  33. package/dist/lib/internal/task-orchestration.js +12 -0
  34. package/dist/lib/internal/task-override.js +10 -3
  35. package/dist/lib/internal/task-qualification.js +90 -0
  36. package/dist/lib/internal/task-review.js +4 -1
  37. package/dist/lib/internal/task-short-id.js +11 -1
  38. package/dist/lib/internal/task-snapshot.js +4 -1
  39. package/dist/lib/internal/task-validate.js +7 -2
  40. package/dist/lib/internal/task-verify.js +5 -1
  41. package/dist/lib/internal/task-warning.js +8 -1
  42. package/dist/lib/platform/comment-safety.js +226 -0
  43. package/dist/lib/platform/github-provider.js +233 -6
  44. package/dist/lib/platform/issue-comments.js +146 -34
  45. package/dist/lib/platform/pr-change-report.js +6 -12
  46. package/dist/lib/platform/pr-summary.js +28 -7
  47. package/dist/lib/platform/provider-contract.d.ts +67 -1
  48. package/dist/lib/platform/provider-contract.js +6 -2
  49. package/dist/lib/platform/provider-validation.js +76 -3
  50. package/dist/lib/platform/pull-requests.js +115 -170
  51. package/dist/lib/platform/repository-metadata.js +377 -0
  52. package/dist/lib/platform/security-alerts.js +155 -0
  53. package/dist/lib/platform/verification-sync.js +33 -26
  54. package/dist/lib/process-data/index.js +7 -0
  55. package/dist/lib/run/index.js +5 -0
  56. package/dist/lib/sandbox/commands/control-status.js +22 -0
  57. package/dist/lib/sandbox/commands/create.js +32 -6
  58. package/dist/lib/sandbox/commands/ls.js +5 -0
  59. package/dist/lib/sandbox/commands/rm.js +827 -120
  60. package/dist/lib/sandbox/commands/show.js +16 -0
  61. package/dist/lib/sandbox/control/client.js +83 -9
  62. package/dist/lib/sandbox/control/completed-reentry.js +85 -0
  63. package/dist/lib/sandbox/control/container-identity.js +28 -31
  64. package/dist/lib/sandbox/control/executor.js +149 -14
  65. package/dist/lib/sandbox/control/lifecycle.js +621 -127
  66. package/dist/lib/sandbox/control/native-file-lock.js +177 -0
  67. package/dist/lib/sandbox/control/server.js +305 -35
  68. package/dist/lib/sandbox/control/state.js +5 -3
  69. package/dist/lib/sandbox/control/task-view.js +183 -0
  70. package/dist/lib/sandbox/engines/authority.js +289 -0
  71. package/dist/lib/sandbox/engines/docker-exact-discovery.js +97 -0
  72. package/dist/lib/sandbox/index.js +5 -0
  73. package/dist/lib/sandbox/recovery.js +21 -1
  74. package/dist/lib/sandbox/workspace-view.js +2 -1
  75. package/dist/lib/sandbox/worktree-safety.js +4 -1
  76. package/dist/lib/server/index.js +8 -0
  77. package/dist/lib/task/artifact-lifecycle.js +42 -13
  78. package/dist/lib/task/artifact-operations.js +520 -0
  79. package/dist/lib/task/artifact-repair-intent.js +59 -0
  80. package/dist/lib/task/artifact-schema.js +135 -0
  81. package/dist/lib/task/capabilities.js +72 -1
  82. package/dist/lib/task/control-authority.js +4 -0
  83. package/dist/lib/task/create-service.js +103 -4
  84. package/dist/lib/task/create.js +25 -0
  85. package/dist/lib/task/events.js +186 -67
  86. package/dist/lib/task/index.js +11 -3
  87. package/dist/lib/task/local-artifact-finalization.js +75 -150
  88. package/dist/lib/task/orchestration.js +192 -52
  89. package/dist/lib/task/qualification-audit.js +490 -0
  90. package/dist/lib/task/qualification-confirmation.js +109 -0
  91. package/dist/lib/task/qualification-intents.js +148 -0
  92. package/dist/lib/task/review-artifacts.js +22 -23
  93. package/dist/lib/task/review-finalization.js +121 -4
  94. package/dist/lib/task/sections.js +6 -4
  95. package/dist/lib/task/verification-config.js +11 -0
  96. package/dist/lib/task/verification-engine.js +54 -4
  97. package/dist/lifecycle-build-manifest.json +3 -3
  98. package/lib/agent-client.ts +9 -0
  99. package/lib/internal/agent-client.ts +4 -2
  100. package/lib/internal/cli-route-inventory.ts +140 -0
  101. package/lib/internal/codex-lifecycle.ts +8 -6
  102. package/lib/internal/codex-sandbox-controller.ts +7 -2
  103. package/lib/internal/git-workflow.ts +9 -6
  104. package/lib/internal/platform-checks.ts +12 -4
  105. package/lib/internal/platform-comment.ts +12 -4
  106. package/lib/internal/platform-context.ts +3 -1
  107. package/lib/internal/platform-issue.ts +11 -4
  108. package/lib/internal/platform-metadata.ts +70 -0
  109. package/lib/internal/platform-pr-review.ts +14 -3
  110. package/lib/internal/platform-pr.ts +23 -10
  111. package/lib/internal/platform-release-notes.ts +5 -3
  112. package/lib/internal/platform-security.ts +83 -0
  113. package/lib/internal/pr-review-grade.ts +10 -3
  114. package/lib/internal/release-workflow.ts +17 -5
  115. package/lib/internal/sandbox-control.ts +19 -5
  116. package/lib/internal/task-activity.ts +12 -4
  117. package/lib/internal/task-artifact.ts +164 -25
  118. package/lib/internal/task-context.ts +3 -1
  119. package/lib/internal/task-create.ts +76 -15
  120. package/lib/internal/task-delivery.ts +3 -1
  121. package/lib/internal/task-event.ts +3 -1
  122. package/lib/internal/task-finalization.ts +3 -1
  123. package/lib/internal/task-invalidation.ts +3 -1
  124. package/lib/internal/task-ledger.ts +11 -1
  125. package/lib/internal/task-lifecycle.ts +3 -1
  126. package/lib/internal/task-operation-registry.ts +482 -0
  127. package/lib/internal/task-orchestration.ts +12 -0
  128. package/lib/internal/task-override.ts +9 -3
  129. package/lib/internal/task-qualification.ts +61 -0
  130. package/lib/internal/task-review.ts +3 -1
  131. package/lib/internal/task-short-id.ts +10 -1
  132. package/lib/internal/task-snapshot.ts +3 -1
  133. package/lib/internal/task-validate.ts +6 -2
  134. package/lib/internal/task-verify.ts +4 -1
  135. package/lib/internal/task-warning.ts +7 -1
  136. package/lib/platform/comment-safety.ts +250 -0
  137. package/lib/platform/github-provider.ts +229 -7
  138. package/lib/platform/issue-comments.ts +160 -35
  139. package/lib/platform/pr-change-report.ts +6 -12
  140. package/lib/platform/pr-summary.ts +27 -6
  141. package/lib/platform/provider-contract.ts +78 -3
  142. package/lib/platform/provider-validation.ts +85 -3
  143. package/lib/platform/pull-requests.ts +126 -178
  144. package/lib/platform/repository-metadata.ts +453 -0
  145. package/lib/platform/security-alerts.ts +191 -0
  146. package/lib/platform/verification-sync.ts +32 -28
  147. package/lib/process-data/index.ts +7 -0
  148. package/lib/run/index.ts +5 -0
  149. package/lib/sandbox/commands/control-status.ts +26 -0
  150. package/lib/sandbox/commands/create.ts +46 -6
  151. package/lib/sandbox/commands/ls.ts +7 -0
  152. package/lib/sandbox/commands/rm.ts +1000 -87
  153. package/lib/sandbox/commands/show.ts +16 -0
  154. package/lib/sandbox/control/client.ts +103 -9
  155. package/lib/sandbox/control/completed-reentry.ts +98 -0
  156. package/lib/sandbox/control/container-identity.ts +27 -43
  157. package/lib/sandbox/control/executor.ts +177 -12
  158. package/lib/sandbox/control/lifecycle.ts +629 -17
  159. package/lib/sandbox/control/native-file-lock.ts +209 -0
  160. package/lib/sandbox/control/protocol.ts +5 -2
  161. package/lib/sandbox/control/server.ts +319 -37
  162. package/lib/sandbox/control/state.ts +6 -3
  163. package/lib/sandbox/control/task-view.ts +240 -0
  164. package/lib/sandbox/engines/authority.ts +365 -0
  165. package/lib/sandbox/engines/docker-exact-discovery.ts +132 -0
  166. package/lib/sandbox/index.ts +7 -0
  167. package/lib/sandbox/recovery.ts +21 -1
  168. package/lib/sandbox/workspace-view.ts +9 -3
  169. package/lib/sandbox/worktree-safety.ts +4 -1
  170. package/lib/server/index.ts +10 -0
  171. package/lib/task/artifact-lifecycle.ts +38 -16
  172. package/lib/task/artifact-operations.ts +626 -0
  173. package/lib/task/artifact-repair-intent.ts +71 -0
  174. package/lib/task/artifact-schema.ts +192 -0
  175. package/lib/task/capabilities.ts +70 -1
  176. package/lib/task/control-authority.ts +11 -1
  177. package/lib/task/create-service.ts +131 -6
  178. package/lib/task/create.ts +27 -0
  179. package/lib/task/events.ts +161 -65
  180. package/lib/task/index.ts +16 -3
  181. package/lib/task/local-artifact-finalization.ts +87 -218
  182. package/lib/task/orchestration.ts +172 -49
  183. package/lib/task/qualification-audit.ts +599 -0
  184. package/lib/task/qualification-confirmation.ts +117 -0
  185. package/lib/task/qualification-intents.ts +150 -0
  186. package/lib/task/review-artifacts.ts +21 -22
  187. package/lib/task/review-finalization.ts +133 -4
  188. package/lib/task/sections.ts +6 -4
  189. package/lib/task/verification-config.ts +12 -0
  190. package/lib/task/verification-engine.ts +55 -4
  191. package/package.json +6 -1
  192. package/templates/.agents/rules/README.en.md +2 -0
  193. package/templates/.agents/rules/README.zh-CN.md +2 -0
  194. package/templates/.agents/rules/decision-qualification.en.md +22 -0
  195. package/templates/.agents/rules/decision-qualification.zh-CN.md +22 -0
  196. package/templates/.agents/rules/evidence-reporting.en.md +28 -0
  197. package/templates/.agents/rules/evidence-reporting.zh-CN.md +28 -0
  198. package/templates/.agents/rules/label-milestone-setup.en.md +7 -7
  199. package/templates/.agents/rules/label-milestone-setup.zh-CN.md +7 -7
  200. package/templates/.agents/rules/local-artifact-repair.en.md +5 -1
  201. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +5 -1
  202. package/templates/.agents/rules/security-alerts.en.md +8 -8
  203. package/templates/.agents/rules/security-alerts.zh-CN.md +8 -8
  204. package/templates/.agents/skills/analyze-task/SKILL.en.md +15 -2
  205. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +19 -2
  206. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -12
  207. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -12
  208. package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
  209. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
  210. package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
  211. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
  212. package/templates/.agents/skills/code-task/SKILL.en.md +14 -1
  213. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +14 -1
  214. package/templates/.agents/skills/code-task/config/verify.en.json +5 -14
  215. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +5 -14
  216. package/templates/.agents/skills/code-task/reference/code-rules.en.md +5 -4
  217. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +5 -4
  218. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
  219. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
  220. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +3 -1
  221. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +3 -1
  222. package/templates/.agents/skills/code-task/reference/report-template.en.md +42 -15
  223. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +39 -12
  224. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +5 -1
  225. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -1
  226. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  227. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  228. package/templates/.agents/skills/complete-task/SKILL.en.md +9 -22
  229. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +9 -22
  230. package/templates/.agents/skills/create-task/SKILL.en.md +8 -0
  231. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +7 -0
  232. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  233. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  234. package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
  235. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  236. package/templates/.agents/skills/init-labels/SKILL.en.md +8 -8
  237. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +8 -8
  238. package/templates/.agents/skills/init-milestones/SKILL.en.md +9 -11
  239. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +9 -11
  240. package/templates/.agents/skills/plan-task/SKILL.en.md +14 -1
  241. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +16 -1
  242. package/templates/.agents/skills/plan-task/config/verify.en.json +4 -14
  243. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +4 -14
  244. package/templates/.agents/skills/review-analysis/SKILL.en.md +14 -1
  245. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -1
  246. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -18
  247. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -18
  248. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +50 -23
  249. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +45 -18
  250. package/templates/.agents/skills/review-code/SKILL.en.md +14 -1
  251. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +14 -1
  252. package/templates/.agents/skills/review-code/config/verify.en.json +1 -23
  253. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -23
  254. package/templates/.agents/skills/review-code/reference/report-template.en.md +49 -22
  255. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +45 -18
  256. package/templates/.agents/skills/review-plan/SKILL.en.md +14 -1
  257. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -1
  258. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -18
  259. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -18
  260. package/templates/.agents/skills/review-plan/reference/report-template.en.md +49 -22
  261. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +45 -18
  262. package/templates/.agents/skills/review-pr/SKILL.en.md +5 -1
  263. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +5 -1
  264. package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -3
  265. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -3
  266. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +5 -1
  267. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +5 -1
  268. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +3 -1
  269. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +3 -1
  270. package/templates/.agents/skills/test/SKILL.en.md +13 -2
  271. package/templates/.agents/skills/test/SKILL.zh-CN.md +13 -2
  272. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +14 -0
  273. package/templates/.agents/templates/task.en.md +6 -0
  274. package/templates/.agents/templates/task.zh-CN.md +6 -0
  275. package/templates/.agents/scripts/security-alerts.github.sh +0 -151
  276. package/templates/.agents/scripts/security-alerts.sh +0 -31
  277. package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +0 -146
  278. package/templates/.agents/skills/init-labels/scripts/init-labels.sh +0 -32
  279. package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +0 -294
  280. package/templates/.agents/skills/init-milestones/scripts/init-milestones.sh +0 -6
@@ -5,12 +5,10 @@ import { parseTypedTaskFrontmatter } from '../task/frontmatter.js';
5
5
  import { resolveTaskRef } from '../task/resolve-ref.js';
6
6
  import { extractSection } from '../task/sections.js';
7
7
  import { captureTaskWriteMetadata, writeTask } from '../task/write.js';
8
- import { parseGitHubRemote, resolvePlatformContext, resolvePlatformProviderContext } from './context.js';
9
- import { createGitHubClient } from './github-client.js';
8
+ import { resolvePlatformProviderContext } from './context.js';
10
9
  import { inspectPlatformIssue } from './issues.js';
11
- import { inspectGitHubIssue } from './issues.js';
12
10
  import { planPullRequestMetadata } from './pull-request-metadata.js';
13
- import { extractPullRequestFileNames, planInLabelUpdate, syncLabelDelta, validateInLabelMapping, validateRepositoryLabelPayload } from './in-label-sync.js';
11
+ import { planInLabelUpdate, validateInLabelMapping } from './in-label-sync.js';
14
12
  import { platformResult } from './types.js';
15
13
  import { inspectCompletionArtifacts } from '../task/finalization-artifacts.js';
16
14
  import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
@@ -18,7 +16,7 @@ import { resolveDeliveryTarget } from '../task/delivery-target.js';
18
16
  import { mergeOperationWarnings } from '../task/operation-outcome.js';
19
17
  import { buildBoundFact, buildSkippedFact, factFrontmatterMutation, readPrDeliveryFact } from '../task/pr-delivery-fact.js';
20
18
  import { providerError, providerOperationContext, providerStatus, providerResourceToken, providerResourceIdentity, resourceIdentity, unsupportedProviderOperation } from './provider-bridge.js';
21
- import { isResourceIdentity, resourceIdentityEquals, resourceIdentityNumber } from './resource-identity.js';
19
+ import { isResourceIdentity, resourceIdentityEquals, resourceIdentityNumber, serializeResourceIdentity } from './resource-identity.js';
22
20
  import { taskIssueIdentity, taskIssueIdentityError } from './task-identities.js';
23
21
  function warningResultForPrimary(primaryResult) {
24
22
  if (primaryResult === 'pr_created')
@@ -487,7 +485,6 @@ async function inspectPlatformPullRequestByNumber(prNumber, options = {}) {
487
485
  error: { code: 'PLATFORM_PROVIDER_LOAD_FAILED', message: 'Selected provider failed to load', retryable: false }
488
486
  });
489
487
  }
490
- const CLOSING_ISSUES_QUERY = 'query($owner:String!,$name:String!,$number:Int!,$cursor:String){repository(owner:$owner,name:$name){pullRequest(number:$number){closingIssuesReferences(first:100,after:$cursor){nodes{number} pageInfo{hasNextPage endCursor}}}}}';
491
488
  function readInLabelMapping(repoRoot) {
492
489
  try {
493
490
  const config = JSON.parse(fs.readFileSync(path.join(repoRoot, '.agents', '.airc.json'), 'utf8'));
@@ -528,105 +525,19 @@ function taskInLabelTarget(repoRoot, frontmatter, currentLabels, repositoryLabel
528
525
  return { ok: false, error: planned.error };
529
526
  return { ok: true, value: planned.target };
530
527
  }
531
- function inspectClosingIssueNumbers(client, repository, pullRequest, cwd) {
532
- const [owner, name] = repository.split('/');
533
- if (!owner || !name)
534
- return {
535
- ok: false,
536
- error: { code: 'IN_LABEL_SYNC_REPOSITORY_INVALID', message: 'Repository identity is invalid', retryable: false }
537
- };
538
- let cursor = null;
539
- const numbers = [];
540
- for (;;) {
541
- const args = ['api', 'graphql', '-f', `query=${CLOSING_ISSUES_QUERY}`, '-F', `owner=${owner}`, '-F', `name=${name}`, '-F', `number=${pullRequest}`];
542
- if (cursor)
543
- args.push('-F', `cursor=${cursor}`);
544
- const response = client.json(args, { cwd });
545
- if (!response.ok)
546
- return response;
547
- const connection = response.value?.data?.repository?.pullRequest?.closingIssuesReferences;
548
- if (!connection || !Array.isArray(connection.nodes) || !connection.pageInfo)
549
- return {
550
- ok: false,
551
- error: { code: 'IN_LABEL_SYNC_ASSOCIATION_INVALID', message: 'Closing Issue response is incomplete', retryable: false }
552
- };
553
- for (const node of connection.nodes) {
554
- const issue = Number(node?.number);
555
- if (!Number.isSafeInteger(issue) || issue <= 0)
556
- return {
557
- ok: false,
558
- error: { code: 'IN_LABEL_SYNC_ASSOCIATION_INVALID', message: 'Closing Issue identity is invalid', retryable: false }
559
- };
560
- numbers.push(issue);
561
- }
562
- if (!connection.pageInfo.hasNextPage)
563
- return { ok: true, value: [...new Set(numbers)] };
564
- if (!connection.pageInfo.endCursor || connection.pageInfo.endCursor === cursor)
565
- return {
566
- ok: false,
567
- error: { code: 'IN_LABEL_SYNC_ASSOCIATION_INVALID', message: 'Closing Issue pagination cursor is invalid', retryable: false }
568
- };
569
- cursor = connection.pageInfo.endCursor;
570
- }
571
- }
572
- function inspectRepositoryLabels(client, repository, cwd) {
573
- const response = client.json(['api', '--paginate', '--slurp', `repos/${repository}/labels?per_page=100`], { cwd });
574
- if (!response.ok)
575
- return response;
576
- const labels = validateRepositoryLabelPayload(response.value);
577
- return labels.ok ? { ok: true, value: new Set(labels.value) } : labels;
578
- }
579
- function inspectPullRequestFiles(client, repository, pullRequest, cwd) {
580
- const response = client.json(['api', '--paginate', '--slurp', `repos/${repository}/pulls/${pullRequest}/files?per_page=100`], { cwd });
581
- if (!response.ok)
582
- return response;
583
- const files = extractPullRequestFileNames(response.value);
584
- return files ? { ok: true, value: files } : {
585
- ok: false,
586
- error: { code: 'IN_LABEL_SYNC_FILES_INVALID', message: 'Pull request files response is incomplete', retryable: false }
587
- };
588
- }
589
- function inspectTaskInLabelTarget(repoRoot, frontmatter, pullRequest, repository, client) {
590
- const baseRef = typeof frontmatter.delivery_base_ref === 'string' ? frontmatter.delivery_base_ref.trim() : '';
591
- if (!baseRef)
592
- return {
593
- ok: false,
594
- error: { code: 'IN_LABEL_SYNC_BASE_MISSING', message: 'Task has no delivery_base_ref for in-label evidence', retryable: false }
595
- };
596
- let changedFiles;
597
- try {
598
- changedFiles = execFileSync('git', ['diff', `${baseRef}...HEAD`, '--name-only'], {
599
- cwd: repoRoot, encoding: 'utf8'
600
- }).trim().split(/\r?\n/).filter(Boolean);
601
- }
602
- catch (error) {
603
- return {
604
- ok: false,
605
- error: { code: 'IN_LABEL_SYNC_EVIDENCE_UNAVAILABLE', message: error instanceof Error ? error.message : String(error), retryable: false }
606
- };
607
- }
608
- const mapping = readInLabelMapping(repoRoot);
609
- if (!mapping.ok)
610
- return mapping;
611
- const labels = inspectRepositoryLabels(client, repository, repoRoot);
612
- if (!labels.ok)
613
- return labels;
614
- const planned = planInLabelUpdate({
615
- changedFiles, currentLabels: pullRequest.labels, mapping: mapping.value, repositoryLabels: labels.value
616
- });
617
- if (planned.error)
618
- return { ok: false, error: planned.error };
619
- return { ok: true, value: { changedFiles, target: planned.target } };
528
+ function resourceIdentityLabel(identity) {
529
+ const number = resourceIdentityNumber(identity);
530
+ return number === null ? `${identity.kind} ${identity.value}` : `#${number}`;
620
531
  }
621
- function inLabelResource(kind, number, before, expected, after, effect) {
622
- return { kind, number, before: [...before].sort(), expected: [...expected].sort(), after: after ? [...after].sort() : null, effect };
532
+ function inLabelResource(kind, identity, before, expected, after, effect) {
533
+ return { kind, number: resourceIdentityNumber(identity), identity, before: [...before].sort(), expected: [...expected].sort(), after: after ? [...after].sort() : null, effect };
623
534
  }
624
535
  async function syncPlatformPullRequestInLabels(prNumber, options = {}) {
625
536
  const cwd = path.resolve(options.cwd || process.cwd());
626
- const client = options.client || createGitHubClient();
627
- const context = await resolvePlatformContext({ cwd, client });
537
+ const loaded = await resolvePlatformProviderContext({ cwd, client: options.client });
538
+ const context = loaded.ok ? loaded.value.context : loaded.context;
628
539
  const usable = (context.status === 'no-op' || context.status === 'degraded') && context.platform.repository;
629
- if (!usable)
540
+ if (!loaded.ok || !usable)
630
541
  return result(context.status, null, null, prNumber, {
631
542
  platform: context.platform, capabilities: context.capabilities, operations: context.operations, error: context.error
632
543
  });
@@ -635,68 +546,79 @@ async function syncPlatformPullRequestInLabels(prNumber, options = {}) {
635
546
  platform: context.platform, capabilities: context.capabilities,
636
547
  error: { code: 'PR_NUMBER_INVALID', message: 'PR number must be positive', retryable: false }
637
548
  });
549
+ const provider = loaded.value.provider;
550
+ const operationContext = providerOperationContext(loaded.value);
638
551
  const repository = context.platform.repository;
639
- const fetched = inspectGitHubPullRequest(client, repository, prNumber, cwd);
640
- if (!fetched.ok)
641
- return result(fetched.error.retryable ? 'blocked' : 'failed', null, null, prNumber, {
642
- platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, error: fetched.error
643
- });
644
- const files = inspectPullRequestFiles(client, repository, prNumber, cwd);
552
+ const pullRequestIdentity = providerResourceToken(provider, 'pull-request', String(prNumber));
553
+ const providerFailure = (error) => result(error.code === 'PLATFORM_CAPABILITY_UNSUPPORTED' ? 'degraded' : providerStatus(error), null, null, prNumber, {
554
+ platform: context.platform,
555
+ capabilities: context.capabilities,
556
+ resource: { kind: 'pull-request', number: prNumber, identity: pullRequestIdentity },
557
+ error: providerError(error, 'PLATFORM_PROVIDER_OPERATION_FAILED')
558
+ });
559
+ const inspected = provider.changeRequests?.inspect
560
+ ? await provider.changeRequests.inspect({ context: operationContext, target: pullRequestIdentity })
561
+ : unsupportedProviderOperation(provider, 'changeRequests.inspect');
562
+ if (!inspected.ok)
563
+ return providerFailure(inspected.error);
564
+ const fetched = normalizeProviderPullRequest(inspected.value, repository, prNumber);
565
+ const files = provider.changeRequests?.listFiles
566
+ ? await provider.changeRequests.listFiles({ context: operationContext, target: pullRequestIdentity })
567
+ : unsupportedProviderOperation(provider, 'changeRequests.listFiles');
645
568
  if (!files.ok)
646
- return result(files.error.retryable ? 'blocked' : 'failed', null, null, prNumber, {
647
- platform: context.platform, capabilities: context.capabilities, pullRequest: fetched.value,
648
- resource: { kind: 'pull-request', number: prNumber }, error: files.error
649
- });
569
+ return providerFailure(files.error);
650
570
  const mapping = readInLabelMapping(cwd);
651
571
  if (!mapping.ok)
652
572
  return result('failed', null, null, prNumber, {
653
- platform: context.platform, capabilities: context.capabilities, pullRequest: fetched.value,
654
- resource: { kind: 'pull-request', number: prNumber }, error: mapping.error
655
- });
656
- const repositoryLabels = inspectRepositoryLabels(client, repository, cwd);
657
- if (!repositoryLabels.ok)
658
- return result(repositoryLabels.error.retryable ? 'blocked' : 'failed', null, null, prNumber, {
659
- platform: context.platform, capabilities: context.capabilities, pullRequest: fetched.value,
660
- resource: { kind: 'pull-request', number: prNumber }, error: repositoryLabels.error
573
+ platform: context.platform, capabilities: context.capabilities, pullRequest: fetched,
574
+ resource: { kind: 'pull-request', number: prNumber, identity: pullRequestIdentity }, error: mapping.error
661
575
  });
576
+ let repositoryLabels = new Set();
577
+ if (Object.keys(mapping.value).length > 0) {
578
+ const labels = provider.issues?.listLabels
579
+ ? await provider.issues.listLabels({ context: operationContext })
580
+ : unsupportedProviderOperation(provider, 'issues.listLabels');
581
+ if (!labels.ok)
582
+ return providerFailure(labels.error);
583
+ repositoryLabels = new Set(labels.value);
584
+ }
662
585
  const plannedTarget = planInLabelUpdate({
663
- changedFiles: files.value, currentLabels: fetched.value.labels,
664
- mapping: mapping.value, repositoryLabels: repositoryLabels.value
586
+ changedFiles: files.value, currentLabels: fetched.labels,
587
+ mapping: mapping.value, repositoryLabels
665
588
  });
666
589
  if (plannedTarget.error)
667
590
  return result('failed', null, null, prNumber, {
668
- platform: context.platform, capabilities: context.capabilities, pullRequest: fetched.value,
669
- resource: { kind: 'pull-request', number: prNumber }, error: plannedTarget.error
591
+ platform: context.platform, capabilities: context.capabilities, pullRequest: fetched,
592
+ resource: { kind: 'pull-request', number: prNumber, identity: pullRequestIdentity }, error: plannedTarget.error
670
593
  });
671
- const target = plannedTarget.target;
672
- const association = inspectClosingIssueNumbers(client, repository, prNumber, cwd);
594
+ const association = provider.changeRequests?.listClosingIssues
595
+ ? await provider.changeRequests.listClosingIssues({ context: operationContext, target: pullRequestIdentity })
596
+ : unsupportedProviderOperation(provider, 'changeRequests.listClosingIssues');
673
597
  if (!association.ok)
674
- return result(association.error.retryable ? 'blocked' : 'failed', null, null, prNumber, {
675
- platform: context.platform, capabilities: context.capabilities, pullRequest: fetched.value,
676
- resource: { kind: 'pull-request', number: prNumber }, evidence: { kind: 'pull-request-files', pullRequestFiles: files.value }, error: association.error
677
- });
678
- const issueNumber = association.value.length === 1 ? association.value[0] : null;
598
+ return providerFailure(association.error);
599
+ const issueIdentity = association.value.length === 1 ? association.value[0] : null;
600
+ const issueNumber = issueIdentity ? resourceIdentityNumber(issueIdentity) : null;
601
+ const closingIssueIdentities = association.value;
679
602
  let issue = null;
680
- if (issueNumber) {
681
- const fetchedIssue = inspectGitHubIssue(client, repository, issueNumber, cwd);
603
+ if (issueIdentity) {
604
+ const fetchedIssue = provider.issues?.inspect
605
+ ? await provider.issues.inspect({ context: operationContext, target: issueIdentity })
606
+ : unsupportedProviderOperation(provider, 'issues.inspect');
682
607
  if (!fetchedIssue.ok)
683
- return result(fetchedIssue.error.retryable ? 'blocked' : 'failed', null, issueNumber, prNumber, {
684
- platform: context.platform, capabilities: context.capabilities, pullRequest: fetched.value,
685
- resource: { kind: 'issue', number: issueNumber }, evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: association.value }, error: fetchedIssue.error
686
- });
608
+ return providerFailure(fetchedIssue.error);
687
609
  issue = fetchedIssue.value;
688
610
  }
689
611
  const resources = issue
690
612
  ? [
691
- { kind: 'issue', number: issue.number, snapshot: issue },
692
- { kind: 'pull-request', number: prNumber, snapshot: fetched.value }
613
+ { kind: 'issue', identity: issueIdentity, number: issueNumber, snapshot: issue },
614
+ { kind: 'pull-request', identity: pullRequestIdentity, number: prNumber, snapshot: fetched }
693
615
  ]
694
- : [{ kind: 'pull-request', number: prNumber, snapshot: fetched.value }];
616
+ : [{ kind: 'pull-request', identity: pullRequestIdentity, number: prNumber, snapshot: fetched }];
695
617
  const plans = resources.map((resource) => ({
696
618
  ...resource,
697
619
  plan: planInLabelUpdate({
698
620
  changedFiles: files.value, currentLabels: resource.snapshot.labels,
699
- mapping: mapping.value, repositoryLabels: repositoryLabels.value
621
+ mapping: mapping.value, repositoryLabels
700
622
  })
701
623
  }));
702
624
  const operations = [
@@ -708,63 +630,86 @@ async function syncPlatformPullRequestInLabels(prNumber, options = {}) {
708
630
  }))
709
631
  ];
710
632
  const willWrite = context.capabilities.triage && !options.dryRun;
711
- const resourcesState = plans.map((item) => inLabelResource(item.kind, item.number, item.snapshot.labels, target, item.plan.changed && willWrite ? null : item.snapshot.labels, item.plan.changed && willWrite ? 'unknown' : 'no-op'));
633
+ const resourcesState = plans.map((item) => inLabelResource(item.kind, item.identity, item.snapshot.labels, item.plan.target, item.plan.changed && willWrite ? null : item.snapshot.labels, item.plan.changed && willWrite ? 'unknown' : 'no-op'));
712
634
  if (options.dryRun || !context.capabilities.triage)
713
635
  return result(!context.capabilities.triage || association.value.length !== 1 ? 'degraded' : plans.some((item) => item.plan.changed) ? 'planned' : 'no-op', null, issueNumber, prNumber, {
714
- platform: context.platform, capabilities: context.capabilities, pullRequest: fetched.value,
715
- resource: { kind: 'pull-request', number: prNumber }, operations, resources: resourcesState,
716
- evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: association.value }, error: null
636
+ platform: context.platform, capabilities: context.capabilities, pullRequest: fetched,
637
+ resource: { kind: 'pull-request', number: prNumber, identity: pullRequestIdentity }, operations, resources: resourcesState,
638
+ evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: closingIssueIdentities }, error: null
717
639
  });
718
- let currentPullRequest = fetched.value;
640
+ let currentPullRequest = fetched;
719
641
  let successfulWrites = 0;
720
642
  for (const item of plans) {
721
643
  if (!item.plan.changed)
722
644
  continue;
723
- const synced = syncLabelDelta(client, repository, item.number, cwd, item.snapshot.labels, item.plan.target);
724
- if (synced.status === 'failed' || synced.status === 'blocked') {
725
- const state = resourcesState.find((resource) => resource.kind === item.kind && resource.number === item.number);
726
- state.effect = synced.status === 'blocked' ? 'unknown' : 'no-op';
727
- state.after = synced.status === 'blocked' ? null : item.snapshot.labels;
728
- const partial = successfulWrites > 0 || synced.status === 'blocked';
729
- const error = partial && synced.error
645
+ const targetIdentity = item.kind === 'issue' ? issueIdentity : pullRequestIdentity;
646
+ const synced = item.kind === 'issue'
647
+ ? provider.issues?.update
648
+ ? await provider.issues.update({
649
+ context: operationContext,
650
+ target: targetIdentity,
651
+ currentLabels: item.snapshot.labels,
652
+ patch: { labels: item.plan.labels },
653
+ mutation: { idempotencyKey: `issue:update:${serializeResourceIdentity(targetIdentity)}:in-labels`, target: targetIdentity }
654
+ })
655
+ : unsupportedProviderOperation(provider, 'issues.update')
656
+ : provider.changeRequests?.update
657
+ ? await provider.changeRequests.update({
658
+ context: operationContext,
659
+ target: targetIdentity,
660
+ currentLabels: item.snapshot.labels,
661
+ patch: { labels: item.plan.labels },
662
+ mutation: { idempotencyKey: `pull-request:update:${serializeResourceIdentity(targetIdentity)}:in-labels`, target: targetIdentity }
663
+ })
664
+ : unsupportedProviderOperation(provider, 'changeRequests.update');
665
+ if (!synced.ok) {
666
+ const state = resourcesState.find((resource) => resource.kind === item.kind && resourceIdentityEquals(resource.identity, item.identity));
667
+ state.effect = synced.error.retryable ? 'unknown' : 'no-op';
668
+ state.after = state.effect === 'unknown' ? null : item.snapshot.labels;
669
+ const partial = successfulWrites > 0 || synced.error.retryable;
670
+ const error = partial
730
671
  ? { ...synced.error, code: 'IN_LABEL_SYNC_PARTIAL', message: `In-label synchronization is partial or unknown: ${synced.error.message}` }
731
672
  : synced.error;
732
- return result(partial ? 'blocked' : synced.status, null, issueNumber, prNumber, {
733
- changed: successfulWrites > 0 || synced.changed, platform: context.platform, capabilities: context.capabilities,
734
- pullRequest: currentPullRequest, resource: { kind: 'pull-request', number: prNumber }, operations, resources: resourcesState,
735
- evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: association.value }, error
673
+ return result(partial ? 'blocked' : providerStatus(synced.error), null, issueNumber, prNumber, {
674
+ changed: successfulWrites > 0, platform: context.platform, capabilities: context.capabilities,
675
+ pullRequest: currentPullRequest, resource: { kind: 'pull-request', number: prNumber, identity: pullRequestIdentity }, operations, resources: resourcesState,
676
+ evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: closingIssueIdentities }, error: providerError(error, 'PLATFORM_PROVIDER_OPERATION_FAILED')
736
677
  });
737
678
  }
738
- successfulWrites += synced.changed ? 1 : 0;
679
+ successfulWrites += synced.value.changed ? 1 : 0;
739
680
  const reread = item.kind === 'issue'
740
- ? inspectGitHubIssue(client, repository, item.number, cwd)
741
- : inspectGitHubPullRequest(client, repository, item.number, cwd);
681
+ ? provider.issues?.inspect
682
+ ? await provider.issues.inspect({ context: operationContext, target: targetIdentity })
683
+ : unsupportedProviderOperation(provider, 'issues.inspect')
684
+ : provider.changeRequests?.inspect
685
+ ? await provider.changeRequests.inspect({ context: operationContext, target: targetIdentity })
686
+ : unsupportedProviderOperation(provider, 'changeRequests.inspect');
742
687
  if (!reread.ok)
743
688
  return result('blocked', null, issueNumber, prNumber, {
744
689
  changed: true, platform: context.platform, capabilities: context.capabilities,
745
- pullRequest: currentPullRequest, resource: { kind: 'pull-request', number: prNumber }, operations, resources: resourcesState,
746
- evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: association.value },
747
- error: { ...reread.error, code: 'IN_LABEL_SYNC_PARTIAL', message: `In-label synchronization is partial or unknown: ${reread.error.message}` }
690
+ pullRequest: currentPullRequest, resource: { kind: 'pull-request', number: prNumber, identity: pullRequestIdentity }, operations, resources: resourcesState,
691
+ evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: closingIssueIdentities },
692
+ error: { ...providerError(reread.error, 'PLATFORM_PROVIDER_OPERATION_FAILED'), code: 'IN_LABEL_SYNC_PARTIAL', message: `In-label synchronization is partial or unknown: ${reread.error.message}` }
748
693
  });
749
- const after = reread.value.labels;
750
- const state = resourcesState.find((resource) => resource.kind === item.kind && resource.number === item.number);
694
+ const after = reread.value.labels || [];
695
+ const state = resourcesState.find((resource) => resource.kind === item.kind && resourceIdentityEquals(resource.identity, item.identity));
751
696
  state.after = after;
752
- state.effect = after.filter((label) => label.startsWith('in:')).sort().join('\0') === target.join('\0') ? 'applied' : 'unknown';
697
+ state.effect = after.filter((label) => label.startsWith('in:')).sort().join('\0') === item.plan.target.join('\0') ? 'applied' : 'unknown';
753
698
  if (state.effect === 'unknown')
754
699
  return result('blocked', null, issueNumber, prNumber, {
755
700
  changed: true, platform: context.platform, capabilities: context.capabilities,
756
- pullRequest: currentPullRequest, resource: { kind: 'pull-request', number: prNumber }, operations, resources: resourcesState,
757
- evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: association.value },
758
- error: { code: 'IN_LABEL_SYNC_PARTIAL', message: `${item.kind} #${item.number} did not converge to the expected in: labels`, retryable: true }
701
+ pullRequest: currentPullRequest, resource: { kind: 'pull-request', number: prNumber, identity: pullRequestIdentity }, operations, resources: resourcesState,
702
+ evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: closingIssueIdentities },
703
+ error: { code: 'IN_LABEL_SYNC_PARTIAL', message: `${item.kind} ${resourceIdentityLabel(item.identity)} did not converge to the expected in: labels`, retryable: true }
759
704
  });
760
705
  if (item.kind === 'pull-request')
761
- currentPullRequest = reread.value;
706
+ currentPullRequest = normalizeProviderPullRequest(reread.value, repository, prNumber, currentPullRequest);
762
707
  }
763
708
  return result(association.value.length !== 1 ? 'degraded' : successfulWrites > 0 ? 'applied' : 'no-op', null, issueNumber, prNumber, {
764
709
  changed: successfulWrites > 0, platform: context.platform, capabilities: context.capabilities,
765
- pullRequest: currentPullRequest, resource: { kind: 'pull-request', number: prNumber },
710
+ pullRequest: currentPullRequest, resource: { kind: 'pull-request', number: prNumber, identity: pullRequestIdentity },
766
711
  operations: operations.map((operation) => operation.status === 'planned' ? { ...operation, status: 'applied' } : operation),
767
- resources: resourcesState, evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: association.value }, error: null
712
+ resources: resourcesState, evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: closingIssueIdentities }, error: null
768
713
  });
769
714
  }
770
715
  function appendActivity(content, line) {