@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
|
@@ -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
|
-
|
|
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)
|
package/lib/platform/adapters.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
71
|
+
const loaded = await resolvePlatformProviderContext({
|
|
66
72
|
cwd: input.cwd,
|
|
67
|
-
|
|
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 {
|
|
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?:
|
|
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
|
|
63
|
-
if (!
|
|
64
|
-
error: { code: 'ISSUE_NOT_LINKED', message: 'Task has no valid
|
|
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,
|
package/lib/platform/context.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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:
|
|
84
|
-
|
|
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
|
|
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
|
-
|
|
128
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
|
154
|
-
|
|
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);
|