@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
@@ -7,12 +7,14 @@ import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, isReviewSt
7
7
  import { resolveTaskRef } from '../task/resolve-ref.ts';
8
8
  import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
9
9
 
10
- const USAGE = `Usage: agent-infra-internal task-ledger <task-ref> <intent> [intent flags] [--dry-run]\n\nIntents: finding-upsert, finding-respond, finding-review, decision-next-id, decision-upsert, stage-status\n`;
10
+ const USAGE = `Usage: agent-infra-internal task-ledger <task-ref> <intent> [intent flags] [--dry-run]\n\nIntents: finding-upsert, finding-respond, finding-review, decision-next-id, decision-upsert, rework-intent-upsert, stage-status\n`;
11
11
  const FLAGS: Record<string, string> = {
12
12
  '--stage': 'stage', '--review-artifact': 'reviewArtifact', '--ordinal': 'ordinal',
13
13
  '--severity': 'severity', '--evidence': 'evidence', '--id': 'id', '--round': 'round',
14
- '--status': 'status', '--artifact': 'artifact', '--needs-implementation': 'needsImplementation'
15
- , '--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
14
+ '--status': 'status', '--artifact': 'artifact', '--needs-implementation': 'needsImplementation',
15
+ '--intent-id': 'intentId', '--finding-id': 'findingId', '--source-artifact': 'sourceArtifact',
16
+ '--source-sha256': 'sourceSha256', '--target': 'target',
17
+ '--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
16
18
  };
17
19
  const NUMERIC = new Set(['ordinal', 'round']);
18
20
  const BOOLEAN = new Set(['needsImplementation']);
@@ -23,11 +25,11 @@ function usageFailure(message: string): void {
23
25
  process.exitCode = 1;
24
26
  }
25
27
 
26
- function taskLedger(args: string[] = []): void {
28
+ async function taskLedger(args: string[] = []): Promise<void> {
27
29
  if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
28
30
  const [taskRef, kind] = args;
29
31
  if (!taskRef || !kind) { usageFailure('task ref and intent are required'); return; }
30
- if (!['finding-upsert', 'finding-respond', 'finding-review', 'decision-next-id', 'decision-upsert', 'stage-status'].includes(kind)) {
32
+ if (!['finding-upsert', 'finding-respond', 'finding-review', 'decision-next-id', 'decision-upsert', 'rework-intent-upsert', 'stage-status'].includes(kind)) {
31
33
  usageFailure(`unknown ledger intent '${kind}'`); return;
32
34
  }
33
35
  const values: Record<string, unknown> = { kind, taskRef };
@@ -55,13 +57,14 @@ function taskLedger(args: string[] = []): void {
55
57
  'finding-review': ['id', 'status', 'evidence'],
56
58
  'decision-next-id': [],
57
59
  'decision-upsert': ['id', 'stage', 'artifact'],
60
+ 'rework-intent-upsert': ['intentId', 'findingId', 'sourceArtifact', 'sourceSha256', 'target'],
58
61
  'stage-status': ['stage']
59
62
  };
60
63
  const optional: Record<string, string[]> = {
61
64
  'finding-review': ['needsImplementation'],
62
65
  'decision-upsert': ['needsImplementation']
63
66
  };
64
- const overrideFields = kind === 'stage-status' || kind === 'decision-next-id'
67
+ const overrideFields = kind === 'stage-status' || kind === 'decision-next-id' || kind === 'rework-intent-upsert'
65
68
  ? []
66
69
  : ['overrideTicket', 'overrideTarget', 'overrideScope'];
67
70
  const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind]!, ...(optional[kind] ?? []), ...overrideFields]);
