@fitlab-ai/agent-infra 0.8.4 → 0.9.0
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/bin/cli.ts +2 -2
- package/dist/bin/cli.js +2 -2
- package/dist/bin/internal-cli.js +98 -0
- package/dist/lib/decide.js +81 -90
- package/dist/lib/defaults.json +9 -0
- package/dist/lib/git/review-snapshot.js +79 -0
- package/dist/lib/git/workflow.js +101 -0
- package/dist/lib/init.js +5 -0
- package/dist/lib/internal/git-workflow.js +42 -0
- package/dist/lib/internal/platform-checks.js +119 -0
- package/dist/lib/internal/platform-comment.js +129 -0
- package/dist/lib/internal/platform-context.js +46 -0
- package/dist/lib/internal/platform-issue.js +142 -0
- package/dist/lib/internal/platform-pr.js +146 -0
- package/dist/lib/internal/release-workflow.js +217 -0
- package/dist/lib/internal/task-artifact.js +67 -0
- package/dist/lib/internal/task-context.js +43 -0
- package/dist/lib/internal/task-event.js +70 -0
- package/dist/lib/internal/task-ledger.js +114 -0
- package/dist/lib/internal/task-lifecycle.js +58 -0
- package/dist/lib/internal/task-short-id.js +83 -0
- package/dist/lib/internal/task-snapshot.js +47 -0
- package/dist/lib/internal/task-verify.js +50 -0
- package/dist/lib/internal/task-warning.js +74 -0
- package/dist/lib/platform/adapters.js +14 -0
- package/dist/lib/platform/context.js +146 -0
- package/dist/lib/platform/github-client.js +124 -0
- package/dist/lib/platform/issue-comments.js +409 -0
- package/dist/lib/platform/issue-metadata.js +183 -0
- package/dist/lib/platform/issues.js +617 -0
- package/dist/lib/platform/metadata-labels.js +28 -0
- package/dist/lib/platform/pr-checks.js +216 -0
- package/dist/lib/platform/pr-summary.js +88 -0
- package/dist/lib/platform/pull-request-metadata.js +38 -0
- package/dist/lib/platform/pull-requests.js +338 -0
- package/dist/lib/platform/releases.js +82 -0
- package/dist/lib/platform/types.js +22 -0
- package/dist/lib/platform/verification-required.js +78 -0
- package/dist/lib/platform/verification-sync.js +1014 -0
- package/dist/lib/platform/verification.js +6 -0
- package/dist/lib/release/channels.js +31 -0
- package/dist/lib/release/workflow.js +32 -0
- package/dist/lib/render.js +3 -2
- package/dist/lib/run/index.js +24 -7
- package/dist/lib/sandbox/capture.js +25 -3
- package/dist/lib/sandbox/commands/create.js +66 -26
- package/dist/lib/sandbox/commands/enter.js +34 -11
- package/dist/lib/sandbox/commands/list-running.js +10 -8
- package/dist/lib/sandbox/commands/show.js +2 -2
- package/dist/lib/sandbox/commands/start.js +53 -15
- package/dist/lib/sandbox/engine.js +33 -3
- package/dist/lib/sandbox/engines/colima.js +2 -2
- package/dist/lib/sandbox/index.js +6 -6
- package/dist/lib/sandbox/mounts.js +36 -0
- package/dist/lib/sandbox/recovery.js +562 -0
- package/dist/lib/sandbox/runtimes/base.dockerfile +4 -0
- package/dist/lib/sandbox/task-resolver.js +5 -1
- package/dist/lib/sandbox/tools.js +19 -0
- package/dist/lib/server/daemon.js +9 -0
- package/dist/lib/server/process-control.js +66 -73
- package/dist/lib/server/process-state.js +168 -0
- package/dist/lib/server/protocol.js +5 -5
- package/dist/lib/task/activity-log.js +61 -0
- package/dist/lib/task/artifact-lifecycle.js +427 -0
- package/dist/lib/task/command-options.js +30 -0
- package/dist/lib/task/commands/cat.js +36 -6
- package/dist/lib/task/commands/decisions.js +80 -46
- package/dist/lib/task/commands/files.js +21 -7
- package/dist/lib/task/commands/grep.js +55 -7
- package/dist/lib/task/commands/issue-body.js +23 -14
- package/dist/lib/task/commands/log.js +27 -70
- package/dist/lib/task/commands/show.js +20 -7
- package/dist/lib/task/commands/status.js +18 -8
- package/dist/lib/task/decision-intents.js +154 -0
- package/dist/lib/task/decision-items.js +53 -0
- package/dist/lib/task/events.js +318 -0
- package/dist/lib/task/frontmatter.js +159 -1
- package/dist/lib/task/implementation-inputs.js +117 -0
- package/dist/lib/task/index.js +12 -9
- package/dist/lib/task/issue-form.js +19 -6
- package/dist/lib/task/ledger-intents.js +185 -0
- package/dist/lib/task/ledger.js +65 -35
- package/dist/lib/task/lifecycle.js +484 -0
- package/dist/lib/task/resolve-ref.js +152 -32
- package/dist/lib/task/review-artifacts.js +101 -0
- package/dist/lib/task/review-fingerprint.js +46 -0
- package/dist/lib/task/sections.js +358 -4
- package/dist/lib/task/short-id.js +292 -7
- package/dist/lib/task/snapshot.js +95 -0
- package/dist/lib/task/verification-checks.js +8 -0
- package/dist/lib/task/verification-config.js +14 -0
- package/dist/lib/task/verification-engine.js +906 -0
- package/dist/lib/task/verification-review.js +3 -0
- package/dist/lib/task/verification-types.js +2 -0
- package/dist/lib/task/verification.js +113 -0
- package/dist/lib/task/workflow-warning-intents.js +131 -0
- package/dist/lib/task/workflow-warnings.js +10 -78
- package/dist/lib/task/write.js +241 -0
- package/dist/lib/update.js +2 -0
- package/lib/decide.ts +57 -97
- package/lib/defaults.json +9 -0
- package/lib/git/review-snapshot.ts +73 -0
- package/lib/git/workflow.ts +101 -0
- package/lib/init.ts +9 -0
- package/lib/internal/git-workflow.ts +37 -0
- package/lib/internal/platform-checks.ts +101 -0
- package/lib/internal/platform-comment.ts +99 -0
- package/lib/internal/platform-context.ts +33 -0
- package/lib/internal/platform-issue.ts +116 -0
- package/lib/internal/platform-pr.ts +119 -0
- package/lib/internal/release-workflow.ts +186 -0
- package/lib/internal/task-artifact.ts +47 -0
- package/lib/internal/task-context.ts +35 -0
- package/lib/internal/task-event.ts +51 -0
- package/lib/internal/task-ledger.ts +86 -0
- package/lib/internal/task-lifecycle.ts +41 -0
- package/lib/internal/task-short-id.ts +55 -0
- package/lib/internal/task-snapshot.ts +32 -0
- package/lib/internal/task-verify.ts +33 -0
- package/lib/internal/task-warning.ts +50 -0
- package/lib/platform/adapters.ts +30 -0
- package/lib/platform/context.ts +164 -0
- package/lib/platform/github-client.ts +146 -0
- package/lib/platform/issue-comments.ts +479 -0
- package/lib/platform/issue-metadata.ts +250 -0
- package/lib/platform/issues.ts +691 -0
- package/lib/platform/metadata-labels.ts +33 -0
- package/lib/platform/pr-checks.ts +246 -0
- package/lib/platform/pr-summary.ts +90 -0
- package/lib/platform/pull-request-metadata.ts +69 -0
- package/lib/platform/pull-requests.ts +380 -0
- package/lib/platform/releases.ts +71 -0
- package/lib/platform/types.ts +74 -0
- package/lib/platform/verification-required.ts +83 -0
- package/{templates/.agents/scripts/platform-adapters/platform-sync.github.js → lib/platform/verification-sync.ts} +221 -363
- package/lib/platform/verification.ts +11 -0
- package/lib/release/channels.ts +30 -0
- package/lib/release/workflow.ts +31 -0
- package/lib/render.ts +3 -2
- package/lib/run/index.ts +25 -7
- package/lib/sandbox/capture.ts +29 -3
- package/lib/sandbox/commands/create.ts +75 -28
- package/lib/sandbox/commands/enter.ts +38 -11
- package/lib/sandbox/commands/list-running.ts +10 -8
- package/lib/sandbox/commands/show.ts +2 -2
- package/lib/sandbox/commands/start.ts +50 -16
- package/lib/sandbox/engine.ts +37 -3
- package/lib/sandbox/engines/colima.ts +2 -2
- package/lib/sandbox/index.ts +6 -6
- package/lib/sandbox/mounts.ts +55 -0
- package/lib/sandbox/recovery.ts +828 -0
- package/lib/sandbox/runtimes/base.dockerfile +4 -0
- package/lib/sandbox/task-resolver.ts +5 -1
- package/lib/sandbox/tools.ts +31 -0
- package/lib/server/daemon.ts +9 -0
- package/lib/server/process-control.ts +70 -71
- package/lib/server/process-state.ts +183 -0
- package/lib/server/protocol.ts +5 -5
- package/lib/task/activity-log.ts +62 -0
- package/lib/task/artifact-lifecycle.ts +484 -0
- package/lib/task/command-options.ts +32 -0
- package/lib/task/commands/cat.ts +22 -9
- package/lib/task/commands/decisions.ts +49 -47
- package/lib/task/commands/files.ts +14 -6
- package/lib/task/commands/grep.ts +34 -7
- package/lib/task/commands/issue-body.ts +16 -16
- package/lib/task/commands/log.ts +17 -70
- package/lib/task/commands/show.ts +14 -6
- package/lib/task/commands/status.ts +17 -7
- package/lib/task/decision-intents.ts +163 -0
- package/lib/task/decision-items.ts +64 -0
- package/lib/task/events.ts +320 -0
- package/lib/task/frontmatter.ts +202 -2
- package/lib/task/implementation-inputs.ts +137 -0
- package/lib/task/index.ts +12 -9
- package/lib/task/issue-form.ts +20 -6
- package/lib/task/ledger-intents.ts +201 -0
- package/lib/task/ledger.ts +77 -31
- package/lib/task/lifecycle.ts +458 -0
- package/lib/task/resolve-ref.ts +197 -36
- package/{templates/.agents/scripts/lib/review-artifacts.js → lib/task/review-artifacts.ts} +11 -12
- package/{templates/.agents/scripts/lib/post-review-commit.js → lib/task/review-fingerprint.ts} +20 -10
- package/lib/task/sections.ts +486 -4
- package/lib/task/short-id.ts +403 -9
- package/lib/task/snapshot.ts +143 -0
- package/lib/task/verification-checks.ts +9 -0
- package/lib/task/verification-config.ts +20 -0
- package/{templates/.agents/scripts/validate-artifact.js → lib/task/verification-engine.ts} +257 -451
- package/lib/task/verification-review.ts +9 -0
- package/lib/task/verification-types.ts +29 -0
- package/lib/task/verification.ts +156 -0
- package/lib/task/workflow-warning-intents.ts +142 -0
- package/lib/task/workflow-warnings.ts +10 -78
- package/lib/task/write.ts +468 -0
- package/lib/update.ts +6 -0
- package/package.json +5 -2
- package/templates/.agents/README.en.md +6 -3
- package/templates/.agents/README.zh-CN.md +6 -3
- package/templates/.agents/rules/README.en.md +6 -5
- package/templates/.agents/rules/README.zh-CN.md +6 -5
- package/templates/.agents/rules/create-issue.en.md +1 -1
- package/templates/.agents/rules/create-issue.github.en.md +5 -184
- package/templates/.agents/rules/create-issue.github.zh-CN.md +12 -178
- package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
- package/templates/.agents/rules/human-decision-context.en.md +36 -0
- package/templates/.agents/rules/human-decision-context.zh-CN.md +36 -0
- package/templates/.agents/rules/issue-fields.github.en.md +4 -148
- package/templates/.agents/rules/issue-fields.github.zh-CN.md +9 -144
- package/templates/.agents/rules/issue-pr-commands.en.md +43 -3
- package/templates/.agents/rules/issue-pr-commands.github.en.md +15 -220
- package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +23 -213
- package/templates/.agents/rules/issue-pr-commands.zh-CN.md +45 -3
- package/templates/.agents/rules/issue-sync.en.md +1 -1
- package/templates/.agents/rules/issue-sync.github.en.md +14 -301
- package/templates/.agents/rules/issue-sync.github.zh-CN.md +36 -287
- package/templates/.agents/rules/issue-sync.zh-CN.md +1 -1
- package/templates/.agents/rules/milestone-inference.en.md +1 -1
- package/templates/.agents/rules/milestone-inference.github.en.md +7 -119
- package/templates/.agents/rules/milestone-inference.github.zh-CN.md +8 -108
- package/templates/.agents/rules/milestone-inference.zh-CN.md +1 -1
- package/templates/.agents/rules/next-step-output.en.md +12 -11
- package/templates/.agents/rules/next-step-output.zh-CN.md +12 -11
- package/templates/.agents/rules/no-mid-flow-questions.en.md +3 -3
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +3 -3
- package/templates/.agents/rules/pr-checks-commands.en.md +16 -3
- package/templates/.agents/rules/pr-checks-commands.github.en.md +10 -54
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +19 -47
- package/templates/.agents/rules/pr-checks-commands.zh-CN.md +30 -3
- package/templates/.agents/rules/pr-sync.en.md +30 -3
- package/templates/.agents/rules/pr-sync.github.en.md +12 -115
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +26 -104
- package/templates/.agents/rules/pr-sync.zh-CN.md +43 -3
- package/templates/.agents/rules/review-handshake.en.md +13 -9
- package/templates/.agents/rules/review-handshake.zh-CN.md +13 -9
- package/templates/.agents/rules/task-management.en.md +11 -2
- package/templates/.agents/rules/task-management.zh-CN.md +11 -3
- package/templates/.agents/rules/task-short-id.en.md +25 -29
- package/templates/.agents/rules/task-short-id.zh-CN.md +20 -23
- package/templates/.agents/scripts/lib/agent-infra-package.js +141 -0
- package/templates/.agents/scripts/task-short-id.js +71 -533
- package/templates/.agents/skills/analyze-task/SKILL.en.md +22 -55
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +26 -59
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -3
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -3
- package/templates/.agents/skills/block-task/SKILL.en.md +21 -47
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +23 -47
- package/templates/.agents/skills/block-task/config/verify.json +1 -2
- package/templates/.agents/skills/cancel-task/SKILL.en.md +16 -45
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +16 -45
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -2
- package/templates/.agents/skills/check-task/SKILL.en.md +6 -3
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +6 -3
- package/templates/.agents/skills/close-codescan/SKILL.en.md +6 -24
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +6 -24
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +6 -24
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +6 -24
- package/templates/.agents/skills/code-task/SKILL.en.md +22 -35
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +28 -44
- package/templates/.agents/skills/code-task/config/verify.en.json +3 -3
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +3 -3
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +13 -9
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +13 -9
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/report-template.en.md +11 -1
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +11 -1
- package/templates/.agents/skills/commit/SKILL.en.md +18 -14
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +18 -14
- package/templates/.agents/skills/commit/config/verify.json +1 -2
- package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -11
- package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -11
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +4 -8
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +4 -8
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +6 -18
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +5 -17
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +42 -2
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +42 -2
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +13 -36
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +13 -36
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -3
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -3
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +10 -54
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +10 -79
- package/templates/.agents/skills/complete-task/SKILL.en.md +59 -63
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +59 -63
- package/templates/.agents/skills/complete-task/config/verify.en.json +11 -6
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +11 -6
- package/templates/.agents/skills/create-pr/SKILL.en.md +19 -44
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +19 -43
- package/templates/.agents/skills/create-pr/config/verify.json +1 -2
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +8 -12
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +7 -11
- package/templates/.agents/skills/create-task/SKILL.en.md +12 -13
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +12 -13
- package/templates/.agents/skills/create-task/config/verify.json +1 -2
- package/templates/.agents/skills/import-codescan/SKILL.en.md +2 -2
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/import-codescan/config/verify.json +1 -2
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -2
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/import-dependabot/config/verify.json +1 -2
- package/templates/.agents/skills/import-issue/SKILL.en.md +9 -13
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +10 -14
- package/templates/.agents/skills/import-issue/config/verify.json +1 -2
- package/templates/.agents/skills/init-milestones/SKILL.en.md +2 -2
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +5 -4
- package/templates/.agents/skills/plan-task/SKILL.en.md +17 -42
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +22 -49
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -3
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -3
- package/templates/.agents/skills/post-release/SKILL.en.md +11 -81
- package/templates/.agents/skills/post-release/SKILL.zh-CN.md +8 -78
- package/templates/.agents/skills/refine-title/SKILL.en.md +1 -1
- package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +16 -163
- package/templates/.agents/skills/release/SKILL.zh-CN.md +15 -162
- package/templates/.agents/skills/restore-task/SKILL.en.md +11 -29
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +11 -29
- package/templates/.agents/skills/restore-task/config/verify.json +1 -2
- package/templates/.agents/skills/review-analysis/SKILL.en.md +13 -25
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +13 -33
- package/templates/.agents/skills/review-analysis/config/verify.en.json +3 -4
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +3 -4
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +9 -12
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +9 -12
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +9 -2
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +9 -2
- package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +7 -0
- package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +7 -0
- package/templates/.agents/skills/review-code/SKILL.en.md +22 -36
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +23 -41
- package/templates/.agents/skills/review-code/config/verify.en.json +5 -5
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +5 -5
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +56 -18
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +56 -18
- package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -4
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -4
- package/templates/.agents/skills/review-code/reference/review-criteria.en.md +7 -0
- package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +7 -0
- package/templates/.agents/skills/review-plan/SKILL.en.md +13 -25
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +13 -33
- package/templates/.agents/skills/review-plan/config/verify.en.json +3 -4
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +3 -4
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +9 -12
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +9 -12
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +9 -2
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +9 -2
- package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +7 -0
- package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +7 -0
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +6 -3
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +6 -3
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +177 -151
- package/templates/.agents/skills/watch-pr/SKILL.en.md +24 -14
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +25 -15
- package/templates/.agents/skills/watch-pr/config/verify.json +1 -2
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +2 -2
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +2 -2
- package/templates/.agents/templates/task.en.md +6 -1
- package/templates/.agents/templates/task.zh-CN.md +6 -1
- package/templates/.claude/commands/analyze-task.en.md +1 -1
- package/templates/.claude/commands/analyze-task.zh-CN.md +1 -1
- package/templates/.claude/commands/block-task.en.md +1 -1
- package/templates/.claude/commands/block-task.zh-CN.md +1 -1
- package/templates/.claude/commands/cancel-task.en.md +1 -1
- package/templates/.claude/commands/cancel-task.zh-CN.md +1 -1
- package/templates/.claude/commands/check-task.en.md +1 -1
- package/templates/.claude/commands/check-task.zh-CN.md +1 -1
- package/templates/.claude/commands/code-task.en.md +1 -1
- package/templates/.claude/commands/code-task.zh-CN.md +1 -1
- package/templates/.claude/commands/commit.en.md +1 -0
- package/templates/.claude/commands/commit.zh-CN.md +1 -0
- package/templates/.claude/commands/complete-manual-validation.en.md +1 -1
- package/templates/.claude/commands/complete-manual-validation.zh-CN.md +1 -1
- package/templates/.claude/commands/complete-task.en.md +1 -1
- package/templates/.claude/commands/complete-task.zh-CN.md +1 -1
- package/templates/.claude/commands/create-pr.en.md +1 -1
- package/templates/.claude/commands/create-pr.zh-CN.md +1 -1
- package/templates/.claude/commands/plan-task.en.md +1 -1
- package/templates/.claude/commands/plan-task.zh-CN.md +1 -1
- package/templates/.claude/commands/review-analysis.en.md +1 -1
- package/templates/.claude/commands/review-analysis.zh-CN.md +1 -1
- package/templates/.claude/commands/review-code.en.md +1 -1
- package/templates/.claude/commands/review-code.zh-CN.md +1 -1
- package/templates/.claude/commands/review-plan.en.md +1 -1
- package/templates/.claude/commands/review-plan.zh-CN.md +1 -1
- package/templates/.claude/commands/watch-pr.en.md +1 -1
- package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
- package/templates/.gemini/commands/_project_/analyze-task.en.toml +2 -0
- package/templates/.gemini/commands/_project_/analyze-task.zh-CN.toml +2 -0
- package/templates/.gemini/commands/_project_/block-task.en.toml +2 -0
- package/templates/.gemini/commands/_project_/block-task.zh-CN.toml +2 -0
- package/templates/.gemini/commands/_project_/cancel-task.en.toml +2 -0
- package/templates/.gemini/commands/_project_/cancel-task.zh-CN.toml +2 -0
- package/templates/.gemini/commands/_project_/check-task.en.toml +2 -0
- package/templates/.gemini/commands/_project_/check-task.zh-CN.toml +2 -0
- package/templates/.gemini/commands/_project_/code-task.en.toml +2 -0
- package/templates/.gemini/commands/_project_/code-task.zh-CN.toml +2 -0
- package/templates/.gemini/commands/_project_/commit.en.toml +4 -0
- package/templates/.gemini/commands/_project_/commit.zh-CN.toml +4 -0
- package/templates/.gemini/commands/_project_/complete-manual-validation.en.toml +2 -0
- package/templates/.gemini/commands/_project_/complete-manual-validation.zh-CN.toml +2 -0
- package/templates/.gemini/commands/_project_/complete-task.en.toml +2 -0
- package/templates/.gemini/commands/_project_/complete-task.zh-CN.toml +2 -0
- package/templates/.gemini/commands/_project_/create-pr.en.toml +2 -0
- package/templates/.gemini/commands/_project_/create-pr.zh-CN.toml +2 -0
- package/templates/.gemini/commands/_project_/plan-task.en.toml +2 -0
- package/templates/.gemini/commands/_project_/plan-task.zh-CN.toml +2 -0
- package/templates/.gemini/commands/_project_/review-analysis.en.toml +2 -0
- package/templates/.gemini/commands/_project_/review-analysis.zh-CN.toml +2 -0
- package/templates/.gemini/commands/_project_/review-code.en.toml +2 -0
- package/templates/.gemini/commands/_project_/review-code.zh-CN.toml +2 -0
- package/templates/.gemini/commands/_project_/review-plan.en.toml +2 -0
- package/templates/.gemini/commands/_project_/review-plan.zh-CN.toml +2 -0
- package/templates/.gemini/commands/_project_/watch-pr.en.toml +2 -0
- package/templates/.gemini/commands/_project_/watch-pr.zh-CN.toml +2 -0
- package/templates/.git-hooks/check-large-files.cjs +92 -0
- package/templates/.git-hooks/check-version-format.sh +5 -1
- package/templates/.git-hooks/pre-commit +1 -0
- package/templates/.opencode/commands/analyze-task.en.md +1 -1
- package/templates/.opencode/commands/analyze-task.zh-CN.md +1 -1
- package/templates/.opencode/commands/check-task.en.md +1 -1
- package/templates/.opencode/commands/check-task.zh-CN.md +1 -1
- package/templates/.opencode/commands/code-task.en.md +1 -1
- package/templates/.opencode/commands/code-task.zh-CN.md +1 -1
- package/templates/.opencode/commands/commit.en.md +2 -0
- package/templates/.opencode/commands/commit.zh-CN.md +2 -0
- package/templates/.opencode/commands/complete-task.en.md +1 -1
- package/templates/.opencode/commands/complete-task.zh-CN.md +1 -1
- package/templates/.opencode/commands/plan-task.en.md +1 -1
- package/templates/.opencode/commands/plan-task.zh-CN.md +1 -1
- package/templates/.opencode/commands/review-analysis.en.md +1 -1
- package/templates/.opencode/commands/review-analysis.zh-CN.md +1 -1
- package/templates/.opencode/commands/review-code.en.md +1 -1
- package/templates/.opencode/commands/review-code.zh-CN.md +1 -1
- package/templates/.opencode/commands/review-plan.en.md +1 -1
- package/templates/.opencode/commands/review-plan.zh-CN.md +1 -1
- package/templates/.agents/scripts/platform-adapters/platform-sync.js +0 -13
- package/templates/.agents/scripts/review-diff-fingerprint.js +0 -99
- package/templates/.agents/scripts/workflow-warnings.js +0 -290
- package/templates/.agents/skills/code-task/scripts/detect-mode.js +0 -257
- package/templates/.agents/skills/release/scripts/manage-milestones.github.sh +0 -80
- package/templates/.agents/skills/release/scripts/manage-milestones.sh +0 -6
|
@@ -0,0 +1,1014 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import { createGitHubClient } from "./github-client.js";
|
|
6
|
+
import { resolvePlatformContext } from "./context.js";
|
|
7
|
+
import { hasCheckedRequirement, resolveRequirementSection } from "./issue-metadata.js";
|
|
8
|
+
import { inspectGitHubIssueMetadata, requirementSectionAnchors } from "./issues.js";
|
|
9
|
+
import { listRemoteComments } from "./issue-comments.js";
|
|
10
|
+
import { taskTypeLabel } from "./metadata-labels.js";
|
|
11
|
+
import { inspectGitHubPullRequest } from "./pull-requests.js";
|
|
12
|
+
const CHECK_TYPE = "platform-sync";
|
|
13
|
+
const VERSION_LINE_REGEX = /^[0-9]+\.[0-9]+\.x$/;
|
|
14
|
+
const FRONTMATTER_FIELD_MAP = {
|
|
15
|
+
priority: "Priority",
|
|
16
|
+
effort: "Effort",
|
|
17
|
+
start_date: "Start date",
|
|
18
|
+
target_date: "Target date"
|
|
19
|
+
};
|
|
20
|
+
const OPTION_LOCALIZATION = {
|
|
21
|
+
"紧急": "Urgent",
|
|
22
|
+
"高": "High",
|
|
23
|
+
"中": "Medium",
|
|
24
|
+
"低": "Low"
|
|
25
|
+
};
|
|
26
|
+
let activeShared = null;
|
|
27
|
+
let repoRoot = "";
|
|
28
|
+
const githubClient = createGitHubClient();
|
|
29
|
+
export function getDefaults() {
|
|
30
|
+
return {
|
|
31
|
+
statusLabels: {
|
|
32
|
+
pendingDesignWork: "status: pending-design-work",
|
|
33
|
+
inProgress: "status: in-progress",
|
|
34
|
+
blocked: "status: blocked",
|
|
35
|
+
completed: "status: completed",
|
|
36
|
+
waitingForTriage: "status: waiting-for-triage"
|
|
37
|
+
},
|
|
38
|
+
markers: {
|
|
39
|
+
task: "<!-- sync-issue:{task-id}:task -->",
|
|
40
|
+
artifact: "<!-- sync-issue:{task-id}:{artifact-stem} -->",
|
|
41
|
+
artifactChunk: "<!-- sync-issue:{task-id}:{artifact-stem}:{part}/{total} -->",
|
|
42
|
+
summary: "<!-- sync-issue:{task-id}:summary -->",
|
|
43
|
+
cancel: "<!-- sync-issue:{task-id}:cancel -->",
|
|
44
|
+
prSummary: "<!-- sync-pr:{task-id}:summary -->"
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function getShared() {
|
|
49
|
+
if (!activeShared) {
|
|
50
|
+
throw new Error("platform-sync adapter shared utilities are unavailable");
|
|
51
|
+
}
|
|
52
|
+
return activeShared;
|
|
53
|
+
}
|
|
54
|
+
function loadTask(...args) {
|
|
55
|
+
return getShared().loadTask(...args);
|
|
56
|
+
}
|
|
57
|
+
function getCheckedRequirements(...args) {
|
|
58
|
+
return getShared().getCheckedRequirements(...args);
|
|
59
|
+
}
|
|
60
|
+
function normalizeContent(...args) {
|
|
61
|
+
return getShared().normalizeContent(...args);
|
|
62
|
+
}
|
|
63
|
+
function isBlank(...args) {
|
|
64
|
+
return getShared().isBlank(...args);
|
|
65
|
+
}
|
|
66
|
+
function escapeRegExp(...args) {
|
|
67
|
+
return getShared().escapeRegExp(...args);
|
|
68
|
+
}
|
|
69
|
+
function passResult(...args) {
|
|
70
|
+
return getShared().passResult(...args);
|
|
71
|
+
}
|
|
72
|
+
function failResult(...args) {
|
|
73
|
+
return getShared().failResult(...args);
|
|
74
|
+
}
|
|
75
|
+
function blockedResult(...args) {
|
|
76
|
+
return getShared().blockedResult(...args);
|
|
77
|
+
}
|
|
78
|
+
function safeStat(...args) {
|
|
79
|
+
return getShared().safeStat(...args);
|
|
80
|
+
}
|
|
81
|
+
function parseIssueNumber(...args) {
|
|
82
|
+
return getShared().parseIssueNumber(...args);
|
|
83
|
+
}
|
|
84
|
+
function parsePrNumber(...args) {
|
|
85
|
+
return getShared().parsePrNumber(...args);
|
|
86
|
+
}
|
|
87
|
+
export function check({ taskDir, config, artifactFile }, shared) {
|
|
88
|
+
activeShared = shared;
|
|
89
|
+
repoRoot = shared.repoRoot;
|
|
90
|
+
const context = buildSyncContext({ taskDir, config, artifactFile });
|
|
91
|
+
if (context.earlyReturn) {
|
|
92
|
+
return context.earlyReturn;
|
|
93
|
+
}
|
|
94
|
+
const remoteData = fetchRemoteData(context);
|
|
95
|
+
if (remoteData.earlyReturn) {
|
|
96
|
+
return remoteData.earlyReturn;
|
|
97
|
+
}
|
|
98
|
+
const subChecks = [
|
|
99
|
+
checkClosedIssueStatusLabels,
|
|
100
|
+
checkStatusLabel,
|
|
101
|
+
checkCommentMarker,
|
|
102
|
+
checkPrCommentMarker,
|
|
103
|
+
checkPrCommentLastCommit,
|
|
104
|
+
checkPrCommentRequiredPatterns,
|
|
105
|
+
checkCommentContent,
|
|
106
|
+
checkTaskCommentContent,
|
|
107
|
+
checkInLabelsComputed,
|
|
108
|
+
checkPrTypeLabel,
|
|
109
|
+
checkInLabelsMatchPr,
|
|
110
|
+
checkPrAssignee,
|
|
111
|
+
checkSyncedRequirements,
|
|
112
|
+
checkIssueType,
|
|
113
|
+
checkIssueFields,
|
|
114
|
+
checkMilestone
|
|
115
|
+
];
|
|
116
|
+
for (const subCheck of subChecks) {
|
|
117
|
+
const result = subCheck(context, remoteData);
|
|
118
|
+
if (result) {
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return passResult(CHECK_TYPE, `GitHub sync checks passed for Issue #${context.issueNumber}`);
|
|
123
|
+
}
|
|
124
|
+
function buildSyncContext({ taskDir, config, artifactFile }) {
|
|
125
|
+
const task = loadTask(taskDir);
|
|
126
|
+
if (!task.ok) {
|
|
127
|
+
return { earlyReturn: failResult(CHECK_TYPE, task.message) };
|
|
128
|
+
}
|
|
129
|
+
const issueNumber = parseIssueNumber(task.metadata.issue_number);
|
|
130
|
+
const prNumber = parsePrNumber(task.metadata.pr_number);
|
|
131
|
+
if (config.when === "issue_number_exists" && !issueNumber) {
|
|
132
|
+
return { earlyReturn: passResult(CHECK_TYPE, "Skipped: task has no issue_number") };
|
|
133
|
+
}
|
|
134
|
+
if (config.when === "pr_number_exists" && !prNumber) {
|
|
135
|
+
return { earlyReturn: passResult(CHECK_TYPE, "Skipped: task has no pr_number") };
|
|
136
|
+
}
|
|
137
|
+
if (!issueNumber) {
|
|
138
|
+
return { earlyReturn: passResult(CHECK_TYPE, "Skipped: platform-sync not required for this task") };
|
|
139
|
+
}
|
|
140
|
+
const platformContext = resolvePlatformContext({ cwd: repoRoot, client: githubClient });
|
|
141
|
+
if (platformContext.status === "failed") {
|
|
142
|
+
return { earlyReturn: failResult(CHECK_TYPE, platformContext.error?.message || "Platform context failed", "check_failed") };
|
|
143
|
+
}
|
|
144
|
+
if (platformContext.status === "blocked") {
|
|
145
|
+
return { earlyReturn: blockedResult(CHECK_TYPE, platformContext.error?.message || "Platform context blocked", "network_error") };
|
|
146
|
+
}
|
|
147
|
+
if (!platformContext.platform.repository) {
|
|
148
|
+
if (platformContext.error?.code === "REMOTE_MISSING" || platformContext.error?.code === "REMOTE_INVALID") {
|
|
149
|
+
return { earlyReturn: blockedResult(CHECK_TYPE, platformContext.error.message, "network_error") };
|
|
150
|
+
}
|
|
151
|
+
return { earlyReturn: passResult(CHECK_TYPE, `Skipped: ${platformContext.error?.message || "platform unavailable"}`) };
|
|
152
|
+
}
|
|
153
|
+
const repoOwnerType = detectRepoOwnerType(platformContext.platform.repository, taskDir);
|
|
154
|
+
const expectedValues = resolveExpectedValues(config);
|
|
155
|
+
if (!expectedValues.ok) {
|
|
156
|
+
return { earlyReturn: failResult(CHECK_TYPE, expectedValues.message, "check_failed") };
|
|
157
|
+
}
|
|
158
|
+
const marker = expectedValues.commentMarker
|
|
159
|
+
? interpolate(expectedValues.commentMarker, taskDir, artifactFile)
|
|
160
|
+
: null;
|
|
161
|
+
const prMarker = expectedValues.prCommentMarker
|
|
162
|
+
? interpolate(expectedValues.prCommentMarker, taskDir, artifactFile)
|
|
163
|
+
: null;
|
|
164
|
+
const artifactPath = artifactFile ? path.join(taskDir, artifactFile) : null;
|
|
165
|
+
return {
|
|
166
|
+
task,
|
|
167
|
+
taskDir,
|
|
168
|
+
config,
|
|
169
|
+
artifactFile,
|
|
170
|
+
artifactPath,
|
|
171
|
+
issueNumber,
|
|
172
|
+
prNumber,
|
|
173
|
+
upstreamRepo: platformContext.platform.repository,
|
|
174
|
+
repoOwnerType,
|
|
175
|
+
hasTriage: platformContext.capabilities.triage,
|
|
176
|
+
hasPush: platformContext.capabilities.push,
|
|
177
|
+
expectedStatusLabel: expectedValues.statusLabel,
|
|
178
|
+
marker,
|
|
179
|
+
prMarker
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function resolveExpectedValues(config) {
|
|
183
|
+
const defaults = getDefaults();
|
|
184
|
+
const statusLabel = resolveDefaultValue({
|
|
185
|
+
collection: defaults.statusLabels,
|
|
186
|
+
key: config.expected_status_label_key,
|
|
187
|
+
value: config.expected_status_label,
|
|
188
|
+
configKey: "expected_status_label_key"
|
|
189
|
+
});
|
|
190
|
+
if (!statusLabel.ok) {
|
|
191
|
+
return statusLabel;
|
|
192
|
+
}
|
|
193
|
+
const commentMarker = resolveDefaultValue({
|
|
194
|
+
collection: defaults.markers,
|
|
195
|
+
key: config.expected_comment_marker_key,
|
|
196
|
+
value: config.expected_comment_marker,
|
|
197
|
+
configKey: "expected_comment_marker_key"
|
|
198
|
+
});
|
|
199
|
+
if (!commentMarker.ok) {
|
|
200
|
+
return commentMarker;
|
|
201
|
+
}
|
|
202
|
+
const prCommentMarker = resolveDefaultValue({
|
|
203
|
+
collection: defaults.markers,
|
|
204
|
+
key: config.expected_pr_comment_marker_key,
|
|
205
|
+
value: config.expected_pr_comment_marker,
|
|
206
|
+
configKey: "expected_pr_comment_marker_key"
|
|
207
|
+
});
|
|
208
|
+
if (!prCommentMarker.ok) {
|
|
209
|
+
return prCommentMarker;
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
ok: true,
|
|
213
|
+
statusLabel: statusLabel.value,
|
|
214
|
+
commentMarker: commentMarker.value,
|
|
215
|
+
prCommentMarker: prCommentMarker.value
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function resolveDefaultValue({ collection, key, value, configKey }) {
|
|
219
|
+
if (!key) {
|
|
220
|
+
return { ok: true, value: value || null };
|
|
221
|
+
}
|
|
222
|
+
const resolvedValue = collection[key];
|
|
223
|
+
if (!resolvedValue) {
|
|
224
|
+
return { ok: false, message: `Unknown ${configKey}: ${key}` };
|
|
225
|
+
}
|
|
226
|
+
return { ok: true, value: resolvedValue };
|
|
227
|
+
}
|
|
228
|
+
function fetchRemoteData(context) {
|
|
229
|
+
const sharedIssue = inspectGitHubIssueMetadata(githubClient, context.upstreamRepo, context.issueNumber, context.taskDir);
|
|
230
|
+
const issueResult = sharedIssue.ok
|
|
231
|
+
? {
|
|
232
|
+
ok: true,
|
|
233
|
+
value: {
|
|
234
|
+
state: sharedIssue.value.state.toUpperCase(),
|
|
235
|
+
labels: sharedIssue.value.labels.map((name) => ({ name })),
|
|
236
|
+
body: sharedIssue.value.body,
|
|
237
|
+
milestone: sharedIssue.value.milestone ? { title: sharedIssue.value.milestone } : null
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
: {
|
|
241
|
+
ok: false,
|
|
242
|
+
type: sharedIssue.error.retryable ? "network_error" : "check_failed",
|
|
243
|
+
message: sharedIssue.error.message
|
|
244
|
+
};
|
|
245
|
+
if (!issueResult.ok) {
|
|
246
|
+
return {
|
|
247
|
+
earlyReturn: issueResult.type === "check_failed"
|
|
248
|
+
? failResult(CHECK_TYPE, issueResult.message, issueResult.type)
|
|
249
|
+
: blockedResult(CHECK_TYPE, issueResult.message, issueResult.type)
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
const issue = issueResult.value;
|
|
253
|
+
if (context.config.issue_must_exist !== false && !issue) {
|
|
254
|
+
return {
|
|
255
|
+
earlyReturn: failResult(CHECK_TYPE, `Issue #${context.issueNumber} not found`, "check_failed")
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
let comments = null;
|
|
259
|
+
if (shouldFetchComments(context.config)) {
|
|
260
|
+
const commentsResult = withRetry(() => ghPaginatedJson([
|
|
261
|
+
"api",
|
|
262
|
+
"--paginate",
|
|
263
|
+
"--slurp",
|
|
264
|
+
`repos/${context.upstreamRepo}/issues/${context.issueNumber}/comments?per_page=100`
|
|
265
|
+
], context.taskDir));
|
|
266
|
+
if (!commentsResult.ok) {
|
|
267
|
+
return {
|
|
268
|
+
earlyReturn: commentsResult.type === "check_failed"
|
|
269
|
+
? failResult(CHECK_TYPE, commentsResult.message, commentsResult.type)
|
|
270
|
+
: blockedResult(CHECK_TYPE, commentsResult.message, commentsResult.type)
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
comments = flattenComments(commentsResult.value);
|
|
274
|
+
}
|
|
275
|
+
let prComments = null;
|
|
276
|
+
if (context.prMarker) {
|
|
277
|
+
if (!context.prNumber) {
|
|
278
|
+
return {
|
|
279
|
+
earlyReturn: failResult(CHECK_TYPE, "Expected a valid pr_number for PR comment verification", "check_failed")
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
const prCommentsResult = listRemoteComments(githubClient, context.upstreamRepo, context.prNumber, context.taskDir);
|
|
283
|
+
if (!prCommentsResult.ok) {
|
|
284
|
+
return {
|
|
285
|
+
earlyReturn: prCommentsResult.error.retryable
|
|
286
|
+
? blockedResult(CHECK_TYPE, prCommentsResult.error.message, "network_error")
|
|
287
|
+
: failResult(CHECK_TYPE, prCommentsResult.error.message, "check_failed")
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
prComments = prCommentsResult.value;
|
|
291
|
+
}
|
|
292
|
+
let issueType;
|
|
293
|
+
if (context.config.verify_issue_type && context.hasPush) {
|
|
294
|
+
const issueTypeResult = withRetry(() => ghText([
|
|
295
|
+
"api",
|
|
296
|
+
`repos/${context.upstreamRepo}/issues/${context.issueNumber}`,
|
|
297
|
+
"--jq",
|
|
298
|
+
".type.name // empty"
|
|
299
|
+
], context.taskDir));
|
|
300
|
+
if (issueTypeResult.ok) {
|
|
301
|
+
issueType = issueTypeResult.value || null;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
let issueFields;
|
|
305
|
+
if (context.config.verify_issue_fields && context.hasPush) {
|
|
306
|
+
const [owner, name] = context.upstreamRepo.split("/");
|
|
307
|
+
const issueFieldsResult = withRetry(() => ghJson([
|
|
308
|
+
"api",
|
|
309
|
+
"graphql",
|
|
310
|
+
"-f",
|
|
311
|
+
`query=${ISSUE_FIELDS_QUERY}`,
|
|
312
|
+
"-F",
|
|
313
|
+
`owner=${owner}`,
|
|
314
|
+
"-F",
|
|
315
|
+
`name=${name}`,
|
|
316
|
+
"-F",
|
|
317
|
+
`number=${context.issueNumber}`
|
|
318
|
+
], context.taskDir));
|
|
319
|
+
if (issueFieldsResult.ok) {
|
|
320
|
+
issueFields = normalizeIssueFields(issueFieldsResult.value);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
let prLabels = null;
|
|
324
|
+
let prMilestone;
|
|
325
|
+
let prAssignees;
|
|
326
|
+
let prHeadSha;
|
|
327
|
+
if (((context.config.verify_in_labels_match_pr && context.hasTriage)
|
|
328
|
+
|| (context.config.verify_pr_type_label && context.hasTriage)
|
|
329
|
+
|| (context.config.verify_milestone && context.hasTriage)
|
|
330
|
+
|| (context.config.verify_pr_assignee && context.hasPush)
|
|
331
|
+
|| context.config.verify_pr_comment_last_commit_matches_head) && context.prNumber) {
|
|
332
|
+
const prResult = inspectGitHubPullRequest(githubClient, context.upstreamRepo, context.prNumber, context.taskDir);
|
|
333
|
+
if (!prResult.ok) {
|
|
334
|
+
return {
|
|
335
|
+
earlyReturn: prResult.error.retryable
|
|
336
|
+
? blockedResult(CHECK_TYPE, prResult.error.message, "network_error")
|
|
337
|
+
: failResult(CHECK_TYPE, prResult.error.message, "check_failed")
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
prLabels = (context.config.verify_in_labels_match_pr || context.config.verify_pr_type_label)
|
|
341
|
+
? prResult.value.labels
|
|
342
|
+
: null;
|
|
343
|
+
prMilestone = context.config.verify_milestone
|
|
344
|
+
? prResult.value.milestone ? { title: prResult.value.milestone } : null
|
|
345
|
+
: undefined;
|
|
346
|
+
prAssignees = context.config.verify_pr_assignee
|
|
347
|
+
? prResult.value.assignees
|
|
348
|
+
: undefined;
|
|
349
|
+
prHeadSha = prResult.value.head.sha;
|
|
350
|
+
}
|
|
351
|
+
return {
|
|
352
|
+
issue,
|
|
353
|
+
comments,
|
|
354
|
+
prComments,
|
|
355
|
+
prLabels,
|
|
356
|
+
issueType,
|
|
357
|
+
issueFields,
|
|
358
|
+
prMilestone,
|
|
359
|
+
prAssignees,
|
|
360
|
+
prHeadSha
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
function mapTaskTypeToLabel(taskType) {
|
|
364
|
+
return taskTypeLabel(taskType);
|
|
365
|
+
}
|
|
366
|
+
function shouldFetchComments(config) {
|
|
367
|
+
return Boolean(config.expected_comment_marker
|
|
368
|
+
|| config.expected_comment_marker_key
|
|
369
|
+
|| config.expected_pr_comment_marker
|
|
370
|
+
|| config.expected_pr_comment_marker_key
|
|
371
|
+
|| config.verify_pr_comment_last_commit_matches_head
|
|
372
|
+
|| config.verify_comment_content
|
|
373
|
+
|| config.verify_task_comment_content);
|
|
374
|
+
}
|
|
375
|
+
function flattenComments(value) {
|
|
376
|
+
if (!Array.isArray(value)) {
|
|
377
|
+
return [];
|
|
378
|
+
}
|
|
379
|
+
return value.flatMap((page) => Array.isArray(page) ? page : []);
|
|
380
|
+
}
|
|
381
|
+
function checkStatusLabel(context, remoteData) {
|
|
382
|
+
if (!context.expectedStatusLabel || !context.hasTriage) {
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
if (String(remoteData.issue.state || "").toUpperCase() !== "OPEN") {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
const labels = extractLabelNames(remoteData.issue.labels);
|
|
389
|
+
if (labels.includes(context.expectedStatusLabel)) {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
return failResult(CHECK_TYPE, `Expected label '${context.expectedStatusLabel}' not found on Issue #${context.issueNumber}`, "check_failed");
|
|
393
|
+
}
|
|
394
|
+
function checkClosedIssueStatusLabels(context, remoteData) {
|
|
395
|
+
if (!context.config.verify_closed_issue_has_no_status_labels) {
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
if (String(remoteData.issue.state || "").toUpperCase() !== "CLOSED") {
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
const statusLabels = extractLabelNames(remoteData.issue.labels)
|
|
402
|
+
.filter((label) => label.startsWith("status:"));
|
|
403
|
+
if (statusLabels.length === 0) {
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
return failResult(CHECK_TYPE, `Closed Issue #${context.issueNumber} retains status labels: ${statusLabels.join(", ")}`, "check_failed");
|
|
407
|
+
}
|
|
408
|
+
function checkCommentMarker(context, remoteData) {
|
|
409
|
+
if (!context.marker) {
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
const comment = findCommentByMarker(remoteData.comments, context.marker);
|
|
413
|
+
if (comment) {
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
return failResult(CHECK_TYPE, `Expected comment marker '${context.marker}' not found on Issue #${context.issueNumber}`, "check_failed");
|
|
417
|
+
}
|
|
418
|
+
function checkPrCommentMarker(context, remoteData) {
|
|
419
|
+
if (!context.prMarker) {
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
const comment = findCommentByMarker(remoteData.prComments, context.prMarker);
|
|
423
|
+
if (comment) {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
return failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed");
|
|
427
|
+
}
|
|
428
|
+
function checkPrCommentLastCommit(context, remoteData) {
|
|
429
|
+
if (!context.config.verify_pr_comment_last_commit_matches_head) {
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
if (!context.prMarker) {
|
|
433
|
+
return failResult(CHECK_TYPE, "verify_pr_comment_last_commit_matches_head requires expected_pr_comment_marker", "check_failed");
|
|
434
|
+
}
|
|
435
|
+
const comment = findCommentByMarker(remoteData.prComments, context.prMarker);
|
|
436
|
+
if (!comment) {
|
|
437
|
+
return failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed");
|
|
438
|
+
}
|
|
439
|
+
const match = String(comment.body || "").match(/<!--\s*last-commit:\s*([0-9a-f]{7,40})\s*-->/i);
|
|
440
|
+
if (!match) {
|
|
441
|
+
return failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment is missing '<!-- last-commit: <sha> -->' metadata`, "check_failed");
|
|
442
|
+
}
|
|
443
|
+
const expectedHead = String(remoteData.prHeadSha || "").trim();
|
|
444
|
+
if (!expectedHead)
|
|
445
|
+
return blockedResult(CHECK_TYPE, "Unable to resolve the PR head SHA", "network_error");
|
|
446
|
+
const actualHead = match[1].trim();
|
|
447
|
+
if (expectedHead === actualHead) {
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
450
|
+
return failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment last-commit metadata mismatch: expected ${expectedHead}, got ${actualHead}`, "check_failed");
|
|
451
|
+
}
|
|
452
|
+
function checkPrCommentRequiredPatterns(context, remoteData) {
|
|
453
|
+
const patterns = context.config.expected_pr_comment_required_patterns || [];
|
|
454
|
+
if (!Array.isArray(patterns) || patterns.length === 0) {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
if (!context.prMarker) {
|
|
458
|
+
return failResult(CHECK_TYPE, "expected_pr_comment_required_patterns requires expected_pr_comment_marker", "check_failed");
|
|
459
|
+
}
|
|
460
|
+
const comment = findCommentByMarker(remoteData.prComments, context.prMarker);
|
|
461
|
+
if (!comment) {
|
|
462
|
+
return failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed");
|
|
463
|
+
}
|
|
464
|
+
const body = String(comment.body || "");
|
|
465
|
+
for (const pattern of patterns) {
|
|
466
|
+
const regex = new RegExp(pattern, "m");
|
|
467
|
+
if (!regex.test(body)) {
|
|
468
|
+
return failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment is missing required pattern: ${pattern}`, "check_failed");
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
function checkCommentContent(context, remoteData) {
|
|
474
|
+
if (!context.config.verify_comment_content) {
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
if (!context.marker) {
|
|
478
|
+
return failResult(CHECK_TYPE, "verify_comment_content requires expected_comment_marker", "check_failed");
|
|
479
|
+
}
|
|
480
|
+
if (!context.artifactPath || !safeStat(context.artifactPath)) {
|
|
481
|
+
return failResult(CHECK_TYPE, `Artifact not found for comment verification: ${context.artifactFile || "(missing artifactFile)"}`, "check_failed");
|
|
482
|
+
}
|
|
483
|
+
const comment = findCommentByMarker(remoteData.comments, context.marker);
|
|
484
|
+
const localContent = normalizeContent(fs.readFileSync(context.artifactPath, "utf8"));
|
|
485
|
+
const commentContent = normalizeContent(extractCommentBody(comment?.body || ""));
|
|
486
|
+
if (localContent === commentContent) {
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
return failResult(CHECK_TYPE, buildCommentContentMismatchMessage(path.basename(context.artifactPath, path.extname(context.artifactPath)), context.issueNumber, localContent, commentContent), "check_failed");
|
|
490
|
+
}
|
|
491
|
+
function checkTaskCommentContent(context, remoteData) {
|
|
492
|
+
if (!context.config.verify_task_comment_content) {
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
const taskMarker = `<!-- sync-issue:${context.task.metadata.id}:task -->`;
|
|
496
|
+
const comment = findCommentByMarker(remoteData.comments, taskMarker);
|
|
497
|
+
if (!comment) {
|
|
498
|
+
return failResult(CHECK_TYPE, `Expected comment marker '${taskMarker}' not found on Issue #${context.issueNumber}`, "check_failed");
|
|
499
|
+
}
|
|
500
|
+
const expectedBody = normalizeContent(buildExpectedTaskBody(context.task.content));
|
|
501
|
+
const commentBody = normalizeContent(extractCommentBody(comment.body || ""));
|
|
502
|
+
if (expectedBody === commentBody) {
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
return failResult(CHECK_TYPE, buildCommentContentMismatchMessage("task", context.issueNumber, expectedBody, commentBody), "check_failed");
|
|
506
|
+
}
|
|
507
|
+
function checkPrTypeLabel(context, remoteData) {
|
|
508
|
+
if (!context.config.verify_pr_type_label || !context.hasTriage || !context.prNumber || !remoteData.prLabels) {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
const expectedLabel = mapTaskTypeToLabel(context.task.metadata.type);
|
|
512
|
+
if (!expectedLabel) {
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
if (remoteData.prLabels.includes(expectedLabel)) {
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
return failResult(CHECK_TYPE, `Expected type label '${expectedLabel}' not found on PR #${context.prNumber}`, "check_failed");
|
|
519
|
+
}
|
|
520
|
+
function checkInLabelsMatchPr(context, remoteData) {
|
|
521
|
+
if (!context.config.verify_in_labels_match_pr || !context.hasTriage || !context.prNumber || !remoteData.prLabels) {
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
const issueInLabels = extractLabelNames(remoteData.issue.labels)
|
|
525
|
+
.filter((label) => label.startsWith("in:"))
|
|
526
|
+
.sort();
|
|
527
|
+
const prInLabels = remoteData.prLabels
|
|
528
|
+
.filter((label) => label.startsWith("in:"))
|
|
529
|
+
.sort();
|
|
530
|
+
if (arraysEqual(issueInLabels, prInLabels)) {
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
return failResult(CHECK_TYPE, `in: labels mismatch — PR #${context.prNumber} has [${formatLabelList(prInLabels)}], Issue #${context.issueNumber} has [${formatLabelList(issueInLabels)}]`, "check_failed");
|
|
534
|
+
}
|
|
535
|
+
function checkInLabelsComputed(context, remoteData) {
|
|
536
|
+
if (!context.config.verify_in_labels_computed || !context.hasTriage) {
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
const expectedInLabels = computeExpectedInLabels(context.taskDir);
|
|
540
|
+
if (!expectedInLabels.ok) {
|
|
541
|
+
return expectedInLabels.type === "check_failed"
|
|
542
|
+
? failResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type)
|
|
543
|
+
: blockedResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type);
|
|
544
|
+
}
|
|
545
|
+
if (expectedInLabels.mode === "skipped") {
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
const actualInLabels = extractLabelNames(remoteData.issue.labels)
|
|
549
|
+
.filter((label) => label.startsWith("in:"))
|
|
550
|
+
.sort();
|
|
551
|
+
if (arraysEqual(expectedInLabels.labels, actualInLabels)) {
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
return failResult(CHECK_TYPE, `Issue #${context.issueNumber} in: labels do not match committed changes: expected [${formatLabelList(expectedInLabels.labels)}], got [${formatLabelList(actualInLabels)}]`, "check_failed");
|
|
555
|
+
}
|
|
556
|
+
function checkSyncedRequirements(context, remoteData) {
|
|
557
|
+
if (!context.config.sync_checked_requirements || !context.hasTriage) {
|
|
558
|
+
return null;
|
|
559
|
+
}
|
|
560
|
+
const checkedRequirements = getCheckedRequirements(context.task.content);
|
|
561
|
+
if (checkedRequirements.length === 0) {
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
const issueBody = remoteData.issue.body || "";
|
|
565
|
+
const resolution = resolveRequirementSection(issueBody, requirementSectionAnchors(repoRoot, context.task.metadata.type || "task"));
|
|
566
|
+
if (resolution.status === "missing") {
|
|
567
|
+
return null;
|
|
568
|
+
}
|
|
569
|
+
if (resolution.status === "ambiguous") {
|
|
570
|
+
return failResult(CHECK_TYPE, `Issue #${context.issueNumber} requirements section is ambiguous`, "check_failed");
|
|
571
|
+
}
|
|
572
|
+
const requirementBody = issueBody.slice(resolution.bodyStart, resolution.bodyEnd);
|
|
573
|
+
const missingRequirements = checkedRequirements.filter((item) => !hasCheckedRequirement(requirementBody, item));
|
|
574
|
+
if (missingRequirements.length === 0) {
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
return failResult(CHECK_TYPE, `Issue body is missing checked requirements: ${missingRequirements.join(", ")}`, "check_failed");
|
|
578
|
+
}
|
|
579
|
+
function checkIssueType(context, remoteData) {
|
|
580
|
+
if (!context.config.verify_issue_type || !context.hasPush) {
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
if (remoteData.issueType === undefined) {
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
if (!remoteData.issueType) {
|
|
587
|
+
if (context.repoOwnerType === "User") {
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
return failResult(CHECK_TYPE, `Issue #${context.issueNumber} has no Issue Type set`, "check_failed");
|
|
591
|
+
}
|
|
592
|
+
const expectedType = mapTaskTypeToIssueType(context.task.metadata.type);
|
|
593
|
+
if (expectedType && remoteData.issueType !== expectedType) {
|
|
594
|
+
return failResult(CHECK_TYPE, `Issue #${context.issueNumber} has type '${remoteData.issueType}', expected '${expectedType}' (from task type '${context.task.metadata.type}')`, "check_failed");
|
|
595
|
+
}
|
|
596
|
+
return null;
|
|
597
|
+
}
|
|
598
|
+
function checkIssueFields(context, remoteData) {
|
|
599
|
+
if (!context.config.verify_issue_fields || !context.hasPush) {
|
|
600
|
+
return null;
|
|
601
|
+
}
|
|
602
|
+
if (remoteData.issueFields === undefined) {
|
|
603
|
+
return null;
|
|
604
|
+
}
|
|
605
|
+
for (const [metadataKey, fieldName] of Object.entries(FRONTMATTER_FIELD_MAP)) {
|
|
606
|
+
const expectedRaw = context.task.metadata[metadataKey];
|
|
607
|
+
if (isBlank(expectedRaw) || !remoteData.issueFields.pinnedNames.has(fieldName)) {
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
const actual = remoteData.issueFields.values.get(fieldName);
|
|
611
|
+
const expected = normalizeExpectedIssueField(metadataKey, expectedRaw);
|
|
612
|
+
if (!expected) {
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
if (!actual) {
|
|
616
|
+
return failResult(CHECK_TYPE, `Issue #${context.issueNumber} field '${fieldName}' is missing, expected '${expected.value}'`, "check_failed");
|
|
617
|
+
}
|
|
618
|
+
if (actual.kind !== expected.kind || actual.value !== expected.value) {
|
|
619
|
+
return failResult(CHECK_TYPE, `Issue #${context.issueNumber} field '${fieldName}' is '${actual.value}', expected '${expected.value}'`, "check_failed");
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return null;
|
|
623
|
+
}
|
|
624
|
+
function checkPrAssignee(context, remoteData) {
|
|
625
|
+
if (!context.config.verify_pr_assignee || !context.hasPush || !context.prNumber) {
|
|
626
|
+
return null;
|
|
627
|
+
}
|
|
628
|
+
if (!remoteData.prAssignees || remoteData.prAssignees.length === 0) {
|
|
629
|
+
return failResult(CHECK_TYPE, `PR #${context.prNumber} has no assignee`, "check_failed");
|
|
630
|
+
}
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
function checkMilestone(context, remoteData) {
|
|
634
|
+
if (!context.config.verify_milestone || !context.hasTriage) {
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
if (!remoteData.issue?.milestone?.title) {
|
|
638
|
+
return failResult(CHECK_TYPE, `Issue #${context.issueNumber} has no milestone set`, "check_failed");
|
|
639
|
+
}
|
|
640
|
+
if (context.prNumber && remoteData.prMilestone !== undefined && !remoteData.prMilestone?.title) {
|
|
641
|
+
return failResult(CHECK_TYPE, `PR #${context.prNumber} has no milestone set`, "check_failed");
|
|
642
|
+
}
|
|
643
|
+
if (context.config.verify_milestone_specific) {
|
|
644
|
+
const issueTitle = remoteData.issue.milestone.title;
|
|
645
|
+
if (VERSION_LINE_REGEX.test(issueTitle)) {
|
|
646
|
+
return failResult(CHECK_TYPE, `Issue #${context.issueNumber} milestone '${issueTitle}' is a release line; narrow to a specific version (e.g. ${issueTitle.replace(/\.x$/, ".N")}) before continuing`, "check_failed");
|
|
647
|
+
}
|
|
648
|
+
if (context.prNumber && remoteData.prMilestone?.title && VERSION_LINE_REGEX.test(remoteData.prMilestone.title)) {
|
|
649
|
+
return failResult(CHECK_TYPE, `PR #${context.prNumber} milestone '${remoteData.prMilestone.title}' is a release line; narrow to a specific version before continuing`, "check_failed");
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
function findCommentByMarker(comments, marker) {
|
|
655
|
+
return (comments || []).find((comment) => typeof comment.body === "string" && comment.body.includes(marker)) || null;
|
|
656
|
+
}
|
|
657
|
+
function isGeneratedMarkerLine(line) {
|
|
658
|
+
return line.startsWith("<!--") && line.endsWith("-->");
|
|
659
|
+
}
|
|
660
|
+
function extractCommentBody(commentBody) {
|
|
661
|
+
const lines = String(commentBody || "").split(/\r?\n/);
|
|
662
|
+
let start = 0;
|
|
663
|
+
while (start < lines.length && (lines[start].trim() === "" || isGeneratedMarkerLine(lines[start].trim()))) {
|
|
664
|
+
start += 1;
|
|
665
|
+
}
|
|
666
|
+
if (start < lines.length && lines[start].startsWith("## ")) {
|
|
667
|
+
start += 1;
|
|
668
|
+
}
|
|
669
|
+
while (start < lines.length && lines[start].trim() === "") {
|
|
670
|
+
start += 1;
|
|
671
|
+
}
|
|
672
|
+
if (start < lines.length && /^> \*\*.+\*\* · .+$/.test(lines[start].trim())) {
|
|
673
|
+
start += 1;
|
|
674
|
+
}
|
|
675
|
+
while (start < lines.length && lines[start].trim() === "") {
|
|
676
|
+
start += 1;
|
|
677
|
+
}
|
|
678
|
+
let end = lines.length;
|
|
679
|
+
for (let index = lines.length - 1; index >= start; index -= 1) {
|
|
680
|
+
const trimmed = lines[index].trim();
|
|
681
|
+
if (trimmed === "") {
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
if (/^\*.*\*$/.test(trimmed)) {
|
|
685
|
+
end = index;
|
|
686
|
+
if (end > start && lines[end - 1].trim() === "---") {
|
|
687
|
+
end -= 1;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
break;
|
|
691
|
+
}
|
|
692
|
+
return lines.slice(start, end).join("\n");
|
|
693
|
+
}
|
|
694
|
+
function buildExpectedTaskBody(taskContent) {
|
|
695
|
+
const frontmatterMatch = taskContent.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
|
|
696
|
+
if (!frontmatterMatch) {
|
|
697
|
+
return taskContent.trim();
|
|
698
|
+
}
|
|
699
|
+
const body = taskContent.slice(frontmatterMatch[0].length).trim();
|
|
700
|
+
return [
|
|
701
|
+
buildTaskFrontmatterSummary(),
|
|
702
|
+
"",
|
|
703
|
+
"```yaml",
|
|
704
|
+
frontmatterMatch[0].trim(),
|
|
705
|
+
"```",
|
|
706
|
+
"",
|
|
707
|
+
"</details>",
|
|
708
|
+
"",
|
|
709
|
+
body
|
|
710
|
+
].join("\n").trim();
|
|
711
|
+
}
|
|
712
|
+
function buildTaskFrontmatterSummary() {
|
|
713
|
+
const language = loadProjectLanguage();
|
|
714
|
+
if (language === "en" || language === "en-US") {
|
|
715
|
+
return "<details><summary>Metadata (frontmatter)</summary>";
|
|
716
|
+
}
|
|
717
|
+
return "<details><summary>元数据 (frontmatter)</summary>";
|
|
718
|
+
}
|
|
719
|
+
function loadProjectLanguage() {
|
|
720
|
+
const override = process.env.VALIDATE_ARTIFACT_LANGUAGE;
|
|
721
|
+
if (!isBlank(override)) {
|
|
722
|
+
return String(override).trim();
|
|
723
|
+
}
|
|
724
|
+
const configPath = path.join(repoRoot, ".agents", ".airc.json");
|
|
725
|
+
if (!fs.existsSync(configPath)) {
|
|
726
|
+
return "";
|
|
727
|
+
}
|
|
728
|
+
try {
|
|
729
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
730
|
+
return String(config.language || "").trim();
|
|
731
|
+
}
|
|
732
|
+
catch {
|
|
733
|
+
return "";
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
function buildCommentContentMismatchMessage(fileStem, issueNumber, localContent, commentContent) {
|
|
737
|
+
const diffIndex = firstDifferenceIndex(localContent, commentContent);
|
|
738
|
+
const position = indexToLineColumn(localContent, diffIndex);
|
|
739
|
+
return `Comment content mismatch for '${fileStem}' on Issue #${issueNumber}: local file has ${localContent.length} chars, comment body has ${commentContent.length} chars (first difference near char ${diffIndex + 1}, line ${position.line}, column ${position.column})`;
|
|
740
|
+
}
|
|
741
|
+
function firstDifferenceIndex(left, right) {
|
|
742
|
+
const limit = Math.max(left.length, right.length);
|
|
743
|
+
for (let index = 0; index < limit; index += 1) {
|
|
744
|
+
if (left[index] !== right[index]) {
|
|
745
|
+
return index;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return limit;
|
|
749
|
+
}
|
|
750
|
+
function indexToLineColumn(text, index) {
|
|
751
|
+
const prefix = text.slice(0, Math.min(index, text.length));
|
|
752
|
+
const lines = prefix.split("\n");
|
|
753
|
+
return {
|
|
754
|
+
line: lines.length,
|
|
755
|
+
column: (lines.at(-1) || "").length + 1
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
function extractLabelNames(labels) {
|
|
759
|
+
return (labels || [])
|
|
760
|
+
.map((label) => typeof label === "string" ? label : label?.name)
|
|
761
|
+
.filter((label) => typeof label === "string" && label.length > 0);
|
|
762
|
+
}
|
|
763
|
+
function mapTaskTypeToIssueType(taskType) {
|
|
764
|
+
const mapping = {
|
|
765
|
+
bug: "Bug",
|
|
766
|
+
bugfix: "Bug",
|
|
767
|
+
enhancement: "Feature",
|
|
768
|
+
feature: "Feature",
|
|
769
|
+
task: "Task",
|
|
770
|
+
documentation: "Task",
|
|
771
|
+
"dependency-upgrade": "Task",
|
|
772
|
+
chore: "Task",
|
|
773
|
+
docs: "Task",
|
|
774
|
+
refactor: "Task",
|
|
775
|
+
refactoring: "Task"
|
|
776
|
+
};
|
|
777
|
+
return mapping[taskType] || "Task";
|
|
778
|
+
}
|
|
779
|
+
const ISSUE_FIELDS_QUERY = `query($owner:String!,$name:String!,$number:Int!){repository(owner:$owner,name:$name){issue(number:$number){issueType{name pinnedFields{__typename ... on IssueFieldSingleSelect{id name} ... on IssueFieldDate{id name} ... on IssueFieldText{id name} ... on IssueFieldNumber{id name}}} issueFieldValues(first:50){nodes{__typename ... on IssueFieldSingleSelectValue{name optionId field{... on IssueFieldSingleSelect{name}}} ... on IssueFieldDateValue{value field{... on IssueFieldDate{name}}} ... on IssueFieldTextValue{value field{... on IssueFieldText{name}}} ... on IssueFieldNumberValue{value field{... on IssueFieldNumber{name}}}}}}}}`;
|
|
780
|
+
function normalizeIssueFields(payload) {
|
|
781
|
+
const issue = payload?.data?.repository?.issue;
|
|
782
|
+
const pinnedFields = Array.isArray(issue?.issueType?.pinnedFields)
|
|
783
|
+
? issue.issueType.pinnedFields
|
|
784
|
+
: [];
|
|
785
|
+
const values = Array.isArray(issue?.issueFieldValues?.nodes)
|
|
786
|
+
? issue.issueFieldValues.nodes
|
|
787
|
+
: [];
|
|
788
|
+
const pinnedNames = new Set(pinnedFields
|
|
789
|
+
.map((field) => typeof field?.name === "string" ? field.name : "")
|
|
790
|
+
.filter(Boolean));
|
|
791
|
+
const normalizedValues = new Map();
|
|
792
|
+
for (const value of values) {
|
|
793
|
+
const fieldName = value?.field?.name;
|
|
794
|
+
if (!fieldName) {
|
|
795
|
+
continue;
|
|
796
|
+
}
|
|
797
|
+
if (value.__typename === "IssueFieldSingleSelectValue") {
|
|
798
|
+
normalizedValues.set(fieldName, {
|
|
799
|
+
kind: "single-select",
|
|
800
|
+
value: normalizeOptionName(value.name)
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
else if (value.__typename === "IssueFieldDateValue") {
|
|
804
|
+
normalizedValues.set(fieldName, {
|
|
805
|
+
kind: "date",
|
|
806
|
+
value: normalizeDateValue(value.value)
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return { pinnedNames, values: normalizedValues };
|
|
811
|
+
}
|
|
812
|
+
function normalizeExpectedIssueField(metadataKey, rawValue) {
|
|
813
|
+
const value = String(rawValue || "").trim();
|
|
814
|
+
if (!value) {
|
|
815
|
+
return null;
|
|
816
|
+
}
|
|
817
|
+
if (metadataKey === "start_date" || metadataKey === "target_date") {
|
|
818
|
+
return { kind: "date", value: normalizeDateValue(value) };
|
|
819
|
+
}
|
|
820
|
+
return { kind: "single-select", value: normalizeOptionName(value) };
|
|
821
|
+
}
|
|
822
|
+
function normalizeOptionName(value) {
|
|
823
|
+
const normalized = String(value || "").trim();
|
|
824
|
+
return OPTION_LOCALIZATION[normalized] || normalized;
|
|
825
|
+
}
|
|
826
|
+
function normalizeDateValue(value) {
|
|
827
|
+
const normalized = String(value || "").trim();
|
|
828
|
+
const match = normalized.match(/^\d{4}-\d{2}-\d{2}/);
|
|
829
|
+
return match ? match[0] : normalized;
|
|
830
|
+
}
|
|
831
|
+
function arraysEqual(left, right) {
|
|
832
|
+
if (left.length !== right.length) {
|
|
833
|
+
return false;
|
|
834
|
+
}
|
|
835
|
+
return left.every((value, index) => value === right[index]);
|
|
836
|
+
}
|
|
837
|
+
function formatLabelList(labels) {
|
|
838
|
+
return labels.length > 0 ? labels.join(", ") : "none";
|
|
839
|
+
}
|
|
840
|
+
function computeExpectedInLabels(taskDir) {
|
|
841
|
+
const changedFilesResult = gitText(["diff", "main...HEAD", "--name-only"], taskDir);
|
|
842
|
+
if (!changedFilesResult.ok) {
|
|
843
|
+
return changedFilesResult;
|
|
844
|
+
}
|
|
845
|
+
const changedFiles = String(changedFilesResult.value || "")
|
|
846
|
+
.split(/\r?\n/)
|
|
847
|
+
.map((value) => value.trim())
|
|
848
|
+
.filter(Boolean);
|
|
849
|
+
const mapping = loadInLabelMapping();
|
|
850
|
+
if (!mapping.ok) {
|
|
851
|
+
return mapping;
|
|
852
|
+
}
|
|
853
|
+
if (Object.keys(mapping.value ?? {}).length > 0) {
|
|
854
|
+
const labels = new Set();
|
|
855
|
+
for (const file of changedFiles) {
|
|
856
|
+
for (const [label, prefixes] of Object.entries(mapping.value ?? {})) {
|
|
857
|
+
if (prefixes.some((prefix) => file.startsWith(prefix))) {
|
|
858
|
+
labels.add(`in: ${label}`);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return { ok: true, labels: Array.from(labels).sort(), mode: "mapped" };
|
|
863
|
+
}
|
|
864
|
+
const repoLabelsResult = withRetry(() => ghJson([
|
|
865
|
+
"label",
|
|
866
|
+
"list",
|
|
867
|
+
"--limit",
|
|
868
|
+
"200",
|
|
869
|
+
"--json",
|
|
870
|
+
"name"
|
|
871
|
+
], taskDir));
|
|
872
|
+
if (!repoLabelsResult.ok) {
|
|
873
|
+
return repoLabelsResult;
|
|
874
|
+
}
|
|
875
|
+
const repoInLabels = new Set(extractLabelNames(repoLabelsResult.value)
|
|
876
|
+
.filter((label) => label.startsWith("in:")));
|
|
877
|
+
if (repoInLabels.size === 0) {
|
|
878
|
+
return { ok: true, labels: [], mode: "fallback" };
|
|
879
|
+
}
|
|
880
|
+
const labels = new Set();
|
|
881
|
+
for (const file of changedFiles) {
|
|
882
|
+
const topLevel = file.split("/")[0];
|
|
883
|
+
if (!topLevel) {
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
const candidate = `in: ${topLevel}`;
|
|
887
|
+
if (repoInLabels.has(candidate)) {
|
|
888
|
+
labels.add(candidate);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
return { ok: true, labels: Array.from(labels).sort(), mode: "fallback" };
|
|
892
|
+
}
|
|
893
|
+
function loadInLabelMapping() {
|
|
894
|
+
const configPath = path.join(repoRoot, ".agents", ".airc.json");
|
|
895
|
+
if (!fs.existsSync(configPath)) {
|
|
896
|
+
return { ok: true, value: {} };
|
|
897
|
+
}
|
|
898
|
+
try {
|
|
899
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
900
|
+
const mapping = config?.labels?.in;
|
|
901
|
+
if (!mapping || typeof mapping !== "object" || Array.isArray(mapping)) {
|
|
902
|
+
return { ok: true, value: {} };
|
|
903
|
+
}
|
|
904
|
+
const normalized = {};
|
|
905
|
+
for (const [label, prefixes] of Object.entries(mapping)) {
|
|
906
|
+
if (!Array.isArray(prefixes)) {
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
const cleaned = prefixes
|
|
910
|
+
.map((value) => String(value || "").trim())
|
|
911
|
+
.filter(Boolean);
|
|
912
|
+
if (cleaned.length > 0) {
|
|
913
|
+
normalized[label] = cleaned;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
return { ok: true, value: normalized };
|
|
917
|
+
}
|
|
918
|
+
catch (error) {
|
|
919
|
+
return { ok: false, type: "check_failed", message: `Unable to parse .agents/.airc.json: ${error.message}` };
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
// === GitHub API ===
|
|
923
|
+
function detectRepoOwnerType(upstreamRepo, taskDir) {
|
|
924
|
+
const ownerTypeResult = withRetry(() => ghText([
|
|
925
|
+
"api",
|
|
926
|
+
`repos/${upstreamRepo}`,
|
|
927
|
+
"--jq",
|
|
928
|
+
".owner.type // empty"
|
|
929
|
+
], taskDir));
|
|
930
|
+
if (!ownerTypeResult.ok) {
|
|
931
|
+
return "unknown";
|
|
932
|
+
}
|
|
933
|
+
return ownerTypeResult.value || "unknown";
|
|
934
|
+
}
|
|
935
|
+
function ghJson(args, cwd) {
|
|
936
|
+
return mapClientResult(githubClient.json(args, { cwd }));
|
|
937
|
+
}
|
|
938
|
+
function ghText(args, cwd) {
|
|
939
|
+
return mapClientResult(githubClient.text(args, { cwd }));
|
|
940
|
+
}
|
|
941
|
+
function ghPaginatedJson(args, cwd) {
|
|
942
|
+
return ghJson(args, cwd);
|
|
943
|
+
}
|
|
944
|
+
function mapClientResult(result) {
|
|
945
|
+
if (result.ok)
|
|
946
|
+
return result;
|
|
947
|
+
const checkFailed = ["RESOURCE_NOT_FOUND", "PERMISSION_DENIED", "PLATFORM_REQUEST_INVALID"].includes(result.error.code);
|
|
948
|
+
return {
|
|
949
|
+
ok: false,
|
|
950
|
+
type: checkFailed ? "check_failed" : "network_error",
|
|
951
|
+
message: result.error.message
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
function gitText(args, cwd) {
|
|
955
|
+
try {
|
|
956
|
+
const value = execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
957
|
+
return { ok: true, value: String(value || "").trim() };
|
|
958
|
+
}
|
|
959
|
+
catch (error) {
|
|
960
|
+
const stderr = `${error?.stderr || ""}${error?.stdout || ""}`.trim();
|
|
961
|
+
return {
|
|
962
|
+
ok: false,
|
|
963
|
+
type: "check_failed",
|
|
964
|
+
message: stderr || `git ${args.join(" ")} failed`
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
function resolvePrHeadSha(context) {
|
|
969
|
+
const fallback = () => withRetry(() => gitText(["rev-parse", "HEAD"], context.taskDir));
|
|
970
|
+
const branch = String(context.task?.metadata?.branch || "").trim();
|
|
971
|
+
if (!branch) {
|
|
972
|
+
return fallback();
|
|
973
|
+
}
|
|
974
|
+
const worktreeList = withRetry(() => gitText(["worktree", "list", "--porcelain"], context.taskDir));
|
|
975
|
+
if (!worktreeList.ok) {
|
|
976
|
+
return fallback();
|
|
977
|
+
}
|
|
978
|
+
const matchedWorktree = findWorktreeForBranch(worktreeList.value, branch);
|
|
979
|
+
if (!matchedWorktree) {
|
|
980
|
+
return fallback();
|
|
981
|
+
}
|
|
982
|
+
const headInWorktree = withRetry(() => gitText(["rev-parse", "HEAD"], matchedWorktree));
|
|
983
|
+
if (!headInWorktree.ok) {
|
|
984
|
+
return fallback();
|
|
985
|
+
}
|
|
986
|
+
return headInWorktree;
|
|
987
|
+
}
|
|
988
|
+
function findWorktreeForBranch(porcelainOutput, branch) {
|
|
989
|
+
let currentWorktree = "";
|
|
990
|
+
for (const rawLine of String(porcelainOutput || "").split("\n")) {
|
|
991
|
+
const line = rawLine.trimEnd();
|
|
992
|
+
if (line.startsWith("worktree ")) {
|
|
993
|
+
currentWorktree = line.slice("worktree ".length).trim();
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
if (line.startsWith("branch refs/heads/")) {
|
|
997
|
+
const usedBranch = line.slice("branch refs/heads/".length).trim();
|
|
998
|
+
if (usedBranch === branch && currentWorktree) {
|
|
999
|
+
return currentWorktree;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
return null;
|
|
1004
|
+
}
|
|
1005
|
+
function withRetry(operation) {
|
|
1006
|
+
return operation();
|
|
1007
|
+
}
|
|
1008
|
+
function interpolate(template, taskDir, artifactFile) {
|
|
1009
|
+
const artifactStem = artifactFile ? path.basename(artifactFile, path.extname(artifactFile)) : "";
|
|
1010
|
+
return template
|
|
1011
|
+
.replace(/\{task-id\}/g, path.basename(taskDir))
|
|
1012
|
+
.replace(/\{artifact-stem\}/g, artifactStem);
|
|
1013
|
+
}
|
|
1014
|
+
//# sourceMappingURL=verification-sync.js.map
|