@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
@@ -7,19 +7,13 @@ import { resolveTaskRef } from '../task/resolve-ref.ts';
7
7
  import { extractSection } from '../task/sections.ts';
8
8
  import { captureTaskWriteMetadata, writeTask } from '../task/write.ts';
9
9
  import type { PlatformChangeRequestSnapshot } from './adapters.ts';
10
- import { parseGitHubRemote, resolvePlatformContext, resolvePlatformProviderContext } from './context.ts';
10
+ import { resolvePlatformProviderContext } from './context.ts';
11
11
  import type { PlatformClient } from './context.ts';
12
- import { createGitHubClient } from './github-client.ts';
13
12
  import { inspectPlatformIssue } from './issues.ts';
14
- import { inspectGitHubIssue } from './issues.ts';
15
- import type { IssueSnapshot } from './issues.ts';
16
13
  import { planPullRequestMetadata } from './pull-request-metadata.ts';
17
14
  import {
18
- extractPullRequestFileNames,
19
15
  planInLabelUpdate,
20
- syncLabelDelta,
21
- validateInLabelMapping,
22
- validateRepositoryLabelPayload
16
+ validateInLabelMapping
23
17
  } from './in-label-sync.ts';
24
18
  import { platformResult } from './types.ts';
25
19
  import type { PlatformOperation, PlatformResult } from './types.ts';
@@ -43,10 +37,10 @@ import {
43
37
  resourceIdentity,
44
38
  unsupportedProviderOperation
45
39
  } from './provider-bridge.ts';
46
- import { isResourceIdentity, resourceIdentityEquals, resourceIdentityNumber } from './resource-identity.ts';
40
+ import { isResourceIdentity, resourceIdentityEquals, resourceIdentityNumber, serializeResourceIdentity } from './resource-identity.ts';
47
41
  import type { ResourceIdentity } from './resource-identity.ts';
48
42
  import { taskIssueIdentity, taskIssueIdentityError } from './task-identities.ts';
49
- import type { ChangeRequestSnapshot as ProviderChangeRequestSnapshot } from './provider-contract.ts';
43
+ import type { ChangeRequestSnapshot as ProviderChangeRequestSnapshot, IssueSnapshot as ProviderIssueSnapshot } from './provider-contract.ts';
50
44
 
51
45
  type PullRequestSnapshot = PlatformChangeRequestSnapshot;
52
46
 
@@ -56,10 +50,11 @@ type PullRequestResult = PlatformResult & {
56
50
  result: 'pr_created' | 'pr_reused' | 'no_op' | 'pr_created_with_warnings' | 'pr_reused_with_warnings' | 'no_op_with_warnings' | 'failed' | 'blocked' | null;
57
51
  creation: CreationOutcome | null;
58
52
  warnings: readonly OperationWarning[];
59
- evidence?: { kind: string; pullRequestFiles?: string[]; closingIssues?: number[] };
53
+ evidence?: { kind: string; pullRequestFiles?: string[]; closingIssues?: ResourceIdentity[] };
60
54
  resources?: Array<{
61
55
  kind: 'issue' | 'pull-request';
62
- number: number;
56
+ number: number | null;
57
+ identity: ResourceIdentity;
63
58
  before: string[];
64
59
  expected: string[];
65
60
  after: string[] | null;
@@ -672,8 +667,6 @@ async function inspectPlatformPullRequestByNumber(prNumber: string | number, opt
672
667
  });
673
668
  }
674
669
 
