@fitlab-ai/agent-infra 0.9.1 → 0.9.2
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 +16 -4
- package/README.zh-CN.md +16 -4
- package/bin/cli.ts +12 -0
- package/dist/bin/cli.js +14 -0
- package/dist/bin/internal-cli.js +15 -0
- package/dist/lib/agent-client.js +163 -0
- package/dist/lib/agent-clients/adapter.js +294 -0
- package/dist/lib/agent-clients/adapters/claude-code.js +62 -0
- package/dist/lib/agent-clients/adapters/codex-sandbox.js +210 -0
- package/dist/lib/agent-clients/adapters/codex.js +54 -0
- package/dist/lib/agent-clients/adapters/gemini-cli.js +64 -0
- package/dist/lib/agent-clients/adapters/index.js +20 -0
- package/dist/lib/agent-clients/adapters/opencode.js +64 -0
- package/dist/lib/agent-clients/config.js +177 -0
- package/dist/lib/agent-clients/custom-tuis.js +99 -0
- package/dist/lib/agent-clients/invocation.js +9 -0
- package/dist/lib/agent-clients/next-steps.js +38 -0
- package/dist/lib/agent-clients/project-assets.js +45 -0
- package/dist/lib/agent-clients/reconcile.js +235 -0
- package/dist/lib/agent-clients/registry.js +72 -0
- package/dist/lib/agent-clients/schema.js +57 -0
- package/dist/lib/agent-clients/types.js +29 -0
- package/dist/lib/builtin-tuis.js +9 -14
- package/dist/lib/defaults.json +85 -73
- package/dist/lib/git/review-snapshot.js +25 -10
- package/dist/lib/git/reviewed-commit-equivalence.js +110 -0
- package/dist/lib/init.js +47 -91
- package/dist/lib/internal/agent-client.js +117 -0
- package/dist/lib/internal/git-workflow.js +1 -1
- package/dist/lib/internal/release-workflow.js +71 -16
- package/dist/lib/internal/task-orchestration.js +151 -0
- package/dist/lib/internal/task-review.js +65 -0
- package/dist/lib/merge.js +2 -1
- package/dist/lib/platform/adapters.js +38 -1
- package/dist/lib/platform/change-request-git-evidence.js +113 -0
- package/dist/lib/platform/merged-pr-equivalence.js +41 -11
- package/dist/lib/platform/pr-checks.js +38 -10
- package/dist/lib/platform/pull-requests.js +110 -6
- package/dist/lib/platform/releases.js +1 -1
- package/dist/lib/platform/verification-required.js +8 -10
- package/dist/lib/prompt.js +15 -3
- package/dist/lib/release/channels.js +4 -1
- package/dist/lib/release/workflow.js +4 -2
- package/dist/lib/remove-dir.js +25 -0
- package/dist/lib/run/tui.js +8 -2
- package/dist/lib/sandbox/agent-client-reconciler.js +234 -0
- package/dist/lib/sandbox/clipboard/linux.js +2 -1
- package/dist/lib/sandbox/commands/create.js +76 -180
- package/dist/lib/sandbox/commands/enter.js +17 -1
- package/dist/lib/sandbox/commands/prune.js +2 -2
- package/dist/lib/sandbox/commands/rm.js +3 -2
- package/dist/lib/sandbox/config.js +21 -4
- package/dist/lib/sandbox/constants.js +3 -0
- package/dist/lib/sandbox/dockerfile.js +2 -1
- package/dist/lib/sandbox/managed-fs.js +10 -5
- package/dist/lib/sandbox/recovery.js +52 -36
- package/dist/lib/sandbox/shell.js +1 -1
- package/dist/lib/sandbox/tool-types.js +8 -0
- package/dist/lib/sandbox/tools.js +2 -126
- package/dist/lib/task/artifact-lifecycle.js +1 -49
- package/dist/lib/task/commands/decisions.js +50 -20
- package/dist/lib/task/delegation-receipts.js +107 -0
- package/dist/lib/task/events.js +100 -1
- package/dist/lib/task/orchestration.js +420 -0
- package/dist/lib/task/review-artifacts.js +143 -59
- package/dist/lib/task/review-finalization.js +166 -0
- package/dist/lib/task/review-fingerprint.js +4 -0
- package/dist/lib/task/verification-checks.js +2 -1
- package/dist/lib/task/verification-engine.js +156 -28
- package/dist/lib/task/verification-review.js +1 -1
- package/dist/lib/task/verification.js +3 -1
- package/dist/lib/task/workspace-snapshot.js +34 -0
- package/dist/lib/update.js +42 -161
- package/lib/agent-client.ts +214 -0
- package/lib/agent-clients/adapter.ts +461 -0
- package/lib/agent-clients/adapters/claude-code.ts +70 -0
- package/lib/agent-clients/adapters/codex-sandbox.ts +245 -0
- package/lib/agent-clients/adapters/codex.ts +58 -0
- package/lib/agent-clients/adapters/gemini-cli.ts +65 -0
- package/lib/agent-clients/adapters/index.ts +27 -0
- package/lib/agent-clients/adapters/opencode.ts +66 -0
- package/lib/agent-clients/config.ts +278 -0
- package/lib/agent-clients/custom-tuis.ts +149 -0
- package/lib/agent-clients/invocation.ts +19 -0
- package/lib/agent-clients/next-steps.ts +72 -0
- package/lib/agent-clients/project-assets.ts +96 -0
- package/lib/agent-clients/reconcile.ts +324 -0
- package/lib/agent-clients/registry.ts +144 -0
- package/lib/agent-clients/schema.ts +62 -0
- package/lib/agent-clients/types.ts +78 -0
- package/lib/benchmark/schemas/v1.0.0/case-manifest.schema.json +120 -0
- package/lib/benchmark/schemas/v1.0.0/common.schema.json +109 -0
- package/lib/benchmark/schemas/v1.0.0/grader-result.schema.json +160 -0
- package/lib/benchmark/schemas/v1.0.0/run-manifest.schema.json +122 -0
- package/lib/benchmark/schemas/v1.0.0/subject.schema.json +227 -0
- package/lib/builtin-tuis.ts +16 -15
- package/lib/defaults.json +24 -12
- package/lib/git/review-snapshot.ts +39 -9
- package/lib/git/reviewed-commit-equivalence.ts +135 -0
- package/lib/init.ts +53 -121
- package/lib/internal/agent-client.ts +131 -0
- package/lib/internal/git-workflow.ts +1 -1
- package/lib/internal/release-workflow.ts +68 -16
- package/lib/internal/task-orchestration.ts +132 -0
- package/lib/internal/task-review.ts +65 -0
- package/lib/merge.ts +2 -1
- package/lib/platform/adapters.ts +142 -3
- package/lib/platform/change-request-git-evidence.ts +159 -0
- package/lib/platform/merged-pr-equivalence.ts +51 -14
- package/lib/platform/pr-checks.ts +50 -22
- package/lib/platform/pull-requests.ts +134 -25
- package/lib/platform/releases.ts +1 -1
- package/lib/platform/verification-required.ts +12 -10
- package/lib/prompt.ts +16 -3
- package/lib/release/channels.ts +4 -1
- package/lib/release/workflow.ts +4 -3
- package/lib/remove-dir.ts +28 -0
- package/lib/run/tui.ts +9 -1
- package/lib/sandbox/agent-client-reconciler.ts +378 -0
- package/lib/sandbox/clipboard/linux.ts +2 -1
- package/lib/sandbox/commands/create.ts +111 -192
- package/lib/sandbox/commands/enter.ts +23 -1
- package/lib/sandbox/commands/prune.ts +2 -2
- package/lib/sandbox/commands/rm.ts +3 -2
- package/lib/sandbox/config.ts +31 -4
- package/lib/sandbox/constants.ts +6 -0
- package/lib/sandbox/dockerfile.ts +2 -1
- package/lib/sandbox/managed-fs.ts +18 -5
- package/lib/sandbox/recovery.ts +98 -67
- package/lib/sandbox/shell.ts +1 -1
- package/lib/sandbox/tool-types.ts +143 -0
- package/lib/sandbox/tools.ts +5 -157
- package/lib/task/artifact-lifecycle.ts +1 -38
- package/lib/task/commands/decisions.ts +53 -20
- package/lib/task/delegation-receipts.ts +170 -0
- package/lib/task/events.ts +110 -2
- package/lib/task/orchestration.ts +543 -0
- package/lib/task/review-artifacts.ts +201 -69
- package/lib/task/review-finalization.ts +275 -0
- package/lib/task/review-fingerprint.ts +5 -0
- package/lib/task/verification-checks.ts +2 -1
- package/lib/task/verification-engine.ts +177 -28
- package/lib/task/verification-review.ts +1 -0
- package/lib/task/verification.ts +5 -2
- package/lib/task/workspace-snapshot.ts +36 -0
- package/lib/update.ts +48 -184
- package/package.json +2 -2
- package/templates/.agents/README.en.md +21 -20
- package/templates/.agents/README.zh-CN.md +21 -20
- package/templates/.agents/hooks/lifecycle-delegation.js +64 -0
- package/templates/.agents/rules/README.en.md +2 -0
- package/templates/.agents/rules/README.zh-CN.md +2 -0
- package/templates/.agents/rules/commit-and-pr.en.md +1 -0
- package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -0
- package/templates/.agents/rules/human-decision-context.en.md +14 -12
- package/templates/.agents/rules/human-decision-context.zh-CN.md +14 -12
- package/templates/.agents/rules/lifecycle-orchestration.en.md +21 -0
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +21 -0
- package/templates/.agents/rules/next-step-output.en.md +16 -0
- package/templates/.agents/rules/next-step-output.zh-CN.md +16 -0
- package/templates/.agents/rules/no-mid-flow-questions.en.md +2 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +2 -0
- package/templates/.agents/rules/release-commands.github.en.md +11 -5
- package/templates/.agents/rules/release-commands.github.zh-CN.md +11 -5
- package/templates/.agents/rules/review-handshake.en.md +4 -4
- package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
- package/templates/.agents/rules/review-method.en.md +113 -0
- package/templates/.agents/rules/review-method.zh-CN.md +98 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/cancel-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/check-task/SKILL.en.md +16 -16
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -16
- package/templates/.agents/skills/close-codescan/SKILL.en.md +4 -4
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +4 -4
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +14 -16
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +14 -16
- package/templates/.agents/skills/code-task/reference/output-template.en.md +3 -3
- package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +3 -3
- package/templates/.agents/skills/commit/SKILL.en.md +11 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +11 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +24 -28
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +24 -28
- package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/SKILL.en.md +10 -10
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +10 -10
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +3 -3
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +3 -3
- package/templates/.agents/skills/create-task/SKILL.en.md +11 -11
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +11 -11
- package/templates/.agents/skills/import-codescan/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/init-labels/SKILL.en.md +4 -4
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/init-milestones/SKILL.en.md +4 -4
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/refine-title/SKILL.en.md +1 -1
- package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +13 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -3
- package/templates/.agents/skills/review-analysis/config/verify.en.json +6 -0
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +6 -0
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +13 -15
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +13 -15
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +51 -5
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +52 -6
- package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +25 -22
- package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +25 -22
- package/templates/.agents/skills/review-code/SKILL.en.md +28 -8
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +28 -8
- package/templates/.agents/skills/review-code/config/verify.en.json +7 -0
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +7 -0
- package/templates/.agents/skills/review-code/reference/concurrency-risks.en.md +25 -0
- package/templates/.agents/skills/review-code/reference/concurrency-risks.zh-CN.md +25 -0
- package/templates/.agents/skills/review-code/reference/cross-platform-risks.en.md +27 -0
- package/templates/.agents/skills/review-code/reference/cross-platform-risks.zh-CN.md +27 -0
- package/templates/.agents/skills/review-code/reference/documentation-antipatterns.en.md +13 -0
- package/templates/.agents/skills/review-code/reference/documentation-antipatterns.zh-CN.md +13 -0
- package/templates/.agents/skills/review-code/reference/migration-risks.en.md +25 -0
- package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +25 -0
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +22 -24
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +22 -24
- package/templates/.agents/skills/review-code/reference/report-template.en.md +54 -7
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +54 -7
- package/templates/.agents/skills/review-code/reference/review-criteria.en.md +29 -24
- package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +29 -24
- package/templates/.agents/skills/review-code/reference/security-risks.en.md +25 -0
- package/templates/.agents/skills/review-code/reference/security-risks.zh-CN.md +25 -0
- package/templates/.agents/skills/review-plan/SKILL.en.md +13 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -3
- package/templates/.agents/skills/review-plan/config/verify.en.json +6 -0
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +6 -0
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +13 -15
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +13 -15
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +61 -5
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +61 -5
- package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +21 -24
- package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +21 -24
- package/templates/.agents/skills/run-task/SKILL.en.md +22 -0
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +22 -0
- package/templates/.agents/skills/run-task/config/verify.en.json +9 -0
- package/templates/.agents/skills/run-task/config/verify.zh-CN.json +9 -0
- package/templates/.agents/skills/test/SKILL.en.md +4 -4
- package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/test-integration/SKILL.en.md +4 -4
- package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -5
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +647 -203
- package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +4 -4
- package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/watch-pr/SKILL.en.md +8 -8
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +8 -8
- package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +6 -0
- package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +6 -0
- package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +6 -0
- package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +6 -0
- package/templates/.claude/commands/run-task.en.md +8 -0
- package/templates/.claude/commands/run-task.zh-CN.md +8 -0
- package/templates/.claude/settings.json +24 -0
- package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +3 -0
- package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +3 -0
- package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +3 -0
- package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +3 -0
- package/templates/.gemini/commands/_project_/run-task.en.toml +8 -0
- package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +8 -0
- package/templates/.opencode/commands/run-task.en.md +11 -0
- package/templates/.opencode/commands/run-task.zh-CN.md +11 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { finalizeReviewSummary } from '../task/review-finalization.ts';
|
|
2
|
+
|
|
3
|
+
const USAGE = 'Usage: agent-infra-internal task-review <task-ref> finalize-summary --stage <analysis|plan|code> --artifact <review-*.md> [--dry-run]\n';
|
|
4
|
+
|
|
5
|
+
function failUsage(message: string): void {
|
|
6
|
+
process.stdout.write(`${JSON.stringify({
|
|
7
|
+
status: 'failed',
|
|
8
|
+
changed: false,
|
|
9
|
+
intent: 'finalize-summary',
|
|
10
|
+
error: { code: 'REVIEW_PAYLOAD_INVALID', message }
|
|
11
|
+
})}\n`);
|
|
12
|
+
process.stderr.write(USAGE);
|
|
13
|
+
process.exitCode = 2;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function taskReview(args: string[] = []): void {
|
|
17
|
+
if (args[0] === '--help' || args[0] === '-h') {
|
|
18
|
+
process.stdout.write(USAGE);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (args.length < 2) {
|
|
22
|
+
failUsage('task ref and intent are required');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (args[1] !== 'finalize-summary') {
|
|
26
|
+
failUsage(`unknown intent '${args[1]}'`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
let stage = '';
|
|
30
|
+
let artifact = '';
|
|
31
|
+
let dryRun = false;
|
|
32
|
+
const seen = new Set<string>();
|
|
33
|
+
for (let index = 2; index < args.length; index += 1) {
|
|
34
|
+
const flag = args[index]!;
|
|
35
|
+
if (!['--stage', '--artifact', '--dry-run'].includes(flag)) {
|
|
36
|
+
failUsage(`unknown option '${flag}'`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (seen.has(flag)) {
|
|
40
|
+
failUsage(`duplicate option '${flag}'`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
seen.add(flag);
|
|
44
|
+
if (flag === '--dry-run') {
|
|
45
|
+
dryRun = true;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const value = args[++index];
|
|
49
|
+
if (!value || value.startsWith('--')) {
|
|
50
|
+
failUsage(`option '${flag}' requires a value`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (flag === '--stage') stage = value;
|
|
54
|
+
else artifact = value;
|
|
55
|
+
}
|
|
56
|
+
if (!stage || !artifact) {
|
|
57
|
+
failUsage("options '--stage' and '--artifact' are required");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const result = finalizeReviewSummary({ taskRef: args[0]!, stage, artifact, dryRun });
|
|
61
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
62
|
+
if (result.status === 'failed') process.exitCode = 1;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { taskReview };
|
package/lib/merge.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { info, ok } from './log.ts';
|
|
4
|
+
import { removeDirRecursive } from './remove-dir.ts';
|
|
4
5
|
|
|
5
6
|
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
6
7
|
const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
|
|
@@ -690,7 +691,7 @@ function mergeMutableSections({ sourceWorkspace, localWorkspace, backupRoot, rep
|
|
|
690
691
|
if (comparison > 0) {
|
|
691
692
|
backupTaskDir(backupRoot, localMatch.section, localMatch.taskDir, localMatch.taskId);
|
|
692
693
|
report.backupCount += 1;
|
|
693
|
-
|
|
694
|
+
removeDirRecursive(localMatch.taskDir);
|
|
694
695
|
|
|
695
696
|
const destinationDir = copyTaskToSection(sourceTask, localWorkspace);
|
|
696
697
|
localIndex.set(sourceTask.taskId, {
|
package/lib/platform/adapters.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PlatformResult } from './types.ts';
|
|
1
|
+
import type { PlatformError, PlatformResult } from './types.ts';
|
|
2
2
|
|
|
3
3
|
type PlatformAdapterContext = {
|
|
4
4
|
cwd: string;
|
|
@@ -6,9 +6,71 @@ type PlatformAdapterContext = {
|
|
|
6
6
|
client?: unknown;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
+
type PlatformChangeRequestSnapshot = {
|
|
10
|
+
repository: string;
|
|
11
|
+
number: number;
|
|
12
|
+
nodeId: string;
|
|
13
|
+
url: string;
|
|
14
|
+
state: 'open' | 'closed';
|
|
15
|
+
title: string;
|
|
16
|
+
body: string;
|
|
17
|
+
draft: boolean;
|
|
18
|
+
head: { repository: string; ref: string; sha: string };
|
|
19
|
+
base: { repository: string; ref: string; sha: string };
|
|
20
|
+
mergedAt: string | null;
|
|
21
|
+
mergeCommitSha: string | null;
|
|
22
|
+
labels: string[];
|
|
23
|
+
assignees: string[];
|
|
24
|
+
milestone: string | null;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type PlatformCheckSnapshot = {
|
|
28
|
+
name: string;
|
|
29
|
+
bucket: 'pass' | 'fail' | 'pending' | 'cancel';
|
|
30
|
+
workflow?: string | null;
|
|
31
|
+
conclusion?: string | null;
|
|
32
|
+
detailsUrl?: string | null;
|
|
33
|
+
startedAt?: string | null;
|
|
34
|
+
completedAt?: string | null;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type PlatformInspectionResult<T> = {
|
|
38
|
+
ok: boolean;
|
|
39
|
+
value?: T;
|
|
40
|
+
error?: PlatformError;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type ChangeRequestInspectionContext = PlatformAdapterContext & {
|
|
44
|
+
repository: string;
|
|
45
|
+
number: number;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
type RequiredChecksInspectionContext = ChangeRequestInspectionContext & {
|
|
49
|
+
headSha: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
type ChangeRequestGitEvidenceContext = ChangeRequestInspectionContext & {
|
|
53
|
+
pullRequest: PlatformChangeRequestSnapshot;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
type PlatformChangeRequestGitEvidenceSpec = {
|
|
57
|
+
remoteUrl: string;
|
|
58
|
+
reviewedHeadRef: string;
|
|
59
|
+
targetHeadRef: string;
|
|
60
|
+
};
|
|
61
|
+
|
|
9
62
|
type PlatformAdapter = {
|
|
10
63
|
type: string;
|
|
11
64
|
resolveContext(context: PlatformAdapterContext): PlatformResult;
|
|
65
|
+
inspectChangeRequest?(
|
|
66
|
+
context: ChangeRequestInspectionContext
|
|
67
|
+
): PlatformInspectionResult<PlatformChangeRequestSnapshot>;
|
|
68
|
+
inspectRequiredChecks?(
|
|
69
|
+
context: RequiredChecksInspectionContext
|
|
70
|
+
): PlatformInspectionResult<PlatformCheckSnapshot[]>;
|
|
71
|
+
resolveChangeRequestGitEvidence?(
|
|
72
|
+
context: ChangeRequestGitEvidenceContext
|
|
73
|
+
): PlatformInspectionResult<PlatformChangeRequestGitEvidenceSpec>;
|
|
12
74
|
};
|
|
13
75
|
|
|
14
76
|
const adapters = new Map<string, PlatformAdapter>();
|
|
@@ -22,9 +84,86 @@ function getPlatformAdapter(type: string | null): PlatformAdapter | null {
|
|
|
22
84
|
return type ? adapters.get(type) ?? null : null;
|
|
23
85
|
}
|
|
24
86
|
|
|
87
|
+
function registerPlatformCapabilities(
|
|
88
|
+
type: string,
|
|
89
|
+
capabilities: Pick<
|
|
90
|
+
PlatformAdapter,
|
|
91
|
+
'inspectChangeRequest' | 'inspectRequiredChecks' | 'resolveChangeRequestGitEvidence'
|
|
92
|
+
>
|
|
93
|
+
): void {
|
|
94
|
+
const adapter = adapters.get(type);
|
|
95
|
+
if (!adapter) throw new Error(`Platform adapter '${type}' must be registered before its capabilities`);
|
|
96
|
+
adapters.set(type, { ...adapter, ...capabilities });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function hasPlatformCapability(
|
|
100
|
+
type: string | null,
|
|
101
|
+
capability: 'change-request' | 'required-checks' | 'change-request-git-evidence'
|
|
102
|
+
): boolean {
|
|
103
|
+
const adapter = getPlatformAdapter(type);
|
|
104
|
+
if (capability === 'change-request') return typeof adapter?.inspectChangeRequest === 'function';
|
|
105
|
+
if (capability === 'required-checks') return typeof adapter?.inspectRequiredChecks === 'function';
|
|
106
|
+
return typeof adapter?.resolveChangeRequestGitEvidence === 'function';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function unsupported<T>(type: string | null, capability: string): PlatformInspectionResult<T> {
|
|
110
|
+
return {
|
|
111
|
+
ok: false,
|
|
112
|
+
error: {
|
|
113
|
+
code: 'PLATFORM_CAPABILITY_UNSUPPORTED',
|
|
114
|
+
message: `Platform '${type || 'none'}' does not provide ${capability}`,
|
|
115
|
+
retryable: false
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function inspectPlatformChangeRequest(
|
|
121
|
+
type: string | null,
|
|
122
|
+
context: ChangeRequestInspectionContext
|
|
123
|
+
): PlatformInspectionResult<PlatformChangeRequestSnapshot> {
|
|
124
|
+
const adapter = getPlatformAdapter(type);
|
|
125
|
+
return adapter?.inspectChangeRequest?.(context) ?? unsupported(type, 'change-request inspection');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function inspectPlatformRequiredChecks(
|
|
129
|
+
type: string | null,
|
|
130
|
+
context: RequiredChecksInspectionContext
|
|
131
|
+
): PlatformInspectionResult<PlatformCheckSnapshot[]> {
|
|
132
|
+
const adapter = getPlatformAdapter(type);
|
|
133
|
+
return adapter?.inspectRequiredChecks?.(context) ?? unsupported(type, 'required-checks inspection');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function resolvePlatformChangeRequestGitEvidence(
|
|
137
|
+
type: string | null,
|
|
138
|
+
context: ChangeRequestGitEvidenceContext
|
|
139
|
+
): PlatformInspectionResult<PlatformChangeRequestGitEvidenceSpec> {
|
|
140
|
+
const adapter = getPlatformAdapter(type);
|
|
141
|
+
return adapter?.resolveChangeRequestGitEvidence?.(context) ??
|
|
142
|
+
unsupported(type, 'change-request Git evidence');
|
|
143
|
+
}
|
|
144
|
+
|
|
25
145
|
function listPlatformAdapters(): string[] {
|
|
26
146
|
return [...adapters.keys()].sort();
|
|
27
147
|
}
|
|
28
148
|
|
|
29
|
-
export {
|
|
30
|
-
|
|
149
|
+
export {
|
|
150
|
+
getPlatformAdapter,
|
|
151
|
+
hasPlatformCapability,
|
|
152
|
+
inspectPlatformChangeRequest,
|
|
153
|
+
inspectPlatformRequiredChecks,
|
|
154
|
+
listPlatformAdapters,
|
|
155
|
+
registerPlatformAdapter,
|
|
156
|
+
registerPlatformCapabilities,
|
|
157
|
+
resolvePlatformChangeRequestGitEvidence
|
|
158
|
+
};
|
|
159
|
+
export type {
|
|
160
|
+
ChangeRequestGitEvidenceContext,
|
|
161
|
+
ChangeRequestInspectionContext,
|
|
162
|
+
PlatformAdapter,
|
|
163
|
+
PlatformAdapterContext,
|
|
164
|
+
PlatformChangeRequestGitEvidenceSpec,
|
|
165
|
+
PlatformChangeRequestSnapshot,
|
|
166
|
+
PlatformCheckSnapshot,
|
|
167
|
+
PlatformInspectionResult,
|
|
168
|
+
RequiredChecksInspectionContext
|
|
169
|
+
};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
import { resolvePlatformChangeRequestGitEvidence } from "./adapters.ts";
|
|
7
|
+
import { resolveReviewedHeadRelation } from "./merged-pr-equivalence.ts";
|
|
8
|
+
import type { ReviewedHeadRelation } from "./merged-pr-equivalence.ts";
|
|
9
|
+
import type { PullRequestSnapshot } from "./pull-requests.ts";
|
|
10
|
+
|
|
11
|
+
const SHA_PATTERN = /^[0-9a-f]{40}$/i;
|
|
12
|
+
const REVIEWED_HEAD_REF = "refs/agent-infra/reviewed-head";
|
|
13
|
+
const TARGET_HEAD_REF = "refs/agent-infra/target-head";
|
|
14
|
+
|
|
15
|
+
function git(cwd: string, args: string[]): string {
|
|
16
|
+
return execFileSync("git", args, {
|
|
17
|
+
cwd,
|
|
18
|
+
encoding: "utf8",
|
|
19
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
20
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
21
|
+
}).trim();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function validRef(ref: string): boolean {
|
|
25
|
+
return spawnSync("git", ["check-ref-format", ref], {
|
|
26
|
+
encoding: "utf8",
|
|
27
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
28
|
+
}).status === 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function blocked(code: string, message: string): ReviewedHeadRelation {
|
|
32
|
+
return { status: "blocked", code, message };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function failed(code: string, message: string): ReviewedHeadRelation {
|
|
36
|
+
return { status: "failed", code, message };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function validMergeIdentity(lastReviewedCommit: string, pullRequest: PullRequestSnapshot):
|
|
40
|
+
ReviewedHeadRelation | null {
|
|
41
|
+
if (
|
|
42
|
+
pullRequest.state !== "closed" ||
|
|
43
|
+
!pullRequest.mergedAt ||
|
|
44
|
+
!pullRequest.mergeCommitSha ||
|
|
45
|
+
!SHA_PATTERN.test(lastReviewedCommit) ||
|
|
46
|
+
lastReviewedCommit !== pullRequest.head.sha
|
|
47
|
+
) {
|
|
48
|
+
return failed(
|
|
49
|
+
"PR_MERGE_IDENTITY_INVALID",
|
|
50
|
+
"PR merge identity does not match the reviewed head"
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function resolveMaterializedReviewedHeadRelation(input: {
|
|
57
|
+
cwd: string;
|
|
58
|
+
platformType: string | null;
|
|
59
|
+
lastReviewedCommit: string;
|
|
60
|
+
pullRequest: PullRequestSnapshot;
|
|
61
|
+
}): ReviewedHeadRelation {
|
|
62
|
+
const identityFailure = validMergeIdentity(input.lastReviewedCommit, input.pullRequest);
|
|
63
|
+
if (identityFailure) return identityFailure;
|
|
64
|
+
|
|
65
|
+
const source = resolvePlatformChangeRequestGitEvidence(input.platformType, {
|
|
66
|
+
cwd: input.cwd,
|
|
67
|
+
repository: input.pullRequest.repository,
|
|
68
|
+
number: input.pullRequest.number,
|
|
69
|
+
pullRequest: input.pullRequest
|
|
70
|
+
});
|
|
71
|
+
if (!source.ok || !source.value) {
|
|
72
|
+
return blocked(
|
|
73
|
+
source.error?.code || "PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE",
|
|
74
|
+
source.error?.message || "Unable to resolve authoritative PR Git evidence"
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
if (
|
|
78
|
+
!source.value.remoteUrl.trim() ||
|
|
79
|
+
source.value.remoteUrl.trimStart().startsWith("-") ||
|
|
80
|
+
!validRef(source.value.reviewedHeadRef) ||
|
|
81
|
+
!validRef(source.value.targetHeadRef)
|
|
82
|
+
) {
|
|
83
|
+
return blocked(
|
|
84
|
+
"PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE",
|
|
85
|
+
"Platform returned an invalid PR Git evidence source"
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let evidenceRoot: string | null = null;
|
|
90
|
+
let phase: "prepare" | "fetch" | "resolve" = "prepare";
|
|
91
|
+
let result: ReviewedHeadRelation = blocked(
|
|
92
|
+
"PR_MERGE_GIT_EVIDENCE_UNAVAILABLE",
|
|
93
|
+
"Unable to prepare isolated PR Git evidence"
|
|
94
|
+
);
|
|
95
|
+
try {
|
|
96
|
+
evidenceRoot = fs.mkdtempSync(path.join(os.tmpdir(), "agent-infra-pr-evidence-"));
|
|
97
|
+
execFileSync("git", ["init", "--bare", "-q", evidenceRoot], {
|
|
98
|
+
cwd: input.cwd,
|
|
99
|
+
encoding: "utf8",
|
|
100
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
phase = "fetch";
|
|
104
|
+
git(evidenceRoot, [
|
|
105
|
+
"fetch",
|
|
106
|
+
"--no-tags",
|
|
107
|
+
"--no-write-fetch-head",
|
|
108
|
+
source.value.remoteUrl,
|
|
109
|
+
`+${source.value.reviewedHeadRef}:${REVIEWED_HEAD_REF}`,
|
|
110
|
+
`+${source.value.targetHeadRef}:${TARGET_HEAD_REF}`
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
phase = "resolve";
|
|
114
|
+
const fetchedReviewedHead = git(evidenceRoot, ["rev-parse", `${REVIEWED_HEAD_REF}^{commit}`]);
|
|
115
|
+
const comparisonHead = git(evidenceRoot, ["rev-parse", `${TARGET_HEAD_REF}^{commit}`]);
|
|
116
|
+
if (fetchedReviewedHead !== input.pullRequest.head.sha) {
|
|
117
|
+
result = failed(
|
|
118
|
+
"PR_MERGE_FETCHED_HEAD_MISMATCH",
|
|
119
|
+
"Fetched PR head does not match the authoritative platform snapshot"
|
|
120
|
+
);
|
|
121
|
+
} else {
|
|
122
|
+
result = resolveReviewedHeadRelation({
|
|
123
|
+
gitRoot: evidenceRoot,
|
|
124
|
+
comparisonHead,
|
|
125
|
+
lastReviewedCommit: input.lastReviewedCommit,
|
|
126
|
+
pullRequest: input.pullRequest
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
} catch {
|
|
130
|
+
result = phase === "fetch"
|
|
131
|
+
? blocked(
|
|
132
|
+
"PR_MERGE_EVIDENCE_FETCH_FAILED",
|
|
133
|
+
"Unable to fetch PR Git evidence; verify Git credentials and platform refs"
|
|
134
|
+
)
|
|
135
|
+
: phase === "resolve"
|
|
136
|
+
? blocked(
|
|
137
|
+
"PR_MERGE_OBJECT_MISSING",
|
|
138
|
+
"Fetched PR Git evidence is incomplete"
|
|
139
|
+
)
|
|
140
|
+
: blocked(
|
|
141
|
+
"PR_MERGE_GIT_EVIDENCE_UNAVAILABLE",
|
|
142
|
+
"Unable to prepare isolated PR Git evidence"
|
|
143
|
+
);
|
|
144
|
+
} finally {
|
|
145
|
+
if (evidenceRoot) {
|
|
146
|
+
try {
|
|
147
|
+
fs.rmSync(evidenceRoot, { recursive: true, force: true });
|
|
148
|
+
} catch {
|
|
149
|
+
result = blocked(
|
|
150
|
+
"PR_MERGE_EVIDENCE_CLEANUP_FAILED",
|
|
151
|
+
"Unable to clean up isolated PR Git evidence"
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export { resolveMaterializedReviewedHeadRelation };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
1
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
2
2
|
|
|
3
3
|
import type { PullRequestSnapshot } from "./pull-requests.ts";
|
|
4
4
|
|
|
@@ -6,11 +6,21 @@ const SHA_PATTERN = /^[0-9a-f]{40}$/i;
|
|
|
6
6
|
|
|
7
7
|
type ReviewedHeadRelation =
|
|
8
8
|
| { status: "strict"; reviewedHead: string }
|
|
9
|
-
| {
|
|
9
|
+
| {
|
|
10
|
+
status: "merged-equivalent";
|
|
11
|
+
reviewedHead: string;
|
|
12
|
+
mergeCommit: string;
|
|
13
|
+
comparisonHead: string;
|
|
14
|
+
}
|
|
10
15
|
| { status: "failed" | "blocked"; code: string; message: string };
|
|
11
16
|
|
|
12
17
|
function git(cwd: string, args: string[]): string {
|
|
13
|
-
return execFileSync("git", args, {
|
|
18
|
+
return execFileSync("git", args, {
|
|
19
|
+
cwd,
|
|
20
|
+
encoding: "utf8",
|
|
21
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
22
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
23
|
+
});
|
|
14
24
|
}
|
|
15
25
|
|
|
16
26
|
function commitExists(gitRoot: string, sha: string): boolean {
|
|
@@ -22,27 +32,57 @@ function commitExists(gitRoot: string, sha: string): boolean {
|
|
|
22
32
|
}
|
|
23
33
|
}
|
|
24
34
|
|
|
35
|
+
function isAncestor(gitRoot: string, ancestor: string, descendant: string):
|
|
36
|
+
| { status: "yes" | "no" }
|
|
37
|
+
| { status: "error" } {
|
|
38
|
+
const result = spawnSync("git", ["merge-base", "--is-ancestor", ancestor, descendant], {
|
|
39
|
+
cwd: gitRoot,
|
|
40
|
+
encoding: "utf8",
|
|
41
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
42
|
+
});
|
|
43
|
+
if (result.status === 0) return { status: "yes" };
|
|
44
|
+
if (result.status === 1) return { status: "no" };
|
|
45
|
+
return { status: "error" };
|
|
46
|
+
}
|
|
47
|
+
|
|
25
48
|
export function resolveReviewedHeadRelation(input: {
|
|
26
49
|
gitRoot: string;
|
|
27
|
-
|
|
50
|
+
comparisonHead: string;
|
|
28
51
|
lastReviewedCommit: string;
|
|
29
52
|
pullRequest: PullRequestSnapshot;
|
|
30
|
-
pathspecs: string[];
|
|
31
53
|
}): ReviewedHeadRelation {
|
|
32
|
-
const { gitRoot,
|
|
33
|
-
if (
|
|
54
|
+
const { gitRoot, comparisonHead, lastReviewedCommit, pullRequest } = input;
|
|
55
|
+
if (
|
|
56
|
+
comparisonHead === lastReviewedCommit &&
|
|
57
|
+
lastReviewedCommit === pullRequest.head.sha &&
|
|
58
|
+
!(pullRequest.state === "closed" && pullRequest.mergedAt && pullRequest.mergeCommitSha)
|
|
59
|
+
) {
|
|
34
60
|
return { status: "strict", reviewedHead: lastReviewedCommit };
|
|
35
61
|
}
|
|
36
62
|
if (pullRequest.state !== "closed" || !pullRequest.mergedAt || !pullRequest.mergeCommitSha ||
|
|
37
63
|
lastReviewedCommit !== pullRequest.head.sha) {
|
|
38
64
|
return { status: "failed", code: "PR_MERGE_IDENTITY_INVALID", message: "PR merge identity does not match the reviewed head" };
|
|
39
65
|
}
|
|
40
|
-
const shas = [pullRequest.base.sha, pullRequest.head.sha, pullRequest.mergeCommitSha,
|
|
66
|
+
const shas = [pullRequest.base.sha, pullRequest.head.sha, pullRequest.mergeCommitSha, comparisonHead];
|
|
41
67
|
if (shas.some((sha) => !SHA_PATTERN.test(sha) || !commitExists(gitRoot, sha))) {
|
|
42
68
|
return { status: "blocked", code: "PR_MERGE_OBJECT_MISSING", message: "Required PR merge Git objects are unavailable" };
|
|
43
69
|
}
|
|
70
|
+
const ancestry = isAncestor(gitRoot, pullRequest.mergeCommitSha, comparisonHead);
|
|
71
|
+
if (ancestry.status === "no") {
|
|
72
|
+
return {
|
|
73
|
+
status: "failed",
|
|
74
|
+
code: "PR_MERGE_TARGET_MISMATCH",
|
|
75
|
+
message: "PR merge commit is not in the authoritative target history"
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (ancestry.status === "error") {
|
|
79
|
+
return {
|
|
80
|
+
status: "blocked",
|
|
81
|
+
code: "PR_MERGE_GIT_EVIDENCE_UNAVAILABLE",
|
|
82
|
+
message: "Unable to verify PR merge ancestry"
|
|
83
|
+
};
|
|
84
|
+
}
|
|
44
85
|
try {
|
|
45
|
-
git(gitRoot, ["merge-base", "--is-ancestor", pullRequest.mergeCommitSha, localHead]);
|
|
46
86
|
const parents = git(gitRoot, ["rev-list", "--parents", "-n", "1", pullRequest.mergeCommitSha]).trim().split(/\s+/);
|
|
47
87
|
if (parents.length !== 2) {
|
|
48
88
|
return { status: "failed", code: "PR_MERGE_TOPOLOGY_INVALID", message: "PR merge commit is not a single-parent squash commit" };
|
|
@@ -53,17 +93,14 @@ export function resolveReviewedHeadRelation(input: {
|
|
|
53
93
|
if (reviewedDiff !== mergedDiff) {
|
|
54
94
|
return { status: "failed", code: "PR_MERGE_CONTENT_MISMATCH", message: "Squash merge content differs from the reviewed PR changes" };
|
|
55
95
|
}
|
|
56
|
-
const postMerge = git(gitRoot, ["rev-list", `${pullRequest.mergeCommitSha}..${localHead}`, "--", ...pathspecs]).trim();
|
|
57
|
-
if (postMerge) {
|
|
58
|
-
return { status: "failed", code: "POST_MERGE_CHANGES", message: "Protected paths changed after the squash merge" };
|
|
59
|
-
}
|
|
60
96
|
} catch {
|
|
61
97
|
return { status: "blocked", code: "PR_MERGE_GIT_EVIDENCE_UNAVAILABLE", message: "Unable to verify PR merge topology or content" };
|
|
62
98
|
}
|
|
63
99
|
return {
|
|
64
100
|
status: "merged-equivalent",
|
|
65
101
|
reviewedHead: lastReviewedCommit,
|
|
66
|
-
mergeCommit: pullRequest.mergeCommitSha
|
|
102
|
+
mergeCommit: pullRequest.mergeCommitSha,
|
|
103
|
+
comparisonHead
|
|
67
104
|
};
|
|
68
105
|
}
|
|
69
106
|
|
|
@@ -2,6 +2,11 @@ import fs from 'node:fs';
|
|
|
2
2
|
|
|
3
3
|
import { parseTaskFrontmatter } from '../task/frontmatter.ts';
|
|
4
4
|
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
5
|
+
import {
|
|
6
|
+
inspectPlatformRequiredChecks,
|
|
7
|
+
registerPlatformCapabilities
|
|
8
|
+
} from './adapters.ts';
|
|
9
|
+
import type { PlatformCheckSnapshot } from './adapters.ts';
|
|
5
10
|
import { resolvePlatformContext } from './context.ts';
|
|
6
11
|
import { createGitHubClient } from './github-client.ts';
|
|
7
12
|
import type { GitHubClient } from './github-client.ts';
|
|
@@ -12,15 +17,7 @@ import type { PullRequestSnapshot } from './pull-requests.ts';
|
|
|
12
17
|
|
|
13
18
|
type CheckBucket = 'pass' | 'fail' | 'pending' | 'cancel';
|
|
14
19
|
type CheckState = 'passed' | 'failed' | 'pending' | 'timed-out' | 'cancelled' | 'no-required';
|
|
15
|
-
type CheckSnapshot =
|
|
16
|
-
name: string;
|
|
17
|
-
bucket: CheckBucket;
|
|
18
|
-
workflow?: string | null;
|
|
19
|
-
conclusion?: string | null;
|
|
20
|
-
detailsUrl?: string | null;
|
|
21
|
-
startedAt?: string | null;
|
|
22
|
-
completedAt?: string | null;
|
|
23
|
-
};
|
|
20
|
+
type CheckSnapshot = PlatformCheckSnapshot;
|
|
24
21
|
type ChecksSnapshot = { state: Exclude<CheckState, 'timed-out'>; required: CheckSnapshot[] };
|
|
25
22
|
type RunCandidate = { id: number; name: string; headSha: string; jobId?: number | null };
|
|
26
23
|
type ChecksResult = PlatformResult & {
|
|
@@ -29,6 +26,7 @@ type ChecksResult = PlatformResult & {
|
|
|
29
26
|
resolution?: { status: 'resolved' | 'missing' | 'ambiguous'; runId: number | null; jobId: number | null };
|
|
30
27
|
logs?: { runId: number; jobId?: number; text: string };
|
|
31
28
|
};
|
|
29
|
+
type InspectionOptions = { cwd?: string; client?: unknown };
|
|
32
30
|
type SharedOptions = { cwd?: string; client?: GitHubClient };
|
|
33
31
|
|
|
34
32
|
function classifyRequiredChecks(required: CheckSnapshot[]): ChecksSnapshot {
|
|
@@ -114,14 +112,27 @@ function normalizeChecks(value: unknown): CheckSnapshot[] {
|
|
|
114
112
|
});
|
|
115
113
|
}
|
|
116
114
|
|
|
117
|
-
|
|
115
|
+
registerPlatformCapabilities('github', {
|
|
116
|
+
inspectRequiredChecks({ client, repository, number, cwd }) {
|
|
117
|
+
const github = (client as GitHubClient | undefined) || createGitHubClient();
|
|
118
|
+
const inspected = github.json<unknown>([
|
|
119
|
+
'pr', 'checks', String(number), '--repo', repository, '--required',
|
|
120
|
+
'--json', 'name,state,bucket,link,workflow,startedAt,completedAt'
|
|
121
|
+
], { cwd });
|
|
122
|
+
return inspected.ok
|
|
123
|
+
? { ok: true, value: normalizeChecks(inspected.value) }
|
|
124
|
+
: { ok: false, error: inspected.error };
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
function resolvedTask(taskRef: string, options: InspectionOptions) {
|
|
118
129
|
const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
|
|
119
130
|
if (!resolved.ok) return { ok: false as const, output: checksResult('failed', { error: { code: resolved.code, message: resolved.message, retryable: false } }) };
|
|
120
131
|
const frontmatter = parseTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8'));
|
|
121
132
|
const pr = Number(frontmatter.pr_number);
|
|
122
133
|
const prNumber = Number.isInteger(pr) && pr > 0 ? pr : null;
|
|
123
134
|
if (!prNumber) return { ok: false as const, output: checksResult('failed', { error: { code: 'PR_NOT_LINKED', message: 'Task has no valid pr_number', retryable: false } }) };
|
|
124
|
-
const client = options.client || createGitHubClient();
|
|
135
|
+
const client = (options.client as GitHubClient | undefined) || createGitHubClient();
|
|
125
136
|
const context = resolvePlatformContext({ cwd: resolved.repoRoot, client });
|
|
126
137
|
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 }) };
|
|
127
138
|
const inspected = inspectPlatformPullRequest(taskRef, { cwd: resolved.repoRoot, client });
|
|
@@ -129,22 +140,30 @@ function resolvedTask(taskRef: string, options: SharedOptions) {
|
|
|
129
140
|
return { ok: true as const, resolved, prNumber, client, context, pullRequest: inspected.pullRequest };
|
|
130
141
|
}
|
|
131
142
|
|
|
132
|
-
function inspectRequiredChecks(taskRef: string, options:
|
|
143
|
+
function inspectRequiredChecks(taskRef: string, options: InspectionOptions = {}): ChecksResult {
|
|
133
144
|
const base = resolvedTask(taskRef, options);
|
|
134
145
|
if (!base.ok) return base.output;
|
|
135
146
|
const repository = base.context.platform.repository!;
|
|
136
|
-
const inspected = base.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
147
|
+
const inspected = inspectPlatformRequiredChecks(base.context.platform.type, {
|
|
148
|
+
cwd: base.resolved.repoRoot,
|
|
149
|
+
repository,
|
|
150
|
+
number: base.prNumber,
|
|
151
|
+
headSha: base.pullRequest.head.sha,
|
|
152
|
+
client: options.client || base.client
|
|
153
|
+
});
|
|
154
|
+
if (!inspected.ok || !inspected.value) {
|
|
155
|
+
const error = inspected.error || {
|
|
156
|
+
code: 'REQUIRED_CHECKS_INSPECTION_INVALID',
|
|
157
|
+
message: 'Platform adapter returned no required-checks snapshot',
|
|
158
|
+
retryable: false
|
|
159
|
+
};
|
|
160
|
+
return checksResult(error.retryable ? 'blocked' : 'failed', {
|
|
142
161
|
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
143
162
|
resource: { kind: 'pull-request', number: base.prNumber },
|
|
144
|
-
pullRequest: base.pullRequest, error
|
|
163
|
+
pullRequest: base.pullRequest, error
|
|
145
164
|
});
|
|
146
165
|
}
|
|
147
|
-
const classified = classifyRequiredChecks(
|
|
166
|
+
const classified = classifyRequiredChecks(inspected.value);
|
|
148
167
|
const status = classified.state === 'passed' || classified.state === 'no-required'
|
|
149
168
|
? 'no-op'
|
|
150
169
|
: classified.state === 'failed' || classified.state === 'cancelled' ? 'failed' : 'blocked';
|
|
@@ -160,7 +179,7 @@ function inspectRequiredChecks(taskRef: string, options: SharedOptions = {}): Ch
|
|
|
160
179
|
});
|
|
161
180
|
}
|
|
162
181
|
|
|
163
|
-
async function watchPlatformChecks(taskRef: string, options:
|
|
182
|
+
async function watchPlatformChecks(taskRef: string, options: InspectionOptions & {
|
|
164
183
|
intervalSeconds: number;
|
|
165
184
|
deadlineSeconds: number;
|
|
166
185
|
signal?: AbortSignal;
|
|
@@ -218,13 +237,21 @@ function resolvePlatformCheckRun(taskRef: string, options: SharedOptions & { che
|
|
|
218
237
|
});
|
|
219
238
|
}
|
|
220
239
|
|
|
240
|
+
function fetchCheckLogText(client: GitHubClient, args: string[], cwd: string) {
|
|
241
|
+
const fetched = client.text(args, { cwd });
|
|
242
|
+
if (fetched.ok || args[0] !== 'api' || !/response contains terminal escape sequences/i.test(fetched.error.message)) {
|
|
243
|
+
return fetched;
|
|
244
|
+
}
|
|
245
|
+
return client.text([...args, '--allow-escape-sequences'], { cwd });
|
|
246
|
+
}
|
|
247
|
+
|
|
221
248
|
function fetchPlatformCheckLogs(taskRef: string, options: SharedOptions & { run: number; job?: number }): ChecksResult {
|
|
222
249
|
const base = resolvedTask(taskRef, options);
|
|
223
250
|
if (!base.ok) return base.output;
|
|
224
251
|
const args = options.job
|
|
225
252
|
? ['api', `repos/${base.context.platform.repository}/actions/jobs/${options.job}/logs`]
|
|
226
253
|
: ['run', 'view', String(options.run), '--repo', base.context.platform.repository!, '--log-failed'];
|
|
227
|
-
const fetched = base.client
|
|
254
|
+
const fetched = fetchCheckLogText(base.client, args, base.resolved.repoRoot);
|
|
228
255
|
if (!fetched.ok) return checksResult(fetched.error.retryable ? 'blocked' : 'failed', { platform: base.context.platform, capabilities: base.context.capabilities, error: fetched.error });
|
|
229
256
|
if (!fetched.value) return checksResult('failed', { error: { code: 'CHECK_LOGS_MISSING', message: 'No failed logs are available', retryable: false } });
|
|
230
257
|
return checksResult('no-op', {
|
|
@@ -236,6 +263,7 @@ function fetchPlatformCheckLogs(taskRef: string, options: SharedOptions & { run:
|
|
|
236
263
|
|
|
237
264
|
export {
|
|
238
265
|
classifyRequiredChecks,
|
|
266
|
+
fetchCheckLogText,
|
|
239
267
|
fetchPlatformCheckLogs,
|
|
240
268
|
inspectRequiredChecks,
|
|
241
269
|
parseRunJobIdentity,
|