@fitlab-ai/agent-infra 0.9.11 → 0.9.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -13
- package/README.zh-CN.md +13 -13
- package/dist/bin/internal-cli.js +29 -19
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +71 -10
- package/dist/lib/agent-clients/next-steps.js +6 -1
- package/dist/lib/decide.js +3 -3
- package/dist/lib/defaults.json +4 -0
- package/dist/lib/git/workflow.js +43 -1
- package/dist/lib/init.js +1 -0
- package/dist/lib/internal/git-workflow.js +8 -1
- package/dist/lib/internal/platform-checks.js +3 -3
- package/dist/lib/internal/platform-comment.js +9 -9
- package/dist/lib/internal/platform-context.js +2 -2
- package/dist/lib/internal/platform-issue.js +8 -9
- package/dist/lib/internal/platform-pr-review.js +10 -10
- package/dist/lib/internal/platform-pr.js +68 -23
- package/dist/lib/internal/platform-release-notes.js +3 -3
- package/dist/lib/internal/pr-review-grade.js +11 -9
- package/dist/lib/internal/release-workflow.js +4 -4
- package/dist/lib/internal/sandbox-control.js +30 -4
- package/dist/lib/internal/task-activity.js +3 -3
- package/dist/lib/internal/task-artifact.js +76 -24
- package/dist/lib/internal/task-create.js +2 -2
- package/dist/lib/internal/task-delivery.js +59 -0
- package/dist/lib/internal/task-event.js +6 -3
- package/dist/lib/internal/task-finalization.js +2 -2
- package/dist/lib/internal/task-invalidation.js +60 -0
- package/dist/lib/internal/task-ledger.js +9 -6
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-override.js +4 -4
- package/dist/lib/internal/task-review.js +3 -3
- package/dist/lib/internal/task-verify.js +6 -6
- package/dist/lib/internal/task-warning.js +3 -3
- package/dist/lib/platform/adapters.js +1 -3
- package/dist/lib/platform/change-request-git-evidence.js +27 -7
- package/dist/lib/platform/completion-backfill.js +6 -5
- package/dist/lib/platform/context.js +70 -130
- package/dist/lib/platform/github-client.js +8 -2
- package/dist/lib/platform/github-provider.js +903 -0
- package/dist/lib/platform/in-label-sync.js +143 -0
- package/dist/lib/platform/issue-comments.js +94 -41
- package/dist/lib/platform/issues.js +335 -189
- package/dist/lib/platform/metadata-labels.js +6 -2
- package/dist/lib/platform/none-provider.js +28 -0
- package/dist/lib/platform/pr-change-report.js +551 -0
- package/dist/lib/platform/pr-checks.js +124 -106
- package/dist/lib/platform/pr-review.js +88 -72
- package/dist/lib/platform/pr-summary.js +523 -49
- package/dist/lib/platform/provider-bridge.js +45 -0
- package/dist/lib/platform/provider-contract.d.ts +487 -0
- package/dist/lib/platform/provider-contract.js +95 -0
- package/dist/lib/platform/provider-loader.js +251 -0
- package/dist/lib/platform/provider-validation.js +504 -0
- package/dist/lib/platform/pull-request-metadata.js +8 -6
- package/dist/lib/platform/pull-requests.js +1050 -502
- package/dist/lib/platform/release-notes.js +78 -55
- package/dist/lib/platform/releases.js +79 -50
- package/dist/lib/platform/resource-identity.d.ts +26 -0
- package/dist/lib/platform/resource-identity.js +92 -0
- package/dist/lib/platform/task-identities.js +31 -0
- package/dist/lib/platform/verification-required.js +22 -12
- package/dist/lib/platform/verification-sync.js +118 -239
- package/dist/lib/pr-review/evidence-grading.js +30 -9
- package/dist/lib/run/index.js +63 -23
- package/dist/lib/sandbox/config.js +5 -0
- package/dist/lib/sandbox/control/client.js +96 -15
- package/dist/lib/sandbox/control/executor.js +3 -3
- package/dist/lib/sandbox/control/lifecycle.js +29 -7
- package/dist/lib/sandbox/control/protocol.js +77 -0
- package/dist/lib/sandbox/control/server.js +398 -25
- package/dist/lib/sandbox/control/state.js +204 -1
- package/dist/lib/server/process-state.js +2 -2
- package/dist/lib/server/protocol.js +38 -1
- package/dist/lib/task/activity-log.js +8 -1
- package/dist/lib/task/artifact-lifecycle.js +35 -7
- package/dist/lib/task/capabilities.js +239 -0
- package/dist/lib/task/commands/status.js +32 -0
- package/dist/lib/task/commit-intent.js +100 -0
- package/dist/lib/task/commit-operation.js +207 -102
- package/dist/lib/task/control-authority.js +8 -5
- package/dist/lib/task/create-service.js +8 -8
- package/dist/lib/task/create.js +33 -7
- package/dist/lib/task/current-contract.js +5 -1
- package/dist/lib/task/decision-details.js +3 -48
- package/dist/lib/task/delivery-target.js +97 -0
- package/dist/lib/task/delivery.js +153 -0
- package/dist/lib/task/events.js +297 -17
- package/dist/lib/task/finalization.js +37 -13
- package/dist/lib/task/human-override.js +61 -36
- package/dist/lib/task/invalidation-command.js +86 -0
- package/dist/lib/task/invalidation.js +260 -0
- package/dist/lib/task/ledger-intents.js +37 -0
- package/dist/lib/task/lifecycle.js +106 -9
- package/dist/lib/task/local-artifact-finalization.js +374 -0
- package/dist/lib/task/orchestration.js +100 -151
- package/dist/lib/task/pr-delivery-fact.js +183 -0
- package/dist/lib/task/review-finalization.js +4 -8
- package/dist/lib/task/review-fingerprint.js +32 -2
- package/dist/lib/task/rework-intent.js +138 -0
- package/dist/lib/task/skill-scope.js +23 -0
- package/dist/lib/task/task-execution-lock.js +27 -17
- package/dist/lib/task/verification-engine.js +89 -38
- package/dist/lib/task/verification-review.js +1 -1
- package/dist/lib/task/verification.js +3 -3
- package/dist/lib/task/workflow-warning-intents.js +1 -1
- package/dist/lib/task/write.js +11 -0
- package/dist/lib/update.js +9 -0
- package/dist/lib/version.js +11 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- 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/next-steps.ts +6 -1
- package/lib/decide.ts +3 -3
- package/lib/defaults.json +4 -0
- package/lib/git/workflow.ts +38 -1
- package/lib/init.ts +4 -1
- package/lib/internal/git-workflow.ts +7 -1
- package/lib/internal/platform-checks.ts +3 -3
- package/lib/internal/platform-comment.ts +8 -8
- package/lib/internal/platform-context.ts +2 -2
- package/lib/internal/platform-issue.ts +7 -8
- package/lib/internal/platform-pr-review.ts +9 -9
- package/lib/internal/platform-pr.ts +53 -21
- package/lib/internal/platform-release-notes.ts +3 -3
- package/lib/internal/pr-review-grade.ts +10 -8
- package/lib/internal/release-workflow.ts +4 -4
- package/lib/internal/sandbox-control.ts +27 -3
- package/lib/internal/task-activity.ts +3 -3
- package/lib/internal/task-artifact.ts +65 -22
- package/lib/internal/task-create.ts +2 -2
- package/lib/internal/task-delivery.ts +53 -0
- package/lib/internal/task-event.ts +6 -3
- package/lib/internal/task-finalization.ts +3 -3
- package/lib/internal/task-invalidation.ts +39 -0
- package/lib/internal/task-ledger.ts +11 -8
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-override.ts +6 -6
- package/lib/internal/task-review.ts +3 -3
- package/lib/internal/task-verify.ts +6 -6
- package/lib/internal/task-warning.ts +3 -3
- package/lib/platform/adapters.ts +3 -2
- package/lib/platform/change-request-git-evidence.ts +36 -9
- package/lib/platform/completion-backfill.ts +9 -8
- package/lib/platform/context.ts +97 -134
- package/lib/platform/github-client.ts +10 -3
- package/lib/platform/github-provider.ts +910 -0
- package/lib/platform/in-label-sync.ts +208 -0
- package/lib/platform/issue-comments.ts +97 -47
- package/lib/platform/issues.ts +344 -174
- package/lib/platform/metadata-labels.ts +5 -2
- package/lib/platform/none-provider.ts +35 -0
- package/lib/platform/pr-change-report.ts +645 -0
- package/lib/platform/pr-checks.ts +129 -107
- package/lib/platform/pr-review.ts +88 -85
- package/lib/platform/pr-summary.ts +562 -52
- package/lib/platform/provider-bridge.ts +82 -0
- package/lib/platform/provider-contract.ts +536 -0
- package/lib/platform/provider-loader.ts +307 -0
- package/lib/platform/provider-validation.ts +529 -0
- package/lib/platform/pull-request-metadata.ts +10 -7
- package/lib/platform/pull-requests.ts +1126 -505
- package/lib/platform/release-notes.ts +76 -59
- package/lib/platform/releases.ts +77 -43
- package/lib/platform/resource-identity.ts +121 -0
- package/lib/platform/task-identities.ts +40 -0
- package/lib/platform/types.ts +4 -2
- package/lib/platform/verification-required.ts +21 -13
- package/lib/platform/verification-sync.ts +118 -293
- package/lib/pr-review/evidence-grading.ts +31 -11
- package/lib/run/index.ts +57 -22
- package/lib/sandbox/config.ts +7 -0
- package/lib/sandbox/control/client.ts +105 -15
- package/lib/sandbox/control/executor.ts +4 -4
- package/lib/sandbox/control/lifecycle.ts +30 -8
- package/lib/sandbox/control/protocol.ts +113 -0
- package/lib/sandbox/control/server.ts +431 -23
- package/lib/sandbox/control/state.ts +251 -1
- package/lib/server/process-state.ts +2 -2
- package/lib/server/protocol.ts +36 -1
- package/lib/task/activity-log.ts +8 -0
- package/lib/task/artifact-lifecycle.ts +34 -8
- package/lib/task/capabilities.ts +262 -0
- package/lib/task/commands/status.ts +38 -0
- package/lib/task/commit-intent.ts +137 -0
- package/lib/task/commit-operation.ts +186 -88
- package/lib/task/control-authority.ts +11 -8
- package/lib/task/create-service.ts +8 -8
- package/lib/task/create.ts +34 -9
- package/lib/task/current-contract.ts +6 -1
- package/lib/task/decision-details.ts +8 -64
- package/lib/task/delivery-target.ts +130 -0
- package/lib/task/delivery.ts +160 -0
- package/lib/task/events.ts +296 -16
- package/lib/task/finalization.ts +50 -14
- package/lib/task/human-override.ts +73 -46
- package/lib/task/invalidation-command.ts +86 -0
- package/lib/task/invalidation.ts +332 -0
- package/lib/task/ledger-intents.ts +35 -1
- package/lib/task/lifecycle.ts +93 -4
- package/lib/task/local-artifact-finalization.ts +563 -0
- package/lib/task/orchestration.ts +110 -146
- package/lib/task/pr-delivery-fact.ts +237 -0
- package/lib/task/review-finalization.ts +4 -14
- package/lib/task/review-fingerprint.ts +33 -2
- package/lib/task/rework-intent.ts +165 -0
- package/lib/task/skill-scope.ts +24 -0
- package/lib/task/task-execution-lock.ts +23 -14
- package/lib/task/verification-engine.ts +95 -43
- package/lib/task/verification-review.ts +2 -0
- package/lib/task/verification.ts +4 -4
- package/lib/task/workflow-warning-intents.ts +1 -1
- package/lib/task/write.ts +19 -0
- package/lib/update.ts +11 -1
- package/lib/version.ts +15 -1
- package/package.json +7 -1
- package/templates/.agents/rules/README.en.md +4 -2
- package/templates/.agents/rules/README.zh-CN.md +4 -2
- 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/issue-pr-commands.en.md +4 -17
- package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +28 -5
- package/templates/.agents/rules/issue-sync.zh-CN.md +64 -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 +53 -0
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +53 -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/pr-sync.en.md +28 -13
- package/templates/.agents/rules/pr-sync.zh-CN.md +39 -5
- 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/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 +1 -1
- package/templates/.agents/rules/task-management.zh-CN.md +1 -1
- package/templates/.agents/rules/task-short-id.en.md +1 -4
- package/templates/.agents/rules/task-short-id.zh-CN.md +1 -3
- package/templates/.agents/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 +14 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -3
- package/templates/.agents/skills/check-task/SKILL.en.md +16 -37
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -37
- package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.en.md +32 -10
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +35 -13
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/commit/SKILL.en.md +3 -3
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +3 -3
- 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/issue-metadata-sync.en.md +2 -0
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +2 -0
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +4 -4
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +4 -4
- 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 -4
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +7 -4
- 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 +5 -4
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +5 -4
- package/templates/.agents/skills/complete-task/SKILL.en.md +31 -13
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +30 -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 +11 -10
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +11 -11
- package/templates/.agents/skills/create-pr/config/verify.json +1 -0
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +16 -4
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +16 -4
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +5 -3
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +5 -3
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/scripts/change-report.mjs +3 -0
- package/templates/.agents/skills/create-task/SKILL.en.md +1 -0
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +1 -0
- 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/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 +14 -3
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +11 -3
- package/templates/.agents/skills/review-analysis/SKILL.en.md +6 -5
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +6 -5
- 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 +10 -9
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +10 -9
- 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 +6 -5
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +6 -5
- 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 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +5 -2
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +4 -0
- package/templates/.agents/skills/watch-pr/SKILL.en.md +6 -6
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +6 -0
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +6 -0
- package/templates/.agents/templates/task.en.md +23 -2
- package/templates/.agents/templates/task.zh-CN.md +23 -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/.github/workflows/pr-label.yml +7 -26
- 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 -44
- package/templates/.agents/rules/release-commands.github.zh-CN.md +0 -44
- package/templates/.agents/rules/security-alerts.github.en.md +0 -43
- package/templates/.agents/rules/security-alerts.github.zh-CN.md +0 -43
- /package/templates/.agents/rules/{issue-fields.github.en.md → issue-fields.en.md} +0 -0
- /package/templates/.agents/rules/{issue-fields.github.zh-CN.md → issue-fields.zh-CN.md} +0 -0
|
@@ -4,8 +4,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
4
4
|
|
|
5
5
|
import { parseTypedTaskFrontmatter } from './frontmatter.ts';
|
|
6
6
|
import { validateCurrentTaskContract } from './current-contract.ts';
|
|
7
|
-
import { parseReviewSummary
|
|
8
|
-
import { inspectArtifactDirectory } from './artifact-lifecycle.ts';
|
|
7
|
+
import { parseReviewSummary } from './review-artifacts.ts';
|
|
9
8
|
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.ts';
|
|
10
9
|
import type { LedgerDocument } from './ledger.ts';
|
|
11
10
|
import { resolveTaskRef } from './resolve-ref.ts';
|
|
@@ -29,7 +28,6 @@ import type {
|
|
|
29
28
|
} from './delegation-receipts.ts';
|
|
30
29
|
import { normalizeAgentClients } from '../agent-clients/config.ts';
|
|
31
30
|
import { resolveAgentRuntimeStoreRoot } from '../runtime/agent-runtime.ts';
|
|
32
|
-
import { inspectPlatformPullRequest } from '../platform/pull-requests.ts';
|
|
33
31
|
import {
|
|
34
32
|
getAgentClientCapability,
|
|
35
33
|
getAgentClientDelegationEvidence,
|
|
@@ -52,6 +50,13 @@ import { assertGitRepositoryBinding } from '../git/worktree-identity.ts';
|
|
|
52
50
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
53
51
|
import { hasActiveCodexLifecycleEvidence } from '../agent-clients/adapters/codex-lifecycle/store.ts';
|
|
54
52
|
import type { CodexCapabilityProvenanceDetail } from '../agent-clients/adapters/codex-lifecycle/capability-store.ts';
|
|
53
|
+
import { extractReviewBaseline, extractReviewDiffBase, extractReviewTargetHead, extractReviewedHead } from './review-fingerprint.ts';
|
|
54
|
+
import { resolveDeliveryTarget, resolveDiffBase, resolveTargetHead } from './delivery-target.ts';
|
|
55
|
+
import { buildLifecycleFacts, canStart, recommendNext } from './capabilities.ts';
|
|
56
|
+
import { hasOpenLifecycleExecution } from './activity-log.ts';
|
|
57
|
+
import { invalidationBlocks } from './invalidation.ts';
|
|
58
|
+
import type { InvalidationDocument } from './invalidation.ts';
|
|
59
|
+
import type { LifecycleAction, LifecycleFacts } from './capabilities.ts';
|
|
55
60
|
|
|
56
61
|
type OrchestrationStatus = 'running' | 'paused' | 'completed';
|
|
57
62
|
type ModelPolicySource = Readonly<{
|
|
@@ -84,8 +89,8 @@ type CleanCompletionEvidence = Readonly<{
|
|
|
84
89
|
headTree: string;
|
|
85
90
|
worktreeTree: string;
|
|
86
91
|
lastReviewedCommit: string;
|
|
87
|
-
prNumber: number;
|
|
88
|
-
prHead: string;
|
|
92
|
+
prNumber: number | null;
|
|
93
|
+
prHead: string | null;
|
|
89
94
|
}>;
|
|
90
95
|
type OrchestrationRun = Readonly<{
|
|
91
96
|
taskId: string;
|
|
@@ -107,7 +112,7 @@ type OrchestrationRun = Readonly<{
|
|
|
107
112
|
updatedAt: string;
|
|
108
113
|
}>;
|
|
109
114
|
type OrchestrationNext = Readonly<{
|
|
110
|
-
action: 'analyze-task' | 'review-analysis' | 'plan-task' | 'review-plan' | 'code-task' | 'review-code'
|
|
115
|
+
action: 'analyze-task' | 'review-analysis' | 'plan-task' | 'review-plan' | 'code-task' | 'review-code';
|
|
111
116
|
role: DelegationRole;
|
|
112
117
|
stage: DelegationStage;
|
|
113
118
|
round: number;
|
|
@@ -273,9 +278,8 @@ function isCompletionEvidence(value: unknown): value is CleanCompletionEvidence
|
|
|
273
278
|
&& exactText(value.headTree)
|
|
274
279
|
&& exactText(value.worktreeTree)
|
|
275
280
|
&& exactText(value.lastReviewedCommit)
|
|
276
|
-
&& Number.isSafeInteger(value.prNumber)
|
|
277
|
-
&& (value.
|
|
278
|
-
&& exactText(value.prHead);
|
|
281
|
+
&& (value.prNumber === null || (Number.isSafeInteger(value.prNumber) && (value.prNumber as number) > 0))
|
|
282
|
+
&& (value.prHead === null || exactText(value.prHead));
|
|
279
283
|
}
|
|
280
284
|
|
|
281
285
|
const ORCHESTRATION_RUN_KEYS = [
|
|
@@ -523,78 +527,57 @@ function artifactName(family: string, round: number): string {
|
|
|
523
527
|
return round === 1 ? `${family}.md` : `${family}-r${round}.md`;
|
|
524
528
|
}
|
|
525
529
|
|
|
526
|
-
function
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
const
|
|
537
|
-
if (
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
catch (error) { return { approved: false, error: String(error) }; }
|
|
541
|
-
const parsed = parseReviewSummary(content);
|
|
542
|
-
if (!parsed.ok) return { approved: false, error: `${parsed.code}: ${parsed.message}` };
|
|
543
|
-
const verdict = resolveCanonicalVerdict(parsed.summary);
|
|
544
|
-
if (!verdict.ok) return { approved: false, error: `${verdict.code}: ${verdict.message}` };
|
|
545
|
-
return { approved: verdict.verdict === 'Approved', error: null };
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
function routeFromFacts(taskDir: string): Omit<OrchestrationNext, 'requestedModel' | 'requestedReasoningEffort'> | { error: string } | null {
|
|
549
|
-
const analysisRound = highestRound(taskDir, 'analysis');
|
|
550
|
-
const analysisReviewRound = highestRound(taskDir, 'review-analysis');
|
|
551
|
-
const planRound = highestRound(taskDir, 'plan');
|
|
552
|
-
const planReviewRound = highestRound(taskDir, 'review-plan');
|
|
553
|
-
const codeRound = highestRound(taskDir, 'code');
|
|
554
|
-
const codeReviewRound = highestRound(taskDir, 'review-code');
|
|
555
|
-
if (analysisRound === 0) {
|
|
556
|
-
const round = analysisRound + 1;
|
|
557
|
-
return { action: 'analyze-task', role: 'executor', stage: 'analysis', round, artifact: artifactName('analysis', round) };
|
|
558
|
-
}
|
|
559
|
-
if (!hasReviewAfterArtifact(taskDir, 'analysis', 'review-analysis')) {
|
|
560
|
-
const round = analysisReviewRound + 1;
|
|
561
|
-
return { action: 'review-analysis', role: 'reviewer', stage: 'review-analysis', round, artifact: artifactName('review-analysis', round) };
|
|
562
|
-
}
|
|
563
|
-
const analysisVerdict = latestReviewApproved(taskDir, 'review-analysis');
|
|
564
|
-
if (analysisVerdict.error) return { error: analysisVerdict.error };
|
|
565
|
-
if (!analysisVerdict.approved) {
|
|
566
|
-
const round = analysisRound + 1;
|
|
567
|
-
return { action: 'analyze-task', role: 'executor', stage: 'analysis', round, artifact: artifactName('analysis', round) };
|
|
568
|
-
}
|
|
569
|
-
if (planRound === 0) {
|
|
570
|
-
const round = planRound + 1;
|
|
571
|
-
return { action: 'plan-task', role: 'executor', stage: 'plan', round, artifact: artifactName('plan', round) };
|
|
530
|
+
function validateSavedCurrentDiffBase(
|
|
531
|
+
repoRoot: string,
|
|
532
|
+
metadata: ReturnType<typeof parseTypedTaskFrontmatter>,
|
|
533
|
+
taskDir: string,
|
|
534
|
+
reviewedHead: string
|
|
535
|
+
): Readonly<{ code: string; message: string }> | null {
|
|
536
|
+
const reviewRound = highestRound(taskDir, 'review-code');
|
|
537
|
+
const reviewContent = fs.readFileSync(path.join(taskDir, artifactName('review-code', reviewRound)), 'utf8');
|
|
538
|
+
const savedTargetHead = extractReviewTargetHead(reviewContent);
|
|
539
|
+
const savedDiffBase = extractReviewDiffBase(reviewContent);
|
|
540
|
+
const savedReviewedHead = extractReviewedHead(reviewContent) || extractReviewBaseline(reviewContent);
|
|
541
|
+
if (!savedTargetHead && !savedDiffBase && !savedReviewedHead) return null;
|
|
542
|
+
if (savedReviewedHead && savedReviewedHead !== reviewedHead) {
|
|
543
|
+
return { code: 'ORCHESTRATION_REVIEWED_HEAD_MISMATCH', message: 'review-code saved reviewed head does not match last_reviewed_commit' };
|
|
572
544
|
}
|
|
573
|
-
if (!
|
|
574
|
-
|
|
575
|
-
return { action: 'review-plan', role: 'reviewer', stage: 'review-plan', round, artifact: artifactName('review-plan', round) };
|
|
545
|
+
if (!savedTargetHead || !savedDiffBase) {
|
|
546
|
+
return { code: 'ORCHESTRATION_REVIEW_TARGET_INVALID', message: 'review-code must save target head and diff base together' };
|
|
576
547
|
}
|
|
577
|
-
const
|
|
578
|
-
if (
|
|
579
|
-
if (
|
|
580
|
-
|
|
581
|
-
return { action: 'plan-task', role: 'executor', stage: 'plan', round, artifact: artifactName('plan', round) };
|
|
548
|
+
const savedBase = resolveDiffBase(repoRoot, reviewedHead, savedTargetHead);
|
|
549
|
+
if (!savedBase.ok) return { code: 'ORCHESTRATION_REVIEW_TARGET_INVALID', message: savedBase.message };
|
|
550
|
+
if (savedBase.diffBase !== savedDiffBase) {
|
|
551
|
+
return { code: 'ORCHESTRATION_REVIEW_DIFF_BASE_MISMATCH', message: 'saved review diff base does not match reviewed head and saved target head' };
|
|
582
552
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
}
|
|
587
|
-
if (!
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
if (
|
|
593
|
-
|
|
594
|
-
const round = codeRound + 1;
|
|
595
|
-
return { action: 'code-task', role: 'executor', stage: 'code', round, artifact: artifactName('code', round) };
|
|
553
|
+
const target = resolveDeliveryTarget(repoRoot, {
|
|
554
|
+
remote: typeof metadata.delivery_remote === 'string' ? metadata.delivery_remote : undefined,
|
|
555
|
+
baseRef: typeof metadata.delivery_base_ref === 'string' ? metadata.delivery_base_ref : undefined
|
|
556
|
+
});
|
|
557
|
+
if (!target.ok) return { code: target.code, message: target.message };
|
|
558
|
+
const currentTarget = resolveTargetHead(repoRoot, target.value);
|
|
559
|
+
if (!currentTarget.ok) return { code: currentTarget.code, message: currentTarget.message };
|
|
560
|
+
const currentBase = resolveDiffBase(repoRoot, reviewedHead, currentTarget.head!);
|
|
561
|
+
if (!currentBase.ok) return { code: currentBase.code, message: currentBase.message };
|
|
562
|
+
if (currentBase.diffBase !== savedDiffBase) {
|
|
563
|
+
return { code: 'ORCHESTRATION_REVIEW_TARGET_CHANGED', message: 'current delivery target changed the reviewed diff base; re-run review-code' };
|
|
596
564
|
}
|
|
597
|
-
return
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function routeFromFacts(facts: LifecycleFacts): Omit<OrchestrationNext, 'requestedModel' | 'requestedReasoningEffort'> | { completion: true } | null {
|
|
569
|
+
const recommendation = recommendNext(facts);
|
|
570
|
+
if (!recommendation.action) return { completion: true };
|
|
571
|
+
const action = recommendation.action;
|
|
572
|
+
if (action === 'manual-validation' || action === 'validation-run') return null;
|
|
573
|
+
const family = action === 'analysis' ? 'analysis' : action;
|
|
574
|
+
const round = Math.max(0, ...[...(facts.artifacts[family] ?? []), ...(facts.staleArtifacts?.[family] ?? [])].map((name) => {
|
|
575
|
+
const match = /-r(\d+)\.md$/.exec(name);
|
|
576
|
+
return match ? Number(match[1]) : 1;
|
|
577
|
+
})) + 1;
|
|
578
|
+
const command = action === 'analysis' ? 'analyze-task' : action === 'plan' ? 'plan-task' : action === 'code' ? 'code-task' : action;
|
|
579
|
+
const role = action.startsWith('review-') ? 'reviewer' : 'executor';
|
|
580
|
+
return { action: command, role, stage: action, round, artifact: artifactName(family, round) };
|
|
598
581
|
}
|
|
599
582
|
|
|
600
583
|
function routeOrchestration(taskRef: string, options: OrchestrationOptions = {}): OrchestrationResult {
|
|
@@ -614,11 +597,13 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
614
597
|
}
|
|
615
598
|
let metadata: ReturnType<typeof parseTypedTaskFrontmatter>;
|
|
616
599
|
let currentLedger: LedgerDocument | null = null;
|
|
600
|
+
let currentInvalidation: InvalidationDocument = { operations: [], targets: [] };
|
|
617
601
|
if (resolved.state === 'active') {
|
|
618
602
|
const contract = validateCurrentTaskContract(content);
|
|
619
603
|
if (!contract.ok) return failed('ORCHESTRATION_CURRENT_CONTRACT_INVALID', contract.message, resolved.taskId);
|
|
620
604
|
metadata = contract.metadata;
|
|
621
605
|
currentLedger = contract.ledger;
|
|
606
|
+
currentInvalidation = contract.invalidation;
|
|
622
607
|
} else {
|
|
623
608
|
try {
|
|
624
609
|
metadata = parseTypedTaskFrontmatter(content);
|
|
@@ -626,17 +611,25 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
626
611
|
return failed('ORCHESTRATION_TASK_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
627
612
|
}
|
|
628
613
|
}
|
|
629
|
-
|
|
630
|
-
|
|
614
|
+
if (invalidationBlocks(currentInvalidation)) {
|
|
615
|
+
return failed('ORCHESTRATION_INVALIDATION_INCOMPLETE', 'artifact invalidation is incomplete; reconcile task.md before routing downstream work', resolved.taskId);
|
|
616
|
+
}
|
|
617
|
+
let run: OrchestrationRun | null;
|
|
618
|
+
try {
|
|
619
|
+
run = readRun(resolved.taskDir);
|
|
620
|
+
} catch (error) {
|
|
621
|
+
return failed('ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
622
|
+
}
|
|
623
|
+
const facts = buildLifecycleFacts(
|
|
624
|
+
resolved.taskDir,
|
|
625
|
+
content,
|
|
626
|
+
resolved.state,
|
|
627
|
+
hasOpenLifecycleExecution(content) || Boolean(run?.pendingDelegation)
|
|
628
|
+
);
|
|
629
|
+
if (!facts.ok) return failed('ORCHESTRATION_CAPABILITY_FACTS_INVALID', facts.message, resolved.taskId);
|
|
630
|
+
const routed = routeFromFacts(facts.facts);
|
|
631
631
|
if (!routed) return failed('ORCHESTRATION_ROUTE_UNKNOWN', 'cannot determine a unique lifecycle action', resolved.taskId);
|
|
632
|
-
|
|
633
|
-
const policy = run ? rolePolicy(run, routed.role) : null;
|
|
634
|
-
const next: OrchestrationNext = {
|
|
635
|
-
...routed,
|
|
636
|
-
requestedModel: policy?.model ?? null,
|
|
637
|
-
requestedReasoningEffort: policy?.reasoningEffort ?? null
|
|
638
|
-
};
|
|
639
|
-
if (next.stage === 'commit') {
|
|
632
|
+
if ('completion' in routed) {
|
|
640
633
|
const reviewRound = highestRound(resolved.taskDir, 'review-code');
|
|
641
634
|
const review = parseReviewSummary(fs.readFileSync(
|
|
642
635
|
path.join(resolved.taskDir, artifactName('review-code', reviewRound)),
|
|
@@ -656,29 +649,8 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
656
649
|
if (run?.status === 'completed') {
|
|
657
650
|
return { status: 'completed', changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
658
651
|
}
|
|
659
|
-
|
|
660
|
-
let config: unknown;
|
|
661
|
-
try {
|
|
662
|
-
config = JSON.parse(fs.readFileSync(path.join(resolved.repoRoot, '.agents', '.airc.json'), 'utf8'));
|
|
663
|
-
} catch (error) {
|
|
664
|
-
return failed('ORCHESTRATION_CONFIG_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
665
|
-
}
|
|
666
|
-
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
667
|
-
return failed('ORCHESTRATION_CONFIG_INVALID', 'project configuration must be an object', resolved.taskId);
|
|
668
|
-
}
|
|
669
|
-
const projectConfig = config as Record<string, unknown>;
|
|
670
|
-
if (!Object.hasOwn(projectConfig, 'prFlow') || projectConfig.prFlow === 'disabled') {
|
|
671
|
-
return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
|
|
672
|
-
}
|
|
673
|
-
if (projectConfig.prFlow !== 'required') {
|
|
674
|
-
return failed('ORCHESTRATION_CONFIG_INVALID', "project configuration 'prFlow' must be 'required' or 'disabled'", resolved.taskId);
|
|
675
|
-
}
|
|
676
|
-
const prNumber = Number(metadata.pr_number);
|
|
677
|
-
if (!Number.isInteger(prNumber) || prNumber <= 0) {
|
|
678
|
-
return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
|
|
679
|
-
}
|
|
680
652
|
if (!run) {
|
|
681
|
-
return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
|
|
653
|
+
return { status: 'running', changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
682
654
|
}
|
|
683
655
|
if (run.status !== 'running' || run.pendingDelegation !== null) {
|
|
684
656
|
return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'clean completion requires an idle running orchestration', resolved.taskId);
|
|
@@ -692,35 +664,19 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
692
664
|
return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
693
665
|
}
|
|
694
666
|
if (before.headTree !== before.worktreeTree) {
|
|
695
|
-
return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
|
|
667
|
+
return { status: 'running', changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
696
668
|
}
|
|
697
669
|
const lastReviewedCommit = String(metadata.last_reviewed_commit ?? '');
|
|
698
670
|
if (before.head !== lastReviewedCommit) {
|
|
699
671
|
return failed('ORCHESTRATION_REVIEWED_HEAD_MISMATCH', 'local HEAD does not match last_reviewed_commit', resolved.taskId);
|
|
700
672
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
return failed('ORCHESTRATION_PR_INSPECTION_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
709
|
-
}
|
|
710
|
-
if (inspection.status === 'blocked') {
|
|
711
|
-
return failed('ORCHESTRATION_PR_INSPECTION_BLOCKED', inspection.error?.message ?? 'pull request inspection is blocked', resolved.taskId);
|
|
712
|
-
}
|
|
713
|
-
if (inspection.status === 'failed') {
|
|
714
|
-
return failed('ORCHESTRATION_PR_INSPECTION_FAILED', inspection.error?.message ?? 'pull request inspection failed', resolved.taskId);
|
|
715
|
-
}
|
|
716
|
-
const inspectedPrNumber = inspection.task?.prNumber ?? inspection.prNumber;
|
|
717
|
-
const prHead = inspection.pullRequest?.head?.sha;
|
|
718
|
-
if (typeof prHead !== 'string') {
|
|
719
|
-
return failed('ORCHESTRATION_PR_INSPECTION_INVALID', 'pull request inspection returned incomplete identity evidence', resolved.taskId);
|
|
720
|
-
}
|
|
721
|
-
if (inspectedPrNumber !== prNumber || prHead !== before.head) {
|
|
722
|
-
return failed('ORCHESTRATION_PR_HEAD_MISMATCH', 'pull request identity does not match the reviewed local HEAD', resolved.taskId);
|
|
723
|
-
}
|
|
673
|
+
const diffBaseError = validateSavedCurrentDiffBase(
|
|
674
|
+
resolved.repoRoot,
|
|
675
|
+
metadata,
|
|
676
|
+
resolved.taskDir,
|
|
677
|
+
lastReviewedCommit
|
|
678
|
+
);
|
|
679
|
+
if (diffBaseError) return failed(diffBaseError.code, diffBaseError.message, resolved.taskId);
|
|
724
680
|
|
|
725
681
|
let after: RepositorySnapshot;
|
|
726
682
|
try {
|
|
@@ -729,7 +685,7 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
729
685
|
return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
730
686
|
}
|
|
731
687
|
if (after.headTree !== after.worktreeTree) {
|
|
732
|
-
return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
|
|
688
|
+
return { status: 'running', changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
733
689
|
}
|
|
734
690
|
if (after.head !== before.head || after.headTree !== before.headTree) {
|
|
735
691
|
return failed('ORCHESTRATION_REPOSITORY_CHANGED', 'repository changed while clean completion evidence was collected', resolved.taskId);
|
|
@@ -743,8 +699,8 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
743
699
|
headTree: after.headTree,
|
|
744
700
|
worktreeTree: after.worktreeTree,
|
|
745
701
|
lastReviewedCommit,
|
|
746
|
-
prNumber,
|
|
747
|
-
prHead
|
|
702
|
+
prNumber: null,
|
|
703
|
+
prHead: null
|
|
748
704
|
};
|
|
749
705
|
const completed = withUpdatedRun(run, {
|
|
750
706
|
status: 'completed',
|
|
@@ -755,6 +711,18 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
755
711
|
saveRun(resolved.taskDir, completed);
|
|
756
712
|
return { status: 'completed', changed: true, taskId: resolved.taskId, run: completed, next: null, error: null };
|
|
757
713
|
}
|
|
714
|
+
const action = routed.stage as LifecycleAction;
|
|
715
|
+
const capability = canStart(action, facts.facts, {
|
|
716
|
+
initiator: 'orchestrator', requestId: `orchestration:${resolved.taskId}:${routed.stage}:${routed.round}`,
|
|
717
|
+
requestedAction: action, reasonCode: 'user-request', explicitRequest: true
|
|
718
|
+
});
|
|
719
|
+
if (!capability.allowed) return failed('ORCHESTRATION_CAPABILITY_DENIED', `${capability.reasonCode}: ${capability.evidence.join(', ')}`, resolved.taskId);
|
|
720
|
+
const policy = run ? rolePolicy(run, routed.role) : null;
|
|
721
|
+
const next: OrchestrationNext = {
|
|
722
|
+
...routed,
|
|
723
|
+
requestedModel: policy?.model ?? null,
|
|
724
|
+
requestedReasoningEffort: policy?.reasoningEffort ?? null
|
|
725
|
+
};
|
|
758
726
|
return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
|
|
759
727
|
}
|
|
760
728
|
|
|
@@ -852,10 +820,7 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
852
820
|
}
|
|
853
821
|
const updated = withUpdatedRun(run, {
|
|
854
822
|
nextStage: next.stage,
|
|
855
|
-
pendingDelegation: receipt
|
|
856
|
-
commitAuthorization: next.stage === 'commit' && run.commitAuthorization.issuedAt === null
|
|
857
|
-
? { issuedAt: (options.now ?? (() => new Date().toISOString()))(), consumedAt: null }
|
|
858
|
-
: run.commitAuthorization
|
|
823
|
+
pendingDelegation: receipt
|
|
859
824
|
});
|
|
860
825
|
saveRun(resolved.taskDir, updated);
|
|
861
826
|
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next, error: null };
|
|
@@ -1460,16 +1425,15 @@ function advanceOrchestration(taskRef: string, options: OrchestrationOptions = {
|
|
|
1460
1425
|
if (!run?.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
1461
1426
|
const result = consumeDelegation(run.pendingDelegation, { now: options.now });
|
|
1462
1427
|
if (!result.ok) return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
1463
|
-
|
|
1428
|
+
if (result.receipt.stage === 'commit') {
|
|
1429
|
+
return failed('ORCHESTRATED_COMMIT_REMOVED', 'Orchestrated commit is no longer a lifecycle stage; use local checkpoint delivery from code-task', resolved.taskId);
|
|
1430
|
+
}
|
|
1464
1431
|
const updated = withUpdatedRun(run, {
|
|
1465
|
-
status:
|
|
1432
|
+
status: 'running',
|
|
1466
1433
|
nextStage: null,
|
|
1467
1434
|
stepCount: run.stepCount + 1,
|
|
1468
1435
|
pendingDelegation: null,
|
|
1469
|
-
receipts: Object.freeze([...run.receipts, result.receipt])
|
|
1470
|
-
commitAuthorization: completed
|
|
1471
|
-
? { ...run.commitAuthorization, consumedAt: (options.now ?? (() => new Date().toISOString()))() }
|
|
1472
|
-
: run.commitAuthorization
|
|
1436
|
+
receipts: Object.freeze([...run.receipts, result.receipt])
|
|
1473
1437
|
});
|
|
1474
1438
|
saveRun(resolved.taskDir, updated);
|
|
1475
1439
|
return { status: updated.status, changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import type { ResourceIdentity } from '../platform/resource-identity.ts';
|
|
2
|
+
import { isLegacyCompatibilityEnabled, legacyCompatibilityError, VERSION } from '../version.ts';
|
|
3
|
+
import {
|
|
4
|
+
isResourceIdentity,
|
|
5
|
+
parseResourceIdentity,
|
|
6
|
+
resourceIdentityNumber,
|
|
7
|
+
serializeResourceIdentity
|
|
8
|
+
} from '../platform/resource-identity.ts';
|
|
9
|
+
|
|
10
|
+
const PR_DELIVERY_FACT_KEY = 'pr_delivery_fact';
|
|
11
|
+
|
|
12
|
+
type PrDeliveryIdentity = {
|
|
13
|
+
resource: ResourceIdentity;
|
|
14
|
+
repository: string;
|
|
15
|
+
url: string;
|
|
16
|
+
head: { repository: string; ref: string; sha: string };
|
|
17
|
+
base: { repository: string; ref: string; sha: string };
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type LegacyPrDeliveryIdentity = {
|
|
21
|
+
repository: string;
|
|
22
|
+
number: number;
|
|
23
|
+
nodeId: string;
|
|
24
|
+
url: string;
|
|
25
|
+
head: { repository: string; ref: string; sha: string };
|
|
26
|
+
base: { repository: string; ref: string; sha: string };
|
|
27
|
+
};
|
|
28
|
+
type CurrentPrDeliveryIdentityInput = {
|
|
29
|
+
resource: ResourceIdentity;
|
|
30
|
+
repository: string;
|
|
31
|
+
url: string;
|
|
32
|
+
head: { repository: string; ref: string; sha: string };
|
|
33
|
+
base: { repository: string; ref: string; sha: string };
|
|
34
|
+
};
|
|
35
|
+
type PrDeliveryIdentityInput = CurrentPrDeliveryIdentityInput;
|
|
36
|
+
|
|
37
|
+
type PrDeliveryBindingSource = 'created' | 'reused' | 'explicit-bind' | 'external-unique' | 'external-explicit';
|
|
38
|
+
type PrDeliveryProvenance = 'create-post' | 'reuse' | 'explicit-bind' | 'external-unique' | 'external-explicit';
|
|
39
|
+
|
|
40
|
+
type PrDeliveryFact =
|
|
41
|
+
| { version: 2; state: 'unbound'; reason: 'initial' }
|
|
42
|
+
| { version: 2; state: 'skipped'; reason: 'explicit'; decidedAt: string }
|
|
43
|
+
| {
|
|
44
|
+
version: 2;
|
|
45
|
+
state: 'bound';
|
|
46
|
+
identity: PrDeliveryIdentity;
|
|
47
|
+
binding: {
|
|
48
|
+
status: 'verified';
|
|
49
|
+
source: PrDeliveryBindingSource;
|
|
50
|
+
verifiedAt: string;
|
|
51
|
+
issueIdentity: ResourceIdentity | null;
|
|
52
|
+
remoteState: 'open' | 'closed';
|
|
53
|
+
mergedAt: string | null;
|
|
54
|
+
mergeCommitSha: string | null;
|
|
55
|
+
};
|
|
56
|
+
provenance: { establishedBy: PrDeliveryProvenance };
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
type CreationOutcome =
|
|
60
|
+
| { kind: 'planned'; action: 'create' | 'reuse'; createdByCurrentOperation: false }
|
|
61
|
+
| { kind: 'not-created'; reason: 'precondition-failed' | 'post-rejected'; createdByCurrentOperation: false }
|
|
62
|
+
| { kind: 'created'; createdByCurrentOperation: true }
|
|
63
|
+
| { kind: 'reused'; createdByCurrentOperation: false }
|
|
64
|
+
| { kind: 'no-op'; createdByCurrentOperation: false }
|
|
65
|
+
| { kind: 'unknown'; errorCode: 'PR_CREATE_OUTCOME_UNKNOWN' };
|
|
66
|
+
|
|
67
|
+
type PrDeliveryFactError = Error & { code: string };
|
|
68
|
+
type PrDeliveryFactReadResult =
|
|
69
|
+
| { status: 'valid'; fact: PrDeliveryFact }
|
|
70
|
+
| { status: 'missing'; fact: null }
|
|
71
|
+
| { status: 'invalid'; fact: null; error: PrDeliveryFactError };
|
|
72
|
+
|
|
73
|
+
function factError(message: string): Error & { code: string } {
|
|
74
|
+
return Object.assign(new Error(`PR_DELIVERY_FACT_INVALID: ${message}`), { code: 'PR_DELIVERY_FACT_INVALID' });
|
|
75
|
+
}
|
|
76
|
+
function asFactError(error: unknown): PrDeliveryFactError {
|
|
77
|
+
if (error instanceof Error && typeof (error as { code?: unknown }).code === 'string') return error as PrDeliveryFactError;
|
|
78
|
+
return factError(error instanceof Error ? error.message : String(error));
|
|
79
|
+
}
|
|
80
|
+
function isRecord(value: unknown): value is Record<string, unknown> { return Boolean(value) && typeof value === 'object' && !Array.isArray(value); }
|
|
81
|
+
function exactKeys(value: Record<string, unknown>, keys: readonly string[], label: string): void {
|
|
82
|
+
const actual = Object.keys(value).sort();
|
|
83
|
+
const expected = [...keys].sort();
|
|
84
|
+
if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) throw factError(`${label} has an invalid field set`);
|
|
85
|
+
}
|
|
86
|
+
function text(value: unknown, label: string, allowEmpty = false): string {
|
|
87
|
+
if (typeof value !== 'string' || (!allowEmpty && !value) || value.trim() !== value) throw factError(`${label} must be a string`);
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
function nullableText(value: unknown, label: string): string | null { return value === null ? null : text(value, label); }
|
|
91
|
+
function timestamp(value: unknown, label: string): string {
|
|
92
|
+
const result = text(value, label);
|
|
93
|
+
if (Number.isNaN(Date.parse(result))) throw factError(`${label} must be a valid timestamp`);
|
|
94
|
+
return new Date(result).toISOString();
|
|
95
|
+
}
|
|
96
|
+
function positiveNumber(value: unknown, label: string): number {
|
|
97
|
+
if (!Number.isSafeInteger(value) || (value as number) <= 0) throw factError(`${label} must be a positive integer`);
|
|
98
|
+
return value as number;
|
|
99
|
+
}
|
|
100
|
+
function parseRef(value: unknown, label: string): { repository: string; ref: string; sha: string } {
|
|
101
|
+
if (!isRecord(value)) throw factError(`${label} must be an object`);
|
|
102
|
+
exactKeys(value, ['repository', 'ref', 'sha'], label);
|
|
103
|
+
return { repository: text(value.repository, `${label}.repository`), ref: text(value.ref, `${label}.ref`), sha: text(value.sha, `${label}.sha`) };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function canonicalIdentity(value: unknown): PrDeliveryIdentity {
|
|
107
|
+
if (!isRecord(value)) throw factError('identity must be an object');
|
|
108
|
+
exactKeys(value, ['resource', 'repository', 'url', 'head', 'base'], 'identity');
|
|
109
|
+
return {
|
|
110
|
+
resource: parseResourceIdentity(value.resource, 'identity.resource'),
|
|
111
|
+
repository: text(value.repository, 'identity.repository'),
|
|
112
|
+
url: text(value.url, 'identity.url'),
|
|
113
|
+
head: parseRef(value.head, 'identity.head'),
|
|
114
|
+
base: parseRef(value.base, 'identity.base')
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function decodeLegacyIdentity(value: unknown): PrDeliveryIdentity {
|
|
119
|
+
if (!isRecord(value)) throw factError('legacy identity must be an object');
|
|
120
|
+
// TODO(compat): Remove the v1 identity decoder before the first stable v1.0.0 release.
|
|
121
|
+
exactKeys(value, ['repository', 'number', 'nodeId', 'url', 'head', 'base'], 'legacy identity');
|
|
122
|
+
const legacy = {
|
|
123
|
+
number: positiveNumber(value.number, 'legacy identity.number'),
|
|
124
|
+
repository: text(value.repository, 'identity.repository'),
|
|
125
|
+
nodeId: text(value.nodeId, 'legacy identity.nodeId'),
|
|
126
|
+
url: text(value.url, 'identity.url'),
|
|
127
|
+
head: parseRef(value.head, 'identity.head'),
|
|
128
|
+
base: parseRef(value.base, 'identity.base')
|
|
129
|
+
} satisfies LegacyPrDeliveryIdentity;
|
|
130
|
+
return {
|
|
131
|
+
resource: { kind: 'number', value: legacy.number },
|
|
132
|
+
repository: legacy.repository,
|
|
133
|
+
url: legacy.url,
|
|
134
|
+
head: legacy.head,
|
|
135
|
+
base: legacy.base
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function provenanceForSource(source: PrDeliveryBindingSource): PrDeliveryProvenance { return source === 'created' ? 'create-post' : source === 'reused' ? 'reuse' : source; }
|
|
140
|
+
|
|
141
|
+
function parseFact(value: unknown, runtimeVersion = VERSION): PrDeliveryFact {
|
|
142
|
+
if (!isRecord(value)) throw factError('fact must be an object');
|
|
143
|
+
if ((value.version !== 1 && value.version !== 2) || typeof value.state !== 'string') throw factError('version or state is invalid');
|
|
144
|
+
if (value.version === 1 && !isLegacyCompatibilityEnabled(runtimeVersion)) {
|
|
145
|
+
throw legacyCompatibilityError('v1 PR delivery fact', runtimeVersion);
|
|
146
|
+
}
|
|
147
|
+
if (value.state === 'unbound') {
|
|
148
|
+
exactKeys(value, ['version', 'state', 'reason'], 'unbound fact');
|
|
149
|
+
if (value.reason !== 'initial') throw factError('unbound reason is invalid');
|
|
150
|
+
return { version: 2, state: 'unbound', reason: 'initial' };
|
|
151
|
+
}
|
|
152
|
+
if (value.state === 'skipped') {
|
|
153
|
+
exactKeys(value, ['version', 'state', 'reason', 'decidedAt'], 'skipped fact');
|
|
154
|
+
if (value.reason !== 'explicit') throw factError('skipped reason is invalid');
|
|
155
|
+
return { version: 2, state: 'skipped', reason: 'explicit', decidedAt: timestamp(value.decidedAt, 'skipped.decidedAt') };
|
|
156
|
+
}
|
|
157
|
+
if (value.state !== 'bound') throw factError('state is invalid');
|
|
158
|
+
if (value.version === 1) {
|
|
159
|
+
// TODO(compat): Remove this v1-to-v2 shape conversion before the first stable v1.0.0 release.
|
|
160
|
+
exactKeys(value, ['version', 'state', 'identity', 'binding', 'provenance'], 'legacy bound fact');
|
|
161
|
+
if (!isRecord(value.binding) || !isRecord(value.provenance)) throw factError('legacy binding or provenance is invalid');
|
|
162
|
+
exactKeys(value.binding, ['status', 'source', 'verifiedAt', 'issueNumber', 'remoteState', 'mergedAt', 'mergeCommitSha'], 'legacy binding');
|
|
163
|
+
const issueNumber = value.binding.issueNumber === null ? null : positiveNumber(value.binding.issueNumber, 'legacy binding.issueNumber');
|
|
164
|
+
return parseFact({
|
|
165
|
+
version: 2,
|
|
166
|
+
state: 'bound',
|
|
167
|
+
identity: decodeLegacyIdentity(value.identity),
|
|
168
|
+
binding: {
|
|
169
|
+
status: 'verified', source: value.binding.source, verifiedAt: timestamp(value.binding.verifiedAt, 'binding.verifiedAt'),
|
|
170
|
+
issueIdentity: issueNumber === null ? null : { kind: 'number', value: issueNumber }, remoteState: value.binding.remoteState,
|
|
171
|
+
mergedAt: value.binding.mergedAt === null ? null : timestamp(value.binding.mergedAt, 'binding.mergedAt'), mergeCommitSha: nullableText(value.binding.mergeCommitSha, 'binding.mergeCommitSha')
|
|
172
|
+
},
|
|
173
|
+
provenance: value.provenance
|
|
174
|
+
}, runtimeVersion);
|
|
175
|
+
}
|
|
176
|
+
exactKeys(value, ['version', 'state', 'identity', 'binding', 'provenance'], 'bound fact');
|
|
177
|
+
if (!isRecord(value.binding) || !isRecord(value.provenance)) throw factError('binding or provenance is invalid');
|
|
178
|
+
exactKeys(value.binding, ['status', 'source', 'verifiedAt', 'issueIdentity', 'remoteState', 'mergedAt', 'mergeCommitSha'], 'binding');
|
|
179
|
+
if (value.binding.status !== 'verified') throw factError('binding status is invalid');
|
|
180
|
+
const sources: readonly PrDeliveryBindingSource[] = ['created', 'reused', 'explicit-bind', 'external-unique', 'external-explicit'];
|
|
181
|
+
if (!sources.includes(value.binding.source as PrDeliveryBindingSource)) throw factError('binding source is invalid');
|
|
182
|
+
const issueIdentity = value.binding.issueIdentity === null ? null : parseResourceIdentity(value.binding.issueIdentity, 'binding.issueIdentity');
|
|
183
|
+
if (value.binding.remoteState !== 'open' && value.binding.remoteState !== 'closed') throw factError('binding.remoteState is invalid');
|
|
184
|
+
const mergedAt = value.binding.mergedAt === null ? null : timestamp(value.binding.mergedAt, 'binding.mergedAt');
|
|
185
|
+
const mergeCommitSha = nullableText(value.binding.mergeCommitSha, 'binding.mergeCommitSha');
|
|
186
|
+
if (Boolean(mergedAt) !== Boolean(mergeCommitSha)) throw factError('mergedAt and mergeCommitSha must be paired');
|
|
187
|
+
exactKeys(value.provenance, ['establishedBy'], 'provenance');
|
|
188
|
+
const provenances: readonly PrDeliveryProvenance[] = ['create-post', 'reuse', 'explicit-bind', 'external-unique', 'external-explicit'];
|
|
189
|
+
if (!provenances.includes(value.provenance.establishedBy as PrDeliveryProvenance)) throw factError('provenance.establishedBy is invalid');
|
|
190
|
+
if (value.provenance.establishedBy !== provenanceForSource(value.binding.source as PrDeliveryBindingSource)) throw factError('binding.source and provenance.establishedBy do not match');
|
|
191
|
+
return {
|
|
192
|
+
version: 2, state: 'bound', identity: canonicalIdentity(value.identity),
|
|
193
|
+
binding: { status: 'verified', source: value.binding.source as PrDeliveryBindingSource, verifiedAt: timestamp(value.binding.verifiedAt, 'binding.verifiedAt'), issueIdentity, remoteState: value.binding.remoteState, mergedAt, mergeCommitSha },
|
|
194
|
+
provenance: { establishedBy: value.provenance.establishedBy as PrDeliveryProvenance }
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function decodePrDeliveryFact(value: unknown, runtimeVersion = VERSION): PrDeliveryFact {
|
|
199
|
+
if (typeof value === 'string') {
|
|
200
|
+
let decoded: unknown;
|
|
201
|
+
try { decoded = JSON.parse(value); }
|
|
202
|
+
catch { throw factError('value is not valid JSON'); }
|
|
203
|
+
return parseFact(decoded, runtimeVersion);
|
|
204
|
+
}
|
|
205
|
+
return parseFact(value, runtimeVersion);
|
|
206
|
+
}
|
|
207
|
+
function encodePrDeliveryFact(fact: PrDeliveryFact): string { return JSON.stringify(parseFact(fact)); }
|
|
208
|
+
function readPrDeliveryFact(metadata: Record<string, unknown>, runtimeVersion = VERSION): PrDeliveryFactReadResult {
|
|
209
|
+
if (!Object.hasOwn(metadata, PR_DELIVERY_FACT_KEY) || metadata[PR_DELIVERY_FACT_KEY] === '') return { status: 'missing', fact: null };
|
|
210
|
+
try { return { status: 'valid', fact: decodePrDeliveryFact(metadata[PR_DELIVERY_FACT_KEY], runtimeVersion) }; }
|
|
211
|
+
catch (error) { return { status: 'invalid', fact: null, error: asFactError(error) }; }
|
|
212
|
+
}
|
|
213
|
+
function buildUnboundFact(): PrDeliveryFact { return { version: 2, state: 'unbound', reason: 'initial' }; }
|
|
214
|
+
function buildSkippedFact(decidedAt: string): PrDeliveryFact { return parseFact({ version: 2, state: 'skipped', reason: 'explicit', decidedAt }); }
|
|
215
|
+
|
|
216
|
+
function buildBoundFact(input: {
|
|
217
|
+
identity: CurrentPrDeliveryIdentityInput;
|
|
218
|
+
source: PrDeliveryBindingSource;
|
|
219
|
+
verifiedAt: string;
|
|
220
|
+
issueIdentity?: ResourceIdentity | null;
|
|
221
|
+
remoteState: 'open' | 'closed';
|
|
222
|
+
mergedAt?: string | null;
|
|
223
|
+
mergeCommitSha?: string | null;
|
|
224
|
+
}): PrDeliveryFact {
|
|
225
|
+
return parseFact({ version: 2, state: 'bound', identity: input.identity, binding: {
|
|
226
|
+
status: 'verified', source: input.source, verifiedAt: input.verifiedAt,
|
|
227
|
+
issueIdentity: input.issueIdentity === undefined ? null : input.issueIdentity,
|
|
228
|
+
remoteState: input.remoteState, mergedAt: input.mergedAt ?? null, mergeCommitSha: input.mergeCommitSha ?? null
|
|
229
|
+
}, provenance: { establishedBy: provenanceForSource(input.source) } });
|
|
230
|
+
}
|
|
231
|
+
function factFrontmatterMutation(fact: PrDeliveryFact): { set: { [PR_DELIVERY_FACT_KEY]: string } } { return { set: { [PR_DELIVERY_FACT_KEY]: encodePrDeliveryFact(fact) } }; }
|
|
232
|
+
function identityFromPullRequest(pullRequest: PrDeliveryIdentityInput): PrDeliveryIdentity {
|
|
233
|
+
return canonicalIdentity(pullRequest);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export { PR_DELIVERY_FACT_KEY, buildBoundFact, buildSkippedFact, buildUnboundFact, decodePrDeliveryFact, encodePrDeliveryFact, factFrontmatterMutation, identityFromPullRequest, readPrDeliveryFact, serializeResourceIdentity };
|
|
237
|
+
export type { CreationOutcome, PrDeliveryBindingSource, PrDeliveryFact, PrDeliveryFactError, PrDeliveryFactReadResult, PrDeliveryIdentity, PrDeliveryIdentityInput, PrDeliveryProvenance };
|