675
- 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}}}}}';
676
-
677
670
  function readInLabelMapping(repoRoot: string): { ok: true; value: Record<string, unknown> } | { ok: false; error: { code: string; message: string; retryable: boolean } } {
678
671
  try {
679
672
  const config = JSON.parse(fs.readFileSync(path.join(repoRoot, '.agents', '.airc.json'), 'utf8')) as {
@@ -718,176 +711,108 @@ function taskInLabelTarget(
718
711
  return { ok: true as const, value: planned.target };
719
712
  }
720
713
 
721
- function inspectClosingIssueNumbers(client: GitHubClient, repository: string, pullRequest: number, cwd: string) {
722
- const [owner, name] = repository.split('/');
723
- if (!owner || !name) return {
724
- ok: false as const,
725
- error: { code: 'IN_LABEL_SYNC_REPOSITORY_INVALID', message: 'Repository identity is invalid', retryable: false }
726
- };
727
- let cursor: string | null = null;
728
- const numbers: number[] = [];
729
- for (;;) {
730
- const args = ['api', 'graphql', '-f', `query=${CLOSING_ISSUES_QUERY}`, '-F', `owner=${owner}`, '-F', `name=${name}`, '-F', `number=${pullRequest}`];
731
- if (cursor) args.push('-F', `cursor=${cursor}`);
732
- const response = client.json<unknown>(args, { cwd });
733
- if (!response.ok) return response;
734
- const connection = (response.value as {
735
- data?: { repository?: { pullRequest?: { closingIssuesReferences?: {
736
- nodes?: unknown[]; pageInfo?: { hasNextPage?: boolean; endCursor?: string | null };
737
- } } } };
738
- })?.data?.repository?.pullRequest?.closingIssuesReferences;
739
- if (!connection || !Array.isArray(connection.nodes) || !connection.pageInfo) return {
740
- ok: false as const,
741
- error: { code: 'IN_LABEL_SYNC_ASSOCIATION_INVALID', message: 'Closing Issue response is incomplete', retryable: false }
742
- };
743
- for (const node of connection.nodes) {
744
- const issue = Number((node as { number?: unknown })?.number);
745
- if (!Number.isSafeInteger(issue) || issue <= 0) return {
746
- ok: false as const,
747
- error: { code: 'IN_LABEL_SYNC_ASSOCIATION_INVALID', message: 'Closing Issue identity is invalid', retryable: false }
748
- };
749
- numbers.push(issue);
750
- }
751
- if (!connection.pageInfo.hasNextPage) return { ok: true as const, value: [...new Set(numbers)] };
752
- if (!connection.pageInfo.endCursor || connection.pageInfo.endCursor === cursor) return {
753
- ok: false as const,
754
- error: { code: 'IN_LABEL_SYNC_ASSOCIATION_INVALID', message: 'Closing Issue pagination cursor is invalid', retryable: false }
755
- };
756
- cursor = connection.pageInfo.endCursor;
757
- }
758
- }
759
-
760
- function inspectRepositoryLabels(client: GitHubClient, repository: string, cwd: string) {
761
- const response = client.json<unknown>(['api', '--paginate', '--slurp', `repos/${repository}/labels?per_page=100`], { cwd });
762
- if (!response.ok) return response;
763
- const labels = validateRepositoryLabelPayload(response.value);
764
- return labels.ok ? { ok: true as const, value: new Set(labels.value) } : labels;
765
- }
766
-
767
- function inspectPullRequestFiles(client: GitHubClient, repository: string, pullRequest: number, cwd: string) {
768
- const response = client.json<unknown>(['api', '--paginate', '--slurp', `repos/${repository}/pulls/${pullRequest}/files?per_page=100`], { cwd });
769
- if (!response.ok) return response;
770
- const files = extractPullRequestFileNames(response.value);
771
- return files ? { ok: true as const, value: files } : {
772
- ok: false as const,
773
- error: { code: 'IN_LABEL_SYNC_FILES_INVALID', message: 'Pull request files response is incomplete', retryable: false }
774
- };
775
- }
776
-
777
- function inspectTaskInLabelTarget(
778
- repoRoot: string,
779
- frontmatter: Record<string, unknown>,
780
- pullRequest: PullRequestSnapshot,
781
- repository: string,
782
- client: GitHubClient
783
- ) {
784
- const baseRef = typeof frontmatter.delivery_base_ref === 'string' ? frontmatter.delivery_base_ref.trim() : '';
785
- if (!baseRef) return {
786
- ok: false as const,
787
- error: { code: 'IN_LABEL_SYNC_BASE_MISSING', message: 'Task has no delivery_base_ref for in-label evidence', retryable: false }
788
- };
789
- let changedFiles: string[];
790
- try {
791
- changedFiles = execFileSync('git', ['diff', `${baseRef}...HEAD`, '--name-only'], {
792
- cwd: repoRoot, encoding: 'utf8'
793
- }).trim().split(/\r?\n/).filter(Boolean);
794
- } catch (error) {
795
- return {
796
- ok: false as const,
797
- error: { code: 'IN_LABEL_SYNC_EVIDENCE_UNAVAILABLE', message: error instanceof Error ? error.message : String(error), retryable: false }
798
- };
799
- }
800
- const mapping = readInLabelMapping(repoRoot);
801
- if (!mapping.ok) return mapping;
802
- const labels = inspectRepositoryLabels(client, repository, repoRoot);
803
- if (!labels.ok) return labels;
804
- const planned = planInLabelUpdate({
805
- changedFiles, currentLabels: pullRequest.labels, mapping: mapping.value, repositoryLabels: labels.value
806
- });
807
- if (planned.error) return { ok: false as const, error: planned.error };
808
- return { ok: true as const, value: { changedFiles, target: planned.target } };
714
+ function resourceIdentityLabel(identity: ResourceIdentity): string {
715
+ const number = resourceIdentityNumber(identity);
716
+ return number === null ? `${identity.kind} ${identity.value}` : `#${number}`;
809
717
  }
810
718
 
811
719
  function inLabelResource(
812
720
  kind: 'issue' | 'pull-request',
813
- number: number,
721
+ identity: ResourceIdentity,
814
722
  before: string[],
815
723
  expected: string[],
816
724
  after: string[] | null,
817
725
  effect: 'no-op' | 'applied' | 'unknown'
818
726
  ) {
819
- return { kind, number, before: [...before].sort(), expected: [...expected].sort(), after: after ? [...after].sort() : null, effect };
727
+ return { kind, number: resourceIdentityNumber(identity), identity, before: [...before].sort(), expected: [...expected].sort(), after: after ? [...after].sort() : null, effect };
820
728
  }
821
729
 
822
730
  async function syncPlatformPullRequestInLabels(prNumber: number, options: SharedOptions & { dryRun?: boolean } = {}): Promise<PullRequestResult> {
823
731
  const cwd = path.resolve(options.cwd || process.cwd());
824
- const client = options.client || createGitHubClient();
825
- const context = await resolvePlatformContext({ cwd, client });
732
+ const loaded = await resolvePlatformProviderContext({ cwd, client: options.client });
733
+ const context = loaded.ok ? loaded.value.context : loaded.context;
826
734
  const usable = (context.status === 'no-op' || context.status === 'degraded') && context.platform.repository;
827
- if (!usable) return result(context.status, null, null, prNumber, {
735
+ if (!loaded.ok || !usable) return result(context.status, null, null, prNumber, {
828
736
  platform: context.platform, capabilities: context.capabilities, operations: context.operations, error: context.error
829
737
  });
830
738
  if (!Number.isSafeInteger(prNumber) || prNumber <= 0) return result('failed', null, null, prNumber, {
831
739
  platform: context.platform, capabilities: context.capabilities,
832
740
  error: { code: 'PR_NUMBER_INVALID', message: 'PR number must be positive', retryable: false }
833
741
  });
742
+ const provider = loaded.value.provider;
743
+ const operationContext = providerOperationContext(loaded.value);
834
744
  const repository = context.platform.repository!;
835
- const fetched = inspectGitHubPullRequest(client, repository, prNumber, cwd);
836
- if (!fetched.ok) return result(fetched.error.retryable ? 'blocked' : 'failed', null, null, prNumber, {
837
- platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, error: fetched.error
838
- });
839
- const files = inspectPullRequestFiles(client, repository, prNumber, cwd);
840
- if (!files.ok) return result(files.error.retryable ? 'blocked' : 'failed', null, null, prNumber, {
841
- platform: context.platform, capabilities: context.capabilities, pullRequest: fetched.value,
842
- resource: { kind: 'pull-request', number: prNumber }, error: files.error
843
- });
745
+ const pullRequestIdentity = providerResourceToken(provider, 'pull-request', String(prNumber));
746
+ const providerFailure = (error: { code: string; message: string; retryable: boolean }): PullRequestResult => result(
747
+ error.code === 'PLATFORM_CAPABILITY_UNSUPPORTED' ? 'degraded' : providerStatus(error),
748
+ null,
749
+ null,
750
+ prNumber,
751
+ {
752
+ platform: context.platform,
753
+ capabilities: context.capabilities,
754
+ resource: { kind: 'pull-request', number: prNumber, identity: pullRequestIdentity },
755
+ error: providerError(error, 'PLATFORM_PROVIDER_OPERATION_FAILED')
756
+ }
757
+ );
758
+ const inspected = provider.changeRequests?.inspect
759
+ ? await provider.changeRequests.inspect({ context: operationContext, target: pullRequestIdentity })
760
+ : unsupportedProviderOperation(provider, 'changeRequests.inspect');
761
+ if (!inspected.ok) return providerFailure(inspected.error);
762
+ const fetched = normalizeProviderPullRequest(inspected.value, repository, prNumber);
763
+ const files = provider.changeRequests?.listFiles
764
+ ? await provider.changeRequests.listFiles({ context: operationContext, target: pullRequestIdentity })
765
+ : unsupportedProviderOperation(provider, 'changeRequests.listFiles');
766
+ if (!files.ok) return providerFailure(files.error);
844
767
  const mapping = readInLabelMapping(cwd);
845
768
  if (!mapping.ok) return result('failed', null, null, prNumber, {
846
- platform: context.platform, capabilities: context.capabilities, pullRequest: fetched.value,
847
- resource: { kind: 'pull-request', number: prNumber }, error: mapping.error
848
- });
849
- const repositoryLabels = inspectRepositoryLabels(client, repository, cwd);
850
- if (!repositoryLabels.ok) return result(repositoryLabels.error.retryable ? 'blocked' : 'failed', null, null, prNumber, {
851
- platform: context.platform, capabilities: context.capabilities, pullRequest: fetched.value,
852
- resource: { kind: 'pull-request', number: prNumber }, error: repositoryLabels.error
769
+ platform: context.platform, capabilities: context.capabilities, pullRequest: fetched,
770
+ resource: { kind: 'pull-request', number: prNumber, identity: pullRequestIdentity }, error: mapping.error
853
771
  });
772
+ let repositoryLabels = new Set<string>();
773
+ if (Object.keys(mapping.value).length > 0) {
774
+ const labels = provider.issues?.listLabels
775
+ ? await provider.issues.listLabels({ context: operationContext })
776
+ : unsupportedProviderOperation(provider, 'issues.listLabels');
777
+ if (!labels.ok) return providerFailure(labels.error);
778
+ repositoryLabels = new Set(labels.value);
779
+ }
854
780
  const plannedTarget = planInLabelUpdate({
855
- changedFiles: files.value, currentLabels: fetched.value.labels,
856
- mapping: mapping.value, repositoryLabels: repositoryLabels.value
781
+ changedFiles: files.value, currentLabels: fetched.labels,
782
+ mapping: mapping.value, repositoryLabels
857
783
  });
858
784
  if (plannedTarget.error) return result('failed', null, null, prNumber, {
859
- platform: context.platform, capabilities: context.capabilities, pullRequest: fetched.value,
860
- resource: { kind: 'pull-request', number: prNumber }, error: plannedTarget.error
861
- });
862
- const target = plannedTarget.target;
863
- const association = inspectClosingIssueNumbers(client, repository, prNumber, cwd);
864
- if (!association.ok) return result(association.error.retryable ? 'blocked' : 'failed', null, null, prNumber, {
865
- platform: context.platform, capabilities: context.capabilities, pullRequest: fetched.value,
866
- resource: { kind: 'pull-request', number: prNumber }, evidence: { kind: 'pull-request-files', pullRequestFiles: files.value }, error: association.error
785
+ platform: context.platform, capabilities: context.capabilities, pullRequest: fetched,
786
+ resource: { kind: 'pull-request', number: prNumber, identity: pullRequestIdentity }, error: plannedTarget.error
867
787
  });
868
-
869
- const issueNumber = association.value.length === 1 ? association.value[0]! : null;
870
- let issue: IssueSnapshot | null = null;
871
- if (issueNumber) {
872
- const fetchedIssue = inspectGitHubIssue(client, repository, issueNumber, cwd);
873
- if (!fetchedIssue.ok) return result(fetchedIssue.error.retryable ? 'blocked' : 'failed', null, issueNumber, prNumber, {
874
- platform: context.platform, capabilities: context.capabilities, pullRequest: fetched.value,
875
- resource: { kind: 'issue', number: issueNumber }, evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: association.value }, error: fetchedIssue.error
876
- });
788
+ const association = provider.changeRequests?.listClosingIssues
789
+ ? await provider.changeRequests.listClosingIssues({ context: operationContext, target: pullRequestIdentity })
790
+ : unsupportedProviderOperation(provider, 'changeRequests.listClosingIssues');
791
+ if (!association.ok) return providerFailure(association.error);
792
+
793
+ const issueIdentity = association.value.length === 1 ? association.value[0]! : null;
794
+ const issueNumber = issueIdentity ? resourceIdentityNumber(issueIdentity) : null;
795
+ const closingIssueIdentities = association.value;
796
+ let issue: ProviderIssueSnapshot | null = null;
797
+ if (issueIdentity) {
798
+ const fetchedIssue = provider.issues?.inspect
799
+ ? await provider.issues.inspect({ context: operationContext, target: issueIdentity })
800
+ : unsupportedProviderOperation(provider, 'issues.inspect');
801
+ if (!fetchedIssue.ok) return providerFailure(fetchedIssue.error);
877
802
  issue = fetchedIssue.value;
878
803
  }
879
804
 
880
805
  const resources = issue
881
806
  ? [
882
- { kind: 'issue' as const, number: issue.number, snapshot: issue },
883
- { kind: 'pull-request' as const, number: prNumber, snapshot: fetched.value }
807
+ { kind: 'issue' as const, identity: issueIdentity!, number: issueNumber, snapshot: issue },
808
+ { kind: 'pull-request' as const, identity: pullRequestIdentity, number: prNumber, snapshot: fetched }
884
809
  ]
885
- : [{ kind: 'pull-request' as const, number: prNumber, snapshot: fetched.value }];
810
+ : [{ kind: 'pull-request' as const, identity: pullRequestIdentity, number: prNumber, snapshot: fetched }];
886
811
  const plans = resources.map((resource) => ({
887
812
  ...resource,
888
813
  plan: planInLabelUpdate({
889
814
  changedFiles: files.value, currentLabels: resource.snapshot.labels,
890
- mapping: mapping.value, repositoryLabels: repositoryLabels.value
815
+ mapping: mapping.value, repositoryLabels
891
816
  })
892
817
  }));
893
818
  const operations: PlatformOperation[] = [
@@ -901,67 +826,90 @@ async function syncPlatformPullRequestInLabels(prNumber: number, options: Shared
901
826
  const willWrite = context.capabilities.triage && !options.dryRun;
902
827
  const resourcesState = plans.map((item) => inLabelResource(
903
828
  item.kind,
904
- item.number,
829
+ item.identity,
905
830
  item.snapshot.labels,
906
- target,
831
+ item.plan.target,
907
832
  item.plan.changed && willWrite ? null : item.snapshot.labels,
908
833
  item.plan.changed && willWrite ? 'unknown' : 'no-op'
909
834
  ));
910
835
  if (options.dryRun || !context.capabilities.triage) return result(
911
836
  !context.capabilities.triage || association.value.length !== 1 ? 'degraded' : plans.some((item) => item.plan.changed) ? 'planned' : 'no-op',
912
837
  null, issueNumber, prNumber, {
913
- platform: context.platform, capabilities: context.capabilities, pullRequest: fetched.value,
914
- resource: { kind: 'pull-request', number: prNumber }, operations, resources: resourcesState,
915
- evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: association.value }, error: null
838
+ platform: context.platform, capabilities: context.capabilities, pullRequest: fetched,
839
+ resource: { kind: 'pull-request', number: prNumber, identity: pullRequestIdentity }, operations, resources: resourcesState,
840
+ evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: closingIssueIdentities }, error: null
916
841
  }
917
842
  );
918
843
 
919
- let currentPullRequest = fetched.value;
844
+ let currentPullRequest = fetched;
920
845
  let successfulWrites = 0;
921
846
  for (const item of plans) {
922
847
  if (!item.plan.changed) continue;
923
- const synced = syncLabelDelta(client, repository, item.number, cwd, item.snapshot.labels, item.plan.target);
924
- if (synced.status === 'failed' || synced.status === 'blocked') {
925
- const state = resourcesState.find((resource) => resource.kind === item.kind && resource.number === item.number)!;
926
- state.effect = synced.status === 'blocked' ? 'unknown' : 'no-op';
927
- state.after = synced.status === 'blocked' ? null : item.snapshot.labels;
928
- const partial = successfulWrites > 0 || synced.status === 'blocked';
929
- const error = partial && synced.error
848
+ const targetIdentity = item.kind === 'issue' ? issueIdentity! : pullRequestIdentity;
849
+ const synced = item.kind === 'issue'
850
+ ? provider.issues?.update
851
+ ? await provider.issues.update({
852
+ context: operationContext,
853
+ target: targetIdentity,
854
+ currentLabels: item.snapshot.labels,
855
+ patch: { labels: item.plan.labels },
856
+ mutation: { idempotencyKey: `issue:update:${serializeResourceIdentity(targetIdentity)}:in-labels`, target: targetIdentity }
857
+ })
858
+ : unsupportedProviderOperation(provider, 'issues.update')
859
+ : provider.changeRequests?.update
860
+ ? await provider.changeRequests.update({
861
+ context: operationContext,
862
+ target: targetIdentity,
863
+ currentLabels: item.snapshot.labels,
864
+ patch: { labels: item.plan.labels },
865
+ mutation: { idempotencyKey: `pull-request:update:${serializeResourceIdentity(targetIdentity)}:in-labels`, target: targetIdentity }
866
+ })
867
+ : unsupportedProviderOperation(provider, 'changeRequests.update');
868
+ if (!synced.ok) {
869
+ const state = resourcesState.find((resource) => resource.kind === item.kind && resourceIdentityEquals(resource.identity, item.identity))!;
870
+ state.effect = synced.error.retryable ? 'unknown' : 'no-op';
871
+ state.after = state.effect === 'unknown' ? null : item.snapshot.labels;
872
+ const partial = successfulWrites > 0 || synced.error.retryable;
873
+ const error = partial
930
874
  ? { ...synced.error, code: 'IN_LABEL_SYNC_PARTIAL', message: `In-label synchronization is partial or unknown: ${synced.error.message}` }
931
875
  : synced.error;
932
- return result(partial ? 'blocked' : synced.status, null, issueNumber, prNumber, {
933
- changed: successfulWrites > 0 || synced.changed, platform: context.platform, capabilities: context.capabilities,
934
- pullRequest: currentPullRequest, resource: { kind: 'pull-request', number: prNumber }, operations, resources: resourcesState,
935
- evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: association.value }, error
876
+ return result(partial ? 'blocked' : providerStatus(synced.error), null, issueNumber, prNumber, {
877
+ changed: successfulWrites > 0, platform: context.platform, capabilities: context.capabilities,
878
+ pullRequest: currentPullRequest, resource: { kind: 'pull-request', number: prNumber, identity: pullRequestIdentity }, operations, resources: resourcesState,
879
+ evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: closingIssueIdentities }, error: providerError(error, 'PLATFORM_PROVIDER_OPERATION_FAILED')
936
880
  });
937
881
  }
938
- successfulWrites += synced.changed ? 1 : 0;
882
+ successfulWrites += synced.value.changed ? 1 : 0;
939
883
  const reread = item.kind === 'issue'
940
- ? inspectGitHubIssue(client, repository, item.number, cwd)
941
- : inspectGitHubPullRequest(client, repository, item.number, cwd);
884
+ ? provider.issues?.inspect
885
+ ? await provider.issues.inspect({ context: operationContext, target: targetIdentity })
886
+ : unsupportedProviderOperation(provider, 'issues.inspect')
887
+ : provider.changeRequests?.inspect
888
+ ? await provider.changeRequests.inspect({ context: operationContext, target: targetIdentity })
889
+ : unsupportedProviderOperation(provider, 'changeRequests.inspect');
942
890
  if (!reread.ok) return result('blocked', null, issueNumber, prNumber, {
943
891
  changed: true, platform: context.platform, capabilities: context.capabilities,
944
- pullRequest: currentPullRequest, resource: { kind: 'pull-request', number: prNumber }, operations, resources: resourcesState,
945
- evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: association.value },
946
- error: { ...reread.error, code: 'IN_LABEL_SYNC_PARTIAL', message: `In-label synchronization is partial or unknown: ${reread.error.message}` }
892
+ pullRequest: currentPullRequest, resource: { kind: 'pull-request', number: prNumber, identity: pullRequestIdentity }, operations, resources: resourcesState,
893
+ evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: closingIssueIdentities },
894
+ error: { ...providerError(reread.error, 'PLATFORM_PROVIDER_OPERATION_FAILED'), code: 'IN_LABEL_SYNC_PARTIAL', message: `In-label synchronization is partial or unknown: ${reread.error.message}` }
947
895
  });
948
- const after = reread.value.labels;
949
- const state = resourcesState.find((resource) => resource.kind === item.kind && resource.number === item.number)!;
896
+ const after = reread.value.labels || [];
897
+ const state = resourcesState.find((resource) => resource.kind === item.kind && resourceIdentityEquals(resource.identity, item.identity))!;
950
898
  state.after = after;
951
- state.effect = after.filter((label: string) => label.startsWith('in:')).sort().join('\0') === target.join('\0') ? 'applied' : 'unknown';
899
+ state.effect = after.filter((label: string) => label.startsWith('in:')).sort().join('\0') === item.plan.target.join('\0') ? 'applied' : 'unknown';
952
900
  if (state.effect === 'unknown') return result('blocked', null, issueNumber, prNumber, {
953
901
  changed: true, platform: context.platform, capabilities: context.capabilities,
954
- pullRequest: currentPullRequest, resource: { kind: 'pull-request', number: prNumber }, operations, resources: resourcesState,
955
- evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: association.value },
956
- error: { code: 'IN_LABEL_SYNC_PARTIAL', message: `${item.kind} #${item.number} did not converge to the expected in: labels`, retryable: true }
902
+ pullRequest: currentPullRequest, resource: { kind: 'pull-request', number: prNumber, identity: pullRequestIdentity }, operations, resources: resourcesState,
903
+ evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: closingIssueIdentities },
904
+ error: { code: 'IN_LABEL_SYNC_PARTIAL', message: `${item.kind} ${resourceIdentityLabel(item.identity)} did not converge to the expected in: labels`, retryable: true }
957
905
  });
958
- if (item.kind === 'pull-request') currentPullRequest = reread.value as PullRequestSnapshot;
906
+ if (item.kind === 'pull-request') currentPullRequest = normalizeProviderPullRequest(reread.value as ProviderChangeRequestSnapshot, repository, prNumber, currentPullRequest);
959
907
  }
960
908
  return result(association.value.length !== 1 ? 'degraded' : successfulWrites > 0 ? 'applied' : 'no-op', null, issueNumber, prNumber, {
961
909
  changed: successfulWrites > 0, platform: context.platform, capabilities: context.capabilities,
962
- pullRequest: currentPullRequest, resource: { kind: 'pull-request', number: prNumber },
910
+ pullRequest: currentPullRequest, resource: { kind: 'pull-request', number: prNumber, identity: pullRequestIdentity },
963
911
  operations: operations.map((operation) => operation.status === 'planned' ? { ...operation, status: 'applied' as const } : operation),
964
- resources: resourcesState, evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: association.value }, error: null
912
+ resources: resourcesState, evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: closingIssueIdentities }, error: null
965
913
  });
966
914
  }
967
915