@fitlab-ai/agent-infra 0.9.12 → 0.9.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -11
- package/README.zh-CN.md +11 -11
- package/bin/cli.ts +26 -9
- package/dist/bin/cli.js +173 -157
- package/dist/bin/internal-cli.js +66 -40
- package/dist/lib/agent-client.js +5 -0
- package/dist/lib/agent-clients/next-steps.js +6 -1
- package/dist/lib/decide.js +3 -3
- package/dist/lib/internal/agent-client.js +5 -2
- package/dist/lib/internal/cli-route-inventory.js +152 -0
- package/dist/lib/internal/codex-lifecycle.js +9 -6
- package/dist/lib/internal/codex-sandbox-controller.js +8 -2
- package/dist/lib/internal/git-workflow.js +10 -6
- package/dist/lib/internal/platform-checks.js +19 -7
- package/dist/lib/internal/platform-comment.js +25 -13
- package/dist/lib/internal/platform-context.js +6 -3
- package/dist/lib/internal/platform-issue.js +20 -13
- package/dist/lib/internal/platform-metadata.js +88 -0
- package/dist/lib/internal/platform-pr-review.js +26 -13
- package/dist/lib/internal/platform-pr.js +84 -31
- package/dist/lib/internal/platform-release-notes.js +9 -6
- package/dist/lib/internal/platform-security.js +93 -0
- package/dist/lib/internal/pr-review-grade.js +25 -12
- package/dist/lib/internal/release-workflow.js +23 -9
- package/dist/lib/internal/sandbox-control.js +50 -7
- package/dist/lib/internal/task-activity.js +16 -7
- package/dist/lib/internal/task-artifact.js +235 -24
- package/dist/lib/internal/task-context.js +4 -1
- package/dist/lib/internal/task-create.js +50 -17
- package/dist/lib/internal/task-delivery.js +4 -1
- package/dist/lib/internal/task-event.js +10 -4
- package/dist/lib/internal/task-finalization.js +6 -3
- package/dist/lib/internal/task-invalidation.js +63 -0
- package/dist/lib/internal/task-ledger.js +20 -6
- package/dist/lib/internal/task-lifecycle.js +6 -3
- package/dist/lib/internal/task-operation-registry.js +421 -0
- package/dist/lib/internal/task-orchestration.js +12 -0
- package/dist/lib/internal/task-override.js +14 -7
- package/dist/lib/internal/task-qualification.js +90 -0
- package/dist/lib/internal/task-review.js +7 -4
- package/dist/lib/internal/task-short-id.js +11 -1
- package/dist/lib/internal/task-snapshot.js +4 -1
- package/dist/lib/internal/task-validate.js +7 -2
- package/dist/lib/internal/task-verify.js +11 -7
- package/dist/lib/internal/task-warning.js +11 -4
- package/dist/lib/platform/adapters.js +1 -3
- package/dist/lib/platform/change-request-git-evidence.js +27 -7
- package/dist/lib/platform/comment-safety.js +226 -0
- package/dist/lib/platform/completion-backfill.js +6 -5
- package/dist/lib/platform/context.js +70 -130
- package/dist/lib/platform/github-client.js +1 -1
- package/dist/lib/platform/github-provider.js +1130 -0
- package/dist/lib/platform/in-label-sync.js +143 -0
- package/dist/lib/platform/issue-comments.js +237 -72
- 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 +545 -0
- package/dist/lib/platform/pr-checks.js +119 -104
- package/dist/lib/platform/pr-review.js +88 -72
- package/dist/lib/platform/pr-summary.js +541 -50
- package/dist/lib/platform/provider-bridge.js +45 -0
- package/dist/lib/platform/provider-contract.d.ts +553 -0
- package/dist/lib/platform/provider-contract.js +99 -0
- package/dist/lib/platform/provider-loader.js +251 -0
- package/dist/lib/platform/provider-validation.js +577 -0
- package/dist/lib/platform/pull-request-metadata.js +8 -6
- package/dist/lib/platform/pull-requests.js +734 -543
- package/dist/lib/platform/release-notes.js +78 -55
- package/dist/lib/platform/releases.js +79 -50
- package/dist/lib/platform/repository-metadata.js +377 -0
- package/dist/lib/platform/resource-identity.d.ts +26 -0
- package/dist/lib/platform/resource-identity.js +92 -0
- package/dist/lib/platform/security-alerts.js +155 -0
- package/dist/lib/platform/task-identities.js +31 -0
- package/dist/lib/platform/verification-required.js +11 -12
- package/dist/lib/platform/verification-sync.js +129 -248
- package/dist/lib/pr-review/evidence-grading.js +19 -6
- package/dist/lib/process-data/index.js +7 -0
- package/dist/lib/run/index.js +68 -23
- package/dist/lib/sandbox/commands/control-status.js +22 -0
- package/dist/lib/sandbox/commands/create.js +32 -6
- package/dist/lib/sandbox/commands/ls.js +5 -0
- package/dist/lib/sandbox/commands/rm.js +827 -120
- package/dist/lib/sandbox/commands/show.js +16 -0
- package/dist/lib/sandbox/control/client.js +174 -19
- package/dist/lib/sandbox/control/completed-reentry.js +85 -0
- package/dist/lib/sandbox/control/container-identity.js +28 -31
- package/dist/lib/sandbox/control/executor.js +151 -16
- package/dist/lib/sandbox/control/lifecycle.js +621 -127
- package/dist/lib/sandbox/control/native-file-lock.js +177 -0
- package/dist/lib/sandbox/control/protocol.js +77 -0
- package/dist/lib/sandbox/control/server.js +677 -34
- package/dist/lib/sandbox/control/state.js +209 -4
- package/dist/lib/sandbox/control/task-view.js +183 -0
- package/dist/lib/sandbox/engines/authority.js +289 -0
- package/dist/lib/sandbox/engines/docker-exact-discovery.js +97 -0
- package/dist/lib/sandbox/index.js +5 -0
- package/dist/lib/sandbox/recovery.js +21 -1
- package/dist/lib/sandbox/workspace-view.js +2 -1
- package/dist/lib/sandbox/worktree-safety.js +4 -1
- package/dist/lib/server/index.js +8 -0
- package/dist/lib/server/protocol.js +38 -1
- package/dist/lib/task/activity-log.js +8 -1
- package/dist/lib/task/artifact-lifecycle.js +75 -18
- package/dist/lib/task/artifact-operations.js +520 -0
- package/dist/lib/task/artifact-repair-intent.js +59 -0
- package/dist/lib/task/artifact-schema.js +135 -0
- package/dist/lib/task/capabilities.js +310 -0
- package/dist/lib/task/commands/status.js +32 -0
- package/dist/lib/task/commit-operation.js +12 -1
- package/dist/lib/task/control-authority.js +12 -5
- package/dist/lib/task/create-service.js +111 -12
- package/dist/lib/task/create.js +25 -0
- package/dist/lib/task/current-contract.js +5 -1
- package/dist/lib/task/decision-details.js +1 -1
- package/dist/lib/task/events.js +416 -17
- package/dist/lib/task/finalization.js +37 -13
- package/dist/lib/task/human-override.js +58 -36
- package/dist/lib/task/index.js +11 -3
- package/dist/lib/task/invalidation-command.js +86 -0
- package/dist/lib/task/invalidation.js +260 -0
- package/dist/lib/task/ledger-intents.js +37 -0
- package/dist/lib/task/local-artifact-finalization.js +299 -0
- package/dist/lib/task/orchestration.js +240 -144
- package/dist/lib/task/pr-delivery-fact.js +102 -101
- package/dist/lib/task/qualification-audit.js +490 -0
- package/dist/lib/task/qualification-confirmation.js +109 -0
- package/dist/lib/task/qualification-intents.js +148 -0
- package/dist/lib/task/review-artifacts.js +22 -23
- package/dist/lib/task/review-finalization.js +121 -4
- package/dist/lib/task/review-fingerprint.js +24 -2
- package/dist/lib/task/rework-intent.js +138 -0
- package/dist/lib/task/sections.js +6 -4
- package/dist/lib/task/skill-scope.js +23 -0
- package/dist/lib/task/task-execution-lock.js +27 -17
- package/dist/lib/task/verification-config.js +11 -0
- package/dist/lib/task/verification-engine.js +93 -16
- package/dist/lib/task/verification.js +3 -3
- package/dist/lib/task/workflow-warning-intents.js +1 -1
- package/dist/lib/task/write.js +11 -0
- package/dist/lib/version.js +11 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-client.ts +9 -0
- package/lib/agent-clients/next-steps.ts +6 -1
- package/lib/decide.ts +3 -3
- package/lib/init.ts +1 -1
- package/lib/internal/agent-client.ts +4 -2
- package/lib/internal/cli-route-inventory.ts +140 -0
- package/lib/internal/codex-lifecycle.ts +8 -6
- package/lib/internal/codex-sandbox-controller.ts +7 -2
- package/lib/internal/git-workflow.ts +9 -6
- package/lib/internal/platform-checks.ts +14 -6
- package/lib/internal/platform-comment.ts +20 -12
- package/lib/internal/platform-context.ts +5 -3
- package/lib/internal/platform-issue.ts +17 -11
- package/lib/internal/platform-metadata.ts +70 -0
- package/lib/internal/platform-pr-review.ts +23 -12
- package/lib/internal/platform-pr.ts +65 -27
- package/lib/internal/platform-release-notes.ts +8 -6
- package/lib/internal/platform-security.ts +83 -0
- package/lib/internal/pr-review-grade.ts +20 -11
- package/lib/internal/release-workflow.ts +21 -9
- package/lib/internal/sandbox-control.ts +44 -6
- package/lib/internal/task-activity.ts +15 -7
- package/lib/internal/task-artifact.ts +204 -22
- package/lib/internal/task-context.ts +3 -1
- package/lib/internal/task-create.ts +78 -17
- package/lib/internal/task-delivery.ts +3 -1
- package/lib/internal/task-event.ts +9 -4
- package/lib/internal/task-finalization.ts +6 -4
- package/lib/internal/task-invalidation.ts +41 -0
- package/lib/internal/task-ledger.ts +21 -8
- package/lib/internal/task-lifecycle.ts +5 -3
- package/lib/internal/task-operation-registry.ts +482 -0
- package/lib/internal/task-orchestration.ts +12 -0
- package/lib/internal/task-override.ts +15 -9
- package/lib/internal/task-qualification.ts +61 -0
- package/lib/internal/task-review.ts +6 -4
- package/lib/internal/task-short-id.ts +10 -1
- package/lib/internal/task-snapshot.ts +3 -1
- package/lib/internal/task-validate.ts +6 -2
- package/lib/internal/task-verify.ts +10 -7
- package/lib/internal/task-warning.ts +10 -4
- package/lib/platform/adapters.ts +3 -2
- package/lib/platform/change-request-git-evidence.ts +36 -9
- package/lib/platform/comment-safety.ts +250 -0
- package/lib/platform/completion-backfill.ts +9 -8
- package/lib/platform/context.ts +97 -134
- package/lib/platform/github-client.ts +2 -2
- package/lib/platform/github-provider.ts +1132 -0
- package/lib/platform/in-label-sync.ts +208 -0
- package/lib/platform/issue-comments.ts +255 -80
- 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 +639 -0
- package/lib/platform/pr-checks.ts +126 -106
- package/lib/platform/pr-review.ts +88 -85
- package/lib/platform/pr-summary.ts +581 -53
- package/lib/platform/provider-bridge.ts +82 -0
- package/lib/platform/provider-contract.ts +611 -0
- package/lib/platform/provider-loader.ts +307 -0
- package/lib/platform/provider-validation.ts +611 -0
- package/lib/platform/pull-request-metadata.ts +10 -7
- package/lib/platform/pull-requests.ts +781 -550
- package/lib/platform/release-notes.ts +76 -59
- package/lib/platform/releases.ts +77 -43
- package/lib/platform/repository-metadata.ts +453 -0
- package/lib/platform/resource-identity.ts +121 -0
- package/lib/platform/security-alerts.ts +191 -0
- package/lib/platform/task-identities.ts +40 -0
- package/lib/platform/types.ts +4 -2
- package/lib/platform/verification-required.ts +11 -13
- package/lib/platform/verification-sync.ts +128 -304
- package/lib/pr-review/evidence-grading.ts +25 -8
- package/lib/process-data/index.ts +7 -0
- package/lib/run/index.ts +62 -22
- package/lib/sandbox/commands/control-status.ts +26 -0
- package/lib/sandbox/commands/create.ts +46 -6
- package/lib/sandbox/commands/ls.ts +7 -0
- package/lib/sandbox/commands/rm.ts +1000 -87
- package/lib/sandbox/commands/show.ts +16 -0
- package/lib/sandbox/control/client.ts +203 -19
- package/lib/sandbox/control/completed-reentry.ts +98 -0
- package/lib/sandbox/control/container-identity.ts +27 -43
- package/lib/sandbox/control/executor.ts +180 -15
- package/lib/sandbox/control/lifecycle.ts +629 -17
- package/lib/sandbox/control/native-file-lock.ts +209 -0
- package/lib/sandbox/control/protocol.ts +118 -2
- package/lib/sandbox/control/server.ts +724 -34
- package/lib/sandbox/control/state.ts +257 -4
- package/lib/sandbox/control/task-view.ts +240 -0
- package/lib/sandbox/engines/authority.ts +365 -0
- package/lib/sandbox/engines/docker-exact-discovery.ts +132 -0
- package/lib/sandbox/index.ts +7 -0
- package/lib/sandbox/recovery.ts +21 -1
- package/lib/sandbox/workspace-view.ts +9 -3
- package/lib/sandbox/worktree-safety.ts +4 -1
- package/lib/server/index.ts +10 -0
- package/lib/server/protocol.ts +36 -1
- package/lib/task/activity-log.ts +8 -0
- package/lib/task/artifact-lifecycle.ts +70 -22
- package/lib/task/artifact-operations.ts +626 -0
- package/lib/task/artifact-repair-intent.ts +71 -0
- package/lib/task/artifact-schema.ts +192 -0
- package/lib/task/capabilities.ts +331 -0
- package/lib/task/commands/status.ts +38 -0
- package/lib/task/commit-operation.ts +12 -1
- package/lib/task/control-authority.ts +22 -9
- package/lib/task/create-service.ts +139 -14
- package/lib/task/create.ts +27 -0
- package/lib/task/current-contract.ts +6 -1
- package/lib/task/decision-details.ts +6 -2
- package/lib/task/events.ts +392 -16
- package/lib/task/finalization.ts +50 -14
- package/lib/task/human-override.ts +70 -46
- package/lib/task/index.ts +16 -3
- package/lib/task/invalidation-command.ts +86 -0
- package/lib/task/invalidation.ts +332 -0
- package/lib/task/ledger-intents.ts +35 -1
- package/lib/task/local-artifact-finalization.ts +432 -0
- package/lib/task/orchestration.ts +223 -133
- package/lib/task/pr-delivery-fact.ts +131 -148
- package/lib/task/qualification-audit.ts +599 -0
- package/lib/task/qualification-confirmation.ts +117 -0
- package/lib/task/qualification-intents.ts +150 -0
- package/lib/task/review-artifacts.ts +21 -22
- package/lib/task/review-finalization.ts +133 -4
- package/lib/task/review-fingerprint.ts +23 -2
- package/lib/task/rework-intent.ts +165 -0
- package/lib/task/sections.ts +6 -4
- package/lib/task/skill-scope.ts +24 -0
- package/lib/task/task-execution-lock.ts +23 -14
- package/lib/task/verification-config.ts +12 -0
- package/lib/task/verification-engine.ts +103 -17
- package/lib/task/verification.ts +4 -4
- package/lib/task/workflow-warning-intents.ts +1 -1
- package/lib/task/write.ts +19 -0
- package/lib/update.ts +1 -1
- package/lib/version.ts +15 -1
- package/package.json +12 -1
- package/templates/.agents/rules/README.en.md +3 -1
- package/templates/.agents/rules/README.zh-CN.md +3 -1
- package/templates/.agents/rules/decision-qualification.en.md +22 -0
- package/templates/.agents/rules/decision-qualification.zh-CN.md +22 -0
- package/templates/.agents/rules/evidence-reporting.en.md +28 -0
- package/templates/.agents/rules/evidence-reporting.zh-CN.md +28 -0
- package/templates/.agents/rules/issue-pr-commands.en.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +2 -0
- package/templates/.agents/rules/issue-sync.zh-CN.md +2 -0
- package/templates/.agents/rules/label-milestone-setup.en.md +7 -7
- package/templates/.agents/rules/label-milestone-setup.zh-CN.md +7 -7
- package/templates/.agents/rules/local-artifact-repair.en.md +13 -2
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -2
- package/templates/.agents/rules/pr-sync.en.md +10 -4
- package/templates/.agents/rules/pr-sync.zh-CN.md +17 -4
- package/templates/.agents/rules/security-alerts.en.md +8 -8
- package/templates/.agents/rules/security-alerts.zh-CN.md +8 -8
- package/templates/.agents/rules/task-short-id.en.md +1 -4
- package/templates/.agents/rules/task-short-id.zh-CN.md +1 -3
- package/templates/.agents/skills/analyze-task/SKILL.en.md +28 -5
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +29 -5
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -12
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -12
- 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 +41 -9
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +43 -11
- package/templates/.agents/skills/code-task/config/verify.en.json +6 -14
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +6 -14
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +5 -4
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +5 -4
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +3 -1
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +3 -1
- package/templates/.agents/skills/code-task/reference/report-template.en.md +42 -15
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +39 -12
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +2 -0
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +2 -0
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +3 -3
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +10 -4
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +10 -4
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +4 -3
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +4 -3
- package/templates/.agents/skills/complete-task/SKILL.en.md +26 -21
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +25 -21
- package/templates/.agents/skills/create-pr/SKILL.en.md +7 -6
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/create-pr/config/verify.json +1 -0
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +16 -4
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +16 -4
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +5 -3
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +5 -3
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/scripts/change-report.mjs +3 -0
- package/templates/.agents/skills/create-task/SKILL.en.md +8 -0
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +7 -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 +8 -8
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/init-milestones/SKILL.en.md +9 -11
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +9 -11
- package/templates/.agents/skills/plan-task/SKILL.en.md +27 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +26 -4
- package/templates/.agents/skills/plan-task/config/verify.en.json +4 -14
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +4 -14
- package/templates/.agents/skills/review-analysis/SKILL.en.md +17 -4
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +17 -4
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -18
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -18
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +50 -23
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +45 -18
- package/templates/.agents/skills/review-code/SKILL.en.md +18 -5
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +18 -5
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -23
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -23
- package/templates/.agents/skills/review-code/reference/report-template.en.md +49 -22
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +45 -18
- package/templates/.agents/skills/review-plan/SKILL.en.md +17 -4
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +17 -4
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -18
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -18
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +49 -22
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +45 -18
- package/templates/.agents/skills/review-pr/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -3
- package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +9 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +9 -3
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +3 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +3 -1
- package/templates/.agents/skills/test/SKILL.en.md +13 -2
- package/templates/.agents/skills/test/SKILL.zh-CN.md +13 -2
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +14 -0
- package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +6 -0
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +6 -0
- package/templates/.agents/templates/task.en.md +24 -1
- package/templates/.agents/templates/task.zh-CN.md +24 -1
- package/templates/.github/workflows/pr-label.yml +7 -26
- package/templates/.agents/scripts/security-alerts.github.sh +0 -151
- package/templates/.agents/scripts/security-alerts.sh +0 -31
- package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +0 -146
- package/templates/.agents/skills/init-labels/scripts/init-labels.sh +0 -32
- package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +0 -294
- package/templates/.agents/skills/init-milestones/scripts/init-milestones.sh +0 -6
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
import { parseResourceIdentity, serializeResourceIdentity } from './resource-identity.ts';
|
|
2
|
+
import type { ProviderIdentityDeclaration, ResourceIdentityKind } from './resource-identity.ts';
|
|
3
|
+
import type {
|
|
4
|
+
ChangeRequestSnapshot,
|
|
5
|
+
CheckLogSnapshot,
|
|
6
|
+
CheckRunSnapshot,
|
|
7
|
+
GitEvidenceSnapshot,
|
|
8
|
+
LabelDefinition,
|
|
9
|
+
LabelReconciliation,
|
|
10
|
+
IssueSnapshot,
|
|
11
|
+
MilestoneDefinition,
|
|
12
|
+
MilestoneInitialization,
|
|
13
|
+
MilestoneReconciliation,
|
|
14
|
+
MutationReceipt,
|
|
15
|
+
PlatformContextSnapshot,
|
|
16
|
+
PlatformError,
|
|
17
|
+
PlatformProvider,
|
|
18
|
+
ProviderResult,
|
|
19
|
+
ReleaseNotesFacts,
|
|
20
|
+
ReleaseSnapshot,
|
|
21
|
+
RemoteCommentSnapshot,
|
|
22
|
+
RepositoryMetadataSnapshot,
|
|
23
|
+
RequiredCheckSnapshot,
|
|
24
|
+
ReviewSnapshot,
|
|
25
|
+
SecurityAlertKind,
|
|
26
|
+
SecurityAlertSnapshot,
|
|
27
|
+
VerificationRemoteFacts
|
|
28
|
+
} from './provider-contract.ts';
|
|
29
|
+
import type { JsonValue } from './provider-contract.ts';
|
|
30
|
+
|
|
31
|
+
type ResultValidator<T> = (value: unknown) => T;
|
|
32
|
+
|
|
33
|
+
function validationError(providerType: string, phase: string, message: string, code = 'PLATFORM_PROVIDER_RESULT_INVALID'): PlatformError {
|
|
34
|
+
return {
|
|
35
|
+
code,
|
|
36
|
+
message,
|
|
37
|
+
retryable: false,
|
|
38
|
+
providerType,
|
|
39
|
+
phase
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function record(value: unknown, label: string): Record<string, unknown> {
|
|
44
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${label} must be an object`);
|
|
45
|
+
return value as Record<string, unknown>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function exactKeys(value: Record<string, unknown>, keys: readonly string[], label: string): void {
|
|
49
|
+
const allowed = new Set(keys);
|
|
50
|
+
if (Object.keys(value).some((key) => !allowed.has(key))) throw new Error(`${label} contains unknown fields`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function jsonValue(value: unknown, label: string): JsonValue {
|
|
54
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean') return value;
|
|
55
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
56
|
+
if (Array.isArray(value)) return value.map((entry, index) => jsonValue(entry, `${label}[${index}]`));
|
|
57
|
+
const item = record(value, label);
|
|
58
|
+
return Object.fromEntries(Object.entries(item).map(([key, entry]) => [key, jsonValue(entry, `${label}.${key}`)]));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function jsonObject(value: unknown, label: string): Record<string, JsonValue> {
|
|
62
|
+
const item = record(value, label);
|
|
63
|
+
return Object.fromEntries(Object.entries(item).map(([key, entry]) => [key, jsonValue(entry, `${label}.${key}`)]));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function scalarFields(value: unknown, label: string): Record<string, string | number | null> {
|
|
67
|
+
const item = record(value, label);
|
|
68
|
+
return Object.fromEntries(Object.entries(item).map(([key, entry]) => {
|
|
69
|
+
if (entry === null || typeof entry === 'string') return [key, entry];
|
|
70
|
+
if (typeof entry === 'number' && Number.isFinite(entry)) return [key, entry];
|
|
71
|
+
throw new Error(`${label}.${key} must be a string, number, or null`);
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function booleanValue(value: unknown, label: string): boolean {
|
|
76
|
+
if (typeof value !== 'boolean') throw new Error(`${label} must be boolean`);
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function fieldKind(value: unknown, label: string): 'single-select' | 'date' | 'text' | 'number' {
|
|
81
|
+
if (value !== 'single-select' && value !== 'date' && value !== 'text' && value !== 'number') throw new Error(`${label} is invalid`);
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function mergeability(value: unknown, label: string): { state: 'mergeable' | 'conflicting' | 'unknown'; detail: string | null } {
|
|
86
|
+
const item = record(value, label);
|
|
87
|
+
exactKeys(item, ['state', 'detail'], label);
|
|
88
|
+
if (item.state !== 'mergeable' && item.state !== 'conflicting' && item.state !== 'unknown') throw new Error(`${label}.state is invalid`);
|
|
89
|
+
return { state: item.state, detail: item.detail === null ? null : stringValue(item.detail, `${label}.detail`) };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function stringValue(value: unknown, label: string, allowEmpty = false): string {
|
|
93
|
+
if (typeof value !== 'string' || (!allowEmpty && value.length === 0)) throw new Error(`${label} must be a string`);
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function nullableString(value: unknown, label: string): string | null {
|
|
98
|
+
return value === null ? null : stringValue(value, label);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function timestamp(value: unknown, label: string): string {
|
|
102
|
+
const result = stringValue(value, label);
|
|
103
|
+
if (Number.isNaN(Date.parse(result))) throw new Error(`${label} must be a timestamp`);
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function arrayValue(value: unknown, label: string): unknown[] {
|
|
108
|
+
if (!Array.isArray(value)) throw new Error(`${label} must be an array`);
|
|
109
|
+
return value;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function stableUnique<T>(values: T[], key: (value: T) => string, label: string): T[] {
|
|
113
|
+
const seen = new Set<string>();
|
|
114
|
+
for (const value of values) {
|
|
115
|
+
const identityKey = key(value);
|
|
116
|
+
if (seen.has(identityKey)) throw new Error(`${label} contains duplicate identities`);
|
|
117
|
+
seen.add(identityKey);
|
|
118
|
+
}
|
|
119
|
+
return [...values].sort((left, right) => key(left).localeCompare(key(right)));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function declaredIdentityKind(
|
|
123
|
+
declaration: ProviderIdentityDeclaration | undefined,
|
|
124
|
+
resourceKind: 'issue' | 'pull-request' | 'comment' | 'release'
|
|
125
|
+
): ResourceIdentityKind | undefined {
|
|
126
|
+
const kind = declaration?.[resourceKind];
|
|
127
|
+
return kind === 'id' || kind === 'number' || kind === 'key' ? kind : undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function identity(value: unknown, label: string, expectedKind?: ResourceIdentityKind): ReturnType<typeof parseResourceIdentity> {
|
|
131
|
+
const parsed = parseResourceIdentity(value, label);
|
|
132
|
+
if (expectedKind !== undefined && parsed.kind !== expectedKind) throw new Error(`${label} kind does not match provider declaration`);
|
|
133
|
+
return parsed;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function author(value: unknown, label: string): { id?: string; name?: string } | null {
|
|
137
|
+
if (value === null) return null;
|
|
138
|
+
const item = record(value, label);
|
|
139
|
+
exactKeys(item, ['id', 'name'], label);
|
|
140
|
+
const result: { id?: string; name?: string } = {};
|
|
141
|
+
if (item.id !== undefined) result.id = stringValue(item.id, `${label}.id`);
|
|
142
|
+
if (item.name !== undefined) result.name = stringValue(item.name, `${label}.name`);
|
|
143
|
+
if (!result.id && !result.name) throw new Error(`${label} must identify an author`);
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function validateContext(value: unknown, expectedProviderType?: string): PlatformContextSnapshot {
|
|
148
|
+
const item = record(value, 'context');
|
|
149
|
+
exactKeys(item, ['type', 'scope', 'currentUser', 'capabilities', 'authenticated', 'metadata'], 'context');
|
|
150
|
+
const scope = record(item.scope, 'context.scope');
|
|
151
|
+
exactKeys(scope, ['id', 'label'], 'context.scope');
|
|
152
|
+
const capabilities = record(item.capabilities, 'context.capabilities');
|
|
153
|
+
exactKeys(capabilities, ['authenticated', 'comment', 'triage', 'push', 'admin'], 'context.capabilities');
|
|
154
|
+
const capabilityNames = ['authenticated', 'comment', 'triage', 'push', 'admin'];
|
|
155
|
+
if (capabilityNames.some((name) => typeof capabilities[name] !== 'boolean')) throw new Error('context.capabilities must contain booleans');
|
|
156
|
+
if (typeof item.authenticated !== 'boolean') throw new Error('context.authenticated must be boolean');
|
|
157
|
+
const type = stringValue(item.type, 'context.type');
|
|
158
|
+
if (expectedProviderType !== undefined && type !== expectedProviderType) throw new Error('context.type does not match selected provider');
|
|
159
|
+
return {
|
|
160
|
+
type,
|
|
161
|
+
scope: { id: stringValue(scope.id, 'context.scope.id'), ...(scope.label === undefined ? {} : { label: stringValue(scope.label, 'context.scope.label') }) },
|
|
162
|
+
currentUser: author(item.currentUser, 'context.currentUser'),
|
|
163
|
+
capabilities: {
|
|
164
|
+
authenticated: capabilities.authenticated as boolean,
|
|
165
|
+
comment: capabilities.comment as boolean,
|
|
166
|
+
triage: capabilities.triage as boolean,
|
|
167
|
+
push: capabilities.push as boolean,
|
|
168
|
+
admin: capabilities.admin as boolean
|
|
169
|
+
},
|
|
170
|
+
authenticated: item.authenticated,
|
|
171
|
+
...(item.metadata === undefined ? {} : { metadata: jsonObject(item.metadata, 'context.metadata') })
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function validateIssue(value: unknown, expectedKind?: ResourceIdentityKind, requireIdentity = false): IssueSnapshot {
|
|
176
|
+
const item = record(value, 'issue');
|
|
177
|
+
exactKeys(item, ['id', 'identity', 'number', 'title', 'body', 'state', 'labels', 'assignees', 'milestone', 'fields', 'issueType', 'author', 'displayUrl'], 'issue');
|
|
178
|
+
const result: IssueSnapshot = {
|
|
179
|
+
id: item.id === undefined ? '' : stringValue(item.id, 'issue.id'),
|
|
180
|
+
...(item.identity === undefined ? {} : { identity: identity(item.identity, 'issue.identity', expectedKind) }),
|
|
181
|
+
...(item.number === undefined ? {} : { number: Number.isSafeInteger(item.number) && (item.number as number) > 0 ? item.number as number : (() => { throw new Error('issue.number is invalid'); })() }),
|
|
182
|
+
title: stringValue(item.title, 'issue.title', true),
|
|
183
|
+
body: stringValue(item.body, 'issue.body', true),
|
|
184
|
+
state: item.state === 'closed' ? 'closed' : item.state === 'open' ? 'open' : (() => { throw new Error('issue.state is invalid'); })(),
|
|
185
|
+
labels: arrayValue(item.labels, 'issue.labels').map((entry) => stringValue(entry, 'issue.labels[]')),
|
|
186
|
+
assignees: arrayValue(item.assignees, 'issue.assignees').map((entry) => stringValue(entry, 'issue.assignees[]')),
|
|
187
|
+
milestone: nullableString(item.milestone, 'issue.milestone'),
|
|
188
|
+
fields: scalarFields(item.fields, 'issue.fields'),
|
|
189
|
+
...(item.issueType === undefined ? {} : { issueType: item.issueType === null ? null : issueType(item.issueType, 'issue.issueType') }),
|
|
190
|
+
...(item.author === undefined ? {} : { author: author(item.author, 'issue.author') }),
|
|
191
|
+
...(item.displayUrl === undefined ? {} : { displayUrl: stringValue(item.displayUrl, 'issue.displayUrl') })
|
|
192
|
+
};
|
|
193
|
+
if (!result.id && !result.identity) throw new Error('issue must expose id or identity');
|
|
194
|
+
if (requireIdentity && !result.identity) throw new Error('issue identity is required');
|
|
195
|
+
if (result.number !== undefined && (!Number.isSafeInteger(result.number) || result.number <= 0)) throw new Error('issue.number is invalid');
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function issueType(value: unknown, label: string): NonNullable<IssueSnapshot['issueType']> {
|
|
200
|
+
const item = record(value, label);
|
|
201
|
+
exactKeys(item, ['identity', 'name', 'fields'], label);
|
|
202
|
+
const fields = arrayValue(item.fields, `${label}.fields`).map((rawField) => {
|
|
203
|
+
const field = record(rawField, `${label}.field`);
|
|
204
|
+
exactKeys(field, ['identity', 'name', 'kind', 'options'], `${label}.field`);
|
|
205
|
+
return {
|
|
206
|
+
identity: identity(field.identity, `${label}.field.identity`),
|
|
207
|
+
name: stringValue(field.name, `${label}.field.name`),
|
|
208
|
+
kind: fieldKind(field.kind, `${label}.field.kind`),
|
|
209
|
+
options: stableUnique(arrayValue(field.options, `${label}.field.options`).map((rawOption) => {
|
|
210
|
+
const option = record(rawOption, `${label}.field.option`);
|
|
211
|
+
exactKeys(option, ['identity', 'name'], `${label}.field.option`);
|
|
212
|
+
return { identity: identity(option.identity, `${label}.field.option.identity`), name: stringValue(option.name, `${label}.field.option.name`) };
|
|
213
|
+
}), (entry) => serializeResourceIdentity(entry.identity), `${label}.field.options`)
|
|
214
|
+
};
|
|
215
|
+
});
|
|
216
|
+
return { identity: identity(item.identity, `${label}.identity`), name: stringValue(item.name, `${label}.name`), fields: stableUnique(fields, (entry) => serializeResourceIdentity(entry.identity), `${label}.fields`) };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function validateComment(value: unknown): RemoteCommentSnapshot {
|
|
220
|
+
const item = record(value, 'comment');
|
|
221
|
+
exactKeys(item, ['id', 'author', 'body', 'createdAt', 'updatedAt'], 'comment');
|
|
222
|
+
return {
|
|
223
|
+
id: stringValue(item.id, 'comment.id'),
|
|
224
|
+
author: author(item.author, 'comment.author'),
|
|
225
|
+
body: stringValue(item.body, 'comment.body', true),
|
|
226
|
+
createdAt: utcTimestamp(item.createdAt, 'comment.createdAt'),
|
|
227
|
+
updatedAt: utcTimestamp(item.updatedAt, 'comment.updatedAt')
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function validateChangeRequest(value: unknown, expectedKind?: ResourceIdentityKind, requireIdentity = false): ChangeRequestSnapshot {
|
|
232
|
+
const item = record(value, 'changeRequest');
|
|
233
|
+
exactKeys(item, ['id', 'identity', 'number', 'state', 'title', 'body', 'baseSha', 'headSha', 'author', 'mergedAt', 'displayUrl', 'draft', 'labels', 'assignees', 'milestone', 'mergeCommitSha', 'mergeability', 'head', 'base'], 'changeRequest');
|
|
234
|
+
const result = {
|
|
235
|
+
id: item.id === undefined ? '' : stringValue(item.id, 'changeRequest.id'),
|
|
236
|
+
...(item.identity === undefined ? {} : { identity: identity(item.identity, 'changeRequest.identity', expectedKind) }),
|
|
237
|
+
...(item.number === undefined ? {} : { number: Number.isSafeInteger(item.number) && (item.number as number) > 0 ? item.number as number : (() => { throw new Error('changeRequest.number is invalid'); })() }),
|
|
238
|
+
state: stringValue(item.state, 'changeRequest.state'),
|
|
239
|
+
title: stringValue(item.title, 'changeRequest.title', true),
|
|
240
|
+
body: stringValue(item.body, 'changeRequest.body', true),
|
|
241
|
+
...(item.baseSha === undefined ? {} : { baseSha: stringValue(item.baseSha, 'changeRequest.baseSha') }),
|
|
242
|
+
...(item.headSha === undefined ? {} : { headSha: stringValue(item.headSha, 'changeRequest.headSha') }),
|
|
243
|
+
...(item.author === undefined ? {} : { author: author(item.author, 'changeRequest.author') }),
|
|
244
|
+
...(item.mergedAt === undefined ? {} : { mergedAt: item.mergedAt === null ? null : utcTimestamp(item.mergedAt, 'changeRequest.mergedAt') }),
|
|
245
|
+
...(item.displayUrl === undefined ? {} : { displayUrl: stringValue(item.displayUrl, 'changeRequest.displayUrl') }),
|
|
246
|
+
...(item.draft === undefined ? {} : { draft: booleanValue(item.draft, 'changeRequest.draft') }),
|
|
247
|
+
...(item.labels === undefined ? {} : { labels: arrayValue(item.labels, 'changeRequest.labels').map((entry) => stringValue(entry, 'changeRequest.labels[]')) }),
|
|
248
|
+
...(item.assignees === undefined ? {} : { assignees: arrayValue(item.assignees, 'changeRequest.assignees').map((entry) => stringValue(entry, 'changeRequest.assignees[]')) }),
|
|
249
|
+
...(item.milestone === undefined ? {} : { milestone: nullableString(item.milestone, 'changeRequest.milestone') }),
|
|
250
|
+
...(item.mergeCommitSha === undefined ? {} : { mergeCommitSha: item.mergeCommitSha === null ? null : stringValue(item.mergeCommitSha, 'changeRequest.mergeCommitSha') }),
|
|
251
|
+
...(item.mergeability === undefined ? {} : { mergeability: mergeability(item.mergeability, 'changeRequest.mergeability') }),
|
|
252
|
+
...(item.head === undefined ? {} : { head: validateRef(item.head, 'changeRequest.head') }),
|
|
253
|
+
...(item.base === undefined ? {} : { base: validateRef(item.base, 'changeRequest.base') })
|
|
254
|
+
} as ChangeRequestSnapshot;
|
|
255
|
+
if (!result.id && !result.identity) throw new Error('changeRequest must expose id or identity');
|
|
256
|
+
if (requireIdentity && !result.identity) throw new Error('changeRequest identity is required');
|
|
257
|
+
if (result.number !== undefined && (!Number.isSafeInteger(result.number) || result.number <= 0)) throw new Error('changeRequest.number is invalid');
|
|
258
|
+
if (result.mergedAt && Number.isNaN(Date.parse(result.mergedAt))) throw new Error('changeRequest.mergedAt is invalid');
|
|
259
|
+
return result;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function validateRef(value: unknown, label: string): { repository: string; ref: string; sha: string } {
|
|
263
|
+
const item = record(value, label);
|
|
264
|
+
exactKeys(item, ['repository', 'ref', 'sha'], label);
|
|
265
|
+
return {
|
|
266
|
+
repository: stringValue(item.repository, `${label}.repository`),
|
|
267
|
+
ref: stringValue(item.ref, `${label}.ref`),
|
|
268
|
+
sha: stringValue(item.sha, `${label}.sha`)
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function validateReceipt(value: unknown): MutationReceipt {
|
|
273
|
+
const item = record(value, 'receipt');
|
|
274
|
+
exactKeys(item, ['remoteId', 'changed', 'operationId'], 'receipt');
|
|
275
|
+
return {
|
|
276
|
+
remoteId: stringValue(item.remoteId, 'receipt.remoteId'),
|
|
277
|
+
changed: typeof item.changed === 'boolean' ? item.changed : (() => { throw new Error('receipt.changed is invalid'); })(),
|
|
278
|
+
...(item.operationId === undefined ? {} : { operationId: stringValue(item.operationId, 'receipt.operationId') })
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function positiveInteger(value: unknown, label: string): number {
|
|
283
|
+
if (!Number.isSafeInteger(value) || (value as number) <= 0) throw new Error(`${label} must be a positive integer`);
|
|
284
|
+
return value as number;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function validateSecurityAlert(value: unknown): SecurityAlertSnapshot {
|
|
288
|
+
const item = record(value, 'security alert');
|
|
289
|
+
exactKeys(item, ['kind', 'number', 'state', 'data'], 'security alert');
|
|
290
|
+
if (item.kind !== 'dependabot' && item.kind !== 'code-scanning') throw new Error('security alert.kind is invalid');
|
|
291
|
+
return {
|
|
292
|
+
kind: item.kind as SecurityAlertKind,
|
|
293
|
+
number: positiveInteger(item.number, 'security alert.number'),
|
|
294
|
+
state: stringValue(item.state, 'security alert.state'),
|
|
295
|
+
data: jsonValue(item.data, 'security alert.data')
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function validateLabelDefinition(value: unknown, label: string): LabelDefinition {
|
|
300
|
+
const item = record(value, label);
|
|
301
|
+
exactKeys(item, ['name', 'color', 'description'], label);
|
|
302
|
+
return {
|
|
303
|
+
name: stringValue(item.name, `${label}.name`),
|
|
304
|
+
color: stringValue(item.color, `${label}.color`),
|
|
305
|
+
description: stringValue(item.description, `${label}.description`, true)
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function validateLabelReconciliation(value: unknown): LabelReconciliation {
|
|
310
|
+
const item = record(value, 'label reconciliation');
|
|
311
|
+
exactKeys(item, ['changed', 'created', 'updated', 'removed', 'skipped'], 'label reconciliation');
|
|
312
|
+
const names = (field: string) => arrayValue(item[field], `label reconciliation.${field}`).map((entry) => stringValue(entry, `${field}[]`));
|
|
313
|
+
return {
|
|
314
|
+
changed: booleanValue(item.changed, 'label reconciliation.changed'),
|
|
315
|
+
created: names('created'),
|
|
316
|
+
updated: names('updated'),
|
|
317
|
+
removed: names('removed'),
|
|
318
|
+
skipped: names('skipped')
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function validateMilestoneDefinition(value: unknown, label: string): MilestoneDefinition {
|
|
323
|
+
const item = record(value, label);
|
|
324
|
+
exactKeys(item, ['title', 'description', 'state'], label);
|
|
325
|
+
if (item.state !== 'open' && item.state !== 'closed') throw new Error(`${label}.state is invalid`);
|
|
326
|
+
return {
|
|
327
|
+
title: stringValue(item.title, `${label}.title`),
|
|
328
|
+
description: stringValue(item.description, `${label}.description`, true),
|
|
329
|
+
state: item.state
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function validateMilestoneInitialization(value: unknown): MilestoneInitialization {
|
|
334
|
+
const item = record(value, 'milestone initialization');
|
|
335
|
+
exactKeys(item, ['changed', 'created', 'skipped'], 'milestone initialization');
|
|
336
|
+
return {
|
|
337
|
+
changed: booleanValue(item.changed, 'milestone initialization.changed'),
|
|
338
|
+
created: arrayValue(item.created, 'milestone initialization.created').map((entry) => stringValue(entry, 'created[]')),
|
|
339
|
+
skipped: arrayValue(item.skipped, 'milestone initialization.skipped').map((entry) => stringValue(entry, 'skipped[]'))
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function validateMetadata(value: unknown): RepositoryMetadataSnapshot {
|
|
344
|
+
const item = record(value, 'repository metadata');
|
|
345
|
+
exactKeys(item, ['repository', 'labels', 'milestones', 'issueTypes', 'fields'], 'repository metadata');
|
|
346
|
+
const repository = record(item.repository, 'metadata.repository');
|
|
347
|
+
exactKeys(repository, ['identity', 'name', 'url'], 'metadata.repository');
|
|
348
|
+
const mapFields = (raw: unknown, label: string) => arrayValue(raw, label).map((entry) => {
|
|
349
|
+
const field = record(entry, label);
|
|
350
|
+
exactKeys(field, ['identity', 'name', 'kind', 'options'], label);
|
|
351
|
+
return {
|
|
352
|
+
identity: identity(field.identity, `${label}.identity`),
|
|
353
|
+
name: stringValue(field.name, `${label}.name`),
|
|
354
|
+
kind: fieldKind(field.kind, `${label}.kind`),
|
|
355
|
+
options: stableUnique(arrayValue(field.options, `${label}.options`).map((rawOption) => {
|
|
356
|
+
const option = record(rawOption, `${label}.option`);
|
|
357
|
+
exactKeys(option, ['identity', 'name'], `${label}.option`);
|
|
358
|
+
return { identity: identity(option.identity, `${label}.option.identity`), name: stringValue(option.name, `${label}.option.name`) };
|
|
359
|
+
}), (entry) => serializeResourceIdentity(entry.identity), `${label}.options`)
|
|
360
|
+
};
|
|
361
|
+
});
|
|
362
|
+
const fields = stableUnique(mapFields(item.fields, 'metadata.fields'), (entry) => serializeResourceIdentity(entry.identity), 'metadata.fields');
|
|
363
|
+
const issueTypes = stableUnique(arrayValue(item.issueTypes, 'metadata.issueTypes').map((raw, index) => {
|
|
364
|
+
const issueType = record(raw, `metadata.issueTypes[${index}]`);
|
|
365
|
+
exactKeys(issueType, ['identity', 'name', 'fields'], `metadata.issueTypes[${index}]`);
|
|
366
|
+
return { identity: identity(issueType.identity, `metadata.issueTypes[${index}].identity`), name: stringValue(issueType.name, `metadata.issueTypes[${index}].name`), fields: stableUnique(mapFields(issueType.fields, `metadata.issueTypes[${index}].fields`), (entry) => serializeResourceIdentity(entry.identity), `metadata.issueTypes[${index}].fields`) };
|
|
367
|
+
}), (entry) => serializeResourceIdentity(entry.identity), 'metadata.issueTypes');
|
|
368
|
+
return {
|
|
369
|
+
repository: { identity: identity(repository.identity, 'metadata.repository.identity'), name: stringValue(repository.name, 'metadata.repository.name'), url: repository.url === null ? null : stringValue(repository.url, 'metadata.repository.url') },
|
|
370
|
+
labels: stableUnique(arrayValue(item.labels, 'metadata.labels').map((raw) => {
|
|
371
|
+
const label = record(raw, 'metadata.label');
|
|
372
|
+
exactKeys(label, ['identity', 'name'], 'metadata.label');
|
|
373
|
+
return { identity: identity(label.identity, 'metadata.label.identity'), name: stringValue(label.name, 'metadata.label.name') };
|
|
374
|
+
}), (entry) => serializeResourceIdentity(entry.identity), 'metadata.labels'),
|
|
375
|
+
milestones: stableUnique(arrayValue(item.milestones, 'metadata.milestones').map((raw) => {
|
|
376
|
+
const milestone = record(raw, 'metadata.milestone');
|
|
377
|
+
exactKeys(milestone, ['identity', 'title', 'state'], 'metadata.milestone');
|
|
378
|
+
if (milestone.state !== 'open' && milestone.state !== 'closed') throw new Error('metadata.milestone.state is invalid');
|
|
379
|
+
return { identity: identity(milestone.identity, 'metadata.milestone.identity'), title: stringValue(milestone.title, 'metadata.milestone.title'), state: milestone.state };
|
|
380
|
+
}), (entry) => serializeResourceIdentity(entry.identity), 'metadata.milestones'),
|
|
381
|
+
issueTypes,
|
|
382
|
+
fields
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function validateReleaseNotes(value: unknown, declaration?: ProviderIdentityDeclaration): ReleaseNotesFacts {
|
|
387
|
+
const item = record(value, 'release notes');
|
|
388
|
+
exactKeys(item, ['history', 'mergedPullRequests', 'closingIssues', 'actors'], 'release notes');
|
|
389
|
+
return {
|
|
390
|
+
history: stableUnique(arrayValue(item.history, 'release notes.history').map((raw) => {
|
|
391
|
+
const entry = record(raw, 'release notes.history[]');
|
|
392
|
+
exactKeys(entry, ['sha', 'message', 'authoredAt', 'author'], 'release notes.history[]');
|
|
393
|
+
return { sha: stringValue(entry.sha, 'history.sha'), message: stringValue(entry.message, 'history.message', true), authoredAt: utcTimestamp(entry.authoredAt, 'history.authoredAt'), author: author(entry.author, 'history.author') };
|
|
394
|
+
}), (entry) => entry.sha, 'release notes.history'),
|
|
395
|
+
mergedPullRequests: stableUnique(arrayValue(item.mergedPullRequests, 'release notes.mergedPullRequests').map((entry) => validateChangeRequest(entry, declaredIdentityKind(declaration, 'pull-request'), true)), (entry) => serializeResourceIdentity(entry.identity!), 'release notes.mergedPullRequests'),
|
|
396
|
+
closingIssues: stableUnique(arrayValue(item.closingIssues, 'release notes.closingIssues').map((entry) => validateIssue(entry, declaredIdentityKind(declaration, 'issue'), true)), (entry) => serializeResourceIdentity(entry.identity!), 'release notes.closingIssues'),
|
|
397
|
+
actors: stableUnique(arrayValue(item.actors, 'release notes.actors').map((entry) => {
|
|
398
|
+
const actor = author(entry, 'release notes.actor');
|
|
399
|
+
if (!actor) throw new Error('release notes.actor must identify an actor');
|
|
400
|
+
return actor;
|
|
401
|
+
}), (entry) => entry.id || entry.name || '', 'release notes.actors')
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function utcTimestamp(value: unknown, label: string): string {
|
|
406
|
+
const result = timestamp(value, label);
|
|
407
|
+
if (!result.endsWith('Z')) throw new Error(`${label} must be UTC`);
|
|
408
|
+
return new Date(result).toISOString();
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const PROVIDER_ERROR_CATALOG: Readonly<Record<string, { message: string; retryable: boolean }>> = {
|
|
412
|
+
AUTH_REQUIRED: { message: 'Platform authentication is required', retryable: false },
|
|
413
|
+
PERMISSION_DENIED: { message: 'Platform permission was denied', retryable: false },
|
|
414
|
+
PLATFORM_DEPENDENCY_MISSING: { message: 'A platform dependency is unavailable', retryable: false },
|
|
415
|
+
PLATFORM_OUTPUT_TOO_LARGE: { message: 'Platform output exceeded the configured limit', retryable: false },
|
|
416
|
+
PLATFORM_CAPABILITY_UNSUPPORTED: { message: 'Platform capability is unsupported', retryable: false },
|
|
417
|
+
PLATFORM_UNSUPPORTED: { message: 'The configured platform is unsupported', retryable: false },
|
|
418
|
+
REMOTE_MISSING: { message: 'The platform remote is not configured', retryable: false },
|
|
419
|
+
REMOTE_INVALID: { message: 'The platform remote is invalid', retryable: false },
|
|
420
|
+
UPSTREAM_UNRESOLVED: { message: 'The platform upstream repository could not be resolved', retryable: false },
|
|
421
|
+
RESOURCE_NOT_FOUND: { message: 'The platform resource was not found', retryable: false },
|
|
422
|
+
PR_NOT_FOUND: { message: 'The pull request was not found', retryable: false },
|
|
423
|
+
ISSUE_NOT_FOUND: { message: 'The issue was not found', retryable: false },
|
|
424
|
+
REMOTE_BUSY: { message: 'Platform provider is temporarily busy', retryable: true },
|
|
425
|
+
NETWORK_TRANSIENT: { message: 'The platform request failed temporarily', retryable: true },
|
|
426
|
+
NETWORK_RETRY: { message: 'The platform request failed temporarily', retryable: true },
|
|
427
|
+
NETWORK_RETRY_EXHAUSTED: { message: 'The platform request retries were exhausted', retryable: true },
|
|
428
|
+
NETWORK_ERROR: { message: 'The platform request failed temporarily', retryable: true },
|
|
429
|
+
INVALID_PLATFORM_RESPONSE: { message: 'The platform returned an invalid response', retryable: true },
|
|
430
|
+
PLATFORM_REQUEST_INVALID: { message: 'The platform rejected the request', retryable: false },
|
|
431
|
+
PLATFORM_REQUEST_FAILED: { message: 'The platform request failed', retryable: false },
|
|
432
|
+
GH_CLI_VERSION_INVALID: { message: 'The platform CLI returned an invalid version', retryable: false },
|
|
433
|
+
GH_CLI_VERSION_UNSUPPORTED: { message: 'The installed GitHub CLI version is unsupported', retryable: false },
|
|
434
|
+
PR_HEAD_INVALID: { message: 'The pull request head is invalid', retryable: false },
|
|
435
|
+
PR_REMOTE_BRANCH_MISSING: { message: 'The pull request remote branch is missing', retryable: false },
|
|
436
|
+
PR_REMOTE_BRANCH_UNAVAILABLE: { message: 'The pull request remote branch could not be inspected', retryable: true },
|
|
437
|
+
PR_LOCAL_HEAD_UNAVAILABLE: { message: 'The local pull request head could not be inspected', retryable: false },
|
|
438
|
+
PR_REMOTE_HEAD_MISMATCH: { message: 'The pull request remote head does not match the local head', retryable: false },
|
|
439
|
+
PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE: { message: 'Pull request merge evidence is unavailable', retryable: false },
|
|
440
|
+
PR_CREATE_RESPONSE_INVALID: { message: 'Pull request creation returned an invalid response', retryable: false },
|
|
441
|
+
PR_NUMBER_INVALID: { message: 'The pull request number is invalid', retryable: false },
|
|
442
|
+
ISSUE_NUMBER_INVALID: { message: 'The issue number is invalid', retryable: false },
|
|
443
|
+
ISSUE_CREATE_RESPONSE_INVALID: { message: 'Issue creation returned an invalid response', retryable: false },
|
|
444
|
+
COMMENT_ID_INVALID: { message: 'The comment identity is invalid', retryable: false },
|
|
445
|
+
SECURITY_KIND_INVALID: { message: 'The security alert kind is invalid', retryable: false },
|
|
446
|
+
SECURITY_NUMBER_INVALID: { message: 'The security alert number is invalid', retryable: false },
|
|
447
|
+
SECURITY_RESPONSE_INVALID: { message: 'The platform returned an invalid security alert', retryable: false },
|
|
448
|
+
LABEL_RESPONSE_INVALID: { message: 'The platform returned an invalid label response', retryable: false },
|
|
449
|
+
MILESTONE_RESPONSE_INVALID: { message: 'The platform returned an invalid milestone response', retryable: false },
|
|
450
|
+
MILESTONE_IDENTITY_INVALID: { message: 'The milestone identity is invalid', retryable: false },
|
|
451
|
+
CHECK_RUN_NOT_FOUND: { message: 'The check run was not found', retryable: false },
|
|
452
|
+
RELEASE_NOTES_AUTHORS_TRUNCATED: { message: 'Release note commit authors exceeded the supported limit', retryable: false },
|
|
453
|
+
RELEASE_NOTES_COLLECTION_FAILED: { message: 'Release notes could not be collected', retryable: false },
|
|
454
|
+
RELEASE_NOTES_PUBLISH_FAILED: { message: 'Release notes could not be published', retryable: false },
|
|
455
|
+
PAGINATION_INVALID: { message: 'Platform pagination is invalid', retryable: false },
|
|
456
|
+
PAGINATION_UNSTABLE: { message: 'Platform pagination is unstable', retryable: true },
|
|
457
|
+
PLATFORM_PROVIDER_OPERATION_FAILED: { message: 'Platform provider operation failed', retryable: false }
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
function safeProviderError(providerType: string, operation: string, value: unknown): PlatformError {
|
|
461
|
+
const item = value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
|
462
|
+
const definition = typeof item.code === 'string' ? PROVIDER_ERROR_CATALOG[item.code] : undefined;
|
|
463
|
+
return {
|
|
464
|
+
code: definition ? item.code as string : 'PLATFORM_PROVIDER_OPERATION_FAILED',
|
|
465
|
+
message: definition?.message || PROVIDER_ERROR_CATALOG.PLATFORM_PROVIDER_OPERATION_FAILED!.message,
|
|
466
|
+
retryable: definition?.retryable || false,
|
|
467
|
+
providerType,
|
|
468
|
+
phase: operation
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function validators(providerType: string, declaration?: ProviderIdentityDeclaration): Record<string, ResultValidator<unknown>> {
|
|
473
|
+
return {
|
|
474
|
+
'context.resolve': (value) => validateContext(value, providerType),
|
|
475
|
+
'issues.listLabels': (value) => arrayValue(value, 'issue labels').map((entry) => stringValue(entry, 'issue labels[]')),
|
|
476
|
+
'issues.listMilestones': (value) => arrayValue(value, 'issue milestones').map((entry) => stringValue(entry, 'issue milestones[]')),
|
|
477
|
+
'issues.inspect': (value) => validateIssue(value, declaredIdentityKind(declaration, 'issue'), true),
|
|
478
|
+
'issues.describeRepository': validateMetadata,
|
|
479
|
+
'issues.create': validateReceipt,
|
|
480
|
+
'issues.update': validateReceipt,
|
|
481
|
+
'comments.list': (value) => arrayValue(value, 'comments').map(validateComment),
|
|
482
|
+
'comments.write': validateReceipt,
|
|
483
|
+
'comments.delete': validateReceipt,
|
|
484
|
+
'changeRequests.verifyHead': (value) => {
|
|
485
|
+
const item = record(value, 'change request head');
|
|
486
|
+
exactKeys(item, ['sha'], 'change request head');
|
|
487
|
+
return { sha: stringValue(item.sha, 'change request head.sha') };
|
|
488
|
+
},
|
|
489
|
+
'changeRequests.inspect': (value) => validateChangeRequest(value, declaredIdentityKind(declaration, 'pull-request'), true),
|
|
490
|
+
'changeRequests.listFiles': (value) => arrayValue(value, 'pull request files').map((entry) => stringValue(entry, 'pull request files[]')),
|
|
491
|
+
'changeRequests.listClosingIssues': (value) => arrayValue(value, 'closing issues').map((entry) => identity(entry, 'closing issues[]', declaredIdentityKind(declaration, 'issue'))),
|
|
492
|
+
'changeRequests.listClosing': (value) => arrayValue(value, 'changeRequests').map((entry) => validateChangeRequest(entry, declaredIdentityKind(declaration, 'pull-request'), true)),
|
|
493
|
+
'changeRequests.create': validateReceipt,
|
|
494
|
+
'changeRequests.update': validateReceipt,
|
|
495
|
+
'changeRequests.resolveGitEvidence': (value) => {
|
|
496
|
+
const item = record(value, 'git evidence');
|
|
497
|
+
exactKeys(item, ['remoteUrl', 'reviewedHeadRef', 'targetHeadRef'], 'git evidence');
|
|
498
|
+
return { remoteUrl: stringValue(item.remoteUrl, 'git evidence.remoteUrl'), reviewedHeadRef: stringValue(item.reviewedHeadRef, 'git evidence.reviewedHeadRef'), targetHeadRef: stringValue(item.targetHeadRef, 'git evidence.targetHeadRef') } satisfies GitEvidenceSnapshot;
|
|
499
|
+
},
|
|
500
|
+
'checks.inspectRequired': (value) => arrayValue(value, 'checks').map((entry) => {
|
|
501
|
+
const item = record(entry, 'check');
|
|
502
|
+
exactKeys(item, ['name', 'status', 'conclusion', 'detailsUrl', 'runId', 'jobId'], 'check');
|
|
503
|
+
return { name: stringValue(item.name, 'check.name'), status: stringValue(item.status, 'check.status'), ...(item.conclusion === undefined ? {} : { conclusion: item.conclusion === null ? null : stringValue(item.conclusion, 'check.conclusion') }), ...(item.detailsUrl === undefined ? {} : { detailsUrl: item.detailsUrl === null ? null : stringValue(item.detailsUrl, 'check.detailsUrl') }), ...(item.runId === undefined ? {} : { runId: stringValue(item.runId, 'check.runId') }), ...(item.jobId === undefined ? {} : { jobId: stringValue(item.jobId, 'check.jobId') }) } satisfies RequiredCheckSnapshot;
|
|
504
|
+
}),
|
|
505
|
+
'checks.resolveRun': (value) => {
|
|
506
|
+
const item = record(value, 'check run');
|
|
507
|
+
exactKeys(item, ['name', 'status', 'runId', 'conclusion', 'detailsUrl', 'jobId'], 'check run');
|
|
508
|
+
return { name: stringValue(item.name, 'check run.name', true), status: stringValue(item.status, 'check run.status'), runId: stringValue(item.runId, 'check run.runId'), ...(item.conclusion === undefined ? {} : { conclusion: item.conclusion === null ? null : stringValue(item.conclusion, 'check run.conclusion') }), ...(item.detailsUrl === undefined ? {} : { detailsUrl: item.detailsUrl === null ? null : stringValue(item.detailsUrl, 'check run.detailsUrl') }), ...(item.jobId === undefined ? {} : { jobId: stringValue(item.jobId, 'check run.jobId') }) } satisfies CheckRunSnapshot;
|
|
509
|
+
},
|
|
510
|
+
'checks.fetchLogs': (value) => {
|
|
511
|
+
const item = record(value, 'check log');
|
|
512
|
+
exactKeys(item, ['runId', 'jobId', 'text'], 'check log');
|
|
513
|
+
return { runId: stringValue(item.runId, 'check log.runId'), ...(item.jobId === undefined ? {} : { jobId: stringValue(item.jobId, 'check log.jobId') }), text: stringValue(item.text, 'check log.text', true) } satisfies CheckLogSnapshot;
|
|
514
|
+
},
|
|
515
|
+
'reviews.list': (value) => arrayValue(value, 'reviews').map((entry) => {
|
|
516
|
+
const item = record(entry, 'review');
|
|
517
|
+
exactKeys(item, ['id', 'author', 'body', 'state', 'submittedAt', 'commitSha', 'displayUrl'], 'review');
|
|
518
|
+
return { id: stringValue(item.id, 'review.id'), author: author(item.author, 'review.author'), body: stringValue(item.body, 'review.body', true), state: stringValue(item.state, 'review.state'), submittedAt: utcTimestamp(item.submittedAt, 'review.submittedAt'), ...(item.commitSha === undefined ? {} : { commitSha: stringValue(item.commitSha, 'review.commitSha') }), ...(item.displayUrl === undefined ? {} : { displayUrl: stringValue(item.displayUrl, 'review.displayUrl') }) } satisfies ReviewSnapshot;
|
|
519
|
+
}),
|
|
520
|
+
'reviews.publish': validateReceipt,
|
|
521
|
+
'releases.inspect': (value) => {
|
|
522
|
+
const item = record(value, 'release');
|
|
523
|
+
exactKeys(item, ['id', 'tag', 'title', 'body', 'draft', 'prerelease', 'publishedAt', 'milestone', 'displayUrl', 'workflows'], 'release');
|
|
524
|
+
return { id: stringValue(item.id, 'release.id'), tag: stringValue(item.tag, 'release.tag'), title: stringValue(item.title, 'release.title', true), body: stringValue(item.body, 'release.body', true), draft: booleanValue(item.draft, 'release.draft'), prerelease: booleanValue(item.prerelease, 'release.prerelease'), publishedAt: item.publishedAt === null ? null : utcTimestamp(item.publishedAt, 'release.publishedAt'), ...(item.milestone === undefined ? {} : { milestone: nullableString(item.milestone, 'release.milestone') }), ...(item.displayUrl === undefined ? {} : { displayUrl: stringValue(item.displayUrl, 'release.displayUrl') }), ...(item.workflows === undefined ? {} : { workflows: arrayValue(item.workflows, 'release.workflows').map((entry) => jsonObject(entry, 'release.workflow')) }) } satisfies ReleaseSnapshot;
|
|
525
|
+
},
|
|
526
|
+
'releases.create': validateReceipt,
|
|
527
|
+
'releases.update': validateReceipt,
|
|
528
|
+
'releases.reconcileMilestones': (value) => {
|
|
529
|
+
const item = record(value, 'milestone reconciliation');
|
|
530
|
+
exactKeys(item, ['changed', 'created', 'closed'], 'milestone reconciliation');
|
|
531
|
+
return { changed: booleanValue(item.changed, 'milestone reconciliation.changed'), created: arrayValue(item.created, 'created').map((entry) => stringValue(entry, 'created[]')), closed: arrayValue(item.closed, 'closed').map((entry) => stringValue(entry, 'closed[]')) } satisfies MilestoneReconciliation;
|
|
532
|
+
},
|
|
533
|
+
'releases.publishNotes': validateReceipt,
|
|
534
|
+
'releases.collectNotes': (value) => validateReleaseNotes(value, declaration),
|
|
535
|
+
'securityAlerts.inspect': validateSecurityAlert,
|
|
536
|
+
'securityAlerts.dismiss': validateReceipt,
|
|
537
|
+
'repositoryMetadata.reconcileLabels': validateLabelReconciliation,
|
|
538
|
+
'repositoryMetadata.reconcileMilestones': validateMilestoneInitialization,
|
|
539
|
+
'verification.fetchRemoteFacts': (value) => {
|
|
540
|
+
const item = record(value, 'verification facts');
|
|
541
|
+
exactKeys(item, ['issue', 'comments', 'changeRequest', 'commit', 'fields'], 'verification facts');
|
|
542
|
+
const commit = item.commit === undefined ? undefined : item.commit === null ? null : (() => {
|
|
543
|
+
const value = record(item.commit, 'verification.commit');
|
|
544
|
+
exactKeys(value, ['sha', 'message'], 'verification.commit');
|
|
545
|
+
return { sha: stringValue(value.sha, 'verification.commit.sha'), ...(value.message === undefined ? {} : { message: stringValue(value.message, 'verification.commit.message', true) }) };
|
|
546
|
+
})();
|
|
547
|
+
return { issue: item.issue === undefined ? undefined : item.issue === null ? null : validateIssue(item.issue, declaredIdentityKind(declaration, 'issue'), true), comments: arrayValue(item.comments, 'verification.comments').map(validateComment), changeRequest: item.changeRequest === undefined ? undefined : item.changeRequest === null ? null : validateChangeRequest(item.changeRequest, declaredIdentityKind(declaration, 'pull-request'), true), commit, fields: scalarFields(item.fields, 'verification.fields') } satisfies VerificationRemoteFacts;
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
async function invokeProviderOperation<T>(
|
|
553
|
+
providerType: string,
|
|
554
|
+
operation: string,
|
|
555
|
+
call: () => Promise<unknown>,
|
|
556
|
+
validate: ResultValidator<T>
|
|
557
|
+
): Promise<ProviderResult<T>> {
|
|
558
|
+
try {
|
|
559
|
+
const raw = await call();
|
|
560
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return { ok: false, error: validationError(providerType, operation, 'Provider operation returned an invalid result envelope') };
|
|
561
|
+
const envelope = raw as Record<string, unknown>;
|
|
562
|
+
if (envelope.ok === true) {
|
|
563
|
+
try {
|
|
564
|
+
exactKeys(envelope, ['ok', 'value'], `${operation} result`);
|
|
565
|
+
return { ok: true, value: validate(envelope.value) };
|
|
566
|
+
} catch {
|
|
567
|
+
return { ok: false, error: validationError(providerType, operation, 'Provider operation returned an invalid value') };
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (envelope.ok === false && envelope.error && typeof envelope.error === 'object' && !Array.isArray(envelope.error)) {
|
|
571
|
+
try {
|
|
572
|
+
exactKeys(envelope, Object.hasOwn(envelope, 'value') ? ['ok', 'error', 'value'] : ['ok', 'error'], `${operation} result`);
|
|
573
|
+
const error = envelope.error as Record<string, unknown>;
|
|
574
|
+
exactKeys(error, ['code', 'message', 'retryable'], `${operation} error`);
|
|
575
|
+
if (typeof error.code !== 'string' || !error.code || typeof error.message !== 'string' || !error.message || typeof error.retryable !== 'boolean') throw new Error('invalid provider error');
|
|
576
|
+
return {
|
|
577
|
+
ok: false,
|
|
578
|
+
error: safeProviderError(providerType, operation, error),
|
|
579
|
+
...(Object.hasOwn(envelope, 'value') ? { value: validate(envelope.value) } : {})
|
|
580
|
+
};
|
|
581
|
+
} catch {
|
|
582
|
+
return { ok: false, error: validationError(providerType, operation, 'Provider operation returned an invalid error') };
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return { ok: false, error: validationError(providerType, operation, 'Provider operation returned an invalid result envelope') };
|
|
586
|
+
} catch {
|
|
587
|
+
return { ok: false, error: validationError(providerType, operation, 'Platform provider operation failed', 'PLATFORM_PROVIDER_OPERATION_FAILED') };
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function wrapProviderOperations(provider: PlatformProvider): PlatformProvider {
|
|
592
|
+
const map = validators(provider.type, provider.identity);
|
|
593
|
+
const wrapped: PlatformProvider = { ...provider, context: { ...provider.context } };
|
|
594
|
+
wrapped.context.resolve = (input) => invokeProviderOperation(provider.type, 'context.resolve', () => provider.context.resolve(input), map['context.resolve']! as ResultValidator<PlatformContextSnapshot>);
|
|
595
|
+
for (const groupName of ['issues', 'comments', 'changeRequests', 'checks', 'reviews', 'releases', 'securityAlerts', 'repositoryMetadata', 'verification'] as const) {
|
|
596
|
+
const group = provider[groupName];
|
|
597
|
+
if (!group) continue;
|
|
598
|
+
const target: Record<string, unknown> = { ...group };
|
|
599
|
+
for (const method of Object.keys(group)) {
|
|
600
|
+
const operation = `${groupName}.${method}`;
|
|
601
|
+
const validator = map[operation];
|
|
602
|
+
if (!validator) throw new Error(`Missing validator for ${operation}`);
|
|
603
|
+
const original = group[method as keyof typeof group] as (...args: never[]) => Promise<unknown>;
|
|
604
|
+
target[method] = (input: unknown) => invokeProviderOperation(provider.type, operation, () => original(input as never), validator);
|
|
605
|
+
}
|
|
606
|
+
(wrapped as unknown as Record<string, unknown>)[groupName] = target;
|
|
607
|
+
}
|
|
608
|
+
return wrapped;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
export { invokeProviderOperation, validationError, wrapProviderOperations };
|