@fitlab-ai/agent-infra 0.9.10 → 0.9.12
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 +19 -9
- package/README.zh-CN.md +19 -9
- package/bin/cli.ts +17 -4
- package/dist/bin/cli.js +19 -4
- package/dist/bin/internal-cli.js +55 -4
- package/dist/lib/agent-client.js +0 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +71 -10
- package/dist/lib/agent-clients/adapters/opencode-sandbox.js +0 -4
- package/dist/lib/agent-clients/adapters/traecli.js +7 -8
- package/dist/lib/agent-clients/config.js +14 -99
- package/dist/lib/agent-clients/reconcile.js +1 -11
- package/dist/lib/agent-clients/schema.js +5 -4
- package/dist/lib/agent-clients/tokens.js +5 -15
- package/dist/lib/decide.js +14 -30
- package/dist/lib/defaults.json +8 -0
- package/dist/lib/git/workflow.js +43 -1
- package/dist/lib/init.js +3 -2
- package/dist/lib/internal/git-workflow.js +8 -1
- package/dist/lib/internal/platform-pr.js +8 -2
- package/dist/lib/internal/release-workflow.js +0 -1
- package/dist/lib/internal/task-delivery.js +59 -0
- package/dist/lib/internal/task-finalization.js +19 -9
- package/dist/lib/internal/task-ledger.js +8 -2
- package/dist/lib/internal/task-lifecycle.js +17 -93
- package/dist/lib/internal/task-orchestration.js +30 -216
- package/dist/lib/internal/task-validate.js +2 -1
- package/dist/lib/merge.js +205 -103
- package/dist/lib/platform/github-client.js +7 -1
- package/dist/lib/platform/pr-checks.js +8 -5
- package/dist/lib/platform/pr-summary.js +12 -8
- package/dist/lib/platform/pull-requests.js +393 -91
- package/dist/lib/platform/verification-required.js +13 -2
- package/dist/lib/platform/verification-sync.js +9 -4
- package/dist/lib/pr-review/evidence-grading.js +12 -4
- package/dist/lib/process-data/normalize.js +2 -9
- package/dist/lib/run/host.js +46 -16
- package/dist/lib/sandbox/agent-client-reconciler.js +4 -12
- package/dist/lib/sandbox/commands/create.js +3 -3
- package/dist/lib/sandbox/commands/enter.js +18 -4
- package/dist/lib/sandbox/config.js +8 -12
- package/dist/lib/sandbox/control/executor.js +131 -39
- package/dist/lib/sandbox/control/lifecycle.js +55 -24
- package/dist/lib/sandbox/control/server.js +18 -16
- package/dist/lib/sandbox/mounts.js +3 -2
- package/dist/lib/sandbox/recovery.js +123 -22
- package/dist/lib/sandbox/workspace-identity.js +14 -0
- package/dist/lib/sandbox/workspace-view.js +8 -9
- package/dist/lib/self-update.js +278 -0
- package/dist/lib/server/process-state.js +2 -2
- package/dist/lib/server/protocol.js +5 -5
- package/dist/lib/task/activity-log.js +4 -1
- package/dist/lib/task/artifact-lifecycle.js +6 -4
- package/dist/lib/task/codex-orchestration.js +1 -1
- package/dist/lib/task/command-options.js +1 -3
- package/dist/lib/task/commands/cat.js +0 -5
- package/dist/lib/task/commands/decisions.js +76 -92
- package/dist/lib/task/commands/files.js +6 -6
- package/dist/lib/task/commands/grep.js +25 -61
- package/dist/lib/task/commands/issue-body.js +6 -6
- package/dist/lib/task/commands/log.js +25 -10
- package/dist/lib/task/commands/show.js +5 -6
- package/dist/lib/task/commands/status.js +8 -8
- package/dist/lib/task/commit-intent.js +100 -0
- package/dist/lib/task/commit-operation.js +181 -91
- package/dist/lib/task/commit-policy.js +1 -1
- package/dist/lib/task/control-authority.js +431 -0
- package/dist/lib/task/create.js +39 -8
- package/dist/lib/task/current-contract.js +35 -0
- package/dist/lib/task/decision-details.js +218 -0
- package/dist/lib/task/decision-intents.js +5 -2
- package/dist/lib/task/delivery-target.js +97 -0
- package/dist/lib/task/delivery.js +153 -0
- package/dist/lib/task/events.js +11 -5
- package/dist/lib/task/human-override.js +5 -2
- package/dist/lib/task/index.js +19 -20
- package/dist/lib/task/ledger-intents.js +8 -9
- package/dist/lib/task/ledger.js +13 -6
- package/dist/lib/task/lifecycle.js +122 -9
- package/dist/lib/task/orchestration.js +105 -75
- package/dist/lib/task/pr-delivery-fact.js +182 -0
- package/dist/lib/task/review-artifacts.js +31 -12
- package/dist/lib/task/review-finalization.js +13 -4
- package/dist/lib/task/review-fingerprint.js +8 -0
- package/dist/lib/task/verification-engine.js +86 -39
- package/dist/lib/task/verification-review.js +2 -2
- package/dist/lib/task/workspace-snapshot.js +5 -11
- package/dist/lib/task/write.js +11 -1
- package/dist/lib/update.js +13 -35
- package/dist/lib/version.js +5 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-client.ts +0 -1
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +105 -10
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +12 -1
- package/lib/agent-clients/adapters/opencode-sandbox.ts +0 -5
- package/lib/agent-clients/adapters/traecli.ts +7 -8
- package/lib/agent-clients/config.ts +14 -144
- package/lib/agent-clients/reconcile.ts +1 -12
- package/lib/agent-clients/schema.ts +5 -4
- package/lib/agent-clients/tokens.ts +5 -18
- package/lib/decide.ts +12 -22
- package/lib/defaults.json +8 -0
- package/lib/git/workflow.ts +38 -1
- package/lib/init.ts +5 -2
- package/lib/internal/git-workflow.ts +7 -1
- package/lib/internal/platform-pr.ts +8 -1
- package/lib/internal/release-workflow.ts +0 -1
- package/lib/internal/task-delivery.ts +53 -0
- package/lib/internal/task-finalization.ts +27 -13
- package/lib/internal/task-ledger.ts +8 -2
- package/lib/internal/task-lifecycle.ts +33 -76
- package/lib/internal/task-orchestration.ts +33 -190
- package/lib/internal/task-validate.ts +3 -2
- package/lib/merge.ts +236 -120
- package/lib/platform/github-client.ts +8 -1
- package/lib/platform/pr-checks.ts +7 -5
- package/lib/platform/pr-summary.ts +10 -7
- package/lib/platform/pull-requests.ts +425 -87
- package/lib/platform/verification-required.ts +12 -2
- package/lib/platform/verification-sync.ts +9 -4
- package/lib/pr-review/evidence-grading.ts +7 -4
- package/lib/process-data/normalize.ts +2 -8
- package/lib/run/host.ts +63 -14
- package/lib/sandbox/agent-client-reconciler.ts +5 -15
- package/lib/sandbox/commands/create.ts +4 -4
- package/lib/sandbox/commands/enter.ts +24 -4
- package/lib/sandbox/config.ts +9 -14
- package/lib/sandbox/control/container-identity.ts +2 -2
- package/lib/sandbox/control/executor.ts +148 -51
- package/lib/sandbox/control/lifecycle.ts +59 -31
- package/lib/sandbox/control/protocol.ts +1 -3
- package/lib/sandbox/control/server.ts +15 -12
- package/lib/sandbox/control/state.ts +7 -7
- package/lib/sandbox/mounts.ts +4 -3
- package/lib/sandbox/recovery.ts +162 -34
- package/lib/sandbox/tools.ts +1 -1
- package/lib/sandbox/workspace-identity.ts +28 -0
- package/lib/sandbox/workspace-view.ts +14 -10
- package/lib/self-update.ts +341 -0
- package/lib/server/process-state.ts +2 -2
- package/lib/server/protocol.ts +5 -5
- package/lib/task/activity-log.ts +6 -1
- package/lib/task/artifact-lifecycle.ts +7 -5
- package/lib/task/codex-orchestration.ts +2 -1
- package/lib/task/command-options.ts +1 -2
- package/lib/task/commands/cat.ts +0 -4
- package/lib/task/commands/decisions.ts +80 -75
- package/lib/task/commands/files.ts +6 -6
- package/lib/task/commands/grep.ts +23 -51
- package/lib/task/commands/issue-body.ts +6 -6
- package/lib/task/commands/log.ts +22 -10
- package/lib/task/commands/show.ts +5 -6
- package/lib/task/commands/status.ts +8 -8
- package/lib/task/commit-intent.ts +137 -0
- package/lib/task/commit-operation.ts +161 -79
- package/lib/task/commit-policy.ts +1 -2
- package/lib/task/control-authority.ts +563 -0
- package/lib/task/create.ts +40 -10
- package/lib/task/current-contract.ts +49 -0
- package/lib/task/decision-details.ts +280 -0
- package/lib/task/decision-intents.ts +7 -2
- package/lib/task/delivery-target.ts +130 -0
- package/lib/task/delivery.ts +160 -0
- package/lib/task/events.ts +10 -5
- package/lib/task/human-override.ts +5 -2
- package/lib/task/index.ts +19 -20
- package/lib/task/ledger-intents.ts +6 -8
- package/lib/task/ledger.ts +17 -7
- package/lib/task/lifecycle.ts +113 -4
- package/lib/task/orchestration.ts +102 -80
- package/lib/task/pr-delivery-fact.ts +254 -0
- package/lib/task/review-artifacts.ts +53 -15
- package/lib/task/review-finalization.ts +21 -5
- package/lib/task/review-fingerprint.ts +10 -0
- package/lib/task/verification-engine.ts +78 -46
- package/lib/task/verification-review.ts +3 -1
- package/lib/task/workspace-snapshot.ts +4 -13
- package/lib/task/write.ts +20 -1
- package/lib/update.ts +14 -38
- package/lib/version.ts +6 -1
- package/package.json +1 -1
- package/templates/.agents/README.en.md +5 -3
- package/templates/.agents/README.zh-CN.md +5 -3
- package/templates/.agents/rules/README.en.md +4 -2
- package/templates/.agents/rules/README.zh-CN.md +4 -2
- package/templates/.agents/rules/compatibility-policy.en.md +5 -4
- package/templates/.agents/rules/compatibility-policy.zh-CN.md +5 -4
- package/templates/.agents/rules/create-issue.en.md +11 -2
- package/templates/.agents/rules/create-issue.zh-CN.md +24 -2
- package/templates/.agents/rules/human-decision-context.en.md +9 -7
- package/templates/.agents/rules/human-decision-context.zh-CN.md +9 -7
- package/templates/.agents/rules/{issue-fields.github.en.md → issue-fields.en.md} +1 -1
- package/templates/.agents/rules/{issue-fields.github.zh-CN.md → issue-fields.zh-CN.md} +1 -1
- package/templates/.agents/rules/issue-pr-commands.en.md +5 -18
- package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +26 -5
- package/templates/.agents/rules/issue-sync.zh-CN.md +62 -5
- package/templates/.agents/rules/label-milestone-setup.en.md +15 -2
- package/templates/.agents/rules/label-milestone-setup.zh-CN.md +15 -2
- package/templates/.agents/rules/local-artifact-repair.en.md +46 -0
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +46 -0
- package/templates/.agents/rules/milestone-inference.en.md +12 -2
- package/templates/.agents/rules/milestone-inference.zh-CN.md +25 -3
- package/templates/.agents/rules/next-step-output.en.md +3 -3
- package/templates/.agents/rules/next-step-output.zh-CN.md +3 -3
- package/templates/.agents/rules/no-mid-flow-questions.en.md +1 -1
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +1 -1
- package/templates/.agents/rules/pr-sync.en.md +23 -14
- package/templates/.agents/rules/pr-sync.zh-CN.md +23 -2
- package/templates/.agents/rules/release-commands.en.md +42 -3
- package/templates/.agents/rules/release-commands.zh-CN.md +42 -3
- package/templates/.agents/rules/review-handshake.en.md +1 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
- package/templates/.agents/rules/security-alerts.en.md +17 -2
- package/templates/.agents/rules/security-alerts.zh-CN.md +17 -2
- package/templates/.agents/rules/sync-content-generation.en.md +8 -0
- package/templates/.agents/rules/sync-content-generation.zh-CN.md +8 -0
- package/templates/.agents/rules/task-management.en.md +2 -3
- package/templates/.agents/rules/task-management.zh-CN.md +2 -3
- package/templates/.agents/scripts/security-alerts.github.sh +151 -0
- package/templates/.agents/scripts/security-alerts.sh +31 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +4 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +4 -3
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/cancel-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/check-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/close-codescan/SKILL.en.md +3 -3
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +3 -3
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/code-task/SKILL.en.md +8 -5
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +9 -6
- package/templates/.agents/skills/commit/SKILL.en.md +30 -10
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +6 -7
- package/templates/.agents/skills/commit/config/verify.json +1 -1
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +3 -3
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +3 -3
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -2
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +7 -6
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +7 -6
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +13 -13
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +13 -13
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +2 -2
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/SKILL.en.md +9 -11
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +9 -11
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +9 -4
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +9 -4
- package/templates/.agents/skills/create-pr/scripts/change-report.mjs +203 -0
- package/templates/.agents/skills/create-release-note/SKILL.en.md +5 -6
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +5 -6
- package/templates/.agents/skills/create-task/SKILL.en.md +2 -1
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +2 -1
- package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/init-labels/SKILL.en.md +9 -10
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +9 -10
- package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +57 -1
- package/templates/.agents/skills/init-labels/scripts/init-labels.sh +27 -1
- package/templates/.agents/skills/init-milestones/SKILL.en.md +3 -5
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +3 -5
- package/templates/.agents/skills/plan-task/SKILL.en.md +4 -3
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -3
- package/templates/.agents/skills/restore-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +8 -5
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +8 -5
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -0
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +18 -0
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +18 -0
- package/templates/.agents/skills/review-code/SKILL.en.md +11 -8
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +11 -8
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -0
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/review-code/reference/migration-risks.en.md +2 -1
- package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +2 -1
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +19 -1
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +19 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +4 -2
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +4 -2
- package/templates/.agents/skills/review-plan/SKILL.en.md +8 -5
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +8 -5
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -0
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +18 -0
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +18 -0
- package/templates/.agents/skills/review-pr/SKILL.en.md +1 -0
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +2 -2
- package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +2 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +5 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +5 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +5 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +2 -2
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +144 -109
- package/templates/.agents/skills/watch-pr/SKILL.en.md +5 -5
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +5 -5
- package/templates/.agents/templates/task.en.md +6 -2
- package/templates/.agents/templates/task.zh-CN.md +6 -2
- package/templates/.agents/workflows/bug-fix.en.yaml +9 -8
- package/templates/.agents/workflows/bug-fix.zh-CN.yaml +9 -8
- package/templates/.agents/workflows/feature-development.en.yaml +9 -8
- package/templates/.agents/workflows/feature-development.zh-CN.yaml +9 -8
- package/templates/.agents/workflows/refactoring.en.yaml +9 -8
- package/templates/.agents/workflows/refactoring.zh-CN.yaml +9 -8
- package/templates/.claude/commands/analyze-task.en.md +1 -1
- package/templates/.claude/commands/analyze-task.zh-CN.md +1 -1
- package/templates/.claude/commands/block-task.en.md +1 -1
- package/templates/.claude/commands/block-task.zh-CN.md +1 -1
- package/templates/.claude/commands/cancel-task.en.md +1 -1
- package/templates/.claude/commands/cancel-task.zh-CN.md +1 -1
- package/templates/.claude/commands/check-task.en.md +1 -1
- package/templates/.claude/commands/check-task.zh-CN.md +1 -1
- package/templates/.claude/commands/code-task.en.md +1 -1
- package/templates/.claude/commands/code-task.zh-CN.md +1 -1
- package/templates/.claude/commands/commit.en.md +1 -1
- package/templates/.claude/commands/commit.zh-CN.md +1 -1
- package/templates/.claude/commands/complete-manual-validation.en.md +1 -1
- package/templates/.claude/commands/complete-manual-validation.zh-CN.md +1 -1
- package/templates/.claude/commands/complete-task.en.md +1 -1
- package/templates/.claude/commands/complete-task.zh-CN.md +1 -1
- package/templates/.claude/commands/create-pr.en.md +1 -1
- package/templates/.claude/commands/create-pr.zh-CN.md +1 -1
- package/templates/.claude/commands/plan-task.en.md +1 -1
- package/templates/.claude/commands/plan-task.zh-CN.md +1 -1
- package/templates/.claude/commands/review-analysis.en.md +1 -1
- package/templates/.claude/commands/review-analysis.zh-CN.md +1 -1
- package/templates/.claude/commands/review-code.en.md +1 -1
- package/templates/.claude/commands/review-code.zh-CN.md +1 -1
- package/templates/.claude/commands/review-plan.en.md +1 -1
- package/templates/.claude/commands/review-plan.zh-CN.md +1 -1
- package/templates/.claude/commands/run-manual-validation.en.md +1 -1
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
- package/templates/.claude/commands/run-task.en.md +1 -1
- package/templates/.claude/commands/run-task.zh-CN.md +1 -1
- package/templates/.claude/commands/watch-pr.en.md +1 -1
- package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
- package/templates/{.trae/skills → .traecli/commands}/update-agent-infra.en.md +0 -1
- package/templates/{.trae/skills → .traecli/commands}/update-agent-infra.zh-CN.md +0 -1
- package/dist/lib/builtin-tuis.js +0 -40
- package/lib/builtin-tuis.ts +0 -56
- package/templates/.agents/rules/create-issue.github.en.md +0 -14
- package/templates/.agents/rules/create-issue.github.zh-CN.md +0 -27
- package/templates/.agents/rules/issue-pr-commands.github.en.md +0 -34
- package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +0 -49
- package/templates/.agents/rules/issue-sync.github.en.md +0 -40
- package/templates/.agents/rules/issue-sync.github.zh-CN.md +0 -76
- package/templates/.agents/rules/label-milestone-setup.github.en.md +0 -60
- package/templates/.agents/rules/label-milestone-setup.github.zh-CN.md +0 -60
- package/templates/.agents/rules/milestone-inference.github.en.md +0 -15
- package/templates/.agents/rules/milestone-inference.github.zh-CN.md +0 -27
- package/templates/.agents/rules/pr-checks-commands.github.en.md +0 -18
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +0 -34
- package/templates/.agents/rules/pr-sync.github.en.md +0 -43
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +0 -69
- package/templates/.agents/rules/release-commands.github.en.md +0 -38
- package/templates/.agents/rules/release-commands.github.zh-CN.md +0 -38
- package/templates/.agents/rules/security-alerts.github.en.md +0 -43
- package/templates/.agents/rules/security-alerts.github.zh-CN.md +0 -43
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
2
3
|
import { execFileSync } from 'node:child_process';
|
|
3
|
-
import {
|
|
4
|
+
import { parseTypedTaskFrontmatter } from '../task/frontmatter.js';
|
|
4
5
|
import { resolveTaskRef } from '../task/resolve-ref.js';
|
|
5
6
|
import { extractSection } from '../task/sections.js';
|
|
6
7
|
import { captureTaskWriteMetadata, writeTask } from '../task/write.js';
|
|
@@ -12,7 +13,9 @@ import { planPullRequestMetadata } from './pull-request-metadata.js';
|
|
|
12
13
|
import { platformResult } from './types.js';
|
|
13
14
|
import { inspectCompletionArtifacts } from '../task/finalization-artifacts.js';
|
|
14
15
|
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
|
|
16
|
+
import { resolveDeliveryTarget } from '../task/delivery-target.js';
|
|
15
17
|
import { mergeOperationWarnings } from '../task/operation-outcome.js';
|
|
18
|
+
import { buildBoundFact, buildSkippedFact, factFrontmatterMutation, readPrDeliveryFact } from '../task/pr-delivery-fact.js';
|
|
16
19
|
function warningResultForPrimary(primaryResult) {
|
|
17
20
|
if (primaryResult === 'pr_created')
|
|
18
21
|
return 'pr_created_with_warnings';
|
|
@@ -26,6 +29,7 @@ function result(status, taskId, issueNumber, prNumber, overrides = {}) {
|
|
|
26
29
|
task: { id: taskId, issueNumber, prNumber },
|
|
27
30
|
pullRequest: null,
|
|
28
31
|
result: null,
|
|
32
|
+
creation: null,
|
|
29
33
|
warnings: [],
|
|
30
34
|
...overrides
|
|
31
35
|
};
|
|
@@ -42,6 +46,12 @@ function externalResult(base, overrides = {}) {
|
|
|
42
46
|
...overrides
|
|
43
47
|
};
|
|
44
48
|
}
|
|
49
|
+
function withCreation(output, creation) {
|
|
50
|
+
return { ...output, creation };
|
|
51
|
+
}
|
|
52
|
+
const PRECONDITION_NOT_CREATED = {
|
|
53
|
+
kind: 'not-created', reason: 'precondition-failed', createdByCurrentOperation: false
|
|
54
|
+
};
|
|
45
55
|
function normalizePullRequest(remote, repository) {
|
|
46
56
|
const number = Number(remote.number);
|
|
47
57
|
const headRepository = remote.head?.repo?.full_name;
|
|
@@ -111,7 +121,7 @@ function hasCompleteExternalIdentity(pullRequest) {
|
|
|
111
121
|
];
|
|
112
122
|
return Number.isInteger(pullRequest.number) && pullRequest.number > 0 &&
|
|
113
123
|
required.every((value) => Boolean(value?.trim())) &&
|
|
114
|
-
Boolean(pullRequest.mergedAt) === Boolean(pullRequest.mergeCommitSha);
|
|
124
|
+
(pullRequest.state === 'open' || Boolean(pullRequest.mergedAt) === Boolean(pullRequest.mergeCommitSha));
|
|
115
125
|
}
|
|
116
126
|
function selectExternalPullRequest(candidates, repository, existingPrNumber, explicitPrNumber) {
|
|
117
127
|
const invalid = candidates.find((candidate) => !hasCompleteExternalIdentity(candidate));
|
|
@@ -135,6 +145,7 @@ function selectExternalPullRequest(candidates, repository, existingPrNumber, exp
|
|
|
135
145
|
const wantedRepository = repositoryKey(repository);
|
|
136
146
|
const eligible = unique.filter((candidate) => repositoryKey(candidate.repository) === wantedRepository &&
|
|
137
147
|
repositoryKey(candidate.base.repository) === wantedRepository &&
|
|
148
|
+
candidate.state === 'closed' &&
|
|
138
149
|
Boolean(candidate.mergedAt && candidate.mergeCommitSha));
|
|
139
150
|
if (explicitPrNumber !== null) {
|
|
140
151
|
const selected = unique.find((candidate) => candidate.number === explicitPrNumber);
|
|
@@ -303,6 +314,68 @@ function validateBoundPullRequest(base, pullRequest, head, baseRef, expectedSha)
|
|
|
303
314
|
};
|
|
304
315
|
return { ok: true };
|
|
305
316
|
}
|
|
317
|
+
function resolveTaskDeliveryTarget(repoRoot, frontmatter) {
|
|
318
|
+
const existing = {
|
|
319
|
+
...(typeof frontmatter.delivery_remote === 'string' && frontmatter.delivery_remote
|
|
320
|
+
? { remote: frontmatter.delivery_remote } : {}),
|
|
321
|
+
...(typeof frontmatter.delivery_base_ref === 'string' && frontmatter.delivery_base_ref
|
|
322
|
+
? { baseRef: frontmatter.delivery_base_ref } : {})
|
|
323
|
+
};
|
|
324
|
+
const target = resolveDeliveryTarget(repoRoot, existing);
|
|
325
|
+
return target.ok
|
|
326
|
+
? { ok: true, value: target.value }
|
|
327
|
+
: { ok: false, error: { code: target.code, message: target.message, retryable: false } };
|
|
328
|
+
}
|
|
329
|
+
function taskDeliveryTarget(base) {
|
|
330
|
+
return resolveTaskDeliveryTarget(base.resolved.repoRoot, base.frontmatter);
|
|
331
|
+
}
|
|
332
|
+
function validateIdentityAgainstTarget(repository, target, pullRequest, options = {}) {
|
|
333
|
+
if (!pullRequest || !hasCompleteExternalIdentity(pullRequest))
|
|
334
|
+
return {
|
|
335
|
+
ok: false,
|
|
336
|
+
error: { code: options.errorCode || 'PR_BIND_IDENTITY_MISMATCH', message: 'Pull request identity is incomplete', retryable: false }
|
|
337
|
+
};
|
|
338
|
+
const wantedHead = options.expectedHead ? expectedHead(repository, options.expectedHead) : null;
|
|
339
|
+
const matches = repositoryKey(pullRequest.repository) === repositoryKey(repository)
|
|
340
|
+
&& repositoryKey(pullRequest.base.repository) === repositoryKey(repository)
|
|
341
|
+
&& pullRequest.base.ref === target.baseRef
|
|
342
|
+
&& (!wantedHead || (repositoryKey(pullRequest.head.repository) === repositoryKey(wantedHead.repository) && pullRequest.head.ref === wantedHead.ref))
|
|
343
|
+
&& (!options.expectedHeadSha || pullRequest.head.sha === options.expectedHeadSha);
|
|
344
|
+
if (!matches)
|
|
345
|
+
return {
|
|
346
|
+
ok: false,
|
|
347
|
+
error: {
|
|
348
|
+
code: options.errorCode || 'PR_BIND_IDENTITY_MISMATCH',
|
|
349
|
+
message: 'Pull request identity does not match the task branch and delivery target',
|
|
350
|
+
retryable: false
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
return { ok: true };
|
|
354
|
+
}
|
|
355
|
+
function validateWriterIdentity(base, pullRequest, options = {}) {
|
|
356
|
+
const target = taskDeliveryTarget(base);
|
|
357
|
+
if (!target.ok)
|
|
358
|
+
return target;
|
|
359
|
+
return validateIdentityAgainstTarget(base.context.platform.repository, target.value, pullRequest, options);
|
|
360
|
+
}
|
|
361
|
+
function sameDeliveryIdentity(left, right) {
|
|
362
|
+
return JSON.stringify(deliveryIdentity(left)) === JSON.stringify(deliveryIdentity(right));
|
|
363
|
+
}
|
|
364
|
+
function validateExternalMergedEvidence(initial, rechecked) {
|
|
365
|
+
if (initial.state !== 'closed'
|
|
366
|
+
|| !initial.mergedAt
|
|
367
|
+
|| !initial.mergeCommitSha
|
|
368
|
+
|| rechecked.state !== 'closed'
|
|
369
|
+
|| !rechecked.mergedAt
|
|
370
|
+
|| !rechecked.mergeCommitSha
|
|
371
|
+
|| initial.mergedAt !== rechecked.mergedAt
|
|
372
|
+
|| initial.mergeCommitSha !== rechecked.mergeCommitSha)
|
|
373
|
+
return {
|
|
374
|
+
ok: false,
|
|
375
|
+
error: { code: 'PR_EXTERNAL_IDENTITY_MISMATCH', message: 'Selected pull request merged evidence changed before task binding', retryable: false }
|
|
376
|
+
};
|
|
377
|
+
return { ok: true };
|
|
378
|
+
}
|
|
306
379
|
function resolvedContext(taskRef, options) {
|
|
307
380
|
const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
|
|
308
381
|
if (!resolved.ok)
|
|
@@ -310,9 +383,15 @@ function resolvedContext(taskRef, options) {
|
|
|
310
383
|
error: { code: resolved.code, message: resolved.message, retryable: false }
|
|
311
384
|
}) };
|
|
312
385
|
const content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
313
|
-
const frontmatter =
|
|
386
|
+
const frontmatter = parseTypedTaskFrontmatter(content);
|
|
387
|
+
const factRead = readPrDeliveryFact(frontmatter);
|
|
388
|
+
if (factRead.status === 'invalid')
|
|
389
|
+
return { ok: false, output: result('failed', resolved.taskId, null, null, {
|
|
390
|
+
error: { code: 'PR_DELIVERY_FACT_INVALID', message: factRead.error.message, retryable: false }
|
|
391
|
+
}) };
|
|
392
|
+
const fact = factRead.status === 'valid' ? factRead.fact : null;
|
|
314
393
|
const issue = Number(frontmatter.issue_number);
|
|
315
|
-
const pr =
|
|
394
|
+
const pr = fact?.state === 'bound' ? fact.identity.number : NaN;
|
|
316
395
|
const issueNumber = Number.isInteger(issue) && issue > 0 ? issue : null;
|
|
317
396
|
const prNumber = Number.isInteger(pr) && pr > 0 ? pr : null;
|
|
318
397
|
const client = options.client || createGitHubClient();
|
|
@@ -322,7 +401,7 @@ function resolvedContext(taskRef, options) {
|
|
|
322
401
|
return { ok: false, output: result(context.status, resolved.taskId, issueNumber, prNumber, {
|
|
323
402
|
platform: context.platform, capabilities: context.capabilities, operations: context.operations, error: context.error
|
|
324
403
|
}) };
|
|
325
|
-
return { ok: true, resolved, content, frontmatter, issueNumber, prNumber, client, context };
|
|
404
|
+
return { ok: true, resolved, content, frontmatter, fact, issueNumber, prNumber, client, context };
|
|
326
405
|
}
|
|
327
406
|
function inspectGitHubPullRequest(client, repository, number, cwd) {
|
|
328
407
|
const fetched = client.json(['api', `repos/${repository}/pulls/${number}`], { cwd });
|
|
@@ -450,10 +529,14 @@ function inspectPlatformPullRequest(taskRef, options = {}) {
|
|
|
450
529
|
const base = resolvedContext(taskRef, options);
|
|
451
530
|
if (!base.ok)
|
|
452
531
|
return base.output;
|
|
532
|
+
if (!base.fact)
|
|
533
|
+
return result('no-op', base.resolved.taskId, base.issueNumber, null, {
|
|
534
|
+
error: { code: 'PR_DELIVERY_FACT_MISSING', message: 'Task has no pr_delivery_fact; repair the task metadata or create a new task', retryable: false }
|
|
535
|
+
});
|
|
453
536
|
if (!base.prNumber)
|
|
454
537
|
return result('no-op', base.resolved.taskId, base.issueNumber, null, {
|
|
455
538
|
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
456
|
-
error: { code: 'PR_NOT_LINKED', message: 'Task has no
|
|
539
|
+
error: { code: 'PR_NOT_LINKED', message: 'Task has no verified bound pull request', retryable: false }
|
|
457
540
|
});
|
|
458
541
|
const fetched = inspectPlatformChangeRequest(base.context.platform.type, {
|
|
459
542
|
cwd: base.resolved.repoRoot,
|
|
@@ -518,6 +601,30 @@ function appendActivity(content, line) {
|
|
|
518
601
|
const body = extractSection(content, ['活动日志', 'Activity Log']);
|
|
519
602
|
return `${body.replace(/\s+$/, '')}${body.trim() ? '\n' : ''}${line}`;
|
|
520
603
|
}
|
|
604
|
+
function deliveryIdentity(pullRequest) {
|
|
605
|
+
return {
|
|
606
|
+
repository: pullRequest.repository,
|
|
607
|
+
number: pullRequest.number,
|
|
608
|
+
nodeId: pullRequest.nodeId,
|
|
609
|
+
url: pullRequest.url,
|
|
610
|
+
head: { ...pullRequest.head },
|
|
611
|
+
base: { ...pullRequest.base }
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
function factTimestamp(timestamp) {
|
|
615
|
+
return new Date(timestamp.replace(' ', 'T')).toISOString();
|
|
616
|
+
}
|
|
617
|
+
function boundFactFor(pullRequest, source, issueNumber, verifiedAt) {
|
|
618
|
+
return buildBoundFact({
|
|
619
|
+
identity: deliveryIdentity(pullRequest),
|
|
620
|
+
source,
|
|
621
|
+
issueNumber,
|
|
622
|
+
verifiedAt: factTimestamp(verifiedAt),
|
|
623
|
+
remoteState: pullRequest.state,
|
|
624
|
+
mergedAt: pullRequest.state === 'closed' ? pullRequest.mergedAt : null,
|
|
625
|
+
mergeCommitSha: pullRequest.state === 'closed' ? pullRequest.mergeCommitSha : null
|
|
626
|
+
});
|
|
627
|
+
}
|
|
521
628
|
function writeCreateStarted(base, agent) {
|
|
522
629
|
const starts = (base.content.match(/\*\*Create PR \[started\]\*\*/g) || []).length;
|
|
523
630
|
const dones = (base.content.match(/\*\*Create PR\*\*/g) || []).length;
|
|
@@ -530,21 +637,35 @@ function writeCreateStarted(base, agent) {
|
|
|
530
637
|
mutations: [{ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: '活动日志', body: appendActivity(base.content, `- ${metadata.timestamp} — **Create PR [started]** by ${agent} — started`) }]
|
|
531
638
|
}, { repoRoot: base.resolved.repoRoot, metadataProvider: () => metadata });
|
|
532
639
|
}
|
|
533
|
-
function bindIdentity(base, pullRequest, agent, dryRun = false) {
|
|
640
|
+
function bindIdentity(base, pullRequest, agent, source, dryRun = false, expectedHead, expectedHeadSha) {
|
|
641
|
+
const identity = validateWriterIdentity(base, pullRequest, {
|
|
642
|
+
expectedHead: source === 'explicit-bind' ? String(base.frontmatter.branch || '') : expectedHead,
|
|
643
|
+
expectedHeadSha,
|
|
644
|
+
errorCode: 'PR_BIND_IDENTITY_MISMATCH'
|
|
645
|
+
});
|
|
646
|
+
if (!identity.ok)
|
|
647
|
+
return { status: 'failed', error: identity.error };
|
|
534
648
|
if (base.prNumber && base.prNumber !== pullRequest.number)
|
|
535
649
|
return { status: 'failed', error: {
|
|
536
650
|
code: 'PR_BIND_CONFLICT', message: `Task is already bound to PR #${base.prNumber}`
|
|
537
651
|
} };
|
|
538
|
-
if (base.prNumber === pullRequest.number)
|
|
652
|
+
if (base.prNumber === pullRequest.number && base.fact?.state === 'bound') {
|
|
653
|
+
if (JSON.stringify(base.fact.identity) !== JSON.stringify(deliveryIdentity(pullRequest)))
|
|
654
|
+
return { status: 'failed', error: {
|
|
655
|
+
code: 'PR_BIND_IDENTITY_MISMATCH', message: `Task binding for PR #${pullRequest.number} does not match the fetched pull request identity`
|
|
656
|
+
} };
|
|
539
657
|
return { status: 'no-op', error: null };
|
|
658
|
+
}
|
|
540
659
|
const current = fs.readFileSync(base.resolved.taskMdPath, 'utf8');
|
|
541
660
|
const metadata = captureTaskWriteMetadata();
|
|
661
|
+
const fact = boundFactFor(pullRequest, source, base.issueNumber, metadata.timestamp);
|
|
662
|
+
const factMutation = factFrontmatterMutation(fact);
|
|
542
663
|
return writeTask({
|
|
543
664
|
taskRef: base.resolved.taskId,
|
|
544
665
|
expectedState: 'active',
|
|
545
666
|
dryRun,
|
|
546
667
|
mutations: [
|
|
547
|
-
{ kind: 'frontmatter', set: {
|
|
668
|
+
{ kind: 'frontmatter', set: { ...factMutation.set, assigned_to: agent } },
|
|
548
669
|
{ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: '活动日志', body: appendActivity(current, `- ${metadata.timestamp} — **Create PR** by ${agent} — PR #${pullRequest.number} created → ${pullRequest.url}`) }
|
|
549
670
|
]
|
|
550
671
|
}, { repoRoot: base.resolved.repoRoot, metadataProvider: () => metadata });
|
|
@@ -553,6 +674,10 @@ function bindPlatformPullRequest(taskRef, options) {
|
|
|
553
674
|
const base = resolvedContext(taskRef, options);
|
|
554
675
|
if (!base.ok)
|
|
555
676
|
return base.output;
|
|
677
|
+
if (!base.fact)
|
|
678
|
+
return result('failed', base.resolved.taskId, base.issueNumber, null, {
|
|
679
|
+
error: { code: 'PR_DELIVERY_FACT_MISSING', message: 'Task has no pr_delivery_fact; repair the task metadata or create a new task', retryable: false }
|
|
680
|
+
});
|
|
556
681
|
if (!Number.isInteger(options.pr) || options.pr <= 0)
|
|
557
682
|
return result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
558
683
|
error: { code: 'PR_NUMBER_INVALID', message: 'PR number must be positive', retryable: false }
|
|
@@ -564,14 +689,33 @@ function bindPlatformPullRequest(taskRef, options) {
|
|
|
564
689
|
const fetched = inspectGitHubPullRequest(base.client, base.context.platform.repository, options.pr, base.resolved.repoRoot);
|
|
565
690
|
if (!fetched.ok)
|
|
566
691
|
return result(fetched.error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, base.prNumber, { error: fetched.error });
|
|
567
|
-
const
|
|
692
|
+
const initialIdentity = validateWriterIdentity(base, fetched.value, {
|
|
693
|
+
expectedHead: String(base.frontmatter.branch || ''),
|
|
694
|
+
errorCode: 'PR_BIND_IDENTITY_MISMATCH'
|
|
695
|
+
});
|
|
696
|
+
if (!initialIdentity.ok)
|
|
697
|
+
return result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
698
|
+
pullRequest: fetched.value, error: initialIdentity.error
|
|
699
|
+
});
|
|
700
|
+
const rechecked = inspectGitHubPullRequest(base.client, base.context.platform.repository, options.pr, base.resolved.repoRoot);
|
|
701
|
+
if (!rechecked.ok || !rechecked.value)
|
|
702
|
+
return result(rechecked.ok ? 'failed' : rechecked.error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
703
|
+
pullRequest: fetched.value,
|
|
704
|
+
error: rechecked.ok ? { code: 'PR_BIND_RECHECK_FAILED', message: 'Pull request recheck returned no identity', retryable: false } : rechecked.error
|
|
705
|
+
});
|
|
706
|
+
if (!sameDeliveryIdentity(fetched.value, rechecked.value))
|
|
707
|
+
return result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
708
|
+
pullRequest: rechecked.value,
|
|
709
|
+
error: { code: 'PR_BIND_IDENTITY_MISMATCH', message: 'Pull request identity changed before task binding', retryable: false }
|
|
710
|
+
});
|
|
711
|
+
const written = bindIdentity(base, rechecked.value, options.agent, 'explicit-bind', options.dryRun);
|
|
568
712
|
if (written.status === 'failed')
|
|
569
713
|
return result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
570
714
|
pullRequest: fetched.value, error: { code: written.error.code, message: written.error.message, retryable: false }
|
|
571
715
|
});
|
|
572
|
-
return result(options.dryRun ? 'planned' : written.status === 'no-op' ? 'no-op' : 'applied', base.resolved.taskId, base.issueNumber, options.dryRun ? base.prNumber :
|
|
716
|
+
return result(options.dryRun ? 'planned' : written.status === 'no-op' ? 'no-op' : 'applied', base.resolved.taskId, base.issueNumber, options.dryRun ? base.prNumber : rechecked.value.number, {
|
|
573
717
|
changed: !options.dryRun && written.status === 'applied', platform: base.context.platform, capabilities: base.context.capabilities,
|
|
574
|
-
resource: { kind: 'pull-request', number:
|
|
718
|
+
resource: { kind: 'pull-request', number: rechecked.value.number }, pullRequest: rechecked.value,
|
|
575
719
|
operations: [{ name: 'task:bind-pr', status: options.dryRun ? 'planned' : written.status === 'no-op' ? 'no-op' : 'applied', reasonCode: null }], error: null
|
|
576
720
|
});
|
|
577
721
|
}
|
|
@@ -596,6 +740,10 @@ function resolveExternalPullRequest(taskRef, options) {
|
|
|
596
740
|
const base = resolvedContext(taskRef, options);
|
|
597
741
|
if (!base.ok)
|
|
598
742
|
return externalResult(base.output);
|
|
743
|
+
if (!base.fact)
|
|
744
|
+
return externalResult(result('failed', base.resolved.taskId, base.issueNumber, null, {
|
|
745
|
+
error: { code: 'PR_DELIVERY_FACT_MISSING', message: 'Task has no pr_delivery_fact; repair the task metadata or create a new task', retryable: false }
|
|
746
|
+
}));
|
|
599
747
|
const inventory = inspectCompletionArtifacts(base.resolved.taskId, { repoRoot: base.resolved.repoRoot });
|
|
600
748
|
if (inventory.status === 'failed')
|
|
601
749
|
return externalResult(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
@@ -649,8 +797,40 @@ function resolveExternalPullRequest(taskRef, options) {
|
|
|
649
797
|
capabilities: base.context.capabilities,
|
|
650
798
|
error: { code: selected.code, message: selected.message, retryable: false }
|
|
651
799
|
}), { candidates: selected.candidates, eligible: selected.eligible });
|
|
652
|
-
const
|
|
653
|
-
|
|
800
|
+
const initialIdentity = validateWriterIdentity(base, selected.selected, { errorCode: 'PR_EXTERNAL_IDENTITY_MISMATCH' });
|
|
801
|
+
if (!initialIdentity.ok)
|
|
802
|
+
return externalResult(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
803
|
+
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
804
|
+
pullRequest: selected.selected, error: initialIdentity.error
|
|
805
|
+
}), { candidates: selected.candidates, eligible: selected.eligible, selected: selected.selected });
|
|
806
|
+
const rechecked = inspectGitHubPullRequest(base.client, base.context.platform.repository, selected.selected.number, base.resolved.repoRoot);
|
|
807
|
+
if (!rechecked.ok || !rechecked.value)
|
|
808
|
+
return externalResult(result(rechecked.ok ? 'failed' : rechecked.error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
809
|
+
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
810
|
+
pullRequest: selected.selected,
|
|
811
|
+
error: rechecked.ok ? { code: 'PR_EXTERNAL_RECHECK_FAILED', message: 'Selected pull request recheck returned no identity', retryable: false } : rechecked.error
|
|
812
|
+
}), { candidates: selected.candidates, eligible: selected.eligible, selected: selected.selected });
|
|
813
|
+
if (!sameDeliveryIdentity(selected.selected, rechecked.value))
|
|
814
|
+
return externalResult(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
815
|
+
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
816
|
+
pullRequest: rechecked.value,
|
|
817
|
+
error: { code: 'PR_EXTERNAL_IDENTITY_MISMATCH', message: 'Selected pull request identity changed before task binding', retryable: false }
|
|
818
|
+
}), { candidates: selected.candidates, eligible: selected.eligible, selected: rechecked.value });
|
|
819
|
+
const finalIdentity = validateWriterIdentity(base, rechecked.value, { errorCode: 'PR_EXTERNAL_IDENTITY_MISMATCH' });
|
|
820
|
+
if (!finalIdentity.ok)
|
|
821
|
+
return externalResult(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
822
|
+
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
823
|
+
pullRequest: rechecked.value, error: finalIdentity.error
|
|
824
|
+
}), { candidates: selected.candidates, eligible: selected.eligible, selected: rechecked.value });
|
|
825
|
+
const finalEvidence = validateExternalMergedEvidence(selected.selected, rechecked.value);
|
|
826
|
+
if (!finalEvidence.ok)
|
|
827
|
+
return externalResult(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
828
|
+
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
829
|
+
pullRequest: rechecked.value, error: finalEvidence.error
|
|
830
|
+
}), { candidates: selected.candidates, eligible: selected.eligible, selected: rechecked.value });
|
|
831
|
+
const selectedPullRequest = rechecked.value;
|
|
832
|
+
const note = externalEvidenceNote(base.issueNumber, selected.source, selectedPullRequest);
|
|
833
|
+
const identityNote = externalIdentityEvidenceNote(base.issueNumber, selectedPullRequest);
|
|
654
834
|
const activityEntry = `**Bind External PR** by ${options.agent} — ${note}`;
|
|
655
835
|
const auditLines = base.content.split(/\r?\n/).filter((line) => line.includes('**Bind External PR**'));
|
|
656
836
|
const alreadyAudited = auditLines.some((line) => line.endsWith(identityNote));
|
|
@@ -658,25 +838,25 @@ function resolveExternalPullRequest(taskRef, options) {
|
|
|
658
838
|
return externalResult(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
659
839
|
platform: base.context.platform,
|
|
660
840
|
capabilities: base.context.capabilities,
|
|
661
|
-
pullRequest:
|
|
841
|
+
pullRequest: selectedPullRequest,
|
|
662
842
|
error: { code: 'PR_BIND_CONFLICT', message: 'Existing external PR audit evidence conflicts with the selected identity', retryable: false }
|
|
663
|
-
}), { candidates: selected.candidates, eligible: selected.eligible, selected:
|
|
664
|
-
if (alreadyAudited && base.prNumber !==
|
|
843
|
+
}), { candidates: selected.candidates, eligible: selected.eligible, selected: selectedPullRequest });
|
|
844
|
+
if (alreadyAudited && base.prNumber !== selectedPullRequest.number)
|
|
665
845
|
return externalResult(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
666
846
|
platform: base.context.platform,
|
|
667
847
|
capabilities: base.context.capabilities,
|
|
668
|
-
pullRequest:
|
|
848
|
+
pullRequest: selectedPullRequest,
|
|
669
849
|
error: { code: 'PR_BIND_CONFLICT', message: 'External PR audit evidence does not match the task binding', retryable: false }
|
|
670
|
-
}), { candidates: selected.candidates, eligible: selected.eligible, selected:
|
|
671
|
-
if (base.prNumber ===
|
|
850
|
+
}), { candidates: selected.candidates, eligible: selected.eligible, selected: selectedPullRequest });
|
|
851
|
+
if (base.prNumber === selectedPullRequest.number && alreadyAudited) {
|
|
672
852
|
return externalResult(result('no-op', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
673
853
|
platform: base.context.platform,
|
|
674
854
|
capabilities: base.context.capabilities,
|
|
675
|
-
resource: { kind: 'pull-request', number:
|
|
676
|
-
pullRequest:
|
|
855
|
+
resource: { kind: 'pull-request', number: selectedPullRequest.number },
|
|
856
|
+
pullRequest: selectedPullRequest,
|
|
677
857
|
operations: [{ name: 'task:bind-external-pr', status: 'no-op', reasonCode: 'EVIDENCE_ALREADY_RECORDED' }],
|
|
678
858
|
error: null
|
|
679
|
-
}), { mode: 'external', authorization: selected.source, candidates: selected.candidates, eligible: selected.eligible, selected:
|
|
859
|
+
}), { mode: 'external', authorization: selected.source, candidates: selected.candidates, eligible: selected.eligible, selected: selectedPullRequest });
|
|
680
860
|
}
|
|
681
861
|
const metadata = captureTaskWriteMetadata();
|
|
682
862
|
const write = writeTask({
|
|
@@ -684,7 +864,7 @@ function resolveExternalPullRequest(taskRef, options) {
|
|
|
684
864
|
expectedState: 'active',
|
|
685
865
|
dryRun: options.dryRun,
|
|
686
866
|
mutations: [
|
|
687
|
-
{ kind: 'frontmatter', set: {
|
|
867
|
+
{ kind: 'frontmatter', set: { ...factFrontmatterMutation(boundFactFor(selectedPullRequest, selected.source === 'unique' ? 'external-unique' : 'external-explicit', base.issueNumber, metadata.timestamp)).set, assigned_to: options.agent } },
|
|
688
868
|
{ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: '活动日志', body: appendActivity(base.content, `- ${metadata.timestamp} — ${activityEntry}`) }
|
|
689
869
|
]
|
|
690
870
|
}, { repoRoot: base.resolved.repoRoot, metadataProvider: () => metadata });
|
|
@@ -692,193 +872,221 @@ function resolveExternalPullRequest(taskRef, options) {
|
|
|
692
872
|
return externalResult(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
693
873
|
platform: base.context.platform,
|
|
694
874
|
capabilities: base.context.capabilities,
|
|
695
|
-
pullRequest:
|
|
875
|
+
pullRequest: selectedPullRequest,
|
|
696
876
|
error: { code: write.error.code, message: write.error.message, retryable: false }
|
|
697
|
-
}), { candidates: selected.candidates, eligible: selected.eligible, selected:
|
|
877
|
+
}), { candidates: selected.candidates, eligible: selected.eligible, selected: selectedPullRequest });
|
|
698
878
|
const operationStatus = options.dryRun ? 'planned' : write.status === 'applied' ? 'applied' : 'no-op';
|
|
699
|
-
return externalResult(result(operationStatus, base.resolved.taskId, base.issueNumber, options.dryRun ? base.prNumber :
|
|
879
|
+
return externalResult(result(operationStatus, base.resolved.taskId, base.issueNumber, options.dryRun ? base.prNumber : selectedPullRequest.number, {
|
|
700
880
|
changed: !options.dryRun && write.changed,
|
|
701
881
|
platform: base.context.platform,
|
|
702
882
|
capabilities: base.context.capabilities,
|
|
703
|
-
resource: { kind: 'pull-request', number:
|
|
704
|
-
pullRequest:
|
|
883
|
+
resource: { kind: 'pull-request', number: selectedPullRequest.number },
|
|
884
|
+
pullRequest: selectedPullRequest,
|
|
705
885
|
operations: [{ name: 'task:bind-external-pr', status: operationStatus, reasonCode: null }],
|
|
706
886
|
error: null
|
|
707
|
-
}), { mode: 'external', authorization: selected.source, candidates: selected.candidates, eligible: selected.eligible, selected:
|
|
887
|
+
}), { mode: 'external', authorization: selected.source, candidates: selected.candidates, eligible: selected.eligible, selected: selectedPullRequest });
|
|
708
888
|
}
|
|
709
889
|
function createPlatformPullRequestUnlocked(taskRef, options) {
|
|
710
890
|
const base = resolvedContext(taskRef, options);
|
|
711
891
|
if (!base.ok)
|
|
712
|
-
return base.output;
|
|
892
|
+
return withCreation(base.output, PRECONDITION_NOT_CREATED);
|
|
893
|
+
if (!base.fact)
|
|
894
|
+
return withCreation(result('failed', base.resolved.taskId, base.issueNumber, null, {
|
|
895
|
+
error: { code: 'PR_DELIVERY_FACT_MISSING', message: 'Task has no pr_delivery_fact; repair the task metadata or create a new task', retryable: false }
|
|
896
|
+
}), PRECONDITION_NOT_CREATED);
|
|
713
897
|
if (!options.base || !options.head || !options.title.trim() || !options.body.trim())
|
|
714
|
-
return result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
898
|
+
return withCreation(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
715
899
|
error: { code: 'PR_PAYLOAD_INVALID', message: 'base, head, title and body are required', retryable: false }
|
|
716
|
-
});
|
|
900
|
+
}), PRECONDITION_NOT_CREATED);
|
|
901
|
+
const target = taskDeliveryTarget(base);
|
|
902
|
+
if (!target.ok)
|
|
903
|
+
return withCreation(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
904
|
+
error: target.error
|
|
905
|
+
}), PRECONDITION_NOT_CREATED);
|
|
906
|
+
if (options.base !== target.value.baseRef)
|
|
907
|
+
return withCreation(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
908
|
+
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
909
|
+
error: { code: 'PR_DELIVERY_TARGET_MISMATCH', message: `Pull request base '${options.base}' does not match delivery target '${target.value.baseRef}'`, retryable: false }
|
|
910
|
+
}), PRECONDITION_NOT_CREATED);
|
|
717
911
|
if (base.prNumber) {
|
|
718
912
|
const headCheck = verifyCreateHead(base, options.head);
|
|
719
913
|
if (!headCheck.ok)
|
|
720
|
-
return result(headCheck.error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
914
|
+
return withCreation(result(headCheck.error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
721
915
|
platform: base.context.platform,
|
|
722
916
|
capabilities: base.context.capabilities,
|
|
723
917
|
error: headCheck.error
|
|
724
|
-
});
|
|
918
|
+
}), PRECONDITION_NOT_CREATED);
|
|
725
919
|
const inspected = inspectPlatformPullRequest(taskRef, options);
|
|
726
920
|
const identity = validateBoundPullRequest(base, inspected.pullRequest, options.head, options.base, headCheck.value);
|
|
727
921
|
if (!identity.ok)
|
|
728
|
-
return result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
922
|
+
return withCreation(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
729
923
|
platform: base.context.platform,
|
|
730
924
|
capabilities: base.context.capabilities,
|
|
731
925
|
pullRequest: inspected.pullRequest,
|
|
732
926
|
error: identity.error
|
|
733
|
-
});
|
|
734
|
-
return { ...inspected, result: 'no_op' };
|
|
927
|
+
}), PRECONDITION_NOT_CREATED);
|
|
928
|
+
return withCreation({ ...inspected, result: 'no_op' }, { kind: 'no-op', createdByCurrentOperation: false });
|
|
735
929
|
}
|
|
736
930
|
const headCheck = verifyCreateHead(base, options.head);
|
|
737
931
|
if (!headCheck.ok)
|
|
738
|
-
return result(headCheck.error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, null, {
|
|
932
|
+
return withCreation(result(headCheck.error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, null, {
|
|
739
933
|
platform: base.context.platform,
|
|
740
934
|
capabilities: base.context.capabilities,
|
|
741
935
|
error: headCheck.error
|
|
742
|
-
});
|
|
936
|
+
}), PRECONDITION_NOT_CREATED);
|
|
743
937
|
const expectedHeadSha = headCheck.value;
|
|
744
938
|
const located = locatePullRequest(base, options.head, options.base, expectedHeadSha);
|
|
745
939
|
if (!located.ok && located.error.code === 'PR_IDENTITY_AMBIGUOUS')
|
|
746
|
-
return result('failed', base.resolved.taskId, base.issueNumber, null, {
|
|
940
|
+
return withCreation(result('failed', base.resolved.taskId, base.issueNumber, null, {
|
|
747
941
|
platform: base.context.platform, capabilities: base.context.capabilities, error: located.error
|
|
748
|
-
});
|
|
942
|
+
}), PRECONDITION_NOT_CREATED);
|
|
749
943
|
if (!located.ok && located.error.code === 'PR_HEAD_SHA_MISMATCH')
|
|
750
|
-
return result('failed', base.resolved.taskId, base.issueNumber, null, {
|
|
944
|
+
return withCreation(result('failed', base.resolved.taskId, base.issueNumber, null, {
|
|
751
945
|
platform: base.context.platform, capabilities: base.context.capabilities, error: located.error
|
|
752
|
-
});
|
|
946
|
+
}), PRECONDITION_NOT_CREATED);
|
|
947
|
+
if (!located.ok && located.error.code !== 'PR_NOT_FOUND')
|
|
948
|
+
return withCreation(result(located.error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, null, {
|
|
949
|
+
platform: base.context.platform, capabilities: base.context.capabilities, error: located.error
|
|
950
|
+
}), PRECONDITION_NOT_CREATED);
|
|
753
951
|
if (options.dryRun)
|
|
754
|
-
return result('planned', base.resolved.taskId, base.issueNumber, null, {
|
|
952
|
+
return withCreation(result('planned', base.resolved.taskId, base.issueNumber, null, {
|
|
755
953
|
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
756
954
|
operations: [{ name: located.ok ? 'pr:reuse' : 'pr:create', status: 'planned', reasonCode: null }],
|
|
757
955
|
result: located.ok ? 'pr_reused' : 'pr_created', error: null
|
|
758
|
-
});
|
|
956
|
+
}), { kind: 'planned', action: located.ok ? 'reuse' : 'create', createdByCurrentOperation: false });
|
|
759
957
|
const started = writeCreateStarted(base, options.agent);
|
|
760
958
|
if (started.status === 'failed')
|
|
761
|
-
return result('failed', base.resolved.taskId, base.issueNumber, null, {
|
|
959
|
+
return withCreation(result('failed', base.resolved.taskId, base.issueNumber, null, {
|
|
762
960
|
error: { code: started.error.code, message: started.error.message, retryable: false }
|
|
763
|
-
});
|
|
961
|
+
}), PRECONDITION_NOT_CREATED);
|
|
764
962
|
let pullRequest = located.ok ? located.value : null;
|
|
765
963
|
let created = false;
|
|
766
964
|
if (!pullRequest) {
|
|
767
965
|
const repository = base.context.platform.repository;
|
|
966
|
+
if (options.phase)
|
|
967
|
+
options.phase.value = 'post-dispatched';
|
|
768
968
|
const response = base.client.json(['api', `repos/${repository}/pulls`, '-X', 'POST', '--input', '-'], {
|
|
769
969
|
cwd: base.resolved.repoRoot,
|
|
770
970
|
method: 'POST',
|
|
771
971
|
input: JSON.stringify({ title: options.title, body: options.body, head: options.head, base: options.base, draft: Boolean(options.draft) })
|
|
772
972
|
});
|
|
773
|
-
if (response.ok)
|
|
973
|
+
if (response.ok) {
|
|
974
|
+
// The remote POST is accepted before any normalization, recheck, or task write.
|
|
975
|
+
// From this point on the current call has created a remote resource even if
|
|
976
|
+
// local post-processing later fails.
|
|
977
|
+
created = true;
|
|
978
|
+
if (options.phase)
|
|
979
|
+
options.phase.value = 'post-accepted';
|
|
774
980
|
pullRequest = normalizePullRequest(response.value, repository);
|
|
981
|
+
}
|
|
775
982
|
else if (response.error.retryable) {
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
782
|
-
operations: [{ name: 'pr:create', status: 'failed', reasonCode: 'PR_CREATE_OUTCOME_UNKNOWN' }],
|
|
783
|
-
error: { code: 'PR_CREATE_OUTCOME_UNKNOWN', message: response.error.message, retryable: true }
|
|
784
|
-
});
|
|
983
|
+
return withCreation(result('blocked', base.resolved.taskId, base.issueNumber, null, {
|
|
984
|
+
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
985
|
+
operations: [{ name: 'pr:create', status: 'failed', reasonCode: 'PR_CREATE_OUTCOME_UNKNOWN' }],
|
|
986
|
+
error: { code: 'PR_CREATE_OUTCOME_UNKNOWN', message: response.error.message, retryable: true }
|
|
987
|
+
}), { kind: 'unknown', errorCode: 'PR_CREATE_OUTCOME_UNKNOWN' });
|
|
785
988
|
}
|
|
786
989
|
else
|
|
787
|
-
return result('failed', base.resolved.taskId, base.issueNumber, null, {
|
|
990
|
+
return withCreation(result('failed', base.resolved.taskId, base.issueNumber, null, {
|
|
788
991
|
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
789
992
|
operations: [{ name: 'pr:create', status: 'failed', reasonCode: response.error.code }], error: response.error
|
|
790
|
-
});
|
|
993
|
+
}), { kind: 'not-created', reason: 'post-rejected', createdByCurrentOperation: false });
|
|
791
994
|
if (!pullRequest)
|
|
792
|
-
return result('failed', base.resolved.taskId, base.issueNumber, null, {
|
|
995
|
+
return withCreation(result('failed', base.resolved.taskId, base.issueNumber, null, {
|
|
793
996
|
error: { code: 'PR_CREATE_RESPONSE_INVALID', message: 'PR create response lacks validated identity', retryable: false }
|
|
794
|
-
});
|
|
997
|
+
}), { kind: 'created', createdByCurrentOperation: true });
|
|
795
998
|
if (pullRequest.head.sha !== expectedHeadSha)
|
|
796
|
-
return result('failed', base.resolved.taskId, base.issueNumber, pullRequest.number, {
|
|
999
|
+
return withCreation(result('failed', base.resolved.taskId, base.issueNumber, pullRequest.number, {
|
|
797
1000
|
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
798
1001
|
resource: { kind: 'pull-request', number: pullRequest.number }, pullRequest,
|
|
799
1002
|
error: { code: 'PR_HEAD_SHA_MISMATCH', message: 'Created pull request head does not match the expected local HEAD', retryable: false }
|
|
800
|
-
});
|
|
801
|
-
created = true;
|
|
1003
|
+
}), { kind: 'created', createdByCurrentOperation: true });
|
|
802
1004
|
}
|
|
803
1005
|
const bindHeadCheck = verifyCreateHead(base, options.head);
|
|
804
1006
|
if (!bindHeadCheck.ok || bindHeadCheck.value !== expectedHeadSha) {
|
|
805
1007
|
const error = bindHeadCheck.ok
|
|
806
1008
|
? { code: 'PR_REMOTE_HEAD_MISMATCH', message: 'Remote head changed before task binding', retryable: false }
|
|
807
1009
|
: bindHeadCheck.error;
|
|
808
|
-
return result(error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, pullRequest.number, {
|
|
1010
|
+
return withCreation(result(error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, pullRequest.number, {
|
|
809
1011
|
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
810
1012
|
resource: { kind: 'pull-request', number: pullRequest.number }, pullRequest, error
|
|
811
|
-
});
|
|
1013
|
+
}), created ? { kind: 'created', createdByCurrentOperation: true } : { kind: 'not-created', reason: 'precondition-failed', createdByCurrentOperation: false });
|
|
812
1014
|
}
|
|
813
1015
|
const beforeBind = inspectGitHubPullRequest(base.client, base.context.platform.repository, pullRequest.number, base.resolved.repoRoot);
|
|
814
1016
|
if (!beforeBind.ok || !beforeBind.value) {
|
|
815
1017
|
const error = beforeBind.ok
|
|
816
1018
|
? { code: 'PR_BIND_RECHECK_FAILED', message: 'Pull request recheck returned no identity', retryable: false }
|
|
817
1019
|
: beforeBind.error;
|
|
818
|
-
return result(error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, pullRequest.number, {
|
|
1020
|
+
return withCreation(result(error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, pullRequest.number, {
|
|
819
1021
|
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
820
1022
|
resource: { kind: 'pull-request', number: pullRequest.number }, pullRequest, error
|
|
821
|
-
});
|
|
1023
|
+
}), created ? { kind: 'created', createdByCurrentOperation: true } : { kind: 'not-created', reason: 'precondition-failed', createdByCurrentOperation: false });
|
|
822
1024
|
}
|
|
823
1025
|
if (beforeBind.value.head.sha !== expectedHeadSha
|
|
824
1026
|
|| beforeBind.value.head.ref !== expectedHead(base.context.platform.repository, options.head).ref
|
|
825
1027
|
|| beforeBind.value.base.ref !== options.base
|
|
826
1028
|
|| beforeBind.value.base.repository !== base.context.platform.repository)
|
|
827
|
-
return result('failed', base.resolved.taskId, base.issueNumber, pullRequest.number, {
|
|
1029
|
+
return withCreation(result('failed', base.resolved.taskId, base.issueNumber, pullRequest.number, {
|
|
828
1030
|
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
829
1031
|
resource: { kind: 'pull-request', number: pullRequest.number }, pullRequest: beforeBind.value,
|
|
830
1032
|
error: { code: 'PR_BIND_IDENTITY_MISMATCH', message: 'Pull request identity changed before task binding', retryable: false }
|
|
831
|
-
});
|
|
1033
|
+
}), created ? { kind: 'created', createdByCurrentOperation: true } : { kind: 'not-created', reason: 'precondition-failed', createdByCurrentOperation: false });
|
|
832
1034
|
pullRequest = beforeBind.value;
|
|
833
1035
|
const refreshed = resolvedContext(taskRef, options);
|
|
834
1036
|
if (!refreshed.ok)
|
|
835
|
-
return result('failed', base.resolved.taskId, base.issueNumber, null, {
|
|
1037
|
+
return withCreation(result('failed', base.resolved.taskId, base.issueNumber, null, {
|
|
836
1038
|
pullRequest, error: { code: 'PR_CREATED_BIND_FAILED', message: pullRequest.url, retryable: false }
|
|
837
|
-
});
|
|
838
|
-
const bound = bindIdentity(refreshed, pullRequest, options.agent);
|
|
1039
|
+
}), created ? { kind: 'created', createdByCurrentOperation: true } : { kind: 'not-created', reason: 'precondition-failed', createdByCurrentOperation: false });
|
|
1040
|
+
const bound = bindIdentity(refreshed, pullRequest, options.agent, created ? 'created' : 'reused', false, options.head, expectedHeadSha);
|
|
839
1041
|
if (bound.status === 'failed')
|
|
840
|
-
return result('failed', base.resolved.taskId, base.issueNumber, null, {
|
|
1042
|
+
return withCreation(result('failed', base.resolved.taskId, base.issueNumber, null, {
|
|
841
1043
|
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
842
1044
|
resource: { kind: 'pull-request', number: pullRequest.number }, pullRequest,
|
|
843
1045
|
operations: [{ name: created ? 'pr:create' : 'pr:reuse', status: 'applied', reasonCode: null }, { name: 'task:bind-pr', status: 'failed', reasonCode: bound.error.code }],
|
|
844
1046
|
error: { code: 'PR_CREATED_BIND_FAILED', message: `${pullRequest.url}: ${bound.error.message}`, retryable: false }
|
|
845
|
-
});
|
|
846
|
-
return result('applied', base.resolved.taskId, base.issueNumber, pullRequest.number, {
|
|
1047
|
+
}), created ? { kind: 'created', createdByCurrentOperation: true } : { kind: 'reused', createdByCurrentOperation: false });
|
|
1048
|
+
return withCreation(result('applied', base.resolved.taskId, base.issueNumber, pullRequest.number, {
|
|
847
1049
|
changed: true, platform: base.context.platform, capabilities: base.context.capabilities,
|
|
848
1050
|
resource: { kind: 'pull-request', number: pullRequest.number }, pullRequest,
|
|
849
1051
|
operations: [{ name: created ? 'pr:create' : 'pr:reuse', status: created ? 'applied' : 'no-op', reasonCode: null }, { name: 'task:bind-pr', status: 'applied', reasonCode: null }],
|
|
850
1052
|
result: created ? 'pr_created' : 'pr_reused', error: null
|
|
851
|
-
});
|
|
1053
|
+
}), created ? { kind: 'created', createdByCurrentOperation: true } : { kind: 'reused', createdByCurrentOperation: false });
|
|
852
1054
|
}
|
|
853
1055
|
function createPlatformPullRequest(taskRef, options) {
|
|
854
1056
|
const base = resolvedContext(taskRef, options);
|
|
855
1057
|
if (!base.ok)
|
|
856
|
-
return base.output;
|
|
1058
|
+
return withCreation(base.output, PRECONDITION_NOT_CREATED);
|
|
857
1059
|
if (!options.base || !options.head || !options.title.trim() || !options.body.trim()) {
|
|
858
|
-
return result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
1060
|
+
return withCreation(result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
859
1061
|
error: { code: 'PR_PAYLOAD_INVALID', message: 'base, head, title and body are required', retryable: false }
|
|
860
|
-
});
|
|
1062
|
+
}), PRECONDITION_NOT_CREATED);
|
|
861
1063
|
}
|
|
1064
|
+
const phase = { value: 'before-post' };
|
|
862
1065
|
try {
|
|
863
|
-
return withTaskExecutionLock(base.resolved.repoRoot, base.resolved.taskId, 'platform-pr.create', () => createPlatformPullRequestUnlocked(taskRef, options));
|
|
1066
|
+
return withTaskExecutionLock(base.resolved.repoRoot, base.resolved.taskId, 'platform-pr.create', () => createPlatformPullRequestUnlocked(taskRef, { ...options, phase }));
|
|
864
1067
|
}
|
|
865
1068
|
catch (error) {
|
|
866
1069
|
if (error instanceof TaskExecutionLockError) {
|
|
867
|
-
return result('blocked', base.resolved.taskId, base.issueNumber, null, {
|
|
1070
|
+
return withCreation(result('blocked', base.resolved.taskId, base.issueNumber, null, {
|
|
868
1071
|
platform: base.context.platform,
|
|
869
1072
|
capabilities: base.context.capabilities,
|
|
870
1073
|
error: { code: error.code, message: error.message, retryable: true }
|
|
871
|
-
});
|
|
1074
|
+
}), PRECONDITION_NOT_CREATED);
|
|
872
1075
|
}
|
|
873
|
-
|
|
1076
|
+
const creation = phase.value === 'post-accepted'
|
|
1077
|
+
? { kind: 'created', createdByCurrentOperation: true }
|
|
1078
|
+
: phase.value === 'post-dispatched'
|
|
1079
|
+
? { kind: 'unknown', errorCode: 'PR_CREATE_OUTCOME_UNKNOWN' }
|
|
1080
|
+
: PRECONDITION_NOT_CREATED;
|
|
1081
|
+
return withCreation(result(phase.value === 'post-dispatched' ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, null, {
|
|
874
1082
|
platform: base.context.platform,
|
|
875
1083
|
capabilities: base.context.capabilities,
|
|
876
1084
|
error: {
|
|
877
|
-
code: 'PR_CREATE_FAILED',
|
|
1085
|
+
code: phase.value === 'post-dispatched' ? 'PR_CREATE_OUTCOME_UNKNOWN' : 'PR_CREATE_FAILED',
|
|
878
1086
|
message: error instanceof Error ? error.message : String(error),
|
|
879
|
-
retryable:
|
|
1087
|
+
retryable: phase.value === 'post-dispatched'
|
|
880
1088
|
}
|
|
881
|
-
});
|
|
1089
|
+
}), creation);
|
|
882
1090
|
}
|
|
883
1091
|
}
|
|
884
1092
|
function syncPlatformPullRequest(taskRef, options) {
|
|
@@ -902,9 +1110,13 @@ function syncPlatformPullRequest(taskRef, options) {
|
|
|
902
1110
|
const base = resolvedContext(taskRef, options);
|
|
903
1111
|
if (!base.ok)
|
|
904
1112
|
return softenFailure(base.output);
|
|
1113
|
+
if (!base.fact)
|
|
1114
|
+
return softenFailure(result('failed', base.resolved.taskId, base.issueNumber, null, {
|
|
1115
|
+
error: { code: 'PR_DELIVERY_FACT_MISSING', message: 'Task has no pr_delivery_fact; repair the task metadata or create a new task', retryable: false }
|
|
1116
|
+
}));
|
|
905
1117
|
if (!base.prNumber)
|
|
906
1118
|
return softenFailure(result('failed', base.resolved.taskId, base.issueNumber, null, {
|
|
907
|
-
error: { code: 'PR_NOT_LINKED', message: 'Task has no
|
|
1119
|
+
error: { code: 'PR_NOT_LINKED', message: 'Task has no verified bound pull request', retryable: false }
|
|
908
1120
|
}));
|
|
909
1121
|
const fetched = inspectGitHubPullRequest(base.client, base.context.platform.repository, base.prNumber, base.resolved.repoRoot);
|
|
910
1122
|
if (!fetched.ok)
|
|
@@ -925,7 +1137,7 @@ function syncPlatformPullRequest(taskRef, options) {
|
|
|
925
1137
|
const planned = planPullRequestMetadata({
|
|
926
1138
|
pullRequest: fetched.value,
|
|
927
1139
|
issue: issue.issue,
|
|
928
|
-
taskType: base.frontmatter.type || 'task',
|
|
1140
|
+
taskType: String(base.frontmatter.type || 'task'),
|
|
929
1141
|
issueNumber: base.issueNumber,
|
|
930
1142
|
capabilities: base.context.capabilities
|
|
931
1143
|
}).operations.filter((operation) => options.metadata || operation.name === 'closing-issue')
|
|
@@ -970,6 +1182,96 @@ function syncPlatformPullRequest(taskRef, options) {
|
|
|
970
1182
|
operations: operations.map((operation) => operation.status === 'planned' ? { ...operation, status: 'applied' } : operation), error: null
|
|
971
1183
|
}));
|
|
972
1184
|
}
|
|
973
|
-
|
|
1185
|
+
function readProjectPrFlow(repoRoot) {
|
|
1186
|
+
try {
|
|
1187
|
+
const config = JSON.parse(fs.readFileSync(path.join(repoRoot, '.agents', '.airc.json'), 'utf8'));
|
|
1188
|
+
return config.prFlow === 'required' || config.prFlow === 'disabled' ? config.prFlow : undefined;
|
|
1189
|
+
}
|
|
1190
|
+
catch {
|
|
1191
|
+
return undefined;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
function skipPlatformPullRequestFactUnlocked(resolved, options) {
|
|
1195
|
+
let content;
|
|
1196
|
+
let frontmatter;
|
|
1197
|
+
try {
|
|
1198
|
+
content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
1199
|
+
frontmatter = parseTypedTaskFrontmatter(content);
|
|
1200
|
+
}
|
|
1201
|
+
catch (error) {
|
|
1202
|
+
return result('failed', resolved.taskId, null, null, {
|
|
1203
|
+
error: { code: 'TASK_DOCUMENT_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false }
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
const issue = Number(frontmatter.issue_number);
|
|
1207
|
+
const issueNumber = Number.isSafeInteger(issue) && issue > 0 ? issue : null;
|
|
1208
|
+
const existing = readPrDeliveryFact(frontmatter);
|
|
1209
|
+
if (existing.status === 'invalid')
|
|
1210
|
+
return result('failed', resolved.taskId, issueNumber, null, {
|
|
1211
|
+
error: { code: 'PR_DELIVERY_FACT_INVALID', message: existing.error.message, retryable: false }
|
|
1212
|
+
});
|
|
1213
|
+
if (existing.status === 'missing')
|
|
1214
|
+
return result('failed', resolved.taskId, issueNumber, null, {
|
|
1215
|
+
error: { code: 'PR_DELIVERY_FACT_MISSING', message: 'Task has no pr_delivery_fact; repair the task metadata or create a new task', retryable: false }
|
|
1216
|
+
});
|
|
1217
|
+
if (existing.fact.state === 'skipped')
|
|
1218
|
+
return result('no-op', resolved.taskId, issueNumber, null, {
|
|
1219
|
+
operations: [{ name: 'task:skip-pr', status: 'no-op', reasonCode: null }],
|
|
1220
|
+
error: null
|
|
1221
|
+
});
|
|
1222
|
+
if (existing.fact.state === 'bound')
|
|
1223
|
+
return result('failed', resolved.taskId, issueNumber, existing.fact.identity.number, {
|
|
1224
|
+
error: { code: 'PR_DELIVERY_FACT_CONFLICT', message: `Task is already bound to PR #${existing.fact.identity.number}`, retryable: false }
|
|
1225
|
+
});
|
|
1226
|
+
if (readProjectPrFlow(resolved.repoRoot) === 'required')
|
|
1227
|
+
return result('failed', resolved.taskId, issueNumber, null, {
|
|
1228
|
+
error: { code: 'PR_SKIP_FORBIDDEN', message: 'Cannot skip PR delivery when prFlow is required', retryable: false }
|
|
1229
|
+
});
|
|
1230
|
+
const metadata = captureTaskWriteMetadata();
|
|
1231
|
+
const fact = buildSkippedFact(factTimestamp(metadata.timestamp));
|
|
1232
|
+
const write = writeTask({
|
|
1233
|
+
taskRef: resolved.taskId,
|
|
1234
|
+
expectedState: 'active',
|
|
1235
|
+
dryRun: options.dryRun,
|
|
1236
|
+
mutations: [
|
|
1237
|
+
{ kind: 'frontmatter', set: { ...factFrontmatterMutation(fact).set, assigned_to: options.agent } },
|
|
1238
|
+
{ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: '活动日志', body: appendActivity(content, `- ${metadata.timestamp} — **Skip PR** by ${options.agent} — PR delivery explicitly skipped`) }
|
|
1239
|
+
]
|
|
1240
|
+
}, { repoRoot: resolved.repoRoot, metadataProvider: () => metadata });
|
|
1241
|
+
if (write.status === 'failed')
|
|
1242
|
+
return result('failed', resolved.taskId, issueNumber, null, {
|
|
1243
|
+
error: { code: write.error.code, message: write.error.message, retryable: false }
|
|
1244
|
+
});
|
|
1245
|
+
const status = options.dryRun ? 'planned' : write.status === 'no-op' ? 'no-op' : 'applied';
|
|
1246
|
+
return result(status, resolved.taskId, issueNumber, null, {
|
|
1247
|
+
changed: !options.dryRun && write.changed,
|
|
1248
|
+
operations: [{ name: 'task:skip-pr', status, reasonCode: null }],
|
|
1249
|
+
error: null
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
function skipPlatformPullRequestFact(taskRef, options) {
|
|
1253
|
+
const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
|
|
1254
|
+
if (!resolved.ok)
|
|
1255
|
+
return result('failed', resolved.taskId, null, null, {
|
|
1256
|
+
error: { code: resolved.code, message: resolved.message, retryable: false }
|
|
1257
|
+
});
|
|
1258
|
+
if (resolved.state !== 'active')
|
|
1259
|
+
return result('failed', resolved.taskId, null, null, {
|
|
1260
|
+
error: { code: 'TASK_STATE_INVALID', message: 'platform-pr skip requires an active task', retryable: false }
|
|
1261
|
+
});
|
|
1262
|
+
try {
|
|
1263
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'platform-pr.skip', () => skipPlatformPullRequestFactUnlocked(resolved, options));
|
|
1264
|
+
}
|
|
1265
|
+
catch (error) {
|
|
1266
|
+
if (error instanceof TaskExecutionLockError)
|
|
1267
|
+
return result('blocked', resolved.taskId, null, null, {
|
|
1268
|
+
error: { code: error.code, message: error.message, retryable: true }
|
|
1269
|
+
});
|
|
1270
|
+
return result('failed', resolved.taskId, null, null, {
|
|
1271
|
+
error: { code: 'PR_SKIP_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false }
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
export { bindPlatformPullRequest, createPlatformPullRequest, inspectGitHubPullRequest, inspectGitHubIssueClosingChangeRequests, inspectPlatformPullRequest, inspectPlatformPullRequestByNumber, normalizePullRequest, resolveGitHubChangeRequestGitEvidence, resolveExternalPullRequest, skipPlatformPullRequestFact, selectExternalPullRequest, selectPullRequest, syncPlatformPullRequest };
|
|
974
1276
|
export { warningResultForPrimary };
|
|
975
1277
|
//# sourceMappingURL=pull-requests.js.map
|