@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
|
@@ -3,20 +3,23 @@ import path from 'node:path';
|
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
import { parseTypedTaskFrontmatter } from './frontmatter.js';
|
|
5
5
|
import { validateCurrentTaskContract } from './current-contract.js';
|
|
6
|
-
import { parseReviewSummary
|
|
7
|
-
import { inspectArtifactDirectory } from './artifact-lifecycle.js';
|
|
6
|
+
import { parseReviewSummary } from './review-artifacts.js';
|
|
8
7
|
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
|
|
9
8
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
10
9
|
import { activateDelegation, abortPreparedDelegation, completeDelegationStage, consumeDelegation, dispatchDelegation, foldBlankToNull, isDelegationReceipt, managedDelegationRole, prepareDelegation, sealDelegation } from './delegation-receipts.js';
|
|
11
10
|
import { normalizeAgentClients } from '../agent-clients/config.js';
|
|
12
11
|
import { resolveAgentRuntimeStoreRoot } from '../runtime/agent-runtime.js';
|
|
13
|
-
import { inspectPlatformPullRequest } from '../platform/pull-requests.js';
|
|
14
12
|
import { getAgentClientCapability, getAgentClientDelegationEvidence, getAgentClientModelSelection } from '../agent-clients/registry.js';
|
|
15
13
|
import { isAgentClientId } from '../agent-clients/types.js';
|
|
16
14
|
import { captureRepositorySnapshot, captureWorkspaceSnapshot, diffWorkspaceSnapshots } from './workspace-snapshot.js';
|
|
17
15
|
import { assertGitRepositoryBinding } from '../git/worktree-identity.js';
|
|
18
16
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
19
17
|
import { hasActiveCodexLifecycleEvidence } from '../agent-clients/adapters/codex-lifecycle/store.js';
|
|
18
|
+
import { extractReviewBaseline, extractReviewDiffBase, extractReviewTargetHead, extractReviewedHead } from './review-fingerprint.js';
|
|
19
|
+
import { resolveDeliveryTarget, resolveDiffBase, resolveTargetHead } from './delivery-target.js';
|
|
20
|
+
import { buildLifecycleFacts, canStart, recommendNext } from './capabilities.js';
|
|
21
|
+
import { hasOpenLifecycleExecution } from './activity-log.js';
|
|
22
|
+
import { invalidationBlocks } from './invalidation.js';
|
|
20
23
|
function supportsLifecycleDelegation(client) {
|
|
21
24
|
return getAgentClientCapability(client, 'subagents').level !== 'unsupported'
|
|
22
25
|
&& getAgentClientCapability(client, 'orchestration').level !== 'unsupported'
|
|
@@ -106,9 +109,8 @@ function isCompletionEvidence(value) {
|
|
|
106
109
|
&& exactText(value.headTree)
|
|
107
110
|
&& exactText(value.worktreeTree)
|
|
108
111
|
&& exactText(value.lastReviewedCommit)
|
|
109
|
-
&& Number.isSafeInteger(value.prNumber)
|
|
110
|
-
&& value.
|
|
111
|
-
&& exactText(value.prHead);
|
|
112
|
+
&& (value.prNumber === null || (Number.isSafeInteger(value.prNumber) && value.prNumber > 0))
|
|
113
|
+
&& (value.prHead === null || exactText(value.prHead));
|
|
112
114
|
}
|
|
113
115
|
const ORCHESTRATION_RUN_KEYS = [
|
|
114
116
|
'taskId', 'runId', 'status', 'nextStage', 'stepCount', 'maxSteps', 'modelPolicy',
|
|
@@ -340,86 +342,58 @@ function highestRound(taskDir, family) {
|
|
|
340
342
|
function artifactName(family, round) {
|
|
341
343
|
return round === 1 ? `${family}.md` : `${family}-r${round}.md`;
|
|
342
344
|
}
|
|
343
|
-
function
|
|
344
|
-
const
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
if (round === 0)
|
|
354
|
-
return { approved: false, error: null };
|
|
355
|
-
let content;
|
|
356
|
-
try {
|
|
357
|
-
content = fs.readFileSync(path.join(taskDir, artifactName(family, round)), 'utf8');
|
|
358
|
-
}
|
|
359
|
-
catch (error) {
|
|
360
|
-
return { approved: false, error: String(error) };
|
|
361
|
-
}
|
|
362
|
-
const parsed = parseReviewSummary(content);
|
|
363
|
-
if (!parsed.ok)
|
|
364
|
-
return { approved: false, error: `${parsed.code}: ${parsed.message}` };
|
|
365
|
-
const verdict = resolveCanonicalVerdict(parsed.summary);
|
|
366
|
-
if (!verdict.ok)
|
|
367
|
-
return { approved: false, error: `${verdict.code}: ${verdict.message}` };
|
|
368
|
-
return { approved: verdict.verdict === 'Approved', error: null };
|
|
369
|
-
}
|
|
370
|
-
function routeFromFacts(taskDir) {
|
|
371
|
-
const analysisRound = highestRound(taskDir, 'analysis');
|
|
372
|
-
const analysisReviewRound = highestRound(taskDir, 'review-analysis');
|
|
373
|
-
const planRound = highestRound(taskDir, 'plan');
|
|
374
|
-
const planReviewRound = highestRound(taskDir, 'review-plan');
|
|
375
|
-
const codeRound = highestRound(taskDir, 'code');
|
|
376
|
-
const codeReviewRound = highestRound(taskDir, 'review-code');
|
|
377
|
-
if (analysisRound === 0) {
|
|
378
|
-
const round = analysisRound + 1;
|
|
379
|
-
return { action: 'analyze-task', role: 'executor', stage: 'analysis', round, artifact: artifactName('analysis', round) };
|
|
380
|
-
}
|
|
381
|
-
if (!hasReviewAfterArtifact(taskDir, 'analysis', 'review-analysis')) {
|
|
382
|
-
const round = analysisReviewRound + 1;
|
|
383
|
-
return { action: 'review-analysis', role: 'reviewer', stage: 'review-analysis', round, artifact: artifactName('review-analysis', round) };
|
|
384
|
-
}
|
|
385
|
-
const analysisVerdict = latestReviewApproved(taskDir, 'review-analysis');
|
|
386
|
-
if (analysisVerdict.error)
|
|
387
|
-
return { error: analysisVerdict.error };
|
|
388
|
-
if (!analysisVerdict.approved) {
|
|
389
|
-
const round = analysisRound + 1;
|
|
390
|
-
return { action: 'analyze-task', role: 'executor', stage: 'analysis', round, artifact: artifactName('analysis', round) };
|
|
391
|
-
}
|
|
392
|
-
if (planRound === 0) {
|
|
393
|
-
const round = planRound + 1;
|
|
394
|
-
return { action: 'plan-task', role: 'executor', stage: 'plan', round, artifact: artifactName('plan', round) };
|
|
395
|
-
}
|
|
396
|
-
if (!hasReviewAfterArtifact(taskDir, 'plan', 'review-plan')) {
|
|
397
|
-
const round = planReviewRound + 1;
|
|
398
|
-
return { action: 'review-plan', role: 'reviewer', stage: 'review-plan', round, artifact: artifactName('review-plan', round) };
|
|
399
|
-
}
|
|
400
|
-
const planVerdict = latestReviewApproved(taskDir, 'review-plan');
|
|
401
|
-
if (planVerdict.error)
|
|
402
|
-
return { error: planVerdict.error };
|
|
403
|
-
if (!planVerdict.approved) {
|
|
404
|
-
const round = planRound + 1;
|
|
405
|
-
return { action: 'plan-task', role: 'executor', stage: 'plan', round, artifact: artifactName('plan', round) };
|
|
345
|
+
function validateSavedCurrentDiffBase(repoRoot, metadata, taskDir, reviewedHead) {
|
|
346
|
+
const reviewRound = highestRound(taskDir, 'review-code');
|
|
347
|
+
const reviewContent = fs.readFileSync(path.join(taskDir, artifactName('review-code', reviewRound)), 'utf8');
|
|
348
|
+
const savedTargetHead = extractReviewTargetHead(reviewContent);
|
|
349
|
+
const savedDiffBase = extractReviewDiffBase(reviewContent);
|
|
350
|
+
const savedReviewedHead = extractReviewedHead(reviewContent) || extractReviewBaseline(reviewContent);
|
|
351
|
+
if (!savedTargetHead && !savedDiffBase && !savedReviewedHead)
|
|
352
|
+
return null;
|
|
353
|
+
if (savedReviewedHead && savedReviewedHead !== reviewedHead) {
|
|
354
|
+
return { code: 'ORCHESTRATION_REVIEWED_HEAD_MISMATCH', message: 'review-code saved reviewed head does not match last_reviewed_commit' };
|
|
406
355
|
}
|
|
407
|
-
if (
|
|
408
|
-
|
|
409
|
-
return { action: 'code-task', role: 'executor', stage: 'code', round, artifact: artifactName('code', round) };
|
|
356
|
+
if (!savedTargetHead || !savedDiffBase) {
|
|
357
|
+
return { code: 'ORCHESTRATION_REVIEW_TARGET_INVALID', message: 'review-code must save target head and diff base together' };
|
|
410
358
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
return {
|
|
359
|
+
const savedBase = resolveDiffBase(repoRoot, reviewedHead, savedTargetHead);
|
|
360
|
+
if (!savedBase.ok)
|
|
361
|
+
return { code: 'ORCHESTRATION_REVIEW_TARGET_INVALID', message: savedBase.message };
|
|
362
|
+
if (savedBase.diffBase !== savedDiffBase) {
|
|
363
|
+
return { code: 'ORCHESTRATION_REVIEW_DIFF_BASE_MISMATCH', message: 'saved review diff base does not match reviewed head and saved target head' };
|
|
414
364
|
}
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
return {
|
|
365
|
+
const target = resolveDeliveryTarget(repoRoot, {
|
|
366
|
+
remote: typeof metadata.delivery_remote === 'string' ? metadata.delivery_remote : undefined,
|
|
367
|
+
baseRef: typeof metadata.delivery_base_ref === 'string' ? metadata.delivery_base_ref : undefined
|
|
368
|
+
});
|
|
369
|
+
if (!target.ok)
|
|
370
|
+
return { code: target.code, message: target.message };
|
|
371
|
+
const currentTarget = resolveTargetHead(repoRoot, target.value);
|
|
372
|
+
if (!currentTarget.ok)
|
|
373
|
+
return { code: currentTarget.code, message: currentTarget.message };
|
|
374
|
+
const currentBase = resolveDiffBase(repoRoot, reviewedHead, currentTarget.head);
|
|
375
|
+
if (!currentBase.ok)
|
|
376
|
+
return { code: currentBase.code, message: currentBase.message };
|
|
377
|
+
if (currentBase.diffBase !== savedDiffBase) {
|
|
378
|
+
return { code: 'ORCHESTRATION_REVIEW_TARGET_CHANGED', message: 'current delivery target changed the reviewed diff base; re-run review-code' };
|
|
421
379
|
}
|
|
422
|
-
return
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
function routeFromFacts(facts) {
|
|
383
|
+
const recommendation = recommendNext(facts);
|
|
384
|
+
if (!recommendation.action)
|
|
385
|
+
return { completion: true };
|
|
386
|
+
const action = recommendation.action;
|
|
387
|
+
if (action === 'manual-validation' || action === 'validation-run')
|
|
388
|
+
return null;
|
|
389
|
+
const family = action === 'analysis' ? 'analysis' : action;
|
|
390
|
+
const round = Math.max(0, ...[...(facts.artifacts[family] ?? []), ...(facts.staleArtifacts?.[family] ?? [])].map((name) => {
|
|
391
|
+
const match = /-r(\d+)\.md$/.exec(name);
|
|
392
|
+
return match ? Number(match[1]) : 1;
|
|
393
|
+
})) + 1;
|
|
394
|
+
const command = action === 'analysis' ? 'analyze-task' : action === 'plan' ? 'plan-task' : action === 'code' ? 'code-task' : action;
|
|
395
|
+
const role = action.startsWith('review-') ? 'reviewer' : 'executor';
|
|
396
|
+
return { action: command, role, stage: action, round, artifact: artifactName(family, round) };
|
|
423
397
|
}
|
|
424
398
|
function routeOrchestration(taskRef, options = {}) {
|
|
425
399
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
@@ -441,12 +415,14 @@ function routeOrchestration(taskRef, options = {}) {
|
|
|
441
415
|
}
|
|
442
416
|
let metadata;
|
|
443
417
|
let currentLedger = null;
|
|
418
|
+
let currentInvalidation = { operations: [], targets: [] };
|
|
444
419
|
if (resolved.state === 'active') {
|
|
445
420
|
const contract = validateCurrentTaskContract(content);
|
|
446
421
|
if (!contract.ok)
|
|
447
422
|
return failed('ORCHESTRATION_CURRENT_CONTRACT_INVALID', contract.message, resolved.taskId);
|
|
448
423
|
metadata = contract.metadata;
|
|
449
424
|
currentLedger = contract.ledger;
|
|
425
|
+
currentInvalidation = contract.invalidation;
|
|
450
426
|
}
|
|
451
427
|
else {
|
|
452
428
|
try {
|
|
@@ -456,19 +432,23 @@ function routeOrchestration(taskRef, options = {}) {
|
|
|
456
432
|
return failed('ORCHESTRATION_TASK_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
457
433
|
}
|
|
458
434
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
435
|
+
if (invalidationBlocks(currentInvalidation)) {
|
|
436
|
+
return failed('ORCHESTRATION_INVALIDATION_INCOMPLETE', 'artifact invalidation is incomplete; reconcile task.md before routing downstream work', resolved.taskId);
|
|
437
|
+
}
|
|
438
|
+
let run;
|
|
439
|
+
try {
|
|
440
|
+
run = readRun(resolved.taskDir);
|
|
441
|
+
}
|
|
442
|
+
catch (error) {
|
|
443
|
+
return failed('ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
444
|
+
}
|
|
445
|
+
const facts = buildLifecycleFacts(resolved.taskDir, content, resolved.state, hasOpenLifecycleExecution(content) || Boolean(run?.pendingDelegation));
|
|
446
|
+
if (!facts.ok)
|
|
447
|
+
return failed('ORCHESTRATION_CAPABILITY_FACTS_INVALID', facts.message, resolved.taskId);
|
|
448
|
+
const routed = routeFromFacts(facts.facts);
|
|
462
449
|
if (!routed)
|
|
463
450
|
return failed('ORCHESTRATION_ROUTE_UNKNOWN', 'cannot determine a unique lifecycle action', resolved.taskId);
|
|
464
|
-
|
|
465
|
-
const policy = run ? rolePolicy(run, routed.role) : null;
|
|
466
|
-
const next = {
|
|
467
|
-
...routed,
|
|
468
|
-
requestedModel: policy?.model ?? null,
|
|
469
|
-
requestedReasoningEffort: policy?.reasoningEffort ?? null
|
|
470
|
-
};
|
|
471
|
-
if (next.stage === 'commit') {
|
|
451
|
+
if ('completion' in routed) {
|
|
472
452
|
const reviewRound = highestRound(resolved.taskDir, 'review-code');
|
|
473
453
|
const review = parseReviewSummary(fs.readFileSync(path.join(resolved.taskDir, artifactName('review-code', reviewRound)), 'utf8'));
|
|
474
454
|
if (!review.ok || review.summary.manualValidation === null) {
|
|
@@ -487,29 +467,8 @@ function routeOrchestration(taskRef, options = {}) {
|
|
|
487
467
|
if (run?.status === 'completed') {
|
|
488
468
|
return { status: 'completed', changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
489
469
|
}
|
|
490
|
-
let config;
|
|
491
|
-
try {
|
|
492
|
-
config = JSON.parse(fs.readFileSync(path.join(resolved.repoRoot, '.agents', '.airc.json'), 'utf8'));
|
|
493
|
-
}
|
|
494
|
-
catch (error) {
|
|
495
|
-
return failed('ORCHESTRATION_CONFIG_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
496
|
-
}
|
|
497
|
-
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
498
|
-
return failed('ORCHESTRATION_CONFIG_INVALID', 'project configuration must be an object', resolved.taskId);
|
|
499
|
-
}
|
|
500
|
-
const projectConfig = config;
|
|
501
|
-
if (!Object.hasOwn(projectConfig, 'prFlow') || projectConfig.prFlow === 'disabled') {
|
|
502
|
-
return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
|
|
503
|
-
}
|
|
504
|
-
if (projectConfig.prFlow !== 'required') {
|
|
505
|
-
return failed('ORCHESTRATION_CONFIG_INVALID', "project configuration 'prFlow' must be 'required' or 'disabled'", resolved.taskId);
|
|
506
|
-
}
|
|
507
|
-
const prNumber = Number(metadata.pr_number);
|
|
508
|
-
if (!Number.isInteger(prNumber) || prNumber <= 0) {
|
|
509
|
-
return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
|
|
510
|
-
}
|
|
511
470
|
if (!run) {
|
|
512
|
-
return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
|
|
471
|
+
return { status: 'running', changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
513
472
|
}
|
|
514
473
|
if (run.status !== 'running' || run.pendingDelegation !== null) {
|
|
515
474
|
return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'clean completion requires an idle running orchestration', resolved.taskId);
|
|
@@ -523,34 +482,15 @@ function routeOrchestration(taskRef, options = {}) {
|
|
|
523
482
|
return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
524
483
|
}
|
|
525
484
|
if (before.headTree !== before.worktreeTree) {
|
|
526
|
-
return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
|
|
485
|
+
return { status: 'running', changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
527
486
|
}
|
|
528
487
|
const lastReviewedCommit = String(metadata.last_reviewed_commit ?? '');
|
|
529
488
|
if (before.head !== lastReviewedCommit) {
|
|
530
489
|
return failed('ORCHESTRATION_REVIEWED_HEAD_MISMATCH', 'local HEAD does not match last_reviewed_commit', resolved.taskId);
|
|
531
490
|
}
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
inspection = inspect(resolved.taskId, { cwd: gitRoot });
|
|
536
|
-
}
|
|
537
|
-
catch (error) {
|
|
538
|
-
return failed('ORCHESTRATION_PR_INSPECTION_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
539
|
-
}
|
|
540
|
-
if (inspection.status === 'blocked') {
|
|
541
|
-
return failed('ORCHESTRATION_PR_INSPECTION_BLOCKED', inspection.error?.message ?? 'pull request inspection is blocked', resolved.taskId);
|
|
542
|
-
}
|
|
543
|
-
if (inspection.status === 'failed') {
|
|
544
|
-
return failed('ORCHESTRATION_PR_INSPECTION_FAILED', inspection.error?.message ?? 'pull request inspection failed', resolved.taskId);
|
|
545
|
-
}
|
|
546
|
-
const inspectedPrNumber = inspection.task?.prNumber ?? inspection.prNumber;
|
|
547
|
-
const prHead = inspection.pullRequest?.head?.sha;
|
|
548
|
-
if (typeof prHead !== 'string') {
|
|
549
|
-
return failed('ORCHESTRATION_PR_INSPECTION_INVALID', 'pull request inspection returned incomplete identity evidence', resolved.taskId);
|
|
550
|
-
}
|
|
551
|
-
if (inspectedPrNumber !== prNumber || prHead !== before.head) {
|
|
552
|
-
return failed('ORCHESTRATION_PR_HEAD_MISMATCH', 'pull request identity does not match the reviewed local HEAD', resolved.taskId);
|
|
553
|
-
}
|
|
491
|
+
const diffBaseError = validateSavedCurrentDiffBase(resolved.repoRoot, metadata, resolved.taskDir, lastReviewedCommit);
|
|
492
|
+
if (diffBaseError)
|
|
493
|
+
return failed(diffBaseError.code, diffBaseError.message, resolved.taskId);
|
|
554
494
|
let after;
|
|
555
495
|
try {
|
|
556
496
|
after = capture(gitRoot);
|
|
@@ -559,7 +499,7 @@ function routeOrchestration(taskRef, options = {}) {
|
|
|
559
499
|
return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
560
500
|
}
|
|
561
501
|
if (after.headTree !== after.worktreeTree) {
|
|
562
|
-
return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
|
|
502
|
+
return { status: 'running', changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
563
503
|
}
|
|
564
504
|
if (after.head !== before.head || after.headTree !== before.headTree) {
|
|
565
505
|
return failed('ORCHESTRATION_REPOSITORY_CHANGED', 'repository changed while clean completion evidence was collected', resolved.taskId);
|
|
@@ -572,8 +512,8 @@ function routeOrchestration(taskRef, options = {}) {
|
|
|
572
512
|
headTree: after.headTree,
|
|
573
513
|
worktreeTree: after.worktreeTree,
|
|
574
514
|
lastReviewedCommit,
|
|
575
|
-
prNumber,
|
|
576
|
-
prHead
|
|
515
|
+
prNumber: null,
|
|
516
|
+
prHead: null
|
|
577
517
|
};
|
|
578
518
|
const completed = withUpdatedRun(run, {
|
|
579
519
|
status: 'completed',
|
|
@@ -584,6 +524,19 @@ function routeOrchestration(taskRef, options = {}) {
|
|
|
584
524
|
saveRun(resolved.taskDir, completed);
|
|
585
525
|
return { status: 'completed', changed: true, taskId: resolved.taskId, run: completed, next: null, error: null };
|
|
586
526
|
}
|
|
527
|
+
const action = routed.stage;
|
|
528
|
+
const capability = canStart(action, facts.facts, {
|
|
529
|
+
initiator: 'orchestrator', requestId: `orchestration:${resolved.taskId}:${routed.stage}:${routed.round}`,
|
|
530
|
+
requestedAction: action, reasonCode: 'user-request', explicitRequest: true
|
|
531
|
+
});
|
|
532
|
+
if (!capability.allowed)
|
|
533
|
+
return failed('ORCHESTRATION_CAPABILITY_DENIED', `${capability.reasonCode}: ${capability.evidence.join(', ')}`, resolved.taskId);
|
|
534
|
+
const policy = run ? rolePolicy(run, routed.role) : null;
|
|
535
|
+
const next = {
|
|
536
|
+
...routed,
|
|
537
|
+
requestedModel: policy?.model ?? null,
|
|
538
|
+
requestedReasoningEffort: policy?.reasoningEffort ?? null
|
|
539
|
+
};
|
|
587
540
|
return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
|
|
588
541
|
}
|
|
589
542
|
function statusOrchestration(taskRef, options = {}) {
|
|
@@ -668,10 +621,7 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
|
|
|
668
621
|
}
|
|
669
622
|
const updated = withUpdatedRun(run, {
|
|
670
623
|
nextStage: next.stage,
|
|
671
|
-
pendingDelegation: receipt
|
|
672
|
-
commitAuthorization: next.stage === 'commit' && run.commitAuthorization.issuedAt === null
|
|
673
|
-
? { issuedAt: (options.now ?? (() => new Date().toISOString()))(), consumedAt: null }
|
|
674
|
-
: run.commitAuthorization
|
|
624
|
+
pendingDelegation: receipt
|
|
675
625
|
});
|
|
676
626
|
saveRun(resolved.taskDir, updated);
|
|
677
627
|
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next, error: null };
|
|
@@ -1093,16 +1043,15 @@ function advanceOrchestration(taskRef, options = {}) {
|
|
|
1093
1043
|
const result = consumeDelegation(run.pendingDelegation, { now: options.now });
|
|
1094
1044
|
if (!result.ok)
|
|
1095
1045
|
return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
1096
|
-
|
|
1046
|
+
if (result.receipt.stage === 'commit') {
|
|
1047
|
+
return failed('ORCHESTRATED_COMMIT_REMOVED', 'Orchestrated commit is no longer a lifecycle stage; use local checkpoint delivery from code-task', resolved.taskId);
|
|
1048
|
+
}
|
|
1097
1049
|
const updated = withUpdatedRun(run, {
|
|
1098
|
-
status:
|
|
1050
|
+
status: 'running',
|
|
1099
1051
|
nextStage: null,
|
|
1100
1052
|
stepCount: run.stepCount + 1,
|
|
1101
1053
|
pendingDelegation: null,
|
|
1102
|
-
receipts: Object.freeze([...run.receipts, result.receipt])
|
|
1103
|
-
commitAuthorization: completed
|
|
1104
|
-
? { ...run.commitAuthorization, consumedAt: (options.now ?? (() => new Date().toISOString()))() }
|
|
1105
|
-
: run.commitAuthorization
|
|
1054
|
+
receipts: Object.freeze([...run.receipts, result.receipt])
|
|
1106
1055
|
});
|
|
1107
1056
|
saveRun(resolved.taskDir, updated);
|
|
1108
1057
|
return { status: updated.status, changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { isLegacyCompatibilityEnabled, legacyCompatibilityError, VERSION } from '../version.js';
|
|
2
|
+
import { isResourceIdentity, parseResourceIdentity, resourceIdentityNumber, serializeResourceIdentity } from '../platform/resource-identity.js';
|
|
3
|
+
const PR_DELIVERY_FACT_KEY = 'pr_delivery_fact';
|
|
4
|
+
function factError(message) {
|
|
5
|
+
return Object.assign(new Error(`PR_DELIVERY_FACT_INVALID: ${message}`), { code: 'PR_DELIVERY_FACT_INVALID' });
|
|
6
|
+
}
|
|
7
|
+
function asFactError(error) {
|
|
8
|
+
if (error instanceof Error && typeof error.code === 'string')
|
|
9
|
+
return error;
|
|
10
|
+
return factError(error instanceof Error ? error.message : String(error));
|
|
11
|
+
}
|
|
12
|
+
function isRecord(value) { return Boolean(value) && typeof value === 'object' && !Array.isArray(value); }
|
|
13
|
+
function exactKeys(value, keys, label) {
|
|
14
|
+
const actual = Object.keys(value).sort();
|
|
15
|
+
const expected = [...keys].sort();
|
|
16
|
+
if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index]))
|
|
17
|
+
throw factError(`${label} has an invalid field set`);
|
|
18
|
+
}
|
|
19
|
+
function text(value, label, allowEmpty = false) {
|
|
20
|
+
if (typeof value !== 'string' || (!allowEmpty && !value) || value.trim() !== value)
|
|
21
|
+
throw factError(`${label} must be a string`);
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
function nullableText(value, label) { return value === null ? null : text(value, label); }
|
|
25
|
+
function timestamp(value, label) {
|
|
26
|
+
const result = text(value, label);
|
|
27
|
+
if (Number.isNaN(Date.parse(result)))
|
|
28
|
+
throw factError(`${label} must be a valid timestamp`);
|
|
29
|
+
return new Date(result).toISOString();
|
|
30
|
+
}
|
|
31
|
+
function positiveNumber(value, label) {
|
|
32
|
+
if (!Number.isSafeInteger(value) || value <= 0)
|
|
33
|
+
throw factError(`${label} must be a positive integer`);
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
function parseRef(value, label) {
|
|
37
|
+
if (!isRecord(value))
|
|
38
|
+
throw factError(`${label} must be an object`);
|
|
39
|
+
exactKeys(value, ['repository', 'ref', 'sha'], label);
|
|
40
|
+
return { repository: text(value.repository, `${label}.repository`), ref: text(value.ref, `${label}.ref`), sha: text(value.sha, `${label}.sha`) };
|
|
41
|
+
}
|
|
42
|
+
function canonicalIdentity(value) {
|
|
43
|
+
if (!isRecord(value))
|
|
44
|
+
throw factError('identity must be an object');
|
|
45
|
+
exactKeys(value, ['resource', 'repository', 'url', 'head', 'base'], 'identity');
|
|
46
|
+
return {
|
|
47
|
+
resource: parseResourceIdentity(value.resource, 'identity.resource'),
|
|
48
|
+
repository: text(value.repository, 'identity.repository'),
|
|
49
|
+
url: text(value.url, 'identity.url'),
|
|
50
|
+
head: parseRef(value.head, 'identity.head'),
|
|
51
|
+
base: parseRef(value.base, 'identity.base')
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function decodeLegacyIdentity(value) {
|
|
55
|
+
if (!isRecord(value))
|
|
56
|
+
throw factError('legacy identity must be an object');
|
|
57
|
+
// TODO(compat): Remove the v1 identity decoder before the first stable v1.0.0 release.
|
|
58
|
+
exactKeys(value, ['repository', 'number', 'nodeId', 'url', 'head', 'base'], 'legacy identity');
|
|
59
|
+
const legacy = {
|
|
60
|
+
number: positiveNumber(value.number, 'legacy identity.number'),
|
|
61
|
+
repository: text(value.repository, 'identity.repository'),
|
|
62
|
+
nodeId: text(value.nodeId, 'legacy identity.nodeId'),
|
|
63
|
+
url: text(value.url, 'identity.url'),
|
|
64
|
+
head: parseRef(value.head, 'identity.head'),
|
|
65
|
+
base: parseRef(value.base, 'identity.base')
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
resource: { kind: 'number', value: legacy.number },
|
|
69
|
+
repository: legacy.repository,
|
|
70
|
+
url: legacy.url,
|
|
71
|
+
head: legacy.head,
|
|
72
|
+
base: legacy.base
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function provenanceForSource(source) { return source === 'created' ? 'create-post' : source === 'reused' ? 'reuse' : source; }
|
|
76
|
+
function parseFact(value, runtimeVersion = VERSION) {
|
|
77
|
+
if (!isRecord(value))
|
|
78
|
+
throw factError('fact must be an object');
|
|
79
|
+
if ((value.version !== 1 && value.version !== 2) || typeof value.state !== 'string')
|
|
80
|
+
throw factError('version or state is invalid');
|
|
81
|
+
if (value.version === 1 && !isLegacyCompatibilityEnabled(runtimeVersion)) {
|
|
82
|
+
throw legacyCompatibilityError('v1 PR delivery fact', runtimeVersion);
|
|
83
|
+
}
|
|
84
|
+
if (value.state === 'unbound') {
|
|
85
|
+
exactKeys(value, ['version', 'state', 'reason'], 'unbound fact');
|
|
86
|
+
if (value.reason !== 'initial')
|
|
87
|
+
throw factError('unbound reason is invalid');
|
|
88
|
+
return { version: 2, state: 'unbound', reason: 'initial' };
|
|
89
|
+
}
|
|
90
|
+
if (value.state === 'skipped') {
|
|
91
|
+
exactKeys(value, ['version', 'state', 'reason', 'decidedAt'], 'skipped fact');
|
|
92
|
+
if (value.reason !== 'explicit')
|
|
93
|
+
throw factError('skipped reason is invalid');
|
|
94
|
+
return { version: 2, state: 'skipped', reason: 'explicit', decidedAt: timestamp(value.decidedAt, 'skipped.decidedAt') };
|
|
95
|
+
}
|
|
96
|
+
if (value.state !== 'bound')
|
|
97
|
+
throw factError('state is invalid');
|
|
98
|
+
if (value.version === 1) {
|
|
99
|
+
// TODO(compat): Remove this v1-to-v2 shape conversion before the first stable v1.0.0 release.
|
|
100
|
+
exactKeys(value, ['version', 'state', 'identity', 'binding', 'provenance'], 'legacy bound fact');
|
|
101
|
+
if (!isRecord(value.binding) || !isRecord(value.provenance))
|
|
102
|
+
throw factError('legacy binding or provenance is invalid');
|
|
103
|
+
exactKeys(value.binding, ['status', 'source', 'verifiedAt', 'issueNumber', 'remoteState', 'mergedAt', 'mergeCommitSha'], 'legacy binding');
|
|
104
|
+
const issueNumber = value.binding.issueNumber === null ? null : positiveNumber(value.binding.issueNumber, 'legacy binding.issueNumber');
|
|
105
|
+
return parseFact({
|
|
106
|
+
version: 2,
|
|
107
|
+
state: 'bound',
|
|
108
|
+
identity: decodeLegacyIdentity(value.identity),
|
|
109
|
+
binding: {
|
|
110
|
+
status: 'verified', source: value.binding.source, verifiedAt: timestamp(value.binding.verifiedAt, 'binding.verifiedAt'),
|
|
111
|
+
issueIdentity: issueNumber === null ? null : { kind: 'number', value: issueNumber }, remoteState: value.binding.remoteState,
|
|
112
|
+
mergedAt: value.binding.mergedAt === null ? null : timestamp(value.binding.mergedAt, 'binding.mergedAt'), mergeCommitSha: nullableText(value.binding.mergeCommitSha, 'binding.mergeCommitSha')
|
|
113
|
+
},
|
|
114
|
+
provenance: value.provenance
|
|
115
|
+
}, runtimeVersion);
|
|
116
|
+
}
|
|
117
|
+
exactKeys(value, ['version', 'state', 'identity', 'binding', 'provenance'], 'bound fact');
|
|
118
|
+
if (!isRecord(value.binding) || !isRecord(value.provenance))
|
|
119
|
+
throw factError('binding or provenance is invalid');
|
|
120
|
+
exactKeys(value.binding, ['status', 'source', 'verifiedAt', 'issueIdentity', 'remoteState', 'mergedAt', 'mergeCommitSha'], 'binding');
|
|
121
|
+
if (value.binding.status !== 'verified')
|
|
122
|
+
throw factError('binding status is invalid');
|
|
123
|
+
const sources = ['created', 'reused', 'explicit-bind', 'external-unique', 'external-explicit'];
|
|
124
|
+
if (!sources.includes(value.binding.source))
|
|
125
|
+
throw factError('binding source is invalid');
|
|
126
|
+
const issueIdentity = value.binding.issueIdentity === null ? null : parseResourceIdentity(value.binding.issueIdentity, 'binding.issueIdentity');
|
|
127
|
+
if (value.binding.remoteState !== 'open' && value.binding.remoteState !== 'closed')
|
|
128
|
+
throw factError('binding.remoteState is invalid');
|
|
129
|
+
const mergedAt = value.binding.mergedAt === null ? null : timestamp(value.binding.mergedAt, 'binding.mergedAt');
|
|
130
|
+
const mergeCommitSha = nullableText(value.binding.mergeCommitSha, 'binding.mergeCommitSha');
|
|
131
|
+
if (Boolean(mergedAt) !== Boolean(mergeCommitSha))
|
|
132
|
+
throw factError('mergedAt and mergeCommitSha must be paired');
|
|
133
|
+
exactKeys(value.provenance, ['establishedBy'], 'provenance');
|
|
134
|
+
const provenances = ['create-post', 'reuse', 'explicit-bind', 'external-unique', 'external-explicit'];
|
|
135
|
+
if (!provenances.includes(value.provenance.establishedBy))
|
|
136
|
+
throw factError('provenance.establishedBy is invalid');
|
|
137
|
+
if (value.provenance.establishedBy !== provenanceForSource(value.binding.source))
|
|
138
|
+
throw factError('binding.source and provenance.establishedBy do not match');
|
|
139
|
+
return {
|
|
140
|
+
version: 2, state: 'bound', identity: canonicalIdentity(value.identity),
|
|
141
|
+
binding: { status: 'verified', source: value.binding.source, verifiedAt: timestamp(value.binding.verifiedAt, 'binding.verifiedAt'), issueIdentity, remoteState: value.binding.remoteState, mergedAt, mergeCommitSha },
|
|
142
|
+
provenance: { establishedBy: value.provenance.establishedBy }
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function decodePrDeliveryFact(value, runtimeVersion = VERSION) {
|
|
146
|
+
if (typeof value === 'string') {
|
|
147
|
+
let decoded;
|
|
148
|
+
try {
|
|
149
|
+
decoded = JSON.parse(value);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
throw factError('value is not valid JSON');
|
|
153
|
+
}
|
|
154
|
+
return parseFact(decoded, runtimeVersion);
|
|
155
|
+
}
|
|
156
|
+
return parseFact(value, runtimeVersion);
|
|
157
|
+
}
|
|
158
|
+
function encodePrDeliveryFact(fact) { return JSON.stringify(parseFact(fact)); }
|
|
159
|
+
function readPrDeliveryFact(metadata, runtimeVersion = VERSION) {
|
|
160
|
+
if (!Object.hasOwn(metadata, PR_DELIVERY_FACT_KEY) || metadata[PR_DELIVERY_FACT_KEY] === '')
|
|
161
|
+
return { status: 'missing', fact: null };
|
|
162
|
+
try {
|
|
163
|
+
return { status: 'valid', fact: decodePrDeliveryFact(metadata[PR_DELIVERY_FACT_KEY], runtimeVersion) };
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
return { status: 'invalid', fact: null, error: asFactError(error) };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function buildUnboundFact() { return { version: 2, state: 'unbound', reason: 'initial' }; }
|
|
170
|
+
function buildSkippedFact(decidedAt) { return parseFact({ version: 2, state: 'skipped', reason: 'explicit', decidedAt }); }
|
|
171
|
+
function buildBoundFact(input) {
|
|
172
|
+
return parseFact({ version: 2, state: 'bound', identity: input.identity, binding: {
|
|
173
|
+
status: 'verified', source: input.source, verifiedAt: input.verifiedAt,
|
|
174
|
+
issueIdentity: input.issueIdentity === undefined ? null : input.issueIdentity,
|
|
175
|
+
remoteState: input.remoteState, mergedAt: input.mergedAt ?? null, mergeCommitSha: input.mergeCommitSha ?? null
|
|
176
|
+
}, provenance: { establishedBy: provenanceForSource(input.source) } });
|
|
177
|
+
}
|
|
178
|
+
function factFrontmatterMutation(fact) { return { set: { [PR_DELIVERY_FACT_KEY]: encodePrDeliveryFact(fact) } }; }
|
|
179
|
+
function identityFromPullRequest(pullRequest) {
|
|
180
|
+
return canonicalIdentity(pullRequest);
|
|
181
|
+
}
|
|
182
|
+
export { PR_DELIVERY_FACT_KEY, buildBoundFact, buildSkippedFact, buildUnboundFact, decodePrDeliveryFact, encodePrDeliveryFact, factFrontmatterMutation, identityFromPullRequest, readPrDeliveryFact, serializeResourceIdentity };
|
|
183
|
+
//# sourceMappingURL=pr-delivery-fact.js.map
|
|
@@ -5,7 +5,7 @@ import { locateActivityLog, pairEntries, startedBackedRows } from './activity-lo
|
|
|
5
5
|
import { parseArtifactName, validateCompletedArtifact } from './artifact-lifecycle.js';
|
|
6
6
|
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
|
|
7
7
|
import { finalizeReviewSummaryContent } from './review-artifacts.js';
|
|
8
|
-
import { inspectDecisionDetailDuplicates
|
|
8
|
+
import { inspectDecisionDetailDuplicates } from './decision-details.js';
|
|
9
9
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
10
10
|
import { validateLifecycleExecution } from './lifecycle-execution.js';
|
|
11
11
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
@@ -118,19 +118,15 @@ function finalizeReviewSummaryUnlocked(request, options = {}) {
|
|
|
118
118
|
return failed(request, 'REVIEW_LEDGER_INVALID', `${ledgerError.code}: ${ledgerError.message}`, resolved.taskId);
|
|
119
119
|
}
|
|
120
120
|
const stageStatus = summarizeLedgerStage(rows, stage);
|
|
121
|
-
const
|
|
122
|
-
if (!repaired.ok) {
|
|
123
|
-
return failed(request, 'REVIEW_DECISION_DETAIL_INVALID', `${repaired.code}: ${repaired.message}`, resolved.taskId, stageStatus);
|
|
124
|
-
}
|
|
125
|
-
const detailInspection = inspectDecisionDetailDuplicates(repaired.content);
|
|
121
|
+
const detailInspection = inspectDecisionDetailDuplicates(artifactContent);
|
|
126
122
|
if (!detailInspection.ok) {
|
|
127
123
|
return failed(request, 'REVIEW_DECISION_DETAIL_INVALID', `${detailInspection.code}: ${detailInspection.message}`, resolved.taskId, stageStatus);
|
|
128
124
|
}
|
|
129
|
-
const transformed = finalizeReviewSummaryContent(
|
|
125
|
+
const transformed = finalizeReviewSummaryContent(artifactContent, stageStatus.unresolvedFindingCounts);
|
|
130
126
|
if (!transformed.ok) {
|
|
131
127
|
return failed(request, transformed.code, transformed.message, resolved.taskId, stageStatus);
|
|
132
128
|
}
|
|
133
|
-
if (!
|
|
129
|
+
if (!transformed.changed) {
|
|
134
130
|
return {
|
|
135
131
|
...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus),
|
|
136
132
|
status: 'no-op',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { artifactName, maxRound } from "./review-artifacts.js";
|
|
4
|
+
import { invalidationBlocks, isArtifactInvalidated, parseInvalidationDocument } from "./invalidation.js";
|
|
4
5
|
const REVIEWED_TREE_RE = /^[a-f0-9]{40,64}$/;
|
|
5
6
|
export function parseReviewedGitTree(value) {
|
|
6
7
|
return REVIEWED_TREE_RE.test(value) ? value : null;
|
|
@@ -30,20 +31,49 @@ export function findAuthoritativeReviewCodeArtifact(taskDir) {
|
|
|
30
31
|
const entries = fs.existsSync(taskDir) ? fs.readdirSync(taskDir) : [];
|
|
31
32
|
const round = maxRound(entries, "review-code");
|
|
32
33
|
if (round === 0) {
|
|
33
|
-
return { ok: false, round: 0, fileName: null, path: null };
|
|
34
|
+
return { ok: false, round: 0, fileName: null, path: null, error: null };
|
|
34
35
|
}
|
|
35
36
|
const fileName = artifactName("review-code", round);
|
|
37
|
+
let taskContent;
|
|
38
|
+
try {
|
|
39
|
+
taskContent = fs.readFileSync(path.join(taskDir, "task.md"), "utf8");
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
return {
|
|
43
|
+
ok: false, round, fileName, path: null,
|
|
44
|
+
error: error instanceof Error ? error.message : String(error)
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const invalidation = parseInvalidationDocument(taskContent);
|
|
48
|
+
if (!invalidation.ok) {
|
|
49
|
+
return { ok: false, round, fileName, path: null, error: invalidation.message };
|
|
50
|
+
}
|
|
51
|
+
if (invalidationBlocks(invalidation.document)) {
|
|
52
|
+
return { ok: false, round, fileName, path: null, error: "artifact invalidation is incomplete; reconcile task.md before using review-code" };
|
|
53
|
+
}
|
|
54
|
+
if (isArtifactInvalidated(invalidation.document, "review-code", fileName)) {
|
|
55
|
+
return { ok: false, round, fileName, path: null, error: `${fileName} is invalidated; re-run review-code` };
|
|
56
|
+
}
|
|
36
57
|
return {
|
|
37
58
|
ok: true,
|
|
38
59
|
round,
|
|
39
60
|
fileName,
|
|
40
|
-
path: path.join(taskDir, fileName)
|
|
61
|
+
path: path.join(taskDir, fileName),
|
|
62
|
+
error: null
|
|
41
63
|
};
|
|
42
64
|
}
|
|
43
65
|
export function extractReviewBaseline(content) {
|
|
44
66
|
const match = String(content).match(/^[-*]?\s*\*\*(?:审查基线提交|Review Baseline Commit)\*\*[::]\s*(.*?)\s*$/m);
|
|
45
67
|
return match ? match[1].trim().replace(/`/g, "") : "";
|
|
46
68
|
}
|
|
69
|
+
export function extractReviewTargetHead(content) {
|
|
70
|
+
const match = String(content).match(/^[-*]?\s*\*\*(?:审查目标提交|审查目标 HEAD|Review Target Commit|Review Target Head)\*\*[::]\s*(.*?)\s*$/m);
|
|
71
|
+
return match ? match[1].trim().replace(/`/g, "") : "";
|
|
72
|
+
}
|
|
73
|
+
export function extractReviewedHead(content) {
|
|
74
|
+
const match = String(content).match(/^[-*]?\s*\*\*(?:审查已检视提交|已检视提交|Reviewed Head Commit|Reviewed Head)\*\*[::]\s*(.*?)\s*$/m);
|
|
75
|
+
return match ? match[1].trim().replace(/`/g, "") : "";
|
|
76
|
+
}
|
|
47
77
|
export function extractReviewDiffBase(content) {
|
|
48
78
|
const match = String(content).match(/^[-*]?\s*\*\*(?:审查差异基线|Reviewed Diff Base)\*\*[::]\s*(.*?)\s*$/m);
|
|
49
79
|
return match ? match[1].trim().replace(/`/g, "") : "";
|