@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
package/lib/platform/issues.ts
CHANGED
|
@@ -8,13 +8,11 @@ import { requirementFieldLabels, renderTemplateBody } from '../task/issue-form.t
|
|
|
8
8
|
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
9
9
|
import { extractSection, findSectionHeading } from '../task/sections.ts';
|
|
10
10
|
import { writeTask } from '../task/write.ts';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import type { GitHubClient } from './github-client.ts';
|
|
11
|
+
import { resolvePlatformProviderContext } from './context.ts';
|
|
12
|
+
import type { PlatformClient } from './context.ts';
|
|
14
13
|
import {
|
|
15
14
|
DEFAULT_REQUIREMENT_SECTION_ANCHORS,
|
|
16
15
|
chooseMilestone,
|
|
17
|
-
computeInLabels,
|
|
18
16
|
desiredIssueType,
|
|
19
17
|
normalizeOption,
|
|
20
18
|
planIssueMetadata
|
|
@@ -22,10 +20,32 @@ import {
|
|
|
22
20
|
import type { IssueDesiredState, PlannedOperation, Requirement, RequirementSectionAnchor } from './issue-metadata.ts';
|
|
23
21
|
import { platformResult } from './types.ts';
|
|
24
22
|
import type { PlatformResult } from './types.ts';
|
|
23
|
+
import {
|
|
24
|
+
labelDelta,
|
|
25
|
+
planInLabelUpdate,
|
|
26
|
+
syncLabelDelta,
|
|
27
|
+
validateInLabelMapping,
|
|
28
|
+
validateRepositoryLabelPayload
|
|
29
|
+
} from './in-label-sync.ts';
|
|
30
|
+
import {
|
|
31
|
+
providerError,
|
|
32
|
+
providerOperationContext,
|
|
33
|
+
providerStatus,
|
|
34
|
+
providerResourceIdentity,
|
|
35
|
+
providerResourceToken,
|
|
36
|
+
unsupportedProviderOperation
|
|
37
|
+
} from './provider-bridge.ts';
|
|
38
|
+
import { resourceIdentityEquals, resourceIdentityNumber, resourceIdentityString, serializeResourceIdentity } from './resource-identity.ts';
|
|
39
|
+
import { taskIssueIdentity, taskIssueIdentityError } from './task-identities.ts';
|
|
40
|
+
import type { ResourceIdentity } from './resource-identity.ts';
|
|
41
|
+
|
|
42
|
+
type GitHubClient = PlatformClient;
|
|
43
|
+
import type { IssueSnapshot as ProviderIssueSnapshot, RepositoryMetadataSnapshot } from './provider-contract.ts';
|
|
25
44
|
|
|
26
45
|
type IssueSnapshot = {
|
|
27
46
|
repository: string;
|
|
28
47
|
number: number;
|
|
48
|
+
identity?: ResourceIdentity;
|
|
29
49
|
databaseId: number | null;
|
|
30
50
|
nodeId: string;
|
|
31
51
|
url: string;
|
|
@@ -42,9 +62,9 @@ type IssueResult = PlatformResult & {
|
|
|
42
62
|
task: { id: string | null; issueNumber: number | null };
|
|
43
63
|
issue: IssueSnapshot | null;
|
|
44
64
|
};
|
|
45
|
-
type SharedOptions = { cwd?: string; client?:
|
|
65
|
+
type SharedOptions = { cwd?: string; client?: PlatformClient; runtimeVersion?: string };
|
|
46
66
|
type CreateOptions = SharedOptions & { agent: string; dryRun?: boolean };
|
|
47
|
-
type BindOptions = SharedOptions & { issue: number; agent: string; dryRun?: boolean };
|
|
67
|
+
type BindOptions = SharedOptions & { issue: string | number; agent: string; dryRun?: boolean };
|
|
48
68
|
type SyncOptions = SharedOptions & {
|
|
49
69
|
agent: string;
|
|
50
70
|
status?: string | 'none';
|
|
@@ -71,6 +91,7 @@ type RemoteIssue = {
|
|
|
71
91
|
labels?: Array<string | { name?: string }>;
|
|
72
92
|
assignees?: Array<{ login?: string }>;
|
|
73
93
|
milestone?: { title?: string } | null;
|
|
94
|
+
type?: { name?: string } | null;
|
|
74
95
|
pull_request?: unknown;
|
|
75
96
|
};
|
|
76
97
|
|
|
@@ -100,39 +121,138 @@ function result(
|
|
|
100
121
|
};
|
|
101
122
|
}
|
|
102
123
|
|
|
103
|
-
function resolvedContext(taskRef: string, options: SharedOptions) {
|
|
124
|
+
async function resolvedContext(taskRef: string, options: SharedOptions) {
|
|
104
125
|
const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
|
|
105
126
|
if (!resolved.ok) return { ok: false as const, output: result('failed', resolved.taskId, null, {
|
|
106
127
|
error: { code: resolved.code, message: resolved.message, retryable: false }
|
|
107
128
|
}) };
|
|
108
129
|
const content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
109
130
|
const frontmatter = parseTaskFrontmatter(content);
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
131
|
+
let issueIdentity: ResourceIdentity | null;
|
|
132
|
+
try { issueIdentity = taskIssueIdentity(frontmatter, undefined, options.runtimeVersion); }
|
|
133
|
+
catch (error) {
|
|
134
|
+
return { ok: false as const, output: result('failed', resolved.taskId, null, { error: { ...taskIssueIdentityError(error), retryable: false } }) };
|
|
135
|
+
}
|
|
136
|
+
const issueNumber = resourceIdentityNumber(issueIdentity);
|
|
137
|
+
const loaded = await resolvePlatformProviderContext({ cwd: resolved.repoRoot, client: options.client });
|
|
138
|
+
const context = loaded.ok ? loaded.value.context : loaded.context;
|
|
114
139
|
const usable = (context.status === 'no-op' || context.status === 'degraded') && context.platform.repository;
|
|
115
|
-
if (!usable) return { ok: false as const, output: result(context.status, resolved.taskId, issueNumber, {
|
|
140
|
+
if (!usable || !loaded.ok) return { ok: false as const, output: result(context.status, resolved.taskId, issueNumber, {
|
|
116
141
|
platform: context.platform, capabilities: context.capabilities, operations: context.operations, error: context.error
|
|
117
142
|
}) };
|
|
118
|
-
return { ok: true as const, resolved, content, frontmatter, issueNumber, client, context };
|
|
143
|
+
return { ok: true as const, resolved, content, frontmatter, issueIdentity, issueNumber, client: options.client, context, provider: loaded.value.provider, providerType: loaded.value.providerType, loadedContext: loaded.value };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function normalizeProviderIssue(remote: ProviderIssueSnapshot, repository: string, fallbackNumber: number | null): IssueSnapshot {
|
|
147
|
+
const identity = remote.identity || (remote.number ? { kind: 'number' as const, value: remote.number } : { kind: 'id' as const, value: remote.id });
|
|
148
|
+
const snapshot: IssueSnapshot = {
|
|
149
|
+
repository,
|
|
150
|
+
number: remote.number ?? resourceIdentityNumber(identity) ?? fallbackNumber ?? 0,
|
|
151
|
+
identity,
|
|
152
|
+
databaseId: null,
|
|
153
|
+
nodeId: remote.id,
|
|
154
|
+
url: remote.displayUrl || '',
|
|
155
|
+
state: remote.state,
|
|
156
|
+
title: remote.title,
|
|
157
|
+
body: remote.body,
|
|
158
|
+
labels: [...remote.labels].sort(),
|
|
159
|
+
assignees: [...remote.assignees].sort(),
|
|
160
|
+
milestone: remote.milestone,
|
|
161
|
+
issueType: remote.issueType?.name || null,
|
|
162
|
+
fields: { ...remote.fields }
|
|
163
|
+
};
|
|
164
|
+
Object.defineProperty(snapshot, 'identity', { value: identity, enumerable: false, configurable: true });
|
|
165
|
+
if (remote.issueType) Object.defineProperty(snapshot, 'issueTypeSnapshot', { value: remote.issueType, enumerable: false, configurable: true });
|
|
166
|
+
return snapshot;
|
|
119
167
|
}
|
|
120
168
|
|
|
121
|
-
function
|
|
122
|
-
|
|
169
|
+
function planProviderMetadata(
|
|
170
|
+
remote: ProviderIssueSnapshot,
|
|
171
|
+
metadata: RepositoryMetadataSnapshot,
|
|
172
|
+
frontmatter: Record<string, string>,
|
|
173
|
+
options: Pick<SyncOptions, 'issueType' | 'fields'>,
|
|
174
|
+
capabilities: PlatformResult['capabilities']
|
|
175
|
+
): PlannedOperation[] {
|
|
176
|
+
if (!options.issueType && !options.fields) return [];
|
|
177
|
+
if (!capabilities.push) return [
|
|
178
|
+
...(options.issueType ? [{ name: 'issue-type', status: 'skipped' as const, reasonCode: 'PUSH_REQUIRED' }] : []),
|
|
179
|
+
...(options.fields ? [{ name: 'fields', status: 'skipped' as const, reasonCode: 'PUSH_REQUIRED' }] : [])
|
|
180
|
+
];
|
|
181
|
+
const target = metadata.issueTypes.find((candidate) => candidate.name === desiredIssueType(frontmatter.type || ''));
|
|
182
|
+
if (!target) return [
|
|
183
|
+
...(options.issueType ? [{ name: 'issue-type', status: 'skipped' as const, reasonCode: 'ISSUE_TYPES_UNSUPPORTED' }] : []),
|
|
184
|
+
...(options.fields ? [{ name: 'fields', status: 'skipped' as const, reasonCode: 'ISSUE_TYPES_UNSUPPORTED' }] : [])
|
|
185
|
+
];
|
|
186
|
+
const operations: PlannedOperation[] = [];
|
|
187
|
+
if (options.issueType) {
|
|
188
|
+
operations.push(remote.issueType && resourceIdentityEquals(remote.issueType.identity, target.identity)
|
|
189
|
+
? { name: 'issue-type', status: 'no-op', reasonCode: null }
|
|
190
|
+
: { name: 'issue-type', status: 'planned', reasonCode: null, value: target.name });
|
|
191
|
+
}
|
|
192
|
+
if (options.fields) {
|
|
193
|
+
const fields = target.fields.map((field) => ({
|
|
194
|
+
id: resourceIdentityString(field.identity) || '',
|
|
195
|
+
name: field.name,
|
|
196
|
+
kind: field.kind,
|
|
197
|
+
options: field.options.map((option) => ({ id: resourceIdentityString(option.identity) || '', name: option.name }))
|
|
198
|
+
}));
|
|
199
|
+
const desired = desiredFieldValues(frontmatter, fields);
|
|
200
|
+
const values = Object.fromEntries(desired.map((field) => [field.name, field.value]));
|
|
201
|
+
const changed = Object.entries(values).some(([name, value]) => remote.fields[name] !== value);
|
|
202
|
+
operations.push(changed
|
|
203
|
+
? { name: 'fields', status: 'planned', reasonCode: null, value: values }
|
|
204
|
+
: { name: 'fields', status: 'no-op', reasonCode: null });
|
|
205
|
+
}
|
|
206
|
+
return operations;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function providerIssueError(base: Awaited<ReturnType<typeof resolvedContext>> & { ok: true }, error: { code: string; message: string; retryable: boolean }, issueIdentity: ResourceIdentity | null): IssueResult {
|
|
210
|
+
const issueNumber = resourceIdentityNumber(issueIdentity);
|
|
211
|
+
return result(error.code.startsWith('IN_LABEL_SYNC') ? 'blocked' : providerStatus(error), base.resolved.taskId, issueNumber, {
|
|
212
|
+
changed: error.code === 'IN_LABEL_SYNC_PARTIAL',
|
|
213
|
+
platform: base.context.platform,
|
|
214
|
+
capabilities: base.context.capabilities,
|
|
215
|
+
resource: { kind: 'issue', number: issueNumber, ...(issueIdentity ? { identity: issueIdentity } : {}) },
|
|
216
|
+
error: providerError(error, 'PLATFORM_PROVIDER_OPERATION_FAILED')
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function inspectExternalIssue(
|
|
221
|
+
base: Awaited<ReturnType<typeof resolvedContext>> & { ok: true },
|
|
222
|
+
issueIdentity: ResourceIdentity
|
|
223
|
+
): Promise<IssueResult> {
|
|
224
|
+
const operation = base.provider.issues?.inspect
|
|
225
|
+
? await base.provider.issues.inspect({ context: providerOperationContext(base.loadedContext), target: issueIdentity })
|
|
226
|
+
: unsupportedProviderOperation(base.provider, 'issues.inspect');
|
|
227
|
+
if (!operation.ok) return providerIssueError(base, operation.error, issueIdentity);
|
|
228
|
+
const issue = normalizeProviderIssue(operation.value, base.context.platform.repository!, resourceIdentityNumber(issueIdentity));
|
|
229
|
+
return result('no-op', base.resolved.taskId, resourceIdentityNumber(issueIdentity), {
|
|
230
|
+
platform: base.context.platform,
|
|
231
|
+
capabilities: base.context.capabilities,
|
|
232
|
+
resource: { kind: 'issue', number: resourceIdentityNumber(issueIdentity), identity: issueIdentity },
|
|
233
|
+
issue,
|
|
234
|
+
error: null
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function normalizeIssue(remote: RemoteIssue, repository: string, fallbackNumber?: number): IssueSnapshot | null {
|
|
239
|
+
const number = Number.isInteger(remote.number) && Number(remote.number) > 0
|
|
240
|
+
? Number(remote.number)
|
|
241
|
+
: fallbackNumber;
|
|
242
|
+
if (!number || !Number.isSafeInteger(number) || remote.pull_request) return null;
|
|
123
243
|
return {
|
|
124
244
|
repository,
|
|
125
|
-
number
|
|
245
|
+
number,
|
|
126
246
|
databaseId: Number.isInteger(remote.id) ? Number(remote.id) : null,
|
|
127
|
-
nodeId: remote.node_id
|
|
128
|
-
url: remote.html_url || `https://github.com/${repository}/issues/${
|
|
129
|
-
state: remote.state === 'closed' ? 'closed' : 'open',
|
|
247
|
+
nodeId: remote.node_id || `issue-${number}`,
|
|
248
|
+
url: remote.html_url || `https://github.com/${repository}/issues/${number}`,
|
|
249
|
+
state: String(remote.state || '').toLowerCase() === 'closed' ? 'closed' : 'open',
|
|
130
250
|
title: remote.title || '',
|
|
131
251
|
body: remote.body || '',
|
|
132
252
|
labels: (remote.labels || []).map((label) => typeof label === 'string' ? label : label.name || '').filter(Boolean).sort(),
|
|
133
253
|
assignees: (remote.assignees || []).map((assignee) => assignee.login || '').filter(Boolean).sort(),
|
|
134
254
|
milestone: remote.milestone?.title || null,
|
|
135
|
-
issueType: null,
|
|
255
|
+
issueType: remote.type?.name || null,
|
|
136
256
|
fields: {}
|
|
137
257
|
};
|
|
138
258
|
}
|
|
@@ -173,15 +293,17 @@ function normalizeCurrentFields(value: unknown): { issueId: string | null; type:
|
|
|
173
293
|
issueType?: { id?: string; name?: string; pinnedFields?: unknown[] } | null;
|
|
174
294
|
issueFieldValues?: { nodes?: unknown[] };
|
|
175
295
|
} } } })?.data?.repository?.issue;
|
|
176
|
-
const type = issue?.issueType?.
|
|
177
|
-
id: issue.issueType.id
|
|
296
|
+
const type = issue?.issueType?.name ? {
|
|
297
|
+
id: issue.issueType.id || issue.issueType.name,
|
|
298
|
+
name: issue.issueType.name,
|
|
299
|
+
fields: normalizeFieldSchemas(issue.issueType.pinnedFields)
|
|
178
300
|
} : null;
|
|
179
301
|
const values = (issue?.issueFieldValues?.nodes || []).flatMap((raw) => {
|
|
180
302
|
const item = raw as { __typename?: string; name?: string; value?: string | number; field?: { id?: string; name?: string } };
|
|
181
303
|
const kind = fieldKind(item);
|
|
182
304
|
const value = kind === 'single-select' ? item.name : item.value;
|
|
183
|
-
return item.field?.
|
|
184
|
-
? [{ id: item.field.id, name: item.field.name, kind, value: value ?? null }]
|
|
305
|
+
return item.field?.name && kind && value !== undefined
|
|
306
|
+
? [{ id: item.field.id || item.field.name, name: item.field.name, kind, value: value ?? null }]
|
|
185
307
|
: [];
|
|
186
308
|
});
|
|
187
309
|
return { issueId: issue?.id || null, type, values };
|
|
@@ -204,7 +326,7 @@ function fetchIssue(client: GitHubClient, repository: string, issue: number, cwd
|
|
|
204
326
|
function inspectGitHubIssue(client: GitHubClient, repository: string, issue: number, cwd: string) {
|
|
205
327
|
const fetched = fetchIssue(client, repository, issue, cwd);
|
|
206
328
|
if (!fetched.ok) return fetched;
|
|
207
|
-
const snapshot = normalizeIssue(fetched.value, repository);
|
|
329
|
+
const snapshot = normalizeIssue(fetched.value, repository, issue);
|
|
208
330
|
return snapshot
|
|
209
331
|
? { ok: true as const, value: snapshot }
|
|
210
332
|
: { ok: false as const, error: { code: 'ISSUE_IDENTITY_INVALID', message: 'Remote resource is not a valid Issue', retryable: false } };
|
|
@@ -218,37 +340,21 @@ function inspectGitHubIssueMetadata(client: GitHubClient, repository: string, is
|
|
|
218
340
|
ok: true as const,
|
|
219
341
|
value: {
|
|
220
342
|
state: String(remote.state || '').toLowerCase() === 'closed' ? 'closed' as const : 'open' as const,
|
|
221
|
-
labels: (remote.labels || []).map((label) => typeof label === 'string' ? label : label.name || '').filter(Boolean).sort(),
|
|
343
|
+
labels: (remote.labels || []).map((label: string | { name?: string }) => typeof label === 'string' ? label : label.name || '').filter(Boolean).sort(),
|
|
222
344
|
body: remote.body || '',
|
|
223
345
|
milestone: remote.milestone?.title || null
|
|
224
346
|
}
|
|
225
347
|
};
|
|
226
348
|
}
|
|
227
349
|
|
|
228
|
-
function inspectPlatformIssue(taskRef: string, options: SharedOptions = {}): IssueResult {
|
|
229
|
-
const base = resolvedContext(taskRef, options);
|
|
350
|
+
async function inspectPlatformIssue(taskRef: string, options: SharedOptions = {}): Promise<IssueResult> {
|
|
351
|
+
const base = await resolvedContext(taskRef, options);
|
|
230
352
|
if (!base.ok) return base.output;
|
|
231
|
-
if (!base.
|
|
232
|
-
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
233
|
-
error: { code: 'ISSUE_NOT_LINKED', message: 'Task has no valid issue_number', retryable: false }
|
|
234
|
-
});
|
|
235
|
-
const fetched = inspectGitHubIssue(base.client, base.context.platform.repository!, base.issueNumber, base.resolved.repoRoot);
|
|
236
|
-
if (!fetched.ok) return result(fetched.error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, {
|
|
237
|
-
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
238
|
-
resource: { kind: 'issue', number: base.issueNumber }, error: fetched.error
|
|
239
|
-
});
|
|
240
|
-
const issue = fetched.value;
|
|
241
|
-
if (base.context.capabilities.push) {
|
|
242
|
-
const graph = graphState(base.client, base.context.platform.repository!, base.issueNumber, base.resolved.repoRoot);
|
|
243
|
-
if (graph) {
|
|
244
|
-
issue.issueType = graph.type?.name || null;
|
|
245
|
-
issue.fields = Object.fromEntries(graph.values.map((field) => [field.name, field.value]));
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
return result('no-op', base.resolved.taskId, base.issueNumber, {
|
|
353
|
+
if (!base.issueIdentity) return result('no-op', base.resolved.taskId, null, {
|
|
249
354
|
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
250
|
-
|
|
355
|
+
error: { code: 'ISSUE_NOT_LINKED', message: 'Task has no valid platform issue identity', retryable: false }
|
|
251
356
|
});
|
|
357
|
+
return inspectExternalIssue(base, base.issueIdentity);
|
|
252
358
|
}
|
|
253
359
|
|
|
254
360
|
function taskTitle(content: string): string {
|
|
@@ -321,101 +427,112 @@ function deterministicIssueBody(repoRoot: string, content: string, taskType: str
|
|
|
321
427
|
return buildDefaultBody(content);
|
|
322
428
|
}
|
|
323
429
|
|
|
324
|
-
function createPlatformIssue(taskRef: string, options: CreateOptions): IssueResult {
|
|
325
|
-
const base = resolvedContext(taskRef, options);
|
|
430
|
+
async function createPlatformIssue(taskRef: string, options: CreateOptions): Promise<IssueResult> {
|
|
431
|
+
const base = await resolvedContext(taskRef, options);
|
|
326
432
|
if (!base.ok) return base.output;
|
|
327
|
-
if (base.
|
|
433
|
+
if (base.issueIdentity) return inspectPlatformIssue(taskRef, options);
|
|
328
434
|
const repository = base.context.platform.repository!;
|
|
329
435
|
let repositoryLabels: string[] = [];
|
|
330
|
-
let milestones:
|
|
436
|
+
let milestones: string[] = [];
|
|
331
437
|
if (base.context.capabilities.triage) {
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
if (
|
|
336
|
-
|
|
337
|
-
|
|
438
|
+
const metadata = base.provider.issues?.describeRepository
|
|
439
|
+
? await base.provider.issues.describeRepository({ context: providerOperationContext(base.loadedContext) })
|
|
440
|
+
: unsupportedProviderOperation(base.provider, 'issues.describeRepository');
|
|
441
|
+
if (!metadata.ok) return providerIssueError(base, metadata.error, null);
|
|
442
|
+
repositoryLabels = metadata.value.labels.map((label) => label.name);
|
|
443
|
+
milestones = metadata.value.milestones.map((milestone) => milestone.title);
|
|
338
444
|
}
|
|
339
445
|
const typeLabel = ({
|
|
340
446
|
bug: 'type: bug', bugfix: 'type: bug', feature: 'type: feature', enhancement: 'type: enhancement',
|
|
341
447
|
refactor: 'type: enhancement', refactoring: 'type: enhancement', documentation: 'type: documentation',
|
|
342
448
|
docs: 'type: documentation', 'dependency-upgrade': 'type: dependency-upgrade', task: 'type: task', chore: 'type: task'
|
|
343
449
|
} as Record<string, string>)[base.frontmatter.type || ''];
|
|
344
|
-
const explicitMilestone = base.frontmatter.milestone && milestones.some((item) => item
|
|
450
|
+
const explicitMilestone = base.frontmatter.milestone && milestones.some((item) => item === base.frontmatter.milestone)
|
|
345
451
|
? base.frontmatter.milestone
|
|
346
|
-
: chooseMilestone('initial', milestones
|
|
452
|
+
: chooseMilestone('initial', milestones);
|
|
347
453
|
const payload = {
|
|
348
454
|
title: conventionalTitle(base.content, base.frontmatter.type || 'task', base.resolved.repoRoot),
|
|
349
455
|
body: deterministicIssueBody(base.resolved.repoRoot, base.content, base.frontmatter.type || 'task'),
|
|
350
456
|
assignees: base.context.platform.currentUser ? [base.context.platform.currentUser] : [],
|
|
351
457
|
...(typeLabel && repositoryLabels.includes(typeLabel) ? { labels: [typeLabel] } : {}),
|
|
352
|
-
...(explicitMilestone ? { milestone:
|
|
458
|
+
...(explicitMilestone ? { milestone: explicitMilestone } : {})
|
|
353
459
|
};
|
|
354
460
|
if (options.dryRun) return result('planned', base.resolved.taskId, null, {
|
|
355
461
|
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
356
462
|
operations: [{ name: 'issue:create', status: 'planned', reasonCode: null }], error: null
|
|
357
463
|
});
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
464
|
+
{
|
|
465
|
+
const created = base.provider.issues?.create
|
|
466
|
+
? await base.provider.issues.create({
|
|
467
|
+
context: providerOperationContext(base.loadedContext),
|
|
468
|
+
desired: {
|
|
469
|
+
title: payload.title,
|
|
470
|
+
body: payload.body,
|
|
471
|
+
labels: payload.labels || [],
|
|
472
|
+
assignees: payload.assignees,
|
|
473
|
+
milestone: typeof payload.milestone === 'string' ? payload.milestone : null,
|
|
474
|
+
fields: {}
|
|
475
|
+
},
|
|
476
|
+
mutation: { idempotencyKey: `issue:create:${base.resolved.taskId}` }
|
|
477
|
+
})
|
|
478
|
+
: unsupportedProviderOperation(base.provider, 'issues.create');
|
|
479
|
+
if (!created.ok) return providerIssueError(base, created.error, null);
|
|
480
|
+
let createdIdentity: ResourceIdentity;
|
|
481
|
+
try {
|
|
482
|
+
createdIdentity = providerResourceIdentity(base.provider, 'issue', created.value.remoteId);
|
|
483
|
+
} catch (error) {
|
|
484
|
+
return result('failed', base.resolved.taskId, null, {
|
|
485
|
+
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
486
|
+
error: { code: 'ISSUE_CREATE_RESPONSE_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false }
|
|
487
|
+
});
|
|
366
488
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
489
|
+
const inspected = base.provider.issues?.inspect
|
|
490
|
+
? await base.provider.issues.inspect({
|
|
491
|
+
context: providerOperationContext(base.loadedContext),
|
|
492
|
+
target: createdIdentity
|
|
493
|
+
})
|
|
494
|
+
: unsupportedProviderOperation(base.provider, 'issues.inspect');
|
|
495
|
+
if (!inspected.ok) return providerIssueError(base, inspected.error, createdIdentity);
|
|
496
|
+
const issueNumber = resourceIdentityNumber(inspected.value.identity || createdIdentity);
|
|
497
|
+
const written = writeTask({
|
|
498
|
+
taskRef: base.resolved.taskId, expectedState: 'active', mutations: [{ kind: 'frontmatter', set: { platform_issue_identity: serializeResourceIdentity(inspected.value.identity || createdIdentity) } }]
|
|
499
|
+
}, { repoRoot: base.resolved.repoRoot });
|
|
500
|
+
if (written.status === 'failed') return result('failed', base.resolved.taskId, null, {
|
|
371
501
|
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
372
|
-
|
|
502
|
+
resource: { kind: 'issue', number: issueNumber, identity: inspected.value.identity || createdIdentity },
|
|
503
|
+
error: { code: 'ISSUE_CREATED_BIND_FAILED', message: written.error.message, retryable: false }
|
|
504
|
+
});
|
|
505
|
+
return result('applied', base.resolved.taskId, issueNumber, {
|
|
506
|
+
changed: true,
|
|
507
|
+
platform: base.context.platform,
|
|
508
|
+
capabilities: base.context.capabilities,
|
|
509
|
+
resource: { kind: 'issue', number: issueNumber, identity: inspected.value.identity || createdIdentity },
|
|
510
|
+
operations: [{ name: 'issue:create', status: 'applied', reasonCode: null }, { name: 'task:bind', status: 'applied', reasonCode: null }],
|
|
511
|
+
issue: normalizeProviderIssue(inspected.value, repository, issueNumber),
|
|
512
|
+
error: null
|
|
373
513
|
});
|
|
374
514
|
}
|
|
375
|
-
const written = writeTask({
|
|
376
|
-
taskRef: base.resolved.taskId, expectedState: 'active', mutations: [{ kind: 'frontmatter', set: { issue_number: issueNumber } }]
|
|
377
|
-
}, { repoRoot: base.resolved.repoRoot });
|
|
378
|
-
if (written.status === 'failed') return result('failed', base.resolved.taskId, null, {
|
|
379
|
-
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
380
|
-
resource: { kind: 'issue', number: issueNumber },
|
|
381
|
-
operations: [{ name: 'issue:create', status: 'applied', reasonCode: null }, { name: 'task:bind', status: 'failed', reasonCode: written.error.code }],
|
|
382
|
-
error: { code: 'ISSUE_CREATED_BIND_FAILED', message: `${created.value.html_url}: ${written.error.message}`, retryable: false }
|
|
383
|
-
});
|
|
384
|
-
return result('applied', base.resolved.taskId, issueNumber, {
|
|
385
|
-
changed: true, platform: base.context.platform, capabilities: base.context.capabilities,
|
|
386
|
-
resource: { kind: 'issue', number: issueNumber },
|
|
387
|
-
operations: [{ name: 'issue:create', status: 'applied', reasonCode: null }, { name: 'task:bind', status: 'applied', reasonCode: null }],
|
|
388
|
-
issue: normalizeIssue({
|
|
389
|
-
...created.value,
|
|
390
|
-
state: 'open',
|
|
391
|
-
title: payload.title,
|
|
392
|
-
body: payload.body,
|
|
393
|
-
labels: [],
|
|
394
|
-
assignees: payload.assignees.map((login) => ({ login })),
|
|
395
|
-
milestone: null
|
|
396
|
-
}, repository),
|
|
397
|
-
error: null
|
|
398
|
-
});
|
|
399
515
|
}
|
|
400
516
|
|
|
401
|
-
function bindPlatformIssue(taskRef: string, options: BindOptions): IssueResult {
|
|
402
|
-
const base = resolvedContext(taskRef, options);
|
|
517
|
+
async function bindPlatformIssue(taskRef: string, options: BindOptions): Promise<IssueResult> {
|
|
518
|
+
const base = await resolvedContext(taskRef, options);
|
|
403
519
|
if (!base.ok) return base.output;
|
|
404
|
-
|
|
405
|
-
|
|
520
|
+
let identity: ResourceIdentity;
|
|
521
|
+
try { identity = providerResourceToken(base.provider, 'issue', String(options.issue)); }
|
|
522
|
+
catch (error) { return result('failed', base.resolved.taskId, base.issueNumber, { error: { code: 'PLATFORM_IDENTITY_TOKEN_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false } }); }
|
|
523
|
+
if (base.issueIdentity && !resourceIdentityEquals(base.issueIdentity, identity)) return result('failed', base.resolved.taskId, base.issueNumber, {
|
|
524
|
+
error: { code: 'ISSUE_BIND_CONFLICT', message: 'Task is already bound to a different Issue identity', retryable: false }
|
|
406
525
|
});
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
if (base.issueNumber === options.issue) return result('no-op', base.resolved.taskId, options.issue, { issue, error: null });
|
|
415
|
-
const written = writeTask({ taskRef: base.resolved.taskId, expectedState: 'active', dryRun: options.dryRun, mutations: [{ kind: 'frontmatter', set: { issue_number: options.issue } }] }, { repoRoot: base.resolved.repoRoot });
|
|
526
|
+
const fetched = base.provider.issues?.inspect
|
|
527
|
+
? await base.provider.issues.inspect({ context: providerOperationContext(base.loadedContext), target: identity })
|
|
528
|
+
: unsupportedProviderOperation(base.provider, 'issues.inspect');
|
|
529
|
+
if (!fetched.ok) return providerIssueError(base, fetched.error, identity);
|
|
530
|
+
const issue = normalizeProviderIssue(fetched.value, base.context.platform.repository!, resourceIdentityNumber(identity));
|
|
531
|
+
if (base.issueIdentity && resourceIdentityEquals(base.issueIdentity, identity)) return result('no-op', base.resolved.taskId, resourceIdentityNumber(identity), { issue, error: null });
|
|
532
|
+
const written = writeTask({ taskRef: base.resolved.taskId, expectedState: 'active', dryRun: options.dryRun, mutations: [{ kind: 'frontmatter', set: { platform_issue_identity: serializeResourceIdentity(identity) } }] }, { repoRoot: base.resolved.repoRoot });
|
|
416
533
|
if (written.status === 'failed') return result('failed', base.resolved.taskId, null, { issue, error: { code: written.error.code, message: written.error.message, retryable: false } });
|
|
417
|
-
return result(options.dryRun ? 'planned' : 'applied', base.resolved.taskId, options.dryRun ? null :
|
|
418
|
-
changed: !options.dryRun, issue, operations: [{ name: 'task:bind', status: options.dryRun ? 'planned' : 'applied', reasonCode: null }], error: null
|
|
534
|
+
return result(options.dryRun ? 'planned' : 'applied', base.resolved.taskId, options.dryRun ? null : resourceIdentityNumber(identity), {
|
|
535
|
+
changed: !options.dryRun, issue, resource: { kind: 'issue', number: resourceIdentityNumber(identity), identity }, operations: [{ name: 'task:bind', status: options.dryRun ? 'planned' : 'applied', reasonCode: null }], error: null
|
|
419
536
|
});
|
|
420
537
|
}
|
|
421
538
|
|
|
@@ -466,29 +583,34 @@ function applyRestOperations(snapshot: IssueSnapshot, operations: PlannedOperati
|
|
|
466
583
|
let labelsChanged = false;
|
|
467
584
|
for (const operation of operations) {
|
|
468
585
|
if (operation.status !== 'planned') continue;
|
|
586
|
+
if (operation.name === 'assignees') payload.assignees = operation.value;
|
|
587
|
+
if (operation.name === 'requirements') payload.body = operation.value;
|
|
588
|
+
if (operation.name === 'state') payload.state = operation.value;
|
|
589
|
+
if (operation.name === 'milestone') payload.milestone = operation.value;
|
|
469
590
|
if (operation.name === 'labels:status') {
|
|
470
|
-
labels = [
|
|
471
|
-
...labels.filter((label) => !label.startsWith('status:')),
|
|
472
|
-
...(operation.value as string[]).filter((label) => label.startsWith('status:'))
|
|
473
|
-
];
|
|
591
|
+
labels = applyLabelDeltaToSnapshot(labels, operation.value as string[], 'status:');
|
|
474
592
|
labelsChanged = true;
|
|
475
593
|
}
|
|
476
594
|
if (operation.name === 'labels:in') {
|
|
477
|
-
labels = [
|
|
478
|
-
...labels.filter((label) => !label.startsWith('in:')),
|
|
479
|
-
...(operation.value as string[]).filter((label) => label.startsWith('in:'))
|
|
480
|
-
];
|
|
595
|
+
labels = applyLabelDeltaToSnapshot(labels, operation.value as string[], 'in:');
|
|
481
596
|
labelsChanged = true;
|
|
482
597
|
}
|
|
483
|
-
if (operation.name === '
|
|
484
|
-
if (operation.name === '
|
|
485
|
-
if (operation.name === 'state') payload.state = operation.value;
|
|
486
|
-
if (operation.name === 'milestone') payload.milestone = operation.value;
|
|
598
|
+
if (operation.name === 'issue-type') payload.issueType = operation.value;
|
|
599
|
+
if (operation.name === 'fields') payload.fields = operation.value;
|
|
487
600
|
}
|
|
488
|
-
if (labelsChanged) payload.labels =
|
|
601
|
+
if (labelsChanged) payload.labels = labels;
|
|
489
602
|
return payload;
|
|
490
603
|
}
|
|
491
604
|
|
|
605
|
+
function applyLabelDeltaToSnapshot(current: string[], target: readonly string[], prefix: string): string[] {
|
|
606
|
+
const delta = labelDelta(current, target, prefix);
|
|
607
|
+
const targetLabels = [...new Set(target.filter((label) => label.startsWith(prefix)))];
|
|
608
|
+
return [...new Set([
|
|
609
|
+
...current.filter((label) => !delta.remove.includes(label)),
|
|
610
|
+
...targetLabels
|
|
611
|
+
])].sort();
|
|
612
|
+
}
|
|
613
|
+
|
|
492
614
|
type DesiredFieldValue = {
|
|
493
615
|
fieldId: string;
|
|
494
616
|
name: string;
|
|
@@ -587,26 +709,27 @@ function executeGraphOperation(
|
|
|
587
709
|
});
|
|
588
710
|
}
|
|
589
711
|
|
|
590
|
-
function syncPlatformIssue(taskRef: string, options: SyncOptions): IssueResult {
|
|
591
|
-
const base = resolvedContext(taskRef, options);
|
|
712
|
+
async function syncPlatformIssue(taskRef: string, options: SyncOptions): Promise<IssueResult> {
|
|
713
|
+
const base = await resolvedContext(taskRef, options);
|
|
592
714
|
if (!base.ok) return base.output;
|
|
593
|
-
if (!base.
|
|
715
|
+
if (!base.issueIdentity) return result('failed', base.resolved.taskId, null, { error: { code: 'ISSUE_NOT_LINKED', message: 'Task has no valid platform issue identity', retryable: false } });
|
|
594
716
|
const repository = base.context.platform.repository!;
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
if (!
|
|
717
|
+
const inspected = base.provider.issues?.inspect
|
|
718
|
+
? await base.provider.issues.inspect({ context: providerOperationContext(base.loadedContext), target: base.issueIdentity })
|
|
719
|
+
: unsupportedProviderOperation(base.provider, 'issues.inspect');
|
|
720
|
+
if (!inspected.ok) return providerIssueError(base, inspected.error, base.issueIdentity);
|
|
721
|
+
const snapshot = normalizeProviderIssue(inspected.value, repository, resourceIdentityNumber(base.issueIdentity));
|
|
722
|
+
let providerMetadata: RepositoryMetadataSnapshot | null = null;
|
|
599
723
|
let repositoryLabels: string[] = [];
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
milestones = flattenPages(listed.value).map((item) => ({ title: String((item as { title?: string }).title || ''), number: Number((item as { number?: number }).number) })).filter((item) => item.title && Number.isInteger(item.number));
|
|
724
|
+
let milestones: string[] = [];
|
|
725
|
+
if (base.context.capabilities.triage && (options.status !== undefined || options.inLabels !== undefined || options.milestone !== undefined || options.issueType || options.fields)) {
|
|
726
|
+
const metadata = base.provider.issues?.describeRepository
|
|
727
|
+
? await base.provider.issues.describeRepository({ context: providerOperationContext(base.loadedContext) })
|
|
728
|
+
: unsupportedProviderOperation(base.provider, 'issues.describeRepository');
|
|
729
|
+
if (!metadata.ok) return providerIssueError(base, metadata.error, base.issueIdentity);
|
|
730
|
+
providerMetadata = metadata.value;
|
|
731
|
+
repositoryLabels = metadata.value.labels.map((label) => label.name);
|
|
732
|
+
milestones = metadata.value.milestones.map((milestone) => milestone.title);
|
|
610
733
|
}
|
|
611
734
|
const desired: IssueDesiredState = {
|
|
612
735
|
status: options.status,
|
|
@@ -619,7 +742,7 @@ function syncPlatformIssue(taskRef: string, options: SyncOptions): IssueResult {
|
|
|
619
742
|
snapshot: options.milestone === 'specific'
|
|
620
743
|
? { ...snapshot, milestone: milestoneBasis(base.resolved.repoRoot, snapshot.milestone) }
|
|
621
744
|
: snapshot,
|
|
622
|
-
desired, repositoryLabels: new Set(repositoryLabels), milestones
|
|
745
|
+
desired, repositoryLabels: new Set(repositoryLabels), milestones,
|
|
623
746
|
currentUser: base.context.platform.currentUser,
|
|
624
747
|
requirementAnchors: requirementSectionAnchors(base.resolved.repoRoot, base.frontmatter.type || 'task'),
|
|
625
748
|
capabilities: base.context.capabilities
|
|
@@ -634,48 +757,94 @@ function syncPlatformIssue(taskRef: string, options: SyncOptions): IssueResult {
|
|
|
634
757
|
: { name: 'labels:in', status: 'skipped', reasonCode: 'TRIAGE_REQUIRED' });
|
|
635
758
|
}
|
|
636
759
|
if (options.inLabels === 'from-diff') {
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
760
|
+
const taskBase = typeof base.frontmatter.delivery_base_ref === 'string'
|
|
761
|
+
? base.frontmatter.delivery_base_ref.trim()
|
|
762
|
+
: '';
|
|
763
|
+
if (!taskBase) {
|
|
764
|
+
plan.operations.push({ name: 'labels:in', status: 'failed', reasonCode: 'IN_LABEL_SYNC_BASE_MISSING' });
|
|
765
|
+
} else if (options.base && options.base !== taskBase) {
|
|
766
|
+
plan.operations.push({ name: 'labels:in', status: 'failed', reasonCode: 'IN_LABEL_SYNC_BASE_MISMATCH' });
|
|
767
|
+
} else {
|
|
768
|
+
try {
|
|
769
|
+
const changed = execFileSync('git', ['diff', `${taskBase}...HEAD`, '--name-only'], {
|
|
770
|
+
cwd: base.resolved.repoRoot, encoding: 'utf8'
|
|
771
|
+
}).trim().split(/\r?\n/).filter(Boolean);
|
|
772
|
+
const config = JSON.parse(fs.readFileSync(`${base.resolved.repoRoot}/.agents/.airc.json`, 'utf8')) as {
|
|
773
|
+
labels?: { in?: Record<string, unknown> };
|
|
774
|
+
};
|
|
775
|
+
const mapping = validateInLabelMapping(config.labels?.in);
|
|
776
|
+
if (!mapping.ok) {
|
|
777
|
+
plan.operations.push({ name: 'labels:in', status: 'failed', reasonCode: mapping.error.code });
|
|
778
|
+
} else {
|
|
779
|
+
const planned = planInLabelUpdate({
|
|
780
|
+
changedFiles: changed,
|
|
781
|
+
currentLabels: snapshot.labels,
|
|
782
|
+
mapping: mapping.value,
|
|
783
|
+
repositoryLabels: new Set(repositoryLabels)
|
|
784
|
+
});
|
|
785
|
+
if (planned.error) {
|
|
786
|
+
plan.operations.push({ name: 'labels:in', status: 'failed', reasonCode: planned.error.code });
|
|
787
|
+
} else {
|
|
788
|
+
plan.operations.push(planned.changed
|
|
789
|
+
? { name: 'labels:in', status: 'planned', reasonCode: null, value: planned.labels }
|
|
790
|
+
: { name: 'labels:in', status: 'no-op', reasonCode: null });
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
} catch {
|
|
794
|
+
if (!plan.operations.some((operation) => operation.name === 'labels:in' && operation.status === 'failed')) {
|
|
795
|
+
plan.operations.push({ name: 'labels:in', status: 'failed', reasonCode: 'IN_LABEL_SYNC_EVIDENCE_UNAVAILABLE' });
|
|
796
|
+
}
|
|
797
|
+
}
|
|
647
798
|
}
|
|
648
799
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
800
|
+
if (options.issueType || options.fields) {
|
|
801
|
+
plan.operations.push(...(providerMetadata
|
|
802
|
+
? planProviderMetadata(inspected.value, providerMetadata, base.frontmatter, options, base.context.capabilities)
|
|
803
|
+
: [{ name: 'issue-type', status: 'skipped' as const, reasonCode: 'ISSUE_TYPES_UNSUPPORTED' }]));
|
|
804
|
+
}
|
|
654
805
|
const failure = plan.operations.find((operation) => operation.status === 'failed');
|
|
655
806
|
if (failure) return result('failed', base.resolved.taskId, base.issueNumber, { issue: snapshot, operations: plan.operations, error: { code: failure.reasonCode || 'ISSUE_SYNC_FAILED', message: `Operation ${failure.name} failed`, retryable: false } });
|
|
656
807
|
const planned = plan.operations.filter((operation) => operation.status === 'planned');
|
|
657
808
|
const skipped = plan.operations.some((operation) => operation.status === 'skipped');
|
|
658
809
|
if (options.dryRun) return result(planned.length ? 'planned' : skipped ? 'degraded' : 'no-op', base.resolved.taskId, base.issueNumber, { issue: snapshot, operations: plan.operations, error: null });
|
|
659
810
|
if (planned.length === 0) return result(skipped ? 'degraded' : 'no-op', base.resolved.taskId, base.issueNumber, { issue: snapshot, operations: plan.operations, error: null });
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
811
|
+
const payload = applyRestOperations(snapshot, planned) as Partial<{
|
|
812
|
+
title: string; body: string; labels: string[]; assignees: string[];
|
|
813
|
+
milestone: string | null; state: 'open' | 'closed'; fields: Record<string, string | number | null>;
|
|
814
|
+
}>;
|
|
815
|
+
const updated = base.provider.issues?.update
|
|
816
|
+
? await base.provider.issues.update({ context: providerOperationContext(base.loadedContext), target: base.issueIdentity, currentLabels: snapshot.labels, patch: payload, mutation: { idempotencyKey: `issue:update:${base.resolved.taskId}` } })
|
|
817
|
+
: unsupportedProviderOperation(base.provider, 'issues.update');
|
|
818
|
+
if (!updated.ok) return providerIssueError(base, updated.error, base.issueIdentity);
|
|
819
|
+
const labelOperations = planned.filter((operation) => operation.name === 'labels:status' || operation.name === 'labels:in');
|
|
820
|
+
let finalIssue = snapshot;
|
|
821
|
+
if (labelOperations.length > 0) {
|
|
822
|
+
const reread = base.provider.issues?.inspect
|
|
823
|
+
? await base.provider.issues.inspect({ context: providerOperationContext(base.loadedContext), target: base.issueIdentity })
|
|
824
|
+
: unsupportedProviderOperation(base.provider, 'issues.inspect');
|
|
825
|
+
if (!reread.ok) return result('blocked', base.resolved.taskId, base.issueNumber, {
|
|
826
|
+
changed: updated.value.changed,
|
|
827
|
+
issue: snapshot,
|
|
828
|
+
operations: plan.operations,
|
|
829
|
+
error: { ...reread.error, code: 'IN_LABEL_SYNC_PARTIAL', message: `In-label synchronization is partial or unknown: ${reread.error.message}` }
|
|
830
|
+
});
|
|
831
|
+
finalIssue = normalizeProviderIssue(reread.value, repository, resourceIdentityNumber(base.issueIdentity));
|
|
832
|
+
let expectedLabels = [...snapshot.labels];
|
|
833
|
+
for (const operation of labelOperations) {
|
|
834
|
+
expectedLabels = applyLabelDeltaToSnapshot(expectedLabels, operation.value as string[], operation.name === 'labels:status' ? 'status:' : 'in:');
|
|
835
|
+
}
|
|
836
|
+
const expected = expectedLabels.filter((label) => label.startsWith('status:') || label.startsWith('in:')).sort();
|
|
837
|
+
const actual = finalIssue.labels.filter((label) => label.startsWith('status:') || label.startsWith('in:')).sort();
|
|
838
|
+
if (expected.join('\0') !== actual.join('\0')) return result('blocked', base.resolved.taskId, base.issueNumber, {
|
|
839
|
+
changed: updated.value.changed,
|
|
840
|
+
issue: finalIssue,
|
|
841
|
+
operations: plan.operations,
|
|
842
|
+
error: { code: 'IN_LABEL_SYNC_PARTIAL', message: 'Issue labels did not converge after update', retryable: true }
|
|
668
843
|
});
|
|
669
|
-
if (!patched.ok) return result(patched.error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, { issue: snapshot, operations: plan.operations, error: patched.error });
|
|
670
|
-
}
|
|
671
|
-
for (const operation of graphPlanned) {
|
|
672
|
-
if (!graph.issueId) return result('failed', base.resolved.taskId, base.issueNumber, { issue: snapshot, operations: plan.operations, error: { code: 'ISSUE_GRAPH_IDENTITY_INVALID', message: 'Issue GraphQL identity is unavailable', retryable: false } });
|
|
673
|
-
const written = executeGraphOperation(base.client, base.resolved.repoRoot, graph.issueId, operation);
|
|
674
|
-
if (!written.ok) return result(written.error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, { issue: snapshot, operations: plan.operations, error: written.error });
|
|
675
844
|
}
|
|
676
845
|
return result(skipped ? 'degraded' : 'applied', base.resolved.taskId, base.issueNumber, {
|
|
677
|
-
changed:
|
|
678
|
-
resource: { kind: 'issue', number: base.issueNumber }, issue:
|
|
846
|
+
changed: updated.value.changed, platform: base.context.platform, capabilities: base.context.capabilities,
|
|
847
|
+
resource: { kind: 'issue', number: base.issueNumber, identity: base.issueIdentity }, issue: finalIssue,
|
|
679
848
|
operations: plan.operations.map((operation) => operation.status === 'planned' ? { ...operation, status: 'applied' } : operation), error: null
|
|
680
849
|
});
|
|
681
850
|
}
|
|
@@ -683,6 +852,7 @@ function syncPlatformIssue(taskRef: string, options: SyncOptions): IssueResult {
|
|
|
683
852
|
export {
|
|
684
853
|
bindPlatformIssue,
|
|
685
854
|
createPlatformIssue,
|
|
855
|
+
graphState,
|
|
686
856
|
inspectGitHubIssue,
|
|
687
857
|
inspectGitHubIssueMetadata,
|
|
688
858
|
inspectPlatformIssue,
|