@@ -106,11 +109,11 @@ function taskLedger(args: string[] = []): void {
106
109
  let result;
107
110
  let humanOverride: unknown = null;
108
111
  try {
109
- result = withTaskExecutionLock(lockResolved.repoRoot, lockResolved.taskId, `task-ledger.${kind}`, () => {
112
+ result = await withTaskExecutionLock(lockResolved.repoRoot, lockResolved.taskId, `task-ledger.${kind}`, async () => {
110
113
  let current = applyLedgerIntent(values as LedgerIntent);
111
114
  if (current.status !== 'failed' || !values.overrideTicket) return current;
112
115
  if (!values.overrideTarget || !values.overrideScope) { usageFailure('override ticket requires target and scope'); return current; }
113
- const consumed = consumeHumanOverride({
116
+ const consumed = await consumeHumanOverride({
114
117
  taskRef,
115
118
  ticketId: String(values.overrideTicket),
116
119
  failureId: failureId('ledger-intent', current.error?.code ?? 'TASK_STATE_MISMATCH'),
@@ -21,7 +21,7 @@ function parseFailure(error: unknown): string {
21
21
  return message.replace(/^TASK_CONTROL_OPERATION_INVALID: /u, '');
22
22
  }
23
23
 
24
- function taskLifecycle(args: string[] = []): void {
24
+ async function taskLifecycle(args: string[] = []): Promise<void> {
25
25
  if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
26
26
 
27
27
  let operation;
@@ -56,7 +56,7 @@ function taskLifecycle(args: string[] = []): void {
56
56
  repoRoot = resolved.repoRoot;
57
57
  }
58
58
 
59
- const result = dispatchTaskControlOperation(
59
+ const result = await dispatchTaskControlOperation(
60
60
  createDirectHostExecutionContext({ repoRoot }),
61
61
  operation
62
62
  ) as TaskLifecycleResult & { humanOverride?: unknown };
@@ -37,7 +37,7 @@ function usageFailure(message: string): void {
37
37
  process.exitCode = 1;
38
38
  }
39
39
 
40
- function taskOverride(args: string[] = []): void {
40
+ async function taskOverride(args: string[] = []): Promise<void> {
41
41
  if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
42
42
  const [taskRef, operation] = args;
43
43
  if (!taskRef || !operation || !['diagnose', 'issue', 'consume'].includes(operation)) {
@@ -76,17 +76,17 @@ function taskOverride(args: string[] = []): void {
76
76
  let result;
77
77
  try {
78
78
  if (operation === 'diagnose') {
79
- result = diagnoseHumanOverrideForTask(
79
+ result = await diagnoseHumanOverrideForTask(
80
80
  taskRef,
81
81
  values.failureId ? String(values.failureId) : undefined,
82
82
  values.target ? String(values.target) : undefined
83
83
  );
84
84
  } else if (operation === 'issue') {
85
- result = withTaskExecutionLock(
85
+ result = await withTaskExecutionLock(
86
86
  resolved.repoRoot,
87
87
  resolved.taskId,
88
88
  'task-override.issue',
89
- () => issueHumanOverride({
89
+ async () => issueHumanOverride({
90
90
  taskRef,
91
91
  failureId: String(values.failureId),
92
92
  target: String(values.target),
@@ -103,11 +103,11 @@ function taskOverride(args: string[] = []): void {
103
103
  } satisfies HumanOverrideRequest)
104
104
  );
105
105
  } else {
106
- result = withTaskExecutionLock(
106
+ result = await withTaskExecutionLock(
107
107
  resolved.repoRoot,
108
108
  resolved.taskId,
109
109
  'task-override.consume',
110
- () => consumeHumanOverride({
110
+ async () => consumeHumanOverride({
111
111
  taskRef,
112
112
  ticketId: String(values.ticketId),
113
113
  failureId: String(values.failureId),
@@ -16,7 +16,7 @@ function failUsage(message: string): void {
16
16
  process.exitCode = 2;
17
17
  }
18
18
 
19
- function taskReview(args: string[] = []): void {
19
+ async function taskReview(args: string[] = []): Promise<void> {
20
20
  if (args[0] === '--help' || args[0] === '-h') {
21
21
  process.stdout.write(USAGE);
22
22
  return;
@@ -78,11 +78,11 @@ function taskReview(args: string[] = []): void {
78
78
  let result;
79
79
  let humanOverride: unknown = null;
80
80
  try {
81
- result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-review.finalize-summary', () => {
81
+ result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-review.finalize-summary', async () => {
82
82
  let current = finalizeReviewSummary({ taskRef: args[0]!, stage, artifact, orchestrated, dryRun }, { lockAlreadyHeld: true });
83
83
  if (current.status !== 'failed' || !overrideTicket) return current;
84
84
  if (!overrideTarget || !overrideScope) { failUsage('override ticket requires target and scope'); return current; }
85
- const consumed = consumeHumanOverride({
85
+ const consumed = await consumeHumanOverride({
86
86
  taskRef: args[0]!, ticketId: overrideTicket,
87
87
  failureId: failureId('review-finalization', current.error?.code ?? 'TASK_STATE_MISMATCH'),
88
88
  target: overrideTarget, scope: overrideScope
@@ -11,7 +11,7 @@ function fail(message: string): void {
11
11
  process.exitCode = 1;
12
12
  }
13
13
 
14
- function taskVerify(args: string[] = []): void {
14
+ async function taskVerify(args: string[] = []): Promise<void> {
15
15
  if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
16
16
  if (!args[0] || !args[1] || args[0].startsWith('--') || args[1].startsWith('--')) { fail('task ref and verification event are required'); return; }
17
17
  const taskRef = args[0];
@@ -41,8 +41,8 @@ function taskVerify(args: string[] = []): void {
41
41
  let result;
42
42
  let humanOverride: unknown = null;
43
43
  try {
44
- result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-verify.${event}`, () => {
45
- let current = verifyTaskEvent({ taskRef, event, ...(artifact ? { artifact } : {}) });
44
+ result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-verify.${event}`, async () => {
45
+ let current = await verifyTaskEvent({ taskRef, event, ...(artifact ? { artifact } : {}) });
46
46
  if (current.status === 'pass' || !overrideTicket) return current;
47
47
  if (!overrideTarget || !overrideScope) { fail('override ticket requires target and scope'); return current; }
48
48
  const failureProducer = current.status === 'blocked' || current.status === 'fail'
@@ -53,13 +53,13 @@ function taskVerify(args: string[] = []): void {
53
53
  : current.status === 'fail'
54
54
  ? 'CHECK_FAILED'
55
55
  : current.error?.code ?? 'VERIFY_TASK_STATE_MISMATCH';
56
- const consumed = consumeHumanOverride({
56
+ const consumed = await consumeHumanOverride({
57
57
  taskRef, ticketId: overrideTicket,
58
58
  failureId: failureId(failureProducer, failureCode),
59
59
  target: overrideTarget, scope: overrideScope
60
60
  }, {
61
- effectExecutor: (capability) => {
62
- const retried = verifyTaskEvent({ taskRef, event, ...(artifact ? { artifact } : {}) }, { manualOverride: capability });
61
+ effectExecutor: async (capability) => {
62
+ const retried = await verifyTaskEvent({ taskRef, event, ...(artifact ? { artifact } : {}) }, { manualOverride: capability });
63
63
  current = retried;
64
64
  return retried.status !== 'pass'
65
65
  ? { code: 'OVERRIDE_EFFECT_FAILED', message: `${retried.error?.code ?? 'VERIFY_FAILED'}: ${retried.error?.message ?? 'manual verification effect failed'}` }
@@ -17,7 +17,7 @@ function usageFailure(message: string): void {
17
17
  process.exitCode = 1;
18
18
  }
19
19
 
20
- function taskWarning(args: string[] = []): void {
20
+ async function taskWarning(args: string[] = []): Promise<void> {
21
21
  if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
22
22
  const [taskRef, kind] = args;
23
23
  if (!taskRef || !kind || !['add', 'set-status', 'list'].includes(kind)) { usageFailure('task ref and a valid intent are required'); return; }
@@ -54,11 +54,11 @@ function taskWarning(args: string[] = []): void {
54
54
  let result;
55
55
  let humanOverride: unknown = null;
56
56
  try {
57
- result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-warning.${kind}`, () => {
57
+ result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-warning.${kind}`, async () => {
58
58
  let current = applyWorkflowWarningIntent(values as WorkflowWarningIntent);
59
59
  if (current.status !== 'failed' || !values.overrideTicket) return current;
60
60
  if (!values.overrideTarget || !values.overrideScope) { usageFailure('override ticket requires target and scope'); return current; }
61
- const consumed = consumeHumanOverride({
61
+ const consumed = await consumeHumanOverride({
62
62
  taskRef, ticketId: String(values.overrideTicket),
63
63
  failureId: failureId('workflow-warning', current.error?.code ?? 'TASK_STATE_MISMATCH'),
64
64
  target: String(values.overrideTarget), scope: String(values.overrideScope)
@@ -1,4 +1,5 @@
1
1
  import type { PlatformError, PlatformResult } from './types.ts';
2
+ import type { ResourceIdentity } from './resource-identity.ts';
2
3
 
3
4
  type PlatformAdapterContext = {
4
5
  cwd: string;
@@ -28,6 +29,7 @@ type PlatformChangeRequestSnapshot = {
28
29
  assignees: string[];
29
30
  milestone: string | null;
30
31
  mergeability?: PlatformMergeability;
32
+ identity?: ResourceIdentity;
31
33
  };
32
34
 
33
35
  type PlatformCheckSnapshot = {
@@ -105,8 +107,7 @@ function registerPlatformCapabilities(
105
107
  'inspectChangeRequest' | 'inspectIssueClosingChangeRequests' | 'inspectRequiredChecks' | 'resolveChangeRequestGitEvidence'
106
108
  >
107
109
  ): void {
108
- const adapter = adapters.get(type);
109
- if (!adapter) throw new Error(`Platform adapter '${type}' must be registered before its capabilities`);
110
+ const adapter = adapters.get(type) ?? { type } as PlatformAdapter;
110
111
  adapters.set(type, { ...adapter, ...capabilities });
111
112
  }
112
113
 
@@ -3,7 +3,13 @@ import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { execFileSync, spawnSync } from "node:child_process";
5
5
 
6
- import { resolvePlatformChangeRequestGitEvidence } from "./adapters.ts";
6
+ import { resolvePlatformProviderContext } from './context.ts';
7
+ import {
8
+ providerError,
9
+ providerOperationContext,
10
+ unsupportedProviderOperation
11
+ } from './provider-bridge.ts';
12
+ import { isResourceIdentity } from './resource-identity.ts';
7
13
  import { resolveReviewedHeadRelation } from "./merged-pr-equivalence.ts";
8
14
  import type { ReviewedHeadRelation } from "./merged-pr-equivalence.ts";
9
15
  import type { PullRequestSnapshot } from "./pull-requests.ts";
@@ -53,25 +59,46 @@ function validMergeIdentity(lastReviewedCommit: string, pullRequest: PullRequest
53
59
  return null;
54
60
  }
55
61
 
56
- function resolveMaterializedReviewedHeadRelation(input: {
62
+ async function resolveMaterializedReviewedHeadRelation(input: {
57
63
  cwd: string;
58
64
  platformType: string | null;
59
65
  lastReviewedCommit: string;
60
66
  pullRequest: PullRequestSnapshot;
61
- }): ReviewedHeadRelation {
67
+ }): Promise<ReviewedHeadRelation> {
62
68
  const identityFailure = validMergeIdentity(input.lastReviewedCommit, input.pullRequest);
63
69
  if (identityFailure) return identityFailure;
64
70
 
65
- const source = resolvePlatformChangeRequestGitEvidence(input.platformType, {
71
+ const loaded = await resolvePlatformProviderContext({
66
72
  cwd: input.cwd,
67
- repository: input.pullRequest.repository,
68
- number: input.pullRequest.number,
69
- pullRequest: input.pullRequest
73
+ platformType: input.platformType || undefined
70
74
  });
75
+ if (!loaded.ok) {
76
+ return blocked(
77
+ loaded.context.error?.code || 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
78
+ loaded.context.error?.message || 'Unable to resolve the configured platform provider'
79
+ );
80
+ }
81
+ const target = isResourceIdentity(input.pullRequest.identity)
82
+ ? input.pullRequest.identity
83
+ : { kind: 'number' as const, value: input.pullRequest.number };
84
+ const operation = loaded.value.provider.changeRequests?.resolveGitEvidence
85
+ ? await loaded.value.provider.changeRequests.resolveGitEvidence({
86
+ context: providerOperationContext(loaded.value),
87
+ target,
88
+ expected: {
89
+ baseSha: input.pullRequest.base.sha,
90
+ headSha: input.pullRequest.head.sha,
91
+ targetBranch: input.pullRequest.base.ref
92
+ }
93
+ })
94
+ : unsupportedProviderOperation(loaded.value.provider, 'changeRequests.resolveGitEvidence');
95
+ const source = operation.ok
96
+ ? operation
97
+ : { ok: false as const, error: providerError(operation.error, 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE') };
71
98
  if (!source.ok || !source.value) {
72
99
  return blocked(
73
- source.error?.code || "PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE",
74
- source.error?.message || "Unable to resolve authoritative PR Git evidence"
100
+ ('error' in source && source.error?.code) || "PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE",
101
+ ('error' in source && source.error?.message) || "Unable to resolve authoritative PR Git evidence"
75
102
  );
76
103
  }
77
104
  if (
@@ -9,11 +9,12 @@ import { getOpenWorkflowWarnings } from '../task/workflow-warnings.ts';
9
9
  import { applyWorkflowWarningIntent } from '../task/workflow-warning-intents.ts';
10
10
  import { captureTaskWriteMetadata } from '../task/write.ts';
11
11
  import { syncPlatformComment } from './issue-comments.ts';
12
- import type { GitHubClient } from './github-client.ts';
12
+ import type { PlatformClient } from './context.ts';
13
13
  import { platformResult } from './types.ts';
14
14
  import type { PlatformOperation, PlatformResult } from './types.ts';
15
+ import { taskIssueIdentity } from './task-identities.ts';
15
16
 
16
- type CompletionBackfillOptions = { agent: string; cwd?: string; client?: GitHubClient };
17
+ type CompletionBackfillOptions = { agent: string; cwd?: string; client?: PlatformClient };
17
18
  type CompletionBackfillResult = PlatformResult & {
18
19
  artifacts: Array<{ artifact: string; status: PlatformResult['status'] }>;
19
20
  warnings: Array<{ id: string; status: string }>;
@@ -42,10 +43,10 @@ function excludedPrReview(taskDir: string, message: string): string | null {
42
43
  }
43
44
  }
44
45
 
45
- function backfillCompletionComments(
46
+ async function backfillCompletionComments(
46
47
  taskRef: string,
47
48
  options: CompletionBackfillOptions
48
- ): CompletionBackfillResult {
49
+ ): Promise<CompletionBackfillResult> {
49
50
  const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
50
51
  if (!resolved.ok) return result(platformResult('failed', {
51
52
  error: { code: resolved.code, message: resolved.message, retryable: false }
@@ -59,16 +60,16 @@ function backfillCompletionComments(
59
60
  }
60
61
  }), [], []);
61
62
  const initialContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
62
- const issueNumber = Number(parseTaskFrontmatter(initialContent).issue_number);
63
- if (!Number.isInteger(issueNumber) || issueNumber <= 0) return result(platformResult('no-op', {
64
- error: { code: 'ISSUE_NOT_LINKED', message: 'Task has no valid issue_number', retryable: false }
63
+ const issueIdentity = taskIssueIdentity(parseTaskFrontmatter(initialContent));
64
+ if (!issueIdentity) return result(platformResult('no-op', {
65
+ error: { code: 'ISSUE_NOT_LINKED', message: 'Task has no valid platform issue identity', retryable: false }
65
66
  }), [], []);
66
67
 
67
68
  const artifacts: CompletionBackfillResult['artifacts'] = [];
68
69
  const operations: PlatformOperation[] = [];
69
70
  let latest = platformResult('no-op', { error: null });
70
71
  for (const artifact of inventory.artifacts) {
71
- latest = syncPlatformComment(resolved.taskId, {
72
+ latest = await syncPlatformComment(resolved.taskId, {
72
73
  kind: 'artifact',
73
74
  artifact: artifact.name,
74
75
  agent: options.agent,
@@ -1,167 +1,130 @@
1
- import fs from 'node:fs';
2
1
  import path from 'node:path';
3
- import { execFileSync } from 'node:child_process';
4
2
 
5
- import semver from 'semver';
6
-
7
- import { getPlatformAdapter, registerPlatformAdapter } from './adapters.ts';
8
- import { createGitHubClient, MINIMUM_GITHUB_CLI_VERSION } from './github-client.ts';
9
- import type { GitHubClient } from './github-client.ts';
3
+ import { loadPlatformProvider } from './provider-loader.ts';
4
+ import { defaultGitRemote, parseGitHubRemote } from './github-provider.ts';
10
5
  import { platformResult } from './types.ts';
11
6
  import type { PlatformResult } from './types.ts';
7
+ import type { PlatformProvider, PlatformContextSnapshot, PlatformError } from './provider-contract.ts';
8
+
9
+ type PlatformClientResult<T> =
10
+ | { ok: true; value: T }
11
+ | { ok: false; error: { code: string; message: string; retryable: boolean } };
12
+ type PlatformClient = {
13
+ version(options?: { cwd?: string }): unknown;
14
+ json<T = unknown>(args: string[], options?: { cwd?: string; method?: string; input?: string }): any;
15
+ text?<T = string>(args: string[], options?: { cwd?: string; method?: string; input?: string }): any;
16
+ };
12
17
 
13
18
  type ContextOptions = {
14
19
  cwd?: string;
15
20
  gitRemote?: (cwd: string) => string | null;
16
- client?: {
17
- version(options?: Parameters<GitHubClient['version']>[0]): ReturnType<GitHubClient['version']>;
18
- json(args: string[], options?: Parameters<GitHubClient['json']>[1]): ReturnType<GitHubClient['json']>;
19
- };
21
+ client?: PlatformClient;
20
22
  platformType?: string;
21
23
  };
22
24
 
23
- const CURRENT_USER_QUERY = 'query { viewer { login } }';
24
-
25
- function findRepoRoot(cwd: string): string {
26
- try {
27
- return execFileSync('git', ['rev-parse', '--show-toplevel'], {
28
- cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe']
29
- }).trim();
30
- } catch {
31
- return cwd;
32
- }
33
- }
34
-
35
- function parseGitHubRemote(remote: string): string | null {
36
- const trimmed = remote.trim().replace(/\.git$/, '');
37
- const match = trimmed.match(/^(?:https?:\/\/github\.com\/|ssh:\/\/(?:git@)?github\.com\/|git@github\.com:)([^/\s]+\/[^/\s]+)$/i);
38
- return match?.[1] || null;
39
- }
40
-
41
- function defaultGitRemote(cwd: string): string | null {
42
- try {
43
- return execFileSync('git', ['remote', 'get-url', 'origin'], {
44
- cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe']
45
- }).trim();
46
- } catch {
47
- return null;
48
- }
49
- }
25
+ type LoadedContext = {
26
+ provider: PlatformProvider;
27
+ providerType: string;
28
+ repositoryRoot: string;
29
+ workingDirectory: string;
30
+ sourceIdentity: string;
31
+ snapshot: PlatformContextSnapshot;
32
+ context: PlatformResult;
33
+ };
50
34
 
51
- function readPlatformType(repoRoot: string): string | null {
52
- try {
53
- const config = JSON.parse(fs.readFileSync(path.join(repoRoot, '.agents', '.airc.json'), 'utf8'));
54
- return typeof config?.platform?.type === 'string' ? config.platform.type : null;
55
- } catch {
56
- return null;
57
- }
35
+ function errorStatus(error: PlatformError): PlatformResult['status'] {
36
+ if (error.retryable || error.code === 'AUTH_REQUIRED' || error.code === 'PLATFORM_DEPENDENCY_MISSING') return 'blocked';
37
+ if (error.code === 'REMOTE_MISSING' || error.code === 'PLATFORM_UNSUPPORTED') return 'no-op';
38
+ return 'failed';
58
39
  }
59
40
 
60
- function failure(error: { code: string; message: string; retryable: boolean }, repo: string | null): PlatformResult {
61
- const blocked = error.retryable || error.code === 'AUTH_REQUIRED' || error.code === 'PLATFORM_DEPENDENCY_MISSING';
62
- return platformResult(blocked ? 'blocked' : 'failed', {
63
- platform: { type: 'github', repository: repo, currentUser: null }, error
41
+ function contextError(
42
+ providerType: string,
43
+ error: PlatformError,
44
+ repository: string | null = null
45
+ ): PlatformResult {
46
+ return platformResult(errorStatus(error), {
47
+ platform: { type: providerType || null, repository, currentUser: null },
48
+ error: { code: error.code, message: error.message, retryable: error.retryable }
64
49
  });
65
50
  }
66
51
 
67
- function resolveGitHubContext(options: ContextOptions & { cwd: string }): PlatformResult {
68
- const { cwd } = options;
69
- const platform = 'github';
70
- const client = options.client || createGitHubClient();
71
- const version = client.version({ cwd });
72
- if (!version.ok) return failure(version.error, null);
73
- if (!semver.gte(version.value, MINIMUM_GITHUB_CLI_VERSION)) {
74
- return failure({
75
- code: 'GH_CLI_VERSION_UNSUPPORTED',
76
- message: `GitHub CLI ${version.value} is unsupported; install gh >= ${MINIMUM_GITHUB_CLI_VERSION}`,
77
- retryable: false
78
- }, null);
79
- }
80
- const remote = (options.gitRemote || defaultGitRemote)(cwd);
81
- if (!remote) {
52
+ function contextFromSnapshot(
53
+ providerType: string,
54
+ snapshot: PlatformContextSnapshot
55
+ ): PlatformResult {
56
+ if (providerType === 'none') {
82
57
  return platformResult('no-op', {
83
- platform: { type: platform, repository: null, currentUser: null },
84
- error: { code: 'REMOTE_MISSING', message: 'Git origin remote is not configured', retryable: false }
85
- });
86
- }
87
- const ownerRepo = parseGitHubRemote(remote);
88
- if (!ownerRepo) {
89
- const nonGitHub = !/github\.com/i.test(remote);
90
- return platformResult(nonGitHub ? 'no-op' : 'failed', {
91
- platform: { type: platform, repository: null, currentUser: null },
92
- error: {
93
- code: nonGitHub ? 'PLATFORM_UNSUPPORTED' : 'REMOTE_INVALID',
94
- message: `Unable to parse GitHub owner/repo from '${remote}'`,
95
- retryable: false
96
- }
97
- });
98
- }
99
-
100
- const repository = client.json(['api', `repos/${ownerRepo}`], { cwd });
101
- if (!repository.ok) return failure(repository.error, null);
102
- const repositoryValue = repository.value as { fork?: boolean; full_name?: string; parent?: { full_name?: string } } | null;
103
- const upstream = repositoryValue?.fork ? repositoryValue.parent?.full_name : repositoryValue?.full_name;
104
- if (!upstream) {
105
- return platformResult('blocked', {
106
- platform: { type: platform, repository: null, currentUser: null },
107
- error: { code: 'UPSTREAM_UNRESOLVED', message: 'Unable to resolve the upstream repository', retryable: false }
58
+ platform: { type: 'none', repository: null, currentUser: null },
59
+ operations: [{ name: 'resolve', status: 'no-op', reasonCode: 'PLATFORM_DISABLED' }]
108
60
  });
109
61
  }
110
- const user = client.json(['api', 'graphql', '-f', `query=${CURRENT_USER_QUERY}`], { cwd });
111
- if (!user.ok) return failure(user.error, upstream);
112
- const userValue = user.value as { data?: { viewer?: { login?: string } } } | null;
113
- const currentUser = userValue?.data?.viewer?.login || null;
114
- const permissions = client.json(['api', `repos/${upstream}`], { cwd });
115
- if (!permissions.ok) return failure(permissions.error, upstream);
116
- const permissionValue = permissions.value as { permissions?: Record<string, boolean> } | null;
117
- const values = permissionValue?.permissions || {};
118
- const capabilities = {
119
- authenticated: Boolean(currentUser),
120
- comment: Boolean(currentUser),
121
- triage: Boolean(values.triage || values.push || values.admin),
122
- push: Boolean(values.push || values.admin),
123
- admin: Boolean(values.admin)
124
- };
62
+ const capabilities = snapshot.capabilities;
125
63
  const status = Object.values(capabilities).every(Boolean) ? 'no-op' : 'degraded';
64
+ const repository = snapshot.scope.label || snapshot.scope.id || null;
126
65
  return platformResult(status, {
127
- changed: false,
128
- platform: { type: platform, repository: upstream, currentUser },
66
+ platform: {
67
+ type: snapshot.type || providerType,
68
+ repository,
69
+ currentUser: snapshot.currentUser?.name || snapshot.currentUser?.id || null
70
+ },
129
71
  resource: { kind: 'repository', number: null },
130
72
  capabilities,
131
73
  operations: [{ name: 'resolve', status: 'no-op', reasonCode: null }]
132
74
  });
133
75
  }
134
76
 
135
- registerPlatformAdapter({
136
- type: 'github',
137
- resolveContext(options) {
138
- return resolveGitHubContext(options as ContextOptions & { cwd: string });
77
+ async function resolvePlatformProviderContext(options: ContextOptions = {}): Promise<
78
+ { ok: true; value: LoadedContext } | { ok: false; context: PlatformResult }
79
+ > {
80
+ const workingDirectory = path.resolve(options.cwd || process.cwd());
81
+ const loaded = await loadPlatformProvider({
82
+ cwd: workingDirectory,
83
+ platformType: options.platformType,
84
+ client: options.client
85
+ });
86
+ if (!loaded.ok) {
87
+ if (!loaded.error.providerType && options.platformType === undefined) {
88
+ return {
89
+ ok: false,
90
+ context: platformResult('no-op', {
91
+ platform: { type: null, repository: null, currentUser: null },
92
+ error: {
93
+ code: 'PLATFORM_UNSUPPORTED',
94
+ message: 'No platform provider is configured',
95
+ retryable: false
96
+ }
97
+ })
98
+ };
99
+ }
100
+ return { ok: false, context: contextError(loaded.error.providerType || options.platformType || '', loaded.error) };
139
101
  }
140
- });
141
102
 
142
- registerPlatformAdapter({
143
- type: 'none',
144
- resolveContext() {
145
- return platformResult('no-op', {
146
- platform: { type: 'none', repository: null, currentUser: null },
147
- operations: [{ name: 'resolve', status: 'no-op', reasonCode: 'PLATFORM_DISABLED' }]
148
- });
149
- }
150
- });
103
+ const gitRemote = options.gitRemote ? options.gitRemote(workingDirectory) : null;
104
+ const resolved = await loaded.value.provider.context.resolve({
105
+ repositoryRoot: loaded.value.repositoryRoot,
106
+ workingDirectory,
107
+ scopeId: loaded.value.repositoryRoot,
108
+ gitRemote
109
+ });
110
+ if (!resolved.ok) return {
111
+ ok: false,
112
+ context: contextError(loaded.value.providerType, resolved.error)
113
+ };
114
+ return {
115
+ ok: true,
116
+ value: {
117
+ ...loaded.value,
118
+ snapshot: resolved.value,
119
+ context: contextFromSnapshot(loaded.value.providerType, resolved.value)
120
+ }
121
+ };
122
+ }
151
123
 
152
- function resolvePlatformContext(options: ContextOptions = {}): PlatformResult {
153
- const cwd = path.resolve(options.cwd || process.cwd());
154
- const repoRoot = findRepoRoot(cwd);
155
- const platform = options.platformType ?? readPlatformType(repoRoot);
156
- const adapter = getPlatformAdapter(platform);
157
- if (!adapter) {
158
- return platformResult('no-op', {
159
- platform: { type: platform, repository: null, currentUser: null },
160
- error: { code: 'PLATFORM_UNSUPPORTED', message: `Platform '${platform || 'none'}' has no registered adapter`, retryable: false }
161
- });
162
- }
163
- return adapter.resolveContext({ cwd, gitRemote: options.gitRemote, client: options.client });
124
+ async function resolvePlatformContext(options: ContextOptions = {}): Promise<PlatformResult> {
125
+ const resolved = await resolvePlatformProviderContext(options);
126
+ return resolved.ok ? resolved.value.context : resolved.context;
164
127
  }
165
128
 
166
- export { parseGitHubRemote, resolvePlatformContext };
167
- export type { ContextOptions };
129
+ export { defaultGitRemote, parseGitHubRemote, resolvePlatformContext, resolvePlatformProviderContext };
130
+ export type { ContextOptions, LoadedContext, PlatformClient, PlatformClientResult };
@@ -8,7 +8,7 @@ import type { PlatformError } from './types.ts';
8
8
  type RunResult = { status: number | null; stdout: string; stderr: string; error?: Error };
9
9
  type RunOptions = { cwd?: string; input?: string };
10
10
  type Runner = (args: string[], options: RunOptions) => RunResult;
11
- type RequestOptions = RunOptions & { method?: 'GET' | 'PATCH' | 'POST' | 'DELETE' };
11
+ type RequestOptions = RunOptions & { method?: 'GET' | 'PATCH' | 'POST' | 'PUT' | 'DELETE' };
12
12
  type ClientResult<T> = { ok: true; value: T } | { ok: false; error: PlatformError };
13
13
  type ResponseMetadata = {
14
14
  status: number;
@@ -158,7 +158,7 @@ function createGitHubClient(options: ClientOptions = {}): GitHubClient {
158
158
 
159
159
  function run(args: string[], request: RequestOptions = {}): ClientResult<string> {
160
160
  const method = request.method || 'GET';
161
- const retryableMethod = method === 'GET' || method === 'PATCH';
161
+ const retryableMethod = method === 'GET' || method === 'PATCH' || method === 'PUT';
162
162
  let attempt = 0;
163
163
  while (true) {
164
164
  const result = runner(args, request);