@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.
- package/README.md +11 -11
- package/README.zh-CN.md +11 -11
- package/dist/bin/internal-cli.js +24 -19
- package/dist/lib/agent-clients/next-steps.js +6 -1
- package/dist/lib/decide.js +3 -3
- package/dist/lib/internal/platform-checks.js +3 -3
- package/dist/lib/internal/platform-comment.js +9 -9
- package/dist/lib/internal/platform-context.js +2 -2
- package/dist/lib/internal/platform-issue.js +8 -9
- package/dist/lib/internal/platform-pr-review.js +10 -10
- package/dist/lib/internal/platform-pr.js +63 -24
- package/dist/lib/internal/platform-release-notes.js +3 -3
- package/dist/lib/internal/pr-review-grade.js +11 -9
- package/dist/lib/internal/release-workflow.js +4 -4
- package/dist/lib/internal/sandbox-control.js +30 -4
- package/dist/lib/internal/task-activity.js +3 -3
- package/dist/lib/internal/task-artifact.js +76 -24
- package/dist/lib/internal/task-create.js +2 -2
- package/dist/lib/internal/task-event.js +6 -3
- package/dist/lib/internal/task-finalization.js +2 -2
- package/dist/lib/internal/task-invalidation.js +60 -0
- package/dist/lib/internal/task-ledger.js +9 -6
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-override.js +4 -4
- package/dist/lib/internal/task-review.js +3 -3
- package/dist/lib/internal/task-verify.js +6 -6
- package/dist/lib/internal/task-warning.js +3 -3
- package/dist/lib/platform/adapters.js +1 -3
- package/dist/lib/platform/change-request-git-evidence.js +27 -7
- package/dist/lib/platform/completion-backfill.js +6 -5
- package/dist/lib/platform/context.js +70 -130
- package/dist/lib/platform/github-client.js +1 -1
- package/dist/lib/platform/github-provider.js +903 -0
- package/dist/lib/platform/in-label-sync.js +143 -0
- package/dist/lib/platform/issue-comments.js +94 -41
- package/dist/lib/platform/issues.js +335 -189
- package/dist/lib/platform/metadata-labels.js +6 -2
- package/dist/lib/platform/none-provider.js +28 -0
- package/dist/lib/platform/pr-change-report.js +551 -0
- package/dist/lib/platform/pr-checks.js +119 -104
- package/dist/lib/platform/pr-review.js +88 -72
- package/dist/lib/platform/pr-summary.js +520 -50
- package/dist/lib/platform/provider-bridge.js +45 -0
- package/dist/lib/platform/provider-contract.d.ts +487 -0
- package/dist/lib/platform/provider-contract.js +95 -0
- package/dist/lib/platform/provider-loader.js +251 -0
- package/dist/lib/platform/provider-validation.js +504 -0
- package/dist/lib/platform/pull-request-metadata.js +8 -6
- package/dist/lib/platform/pull-requests.js +783 -537
- package/dist/lib/platform/release-notes.js +78 -55
- package/dist/lib/platform/releases.js +79 -50
- package/dist/lib/platform/resource-identity.d.ts +26 -0
- package/dist/lib/platform/resource-identity.js +92 -0
- package/dist/lib/platform/task-identities.js +31 -0
- package/dist/lib/platform/verification-required.js +11 -12
- package/dist/lib/platform/verification-sync.js +112 -238
- package/dist/lib/pr-review/evidence-grading.js +19 -6
- package/dist/lib/run/index.js +63 -23
- package/dist/lib/sandbox/control/client.js +96 -15
- package/dist/lib/sandbox/control/executor.js +3 -3
- package/dist/lib/sandbox/control/protocol.js +77 -0
- package/dist/lib/sandbox/control/server.js +398 -25
- package/dist/lib/sandbox/control/state.js +204 -1
- package/dist/lib/server/protocol.js +38 -1
- package/dist/lib/task/activity-log.js +8 -1
- package/dist/lib/task/artifact-lifecycle.js +33 -5
- package/dist/lib/task/capabilities.js +239 -0
- package/dist/lib/task/commands/status.js +32 -0
- package/dist/lib/task/commit-operation.js +12 -1
- package/dist/lib/task/control-authority.js +8 -5
- package/dist/lib/task/create-service.js +8 -8
- package/dist/lib/task/current-contract.js +5 -1
- package/dist/lib/task/decision-details.js +1 -1
- package/dist/lib/task/events.js +297 -17
- package/dist/lib/task/finalization.js +37 -13
- package/dist/lib/task/human-override.js +58 -36
- package/dist/lib/task/invalidation-command.js +86 -0
- package/dist/lib/task/invalidation.js +260 -0
- package/dist/lib/task/ledger-intents.js +37 -0
- package/dist/lib/task/local-artifact-finalization.js +374 -0
- package/dist/lib/task/orchestration.js +49 -93
- package/dist/lib/task/pr-delivery-fact.js +102 -101
- package/dist/lib/task/review-fingerprint.js +24 -2
- package/dist/lib/task/rework-intent.js +138 -0
- package/dist/lib/task/skill-scope.js +23 -0
- package/dist/lib/task/task-execution-lock.js +27 -17
- package/dist/lib/task/verification-engine.js +43 -16
- package/dist/lib/task/verification.js +3 -3
- package/dist/lib/task/workflow-warning-intents.js +1 -1
- package/dist/lib/task/write.js +11 -0
- package/dist/lib/version.js +11 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/next-steps.ts +6 -1
- package/lib/decide.ts +3 -3
- package/lib/init.ts +1 -1
- package/lib/internal/platform-checks.ts +3 -3
- package/lib/internal/platform-comment.ts +8 -8
- package/lib/internal/platform-context.ts +2 -2
- package/lib/internal/platform-issue.ts +7 -8
- package/lib/internal/platform-pr-review.ts +9 -9
- package/lib/internal/platform-pr.ts +47 -22
- package/lib/internal/platform-release-notes.ts +3 -3
- package/lib/internal/pr-review-grade.ts +10 -8
- package/lib/internal/release-workflow.ts +4 -4
- package/lib/internal/sandbox-control.ts +27 -3
- package/lib/internal/task-activity.ts +3 -3
- package/lib/internal/task-artifact.ts +65 -22
- package/lib/internal/task-create.ts +2 -2
- package/lib/internal/task-event.ts +6 -3
- package/lib/internal/task-finalization.ts +3 -3
- package/lib/internal/task-invalidation.ts +39 -0
- package/lib/internal/task-ledger.ts +11 -8
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-override.ts +6 -6
- package/lib/internal/task-review.ts +3 -3
- package/lib/internal/task-verify.ts +6 -6
- package/lib/internal/task-warning.ts +3 -3
- package/lib/platform/adapters.ts +3 -2
- package/lib/platform/change-request-git-evidence.ts +36 -9
- package/lib/platform/completion-backfill.ts +9 -8
- package/lib/platform/context.ts +97 -134
- package/lib/platform/github-client.ts +2 -2
- package/lib/platform/github-provider.ts +910 -0
- package/lib/platform/in-label-sync.ts +208 -0
- package/lib/platform/issue-comments.ts +97 -47
- package/lib/platform/issues.ts +344 -174
- package/lib/platform/metadata-labels.ts +5 -2
- package/lib/platform/none-provider.ts +35 -0
- package/lib/platform/pr-change-report.ts +645 -0
- package/lib/platform/pr-checks.ts +126 -106
- package/lib/platform/pr-review.ts +88 -85
- package/lib/platform/pr-summary.ts +560 -53
- package/lib/platform/provider-bridge.ts +82 -0
- package/lib/platform/provider-contract.ts +536 -0
- package/lib/platform/provider-loader.ts +307 -0
- package/lib/platform/provider-validation.ts +529 -0
- package/lib/platform/pull-request-metadata.ts +10 -7
- package/lib/platform/pull-requests.ts +826 -543
- package/lib/platform/release-notes.ts +76 -59
- package/lib/platform/releases.ts +77 -43
- package/lib/platform/resource-identity.ts +121 -0
- package/lib/platform/task-identities.ts +40 -0
- package/lib/platform/types.ts +4 -2
- package/lib/platform/verification-required.ts +11 -13
- package/lib/platform/verification-sync.ts +112 -292
- package/lib/pr-review/evidence-grading.ts +25 -8
- package/lib/run/index.ts +57 -22
- package/lib/sandbox/control/client.ts +105 -15
- package/lib/sandbox/control/executor.ts +4 -4
- package/lib/sandbox/control/protocol.ts +113 -0
- package/lib/sandbox/control/server.ts +431 -23
- package/lib/sandbox/control/state.ts +251 -1
- package/lib/server/protocol.ts +36 -1
- package/lib/task/activity-log.ts +8 -0
- package/lib/task/artifact-lifecycle.ts +32 -6
- package/lib/task/capabilities.ts +262 -0
- package/lib/task/commands/status.ts +38 -0
- package/lib/task/commit-operation.ts +12 -1
- package/lib/task/control-authority.ts +11 -8
- package/lib/task/create-service.ts +8 -8
- package/lib/task/current-contract.ts +6 -1
- package/lib/task/decision-details.ts +6 -2
- package/lib/task/events.ts +296 -16
- package/lib/task/finalization.ts +50 -14
- package/lib/task/human-override.ts +70 -46
- package/lib/task/invalidation-command.ts +86 -0
- package/lib/task/invalidation.ts +332 -0
- package/lib/task/ledger-intents.ts +35 -1
- package/lib/task/local-artifact-finalization.ts +563 -0
- package/lib/task/orchestration.ts +52 -85
- package/lib/task/pr-delivery-fact.ts +131 -148
- package/lib/task/review-fingerprint.ts +23 -2
- package/lib/task/rework-intent.ts +165 -0
- package/lib/task/skill-scope.ts +24 -0
- package/lib/task/task-execution-lock.ts +23 -14
- package/lib/task/verification-engine.ts +52 -17
- package/lib/task/verification.ts +4 -4
- package/lib/task/workflow-warning-intents.ts +1 -1
- package/lib/task/write.ts +19 -0
- package/lib/update.ts +1 -1
- package/lib/version.ts +15 -1
- package/package.json +7 -1
- package/templates/.agents/rules/README.en.md +1 -1
- package/templates/.agents/rules/README.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.en.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +2 -0
- package/templates/.agents/rules/issue-sync.zh-CN.md +2 -0
- package/templates/.agents/rules/local-artifact-repair.en.md +9 -2
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +9 -2
- package/templates/.agents/rules/pr-sync.en.md +10 -4
- package/templates/.agents/rules/pr-sync.zh-CN.md +17 -4
- package/templates/.agents/rules/task-short-id.en.md +1 -4
- package/templates/.agents/rules/task-short-id.zh-CN.md +1 -3
- package/templates/.agents/skills/analyze-task/SKILL.en.md +13 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -3
- package/templates/.agents/skills/check-task/SKILL.en.md +16 -37
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -37
- package/templates/.agents/skills/code-task/SKILL.en.md +27 -8
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +29 -10
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +2 -0
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +2 -0
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +3 -3
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +5 -3
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +4 -3
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +4 -3
- package/templates/.agents/skills/complete-task/SKILL.en.md +21 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/create-pr/SKILL.en.md +7 -6
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/create-pr/config/verify.json +1 -0
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +16 -4
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +16 -4
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +5 -3
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +5 -3
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/scripts/change-report.mjs +3 -0
- package/templates/.agents/skills/plan-task/SKILL.en.md +13 -3
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +10 -3
- package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-code/SKILL.en.md +4 -4
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +4 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +4 -2
- package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +6 -0
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +6 -0
- package/templates/.agents/templates/task.en.md +18 -1
- package/templates/.agents/templates/task.zh-CN.md +18 -1
- package/templates/.github/workflows/pr-label.yml +7 -26
|
@@ -2,19 +2,22 @@ import fs from 'node:fs';
|
|
|
2
2
|
|
|
3
3
|
import { parseTypedTaskFrontmatter } from '../task/frontmatter.ts';
|
|
4
4
|
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
5
|
-
import {
|
|
6
|
-
inspectPlatformRequiredChecks,
|
|
7
|
-
registerPlatformCapabilities
|
|
8
|
-
} from './adapters.ts';
|
|
9
5
|
import type { PlatformCheckSnapshot } from './adapters.ts';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import type { GitHubClient } from './github-client.ts';
|
|
6
|
+
import { resolvePlatformProviderContext } from './context.ts';
|
|
7
|
+
import type { PlatformClient } from './context.ts';
|
|
13
8
|
import { inspectPlatformPullRequest } from './pull-requests.ts';
|
|
14
9
|
import { platformResult } from './types.ts';
|
|
15
10
|
import type { PlatformResult } from './types.ts';
|
|
16
11
|
import type { PullRequestSnapshot } from './pull-requests.ts';
|
|
17
12
|
import { readPrDeliveryFact } from '../task/pr-delivery-fact.ts';
|
|
13
|
+
import {
|
|
14
|
+
providerError,
|
|
15
|
+
providerOperationContext,
|
|
16
|
+
providerStatus,
|
|
17
|
+
resourceIdentity,
|
|
18
|
+
resourceIdentityNumber,
|
|
19
|
+
unsupportedProviderOperation
|
|
20
|
+
} from './provider-bridge.ts';
|
|
18
21
|
|
|
19
22
|
type CheckBucket = 'pass' | 'fail' | 'pending' | 'cancel';
|
|
20
23
|
type CheckState = 'passed' | 'failed' | 'pending' | 'timed-out' | 'cancelled' | 'no-required';
|
|
@@ -30,8 +33,9 @@ type ChecksResult = PlatformResult & {
|
|
|
30
33
|
resolution?: { status: 'resolved' | 'missing' | 'ambiguous'; runId: number | null; jobId: number | null };
|
|
31
34
|
logs?: { runId: number; jobId?: number; text: string };
|
|
32
35
|
};
|
|
33
|
-
type
|
|
34
|
-
type
|
|
36
|
+
type GitHubClient = PlatformClient;
|
|
37
|
+
type InspectionOptions = { cwd?: string; client?: PlatformClient; runtimeVersion?: string };
|
|
38
|
+
type SharedOptions = { cwd?: string; client?: PlatformClient };
|
|
35
39
|
|
|
36
40
|
function classifyRequiredChecks(required: CheckSnapshot[]): ChecksSnapshot {
|
|
37
41
|
if (required.length === 0) return { state: 'no-required', required };
|
|
@@ -81,8 +85,8 @@ function checksResult(status: PlatformResult['status'], overrides: Partial<Check
|
|
|
81
85
|
};
|
|
82
86
|
}
|
|
83
87
|
|
|
84
|
-
function normalizeBucket(value: { bucket?: string; state?: string; conclusion?: string }): CheckBucket {
|
|
85
|
-
const raw = String(value.bucket || value.conclusion || value.state || '').toLowerCase();
|
|
88
|
+
function normalizeBucket(value: { bucket?: string; status?: string; state?: string; conclusion?: string }): CheckBucket {
|
|
89
|
+
const raw = String(value.bucket || value.status || value.conclusion || value.state || '').toLowerCase();
|
|
86
90
|
if (['pass', 'success', 'successful', 'neutral'].includes(raw)) return 'pass';
|
|
87
91
|
if (['fail', 'failure', 'failed', 'error', 'timed_out', 'action_required'].includes(raw)) return 'fail';
|
|
88
92
|
if (['cancel', 'cancelled', 'canceled', 'skipped', 'stale'].includes(raw)) return 'cancel';
|
|
@@ -106,85 +110,81 @@ function normalizeChecks(value: unknown): CheckSnapshot[] {
|
|
|
106
110
|
});
|
|
107
111
|
}
|
|
108
112
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
: { ok: false, error: inspected.error };
|
|
119
|
-
}
|
|
120
|
-
});
|
|
113
|
+
function inspectGitHubRequiredChecks(client: GitHubClient, repository: string, number: number, cwd: string) {
|
|
114
|
+
const inspected = client.json<unknown>([
|
|
115
|
+
'pr', 'checks', String(number), '--repo', repository,
|
|
116
|
+
'--json', 'name,state,bucket,link,workflow,startedAt,completedAt'
|
|
117
|
+
], { cwd });
|
|
118
|
+
return inspected.ok
|
|
119
|
+
? { ok: true as const, value: normalizeChecks(inspected.value) }
|
|
120
|
+
: { ok: false as const, error: inspected.error };
|
|
121
|
+
}
|
|
121
122
|
|
|
122
|
-
function resolvedTask(taskRef: string, options: InspectionOptions) {
|
|
123
|
+
async function resolvedTask(taskRef: string, options: InspectionOptions) {
|
|
123
124
|
const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
|
|
124
125
|
if (!resolved.ok) return { ok: false as const, output: checksResult('failed', { error: { code: resolved.code, message: resolved.message, retryable: false } }) };
|
|
125
126
|
const frontmatter = parseTypedTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8'));
|
|
126
|
-
const fact = readPrDeliveryFact(frontmatter);
|
|
127
|
-
if (fact.status === 'invalid') return { ok: false as const, output: checksResult('failed', { error: { code:
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
const
|
|
127
|
+
const fact = readPrDeliveryFact(frontmatter, options.runtimeVersion);
|
|
128
|
+
if (fact.status === 'invalid') return { ok: false as const, output: checksResult('failed', { error: { code: fact.error.code, message: fact.error.message, retryable: false } }) };
|
|
129
|
+
const prIdentity = fact.status === 'valid' && fact.fact.state === 'bound' ? fact.fact.identity.resource : null;
|
|
130
|
+
const prNumber = resourceIdentityNumber(prIdentity);
|
|
131
|
+
if (!prIdentity) return { ok: false as const, output: checksResult('failed', { error: { code: fact.status === 'missing' ? 'PR_DELIVERY_FACT_MISSING' : 'PR_NOT_LINKED', message: 'Task has no verified bound pull request', retryable: false } }) };
|
|
132
|
+
const loaded = await resolvePlatformProviderContext({ cwd: resolved.repoRoot, client: options.client });
|
|
133
|
+
const context = loaded.ok ? loaded.value.context : loaded.context;
|
|
132
134
|
if (!context.platform.repository || !['no-op', 'degraded'].includes(context.status)) return { ok: false as const, output: checksResult(context.status, { platform: context.platform, capabilities: context.capabilities, error: context.error }) };
|
|
133
|
-
const inspected = inspectPlatformPullRequest(taskRef, { cwd: resolved.repoRoot, client });
|
|
135
|
+
const inspected = await inspectPlatformPullRequest(taskRef, { cwd: resolved.repoRoot, client: options.client });
|
|
134
136
|
if (!inspected.pullRequest) return { ok: false as const, output: checksResult(inspected.status, { platform: context.platform, capabilities: context.capabilities, error: inspected.error }) };
|
|
135
|
-
return { ok:
|
|
137
|
+
if (!loaded.ok) return { ok: false as const, output: checksResult('failed', { platform: context.platform, capabilities: context.capabilities, error: context.error }) };
|
|
138
|
+
return { ok: true as const, resolved, prNumber, prIdentity, client: options.client, context, pullRequest: inspected.pullRequest, provider: loaded.value.provider, loadedContext: loaded.value };
|
|
136
139
|
}
|
|
137
140
|
|
|
138
|
-
function inspectRequiredChecks(taskRef: string, options: InspectionOptions = {}): ChecksResult {
|
|
139
|
-
const base = resolvedTask(taskRef, options);
|
|
141
|
+
async function inspectRequiredChecks(taskRef: string, options: InspectionOptions = {}): Promise<ChecksResult> {
|
|
142
|
+
const base = await resolvedTask(taskRef, options);
|
|
140
143
|
if (!base.ok) return base.output;
|
|
141
144
|
return inspectChecksForResolvedTask(base, options);
|
|
142
145
|
}
|
|
143
146
|
|
|
144
|
-
function inspectChecksForResolvedTask(
|
|
145
|
-
base: Extract<ReturnType<typeof resolvedTask
|
|
147
|
+
async function inspectChecksForResolvedTask(
|
|
148
|
+
base: Extract<Awaited<ReturnType<typeof resolvedTask>>, { ok: true }>,
|
|
146
149
|
options: InspectionOptions
|
|
147
|
-
): ChecksResult {
|
|
150
|
+
): Promise<ChecksResult> {
|
|
148
151
|
const repository = base.context.platform.repository!;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
retryable: false
|
|
161
|
-
};
|
|
162
|
-
return checksResult(error.retryable ? 'blocked' : 'failed', {
|
|
163
|
-
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
152
|
+
{
|
|
153
|
+
const inspected = base.provider.checks?.inspectRequired
|
|
154
|
+
? await base.provider.checks.inspectRequired({
|
|
155
|
+
context: providerOperationContext(base.loadedContext),
|
|
156
|
+
changeRequest: base.prIdentity,
|
|
157
|
+
headSha: base.pullRequest.head.sha
|
|
158
|
+
})
|
|
159
|
+
: unsupportedProviderOperation(base.provider, 'checks.inspectRequired');
|
|
160
|
+
if (!inspected.ok) return checksResult(providerStatus(inspected.error), {
|
|
161
|
+
platform: base.context.platform,
|
|
162
|
+
capabilities: base.context.capabilities,
|
|
164
163
|
resource: { kind: 'pull-request', number: base.prNumber },
|
|
165
|
-
pullRequest: base.pullRequest,
|
|
164
|
+
pullRequest: base.pullRequest,
|
|
165
|
+
error: providerError(inspected.error, 'PLATFORM_PROVIDER_OPERATION_FAILED')
|
|
166
|
+
});
|
|
167
|
+
const required = normalizeChecks(inspected.value);
|
|
168
|
+
const classified = classifyRequiredChecks(required);
|
|
169
|
+
const status = classified.state === 'passed' || classified.state === 'no-required'
|
|
170
|
+
? 'no-op'
|
|
171
|
+
: classified.state === 'failed' || classified.state === 'cancelled' ? 'failed' : 'blocked';
|
|
172
|
+
return checksResult(status, {
|
|
173
|
+
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
174
|
+
resource: { kind: 'pull-request', number: base.prNumber }, pullRequest: base.pullRequest,
|
|
175
|
+
checks: classified,
|
|
176
|
+
error: status === 'no-op' ? null : {
|
|
177
|
+
code: classified.state === 'pending' ? 'REQUIRED_CHECKS_PENDING' : `REQUIRED_CHECKS_${classified.state.toUpperCase()}`,
|
|
178
|
+
message: `Required checks are ${classified.state}`, retryable: classified.state === 'pending'
|
|
179
|
+
}
|
|
166
180
|
});
|
|
167
181
|
}
|
|
168
|
-
const classified = classifyRequiredChecks(inspected.value);
|
|
169
|
-
const status = classified.state === 'passed' || classified.state === 'no-required'
|
|
170
|
-
? 'no-op'
|
|
171
|
-
: classified.state === 'failed' || classified.state === 'cancelled' ? 'failed' : 'blocked';
|
|
172
|
-
return checksResult(status, {
|
|
173
|
-
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
174
|
-
resource: { kind: 'pull-request', number: base.prNumber },
|
|
175
|
-
pullRequest: base.pullRequest, checks: classified,
|
|
176
|
-
error: status === 'no-op' ? null : {
|
|
177
|
-
code: classified.state === 'pending' ? 'REQUIRED_CHECKS_PENDING' : `REQUIRED_CHECKS_${classified.state.toUpperCase()}`,
|
|
178
|
-
message: `Required checks are ${classified.state}`,
|
|
179
|
-
retryable: classified.state === 'pending'
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
function inspectPullRequestReadiness(taskRef: string, options: InspectionOptions = {}): ChecksResult {
|
|
185
|
-
const base = resolvedTask(taskRef, options);
|
|
184
|
+
async function inspectPullRequestReadiness(taskRef: string, options: InspectionOptions = {}): Promise<ChecksResult> {
|
|
185
|
+
const base = await resolvedTask(taskRef, options);
|
|
186
186
|
if (!base.ok) return base.output;
|
|
187
|
-
const checked = inspectChecksForResolvedTask(base, options);
|
|
187
|
+
const checked = await inspectChecksForResolvedTask(base, options);
|
|
188
188
|
const classifiedCodes = new Set(['REQUIRED_CHECKS_PENDING', 'REQUIRED_CHECKS_FAILED', 'REQUIRED_CHECKS_CANCELLED']);
|
|
189
189
|
if (checked.error && !classifiedCodes.has(checked.error.code)) return checked;
|
|
190
190
|
const readiness = classifyPullRequestReadiness({
|
|
@@ -226,7 +226,7 @@ async function watchPullRequestReadiness(taskRef: string, options: InspectionOpt
|
|
|
226
226
|
readiness: { state: 'cancelled', headSha: last?.pullRequest?.head.sha ?? '' },
|
|
227
227
|
error: { code: 'PR_READINESS_CANCELLED', message: 'Pull request readiness watch was cancelled', retryable: false }
|
|
228
228
|
});
|
|
229
|
-
last = inspectPullRequestReadiness(taskRef, options);
|
|
229
|
+
last = await inspectPullRequestReadiness(taskRef, options);
|
|
230
230
|
if (last.readiness?.state !== 'pending' || last.error?.code !== 'PR_READINESS_PENDING') return last;
|
|
231
231
|
const elapsed = now() - started;
|
|
232
232
|
if (elapsed >= options.deadlineSeconds * 1000) return checksResult('blocked', {
|
|
@@ -239,33 +239,40 @@ async function watchPullRequestReadiness(taskRef: string, options: InspectionOpt
|
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
-
function resolvePlatformCheckRun(taskRef: string, options: SharedOptions & { checkName: string; detailsUrl?: string }): ChecksResult {
|
|
243
|
-
const base = resolvedTask(taskRef, options);
|
|
242
|
+
async function resolvePlatformCheckRun(taskRef: string, options: SharedOptions & { checkName: string; detailsUrl?: string }): Promise<ChecksResult> {
|
|
243
|
+
const base = await resolvedTask(taskRef, options);
|
|
244
244
|
if (!base.ok) return base.output;
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
245
|
+
{
|
|
246
|
+
const resolved = base.provider.checks?.resolveRun
|
|
247
|
+
? await base.provider.checks.resolveRun({
|
|
248
|
+
context: providerOperationContext(base.loadedContext),
|
|
249
|
+
changeRequest: base.prIdentity,
|
|
250
|
+
checkName: options.checkName,
|
|
251
|
+
...(options.detailsUrl ? { detailsUrl: options.detailsUrl } : {})
|
|
252
|
+
})
|
|
253
|
+
: unsupportedProviderOperation(base.provider, 'checks.resolveRun');
|
|
254
|
+
if (!resolved.ok) return checksResult(providerStatus(resolved.error), {
|
|
255
|
+
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
256
|
+
resource: { kind: 'pull-request', number: base.prNumber }, pullRequest: base.pullRequest,
|
|
257
|
+
error: providerError(resolved.error, 'PLATFORM_PROVIDER_OPERATION_FAILED')
|
|
258
|
+
});
|
|
259
|
+
const runId = Number(resolved.value.runId);
|
|
260
|
+
if (!Number.isSafeInteger(runId) || runId <= 0) return checksResult('failed', {
|
|
261
|
+
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
262
|
+
resource: { kind: 'pull-request', number: base.prNumber }, pullRequest: base.pullRequest,
|
|
263
|
+
error: { code: 'CHECK_RUN_IDENTITY_INVALID', message: 'Provider returned an invalid check run identity', retryable: false }
|
|
264
|
+
});
|
|
265
|
+
const jobId = resolved.value.jobId ? Number(resolved.value.jobId) : null;
|
|
266
|
+
return checksResult('no-op', {
|
|
267
|
+
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
268
|
+
resource: { kind: 'pull-request', number: base.prNumber }, pullRequest: base.pullRequest,
|
|
269
|
+
resolution: { status: 'resolved', runId, jobId: jobId !== null && Number.isSafeInteger(jobId) && jobId > 0 ? jobId : null }, error: null
|
|
270
|
+
});
|
|
251
271
|
}
|
|
252
|
-
const listed = base.client.json<{ check_runs?: Array<{ name?: string; details_url?: string }> }>(['api', `repos/${base.context.platform.repository}/commits/${base.pullRequest.head.sha}/check-runs`], { cwd: base.resolved.repoRoot });
|
|
253
|
-
if (!listed.ok) return checksResult(listed.error.retryable ? 'blocked' : 'failed', { error: listed.error });
|
|
254
|
-
const candidates = (listed.value.check_runs || []).flatMap((check) => {
|
|
255
|
-
if (check.name !== options.checkName || !check.details_url) return [];
|
|
256
|
-
const identity = parseRunJobIdentity(check.details_url);
|
|
257
|
-
return identity ? [{ id: identity.runId, jobId: identity.jobId, name: check.name, headSha: base.pullRequest.head.sha }] : [];
|
|
258
|
-
});
|
|
259
|
-
const resolution = resolveRunCandidate(candidates, base.pullRequest.head.sha, options.checkName);
|
|
260
|
-
return checksResult(resolution.status === 'resolved' ? 'no-op' : 'failed', {
|
|
261
|
-
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
262
|
-
resource: { kind: 'pull-request', number: base.prNumber }, pullRequest: base.pullRequest,
|
|
263
|
-
resolution,
|
|
264
|
-
error: resolution.status === 'resolved' ? null : { code: resolution.status === 'ambiguous' ? 'CHECK_RUN_AMBIGUOUS' : 'CHECK_RUN_NOT_FOUND', message: `Check run is ${resolution.status}`, retryable: false }
|
|
265
|
-
});
|
|
266
272
|
}
|
|
267
273
|
|
|
268
274
|
function fetchCheckLogText(client: GitHubClient, args: string[], cwd: string) {
|
|
275
|
+
if (!client.text) return { ok: false as const, error: { code: 'PLATFORM_CLIENT_TEXT_UNAVAILABLE', message: 'Platform client does not support text responses', retryable: false } };
|
|
269
276
|
const fetched = client.text(args, { cwd });
|
|
270
277
|
if (fetched.ok || args[0] !== 'api' || !/response contains terminal escape sequences/i.test(fetched.error.message)) {
|
|
271
278
|
return fetched;
|
|
@@ -273,20 +280,32 @@ function fetchCheckLogText(client: GitHubClient, args: string[], cwd: string) {
|
|
|
273
280
|
return client.text([...args, '--allow-escape-sequences'], { cwd });
|
|
274
281
|
}
|
|
275
282
|
|
|
276
|
-
function fetchPlatformCheckLogs(taskRef: string, options: SharedOptions & { run: number; job?: number }): ChecksResult {
|
|
277
|
-
const base = resolvedTask(taskRef, options);
|
|
283
|
+
async function fetchPlatformCheckLogs(taskRef: string, options: SharedOptions & { run: number; job?: number }): Promise<ChecksResult> {
|
|
284
|
+
const base = await resolvedTask(taskRef, options);
|
|
278
285
|
if (!base.ok) return base.output;
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
286
|
+
{
|
|
287
|
+
const fetched = base.provider.checks?.fetchLogs
|
|
288
|
+
? await base.provider.checks.fetchLogs({
|
|
289
|
+
context: providerOperationContext(base.loadedContext),
|
|
290
|
+
runId: String(options.run),
|
|
291
|
+
...(options.job ? { jobId: String(options.job) } : {})
|
|
292
|
+
})
|
|
293
|
+
: unsupportedProviderOperation(base.provider, 'checks.fetchLogs');
|
|
294
|
+
if (!fetched.ok) return checksResult(providerStatus(fetched.error), {
|
|
295
|
+
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
296
|
+
resource: { kind: 'pull-request', number: base.prNumber }, pullRequest: base.pullRequest,
|
|
297
|
+
error: providerError(fetched.error, 'PLATFORM_PROVIDER_OPERATION_FAILED')
|
|
298
|
+
});
|
|
299
|
+
if (!fetched.value.text) return checksResult('failed', {
|
|
300
|
+
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
301
|
+
error: { code: 'CHECK_LOGS_MISSING', message: 'No failed logs are available', retryable: false }
|
|
302
|
+
});
|
|
303
|
+
return checksResult('no-op', {
|
|
304
|
+
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
305
|
+
resource: { kind: 'pull-request', number: base.prNumber }, pullRequest: base.pullRequest,
|
|
306
|
+
logs: { runId: options.run, ...(options.job ? { jobId: options.job } : {}), text: fetched.value.text }, error: null
|
|
307
|
+
});
|
|
308
|
+
}
|
|
290
309
|
}
|
|
291
310
|
|
|
292
311
|
export {
|
|
@@ -294,6 +313,7 @@ export {
|
|
|
294
313
|
classifyRequiredChecks,
|
|
295
314
|
fetchCheckLogText,
|
|
296
315
|
fetchPlatformCheckLogs,
|
|
316
|
+
inspectGitHubRequiredChecks,
|
|
297
317
|
inspectRequiredChecks,
|
|
298
318
|
inspectPullRequestReadiness,
|
|
299
319
|
parseRunJobIdentity,
|
|
@@ -1,23 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
3
|
-
import { resolvePlatformContext } from './context.ts';
|
|
1
|
+
import { resolvePlatformProviderContext } from './context.ts';
|
|
2
|
+
import type { PlatformClient } from './context.ts';
|
|
4
3
|
import { platformResult } from './types.ts';
|
|
5
4
|
import type { PlatformOperation, PlatformResult } from './types.ts';
|
|
5
|
+
import {
|
|
6
|
+
providerError,
|
|
7
|
+
providerOperationContext,
|
|
8
|
+
providerStatus,
|
|
9
|
+
providerResourceToken,
|
|
10
|
+
unsupportedProviderOperation
|
|
11
|
+
} from './provider-bridge.ts';
|
|
12
|
+
import { isResourceIdentity, resourceIdentityNumber, reviewMarker as resourceReviewMarker } from './resource-identity.ts';
|
|
13
|
+
import type { ResourceIdentity } from './resource-identity.ts';
|
|
6
14
|
|
|
7
15
|
export type PrReviewEvent = 'COMMENT' | 'APPROVE' | 'REQUEST_CHANGES';
|
|
8
|
-
export type PrReviewIdentity = { scope: string; round: number; commitSha: string };
|
|
9
|
-
export type PrReviewListEntry = { id: number; commitId: string; body: string; url: string };
|
|
16
|
+
export type PrReviewIdentity = { scope: string; round: number; commitSha: string; resource?: ResourceIdentity };
|
|
17
|
+
export type PrReviewListEntry = { id: number | string; commitId: string; body: string; url: string };
|
|
10
18
|
export type PrReviewListResult = PlatformResult & { reviews: PrReviewListEntry[] };
|
|
11
19
|
|
|
12
|
-
type RemoteReview = {
|
|
13
|
-
id?: number;
|
|
14
|
-
node_id?: string;
|
|
15
|
-
commit_id?: string;
|
|
16
|
-
body?: string | null;
|
|
17
|
-
state?: string;
|
|
18
|
-
html_url?: string;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
20
|
const REVIEW_EVENTS: readonly PrReviewEvent[] = ['COMMENT', 'APPROVE', 'REQUEST_CHANGES'];
|
|
22
21
|
// Marker-safe scope: a task id (`TASK-YYYYMMDD-HHMMSS`) or a bare PR number
|
|
23
22
|
// (`pr{N}`). Any other value (e.g. one containing `\r\n` or `-->`) would break
|
|
@@ -25,7 +24,10 @@ const REVIEW_EVENTS: readonly PrReviewEvent[] = ['COMMENT', 'APPROVE', 'REQUEST_
|
|
|
25
24
|
const REVIEW_SCOPE_PATTERN = /^(?:pr\d+|TASK-\d{8}-\d{6})$/;
|
|
26
25
|
|
|
27
26
|
export function reviewMarker(identity: PrReviewIdentity): string {
|
|
28
|
-
|
|
27
|
+
const scope = identity.resource && isResourceIdentity(identity.resource) && identity.resource.kind !== 'number'
|
|
28
|
+
? resourceReviewMarker(identity.resource)
|
|
29
|
+
: identity.scope;
|
|
30
|
+
return `<!-- review-pr:${scope}:r${identity.round} -->`;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
export function reviewedCommitMarker(sha: string): string {
|
|
@@ -44,105 +46,98 @@ function hasUsableContext(context: PlatformResult): boolean {
|
|
|
44
46
|
return (context.status === 'no-op' || context.status === 'degraded') && context.platform.repository !== null;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
return !Number.isInteger(prNumber) || prNumber <= 0;
|
|
49
|
-
}
|
|
49
|
+
type PrToken = string | number;
|
|
50
50
|
|
|
51
|
-
export function listPrReviews(prNumber:
|
|
52
|
-
const
|
|
53
|
-
const context =
|
|
51
|
+
export async function listPrReviews(prNumber: PrToken, options: { cwd?: string; client?: PlatformClient } = {}): Promise<PrReviewListResult> {
|
|
52
|
+
const loaded = await resolvePlatformProviderContext({ cwd: options.cwd || process.cwd(), client: options.client });
|
|
53
|
+
const context = loaded.ok ? loaded.value.context : loaded.context;
|
|
54
54
|
if (!hasUsableContext(context)) {
|
|
55
55
|
return { ...platformResult(context.status, { platform: context.platform, capabilities: context.capabilities, error: context.error }), reviews: [] };
|
|
56
56
|
}
|
|
57
|
-
const
|
|
58
|
-
if (
|
|
57
|
+
const identity = loaded.ok ? (() => { try { return providerResourceToken(loaded.value.provider, 'pull-request', String(prNumber)); } catch { return null; } })() : null;
|
|
58
|
+
if (!identity) {
|
|
59
59
|
return {
|
|
60
60
|
...platformResult('failed', {
|
|
61
61
|
platform: context.platform, capabilities: context.capabilities,
|
|
62
|
-
error: { code: '
|
|
62
|
+
error: { code: 'PLATFORM_IDENTITY_TOKEN_INVALID', message: 'Pull request token is invalid', retryable: false }
|
|
63
63
|
}),
|
|
64
64
|
reviews: []
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
|
-
const
|
|
68
|
-
if (
|
|
67
|
+
const identityNumber = resourceIdentityNumber(identity);
|
|
68
|
+
if (loaded.ok) {
|
|
69
|
+
const fetched = loaded.value.provider.reviews?.list
|
|
70
|
+
? await loaded.value.provider.reviews.list({ context: providerOperationContext(loaded.value), changeRequest: identity })
|
|
71
|
+
: unsupportedProviderOperation(loaded.value.provider, 'reviews.list');
|
|
72
|
+
if (!fetched.ok) return {
|
|
73
|
+
...platformResult(providerStatus(fetched.error), {
|
|
74
|
+
platform: context.platform, capabilities: context.capabilities,
|
|
75
|
+
resource: { kind: 'pull-request', number: identityNumber, identity }, error: providerError(fetched.error, 'PLATFORM_PROVIDER_OPERATION_FAILED')
|
|
76
|
+
}), reviews: []
|
|
77
|
+
};
|
|
69
78
|
return {
|
|
70
|
-
...platformResult(
|
|
79
|
+
...platformResult('no-op', {
|
|
71
80
|
platform: context.platform, capabilities: context.capabilities,
|
|
72
|
-
resource: { kind: 'pull-request', number:
|
|
81
|
+
resource: { kind: 'pull-request', number: identityNumber, identity }, error: null
|
|
73
82
|
}),
|
|
74
|
-
reviews:
|
|
83
|
+
reviews: fetched.value.map((review) => ({ id: review.id, commitId: review.commitSha || '', body: review.body, url: review.displayUrl || '' }))
|
|
75
84
|
};
|
|
76
85
|
}
|
|
77
|
-
|
|
78
|
-
const reviews: PrReviewListEntry[] = flat
|
|
79
|
-
.map((review) => ({
|
|
80
|
-
id: review.id ?? 0,
|
|
81
|
-
commitId: review.commit_id ?? '',
|
|
82
|
-
body: review.body ?? '',
|
|
83
|
-
url: review.html_url ?? ''
|
|
84
|
-
}))
|
|
85
|
-
.filter((review) => review.body !== '');
|
|
86
|
-
return {
|
|
87
|
-
...platformResult('no-op', {
|
|
88
|
-
platform: context.platform, capabilities: context.capabilities,
|
|
89
|
-
resource: { kind: 'pull-request', number: prNumber }, error: null
|
|
90
|
-
}),
|
|
91
|
-
reviews
|
|
92
|
-
};
|
|
86
|
+
return { ...platformResult('failed', { platform: context.platform, capabilities: context.capabilities, error: context.error }), reviews: [] };
|
|
93
87
|
}
|
|
94
88
|
|
|
95
|
-
export function publishPrReview(options: {
|
|
89
|
+
export async function publishPrReview(options: {
|
|
96
90
|
cwd?: string;
|
|
97
|
-
client?:
|
|
91
|
+
client?: PlatformClient;
|
|
98
92
|
dryRun?: boolean;
|
|
99
|
-
prNumber:
|
|
93
|
+
prNumber: PrToken;
|
|
100
94
|
identity: PrReviewIdentity;
|
|
101
95
|
event: PrReviewEvent;
|
|
102
96
|
body: string;
|
|
103
|
-
}): PlatformResult {
|
|
104
|
-
const
|
|
105
|
-
const context =
|
|
97
|
+
}): Promise<PlatformResult> {
|
|
98
|
+
const loaded = await resolvePlatformProviderContext({ cwd: options.cwd || process.cwd(), client: options.client });
|
|
99
|
+
const context = loaded.ok ? loaded.value.context : loaded.context;
|
|
106
100
|
if (!hasUsableContext(context)) {
|
|
107
101
|
return platformResult(context.status, { platform: context.platform, capabilities: context.capabilities, error: context.error });
|
|
108
102
|
}
|
|
109
|
-
const
|
|
110
|
-
if (
|
|
103
|
+
const identity = loaded.ok ? (() => { try { return providerResourceToken(loaded.value.provider, 'pull-request', String(options.prNumber)); } catch { return null; } })() : null;
|
|
104
|
+
if (!identity) {
|
|
111
105
|
return platformResult('failed', {
|
|
112
106
|
platform: context.platform, capabilities: context.capabilities,
|
|
113
|
-
error: { code: '
|
|
107
|
+
error: { code: 'PLATFORM_IDENTITY_TOKEN_INVALID', message: 'Pull request token is invalid', retryable: false }
|
|
114
108
|
});
|
|
115
109
|
}
|
|
110
|
+
const identityNumber = resourceIdentityNumber(identity);
|
|
116
111
|
if (!REVIEW_EVENTS.includes(options.event)) {
|
|
117
112
|
return platformResult('failed', {
|
|
118
113
|
platform: context.platform, capabilities: context.capabilities,
|
|
119
|
-
resource: { kind: 'pull-request', number:
|
|
114
|
+
resource: { kind: 'pull-request', number: identityNumber, identity },
|
|
120
115
|
error: { code: 'REVIEW_EVENT_INVALID', message: `review event must be one of ${REVIEW_EVENTS.join('|')}`, retryable: false }
|
|
121
116
|
});
|
|
122
117
|
}
|
|
123
118
|
if (!REVIEW_SCOPE_PATTERN.test(options.identity.scope) || !/^[0-9a-f]{7,40}$/i.test(options.identity.commitSha)) {
|
|
124
119
|
return platformResult('failed', {
|
|
125
120
|
platform: context.platform, capabilities: context.capabilities,
|
|
126
|
-
resource: { kind: 'pull-request', number:
|
|
121
|
+
resource: { kind: 'pull-request', number: identityNumber, identity },
|
|
127
122
|
error: { code: 'REVIEW_IDENTITY_INVALID', message: 'review scope must be a task id or pr{N}; commitSha is required', retryable: false }
|
|
128
123
|
});
|
|
129
124
|
}
|
|
130
125
|
|
|
131
|
-
const marker = reviewMarker(options.identity);
|
|
132
|
-
const listed = listPrReviews(options.prNumber, { cwd: options.cwd, client });
|
|
126
|
+
const marker = reviewMarker({ ...options.identity, resource: identity });
|
|
127
|
+
const listed = await listPrReviews(options.prNumber, { cwd: options.cwd, client: options.client });
|
|
133
128
|
if (listed.status === 'failed' || listed.status === 'blocked') return listed;
|
|
134
129
|
const existing = listed.reviews.find((review) => firstLine(review.body) === marker) ?? null;
|
|
135
130
|
if (existing) {
|
|
136
131
|
if (existing.commitId === options.identity.commitSha) {
|
|
137
132
|
return platformResult('no-op', {
|
|
138
133
|
platform: context.platform, capabilities: context.capabilities,
|
|
139
|
-
resource: { kind: 'pull-request', number:
|
|
134
|
+
resource: { kind: 'pull-request', number: identityNumber, identity },
|
|
140
135
|
operations: [{ name: 'review:publish', status: 'no-op', reasonCode: null }], error: null
|
|
141
136
|
});
|
|
142
137
|
}
|
|
143
138
|
return platformResult('failed', {
|
|
144
139
|
platform: context.platform, capabilities: context.capabilities,
|
|
145
|
-
resource: { kind: 'pull-request', number:
|
|
140
|
+
resource: { kind: 'pull-request', number: identityNumber, identity },
|
|
146
141
|
operations: [{ name: 'review:publish', status: 'failed', reasonCode: 'REVIEW_MARKER_CONFLICT' }],
|
|
147
142
|
error: { code: 'REVIEW_MARKER_CONFLICT', message: 'A review with the same marker targets a different commit; start a new round', retryable: false }
|
|
148
143
|
});
|
|
@@ -159,43 +154,51 @@ export function publishPrReview(options: {
|
|
|
159
154
|
if (options.dryRun) {
|
|
160
155
|
return platformResult('planned', {
|
|
161
156
|
platform: context.platform, capabilities: context.capabilities,
|
|
162
|
-
resource: { kind: 'pull-request', number:
|
|
157
|
+
resource: { kind: 'pull-request', number: identityNumber, identity },
|
|
163
158
|
operations: [{ name: 'review:publish', status: 'planned', reasonCode: null }], error: null
|
|
164
159
|
});
|
|
165
160
|
}
|
|
166
161
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
162
|
+
if (loaded.ok) {
|
|
163
|
+
const published = loaded.value.provider.reviews?.publish
|
|
164
|
+
? await loaded.value.provider.reviews.publish({
|
|
165
|
+
context: providerOperationContext(loaded.value),
|
|
166
|
+
changeRequest: identity,
|
|
167
|
+
identity: options.identity,
|
|
168
|
+
event: options.event,
|
|
169
|
+
body: wrappedBody,
|
|
170
|
+
mutation: { idempotencyKey: `review:publish:${marker}` }
|
|
171
|
+
})
|
|
172
|
+
: unsupportedProviderOperation(loaded.value.provider, 'reviews.publish');
|
|
173
|
+
if (!published.ok) {
|
|
174
|
+
if (published.error.retryable) {
|
|
175
|
+
const reconciled = await listPrReviews(options.prNumber, { cwd: options.cwd, client: options.client });
|
|
176
|
+
const found = reconciled.reviews.find((review) => firstLine(review.body) === marker);
|
|
177
|
+
if (found) return platformResult('applied', {
|
|
178
178
|
changed: true, platform: context.platform, capabilities: context.capabilities,
|
|
179
|
-
resource: { kind: 'pull-request', number:
|
|
179
|
+
resource: { kind: 'pull-request', number: identityNumber, identity },
|
|
180
180
|
operations: [{ name: 'review:publish', status: 'applied', reasonCode: 'CREATE_RECONCILED' }], error: null
|
|
181
181
|
});
|
|
182
|
+
return platformResult('blocked', {
|
|
183
|
+
platform: context.platform, capabilities: context.capabilities,
|
|
184
|
+
resource: { kind: 'pull-request', number: identityNumber, identity },
|
|
185
|
+
operations: [{ name: 'review:publish', status: 'failed', reasonCode: 'REVIEW_CREATE_OUTCOME_UNKNOWN' }],
|
|
186
|
+
error: { code: 'REVIEW_CREATE_OUTCOME_UNKNOWN', message: published.error.message, retryable: true }
|
|
187
|
+
});
|
|
182
188
|
}
|
|
183
|
-
return platformResult(
|
|
189
|
+
return platformResult(providerStatus(published.error), {
|
|
184
190
|
platform: context.platform, capabilities: context.capabilities,
|
|
185
|
-
resource: { kind: 'pull-request', number:
|
|
186
|
-
operations: [{ name: 'review:publish', status: 'failed', reasonCode:
|
|
187
|
-
error:
|
|
191
|
+
resource: { kind: 'pull-request', number: identityNumber, identity },
|
|
192
|
+
operations: [{ name: 'review:publish', status: 'failed', reasonCode: published.error.code }],
|
|
193
|
+
error: providerError(published.error, 'PLATFORM_PROVIDER_OPERATION_FAILED')
|
|
188
194
|
});
|
|
189
195
|
}
|
|
190
|
-
return platformResult('
|
|
196
|
+
return platformResult('applied', {
|
|
197
|
+
changed: published.value.changed,
|
|
191
198
|
platform: context.platform, capabilities: context.capabilities,
|
|
192
|
-
resource: { kind: 'pull-request', number:
|
|
193
|
-
operations: [{ name: 'review:publish', status: '
|
|
199
|
+
resource: { kind: 'pull-request', number: identityNumber, identity },
|
|
200
|
+
operations: [{ name: 'review:publish', status: 'applied', reasonCode: null }], error: null
|
|
194
201
|
});
|
|
195
202
|
}
|
|
196
|
-
return platformResult('
|
|
197
|
-
changed: true, platform: context.platform, capabilities: context.capabilities,
|
|
198
|
-
resource: { kind: 'pull-request', number: options.prNumber },
|
|
199
|
-
operations: [{ name: 'review:publish', status: 'applied', reasonCode: null }], error: null
|
|
200
|
-
});
|
|
203
|
+
return platformResult('failed', { platform: context.platform, capabilities: context.capabilities, error: context.error });
|
|
201
204
|
}
|