@fitlab-ai/agent-infra 0.9.10 → 0.9.12
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 +19 -9
- package/README.zh-CN.md +19 -9
- package/bin/cli.ts +17 -4
- package/dist/bin/cli.js +19 -4
- package/dist/bin/internal-cli.js +55 -4
- package/dist/lib/agent-client.js +0 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +71 -10
- package/dist/lib/agent-clients/adapters/opencode-sandbox.js +0 -4
- package/dist/lib/agent-clients/adapters/traecli.js +7 -8
- package/dist/lib/agent-clients/config.js +14 -99
- package/dist/lib/agent-clients/reconcile.js +1 -11
- package/dist/lib/agent-clients/schema.js +5 -4
- package/dist/lib/agent-clients/tokens.js +5 -15
- package/dist/lib/decide.js +14 -30
- package/dist/lib/defaults.json +8 -0
- package/dist/lib/git/workflow.js +43 -1
- package/dist/lib/init.js +3 -2
- package/dist/lib/internal/git-workflow.js +8 -1
- package/dist/lib/internal/platform-pr.js +8 -2
- package/dist/lib/internal/release-workflow.js +0 -1
- package/dist/lib/internal/task-delivery.js +59 -0
- package/dist/lib/internal/task-finalization.js +19 -9
- package/dist/lib/internal/task-ledger.js +8 -2
- package/dist/lib/internal/task-lifecycle.js +17 -93
- package/dist/lib/internal/task-orchestration.js +30 -216
- package/dist/lib/internal/task-validate.js +2 -1
- package/dist/lib/merge.js +205 -103
- package/dist/lib/platform/github-client.js +7 -1
- package/dist/lib/platform/pr-checks.js +8 -5
- package/dist/lib/platform/pr-summary.js +12 -8
- package/dist/lib/platform/pull-requests.js +393 -91
- package/dist/lib/platform/verification-required.js +13 -2
- package/dist/lib/platform/verification-sync.js +9 -4
- package/dist/lib/pr-review/evidence-grading.js +12 -4
- package/dist/lib/process-data/normalize.js +2 -9
- package/dist/lib/run/host.js +46 -16
- package/dist/lib/sandbox/agent-client-reconciler.js +4 -12
- package/dist/lib/sandbox/commands/create.js +3 -3
- package/dist/lib/sandbox/commands/enter.js +18 -4
- package/dist/lib/sandbox/config.js +8 -12
- package/dist/lib/sandbox/control/executor.js +131 -39
- package/dist/lib/sandbox/control/lifecycle.js +55 -24
- package/dist/lib/sandbox/control/server.js +18 -16
- package/dist/lib/sandbox/mounts.js +3 -2
- package/dist/lib/sandbox/recovery.js +123 -22
- package/dist/lib/sandbox/workspace-identity.js +14 -0
- package/dist/lib/sandbox/workspace-view.js +8 -9
- package/dist/lib/self-update.js +278 -0
- package/dist/lib/server/process-state.js +2 -2
- package/dist/lib/server/protocol.js +5 -5
- package/dist/lib/task/activity-log.js +4 -1
- package/dist/lib/task/artifact-lifecycle.js +6 -4
- package/dist/lib/task/codex-orchestration.js +1 -1
- package/dist/lib/task/command-options.js +1 -3
- package/dist/lib/task/commands/cat.js +0 -5
- package/dist/lib/task/commands/decisions.js +76 -92
- package/dist/lib/task/commands/files.js +6 -6
- package/dist/lib/task/commands/grep.js +25 -61
- package/dist/lib/task/commands/issue-body.js +6 -6
- package/dist/lib/task/commands/log.js +25 -10
- package/dist/lib/task/commands/show.js +5 -6
- package/dist/lib/task/commands/status.js +8 -8
- package/dist/lib/task/commit-intent.js +100 -0
- package/dist/lib/task/commit-operation.js +181 -91
- package/dist/lib/task/commit-policy.js +1 -1
- package/dist/lib/task/control-authority.js +431 -0
- package/dist/lib/task/create.js +39 -8
- package/dist/lib/task/current-contract.js +35 -0
- package/dist/lib/task/decision-details.js +218 -0
- package/dist/lib/task/decision-intents.js +5 -2
- package/dist/lib/task/delivery-target.js +97 -0
- package/dist/lib/task/delivery.js +153 -0
- package/dist/lib/task/events.js +11 -5
- package/dist/lib/task/human-override.js +5 -2
- package/dist/lib/task/index.js +19 -20
- package/dist/lib/task/ledger-intents.js +8 -9
- package/dist/lib/task/ledger.js +13 -6
- package/dist/lib/task/lifecycle.js +122 -9
- package/dist/lib/task/orchestration.js +105 -75
- package/dist/lib/task/pr-delivery-fact.js +182 -0
- package/dist/lib/task/review-artifacts.js +31 -12
- package/dist/lib/task/review-finalization.js +13 -4
- package/dist/lib/task/review-fingerprint.js +8 -0
- package/dist/lib/task/verification-engine.js +86 -39
- package/dist/lib/task/verification-review.js +2 -2
- package/dist/lib/task/workspace-snapshot.js +5 -11
- package/dist/lib/task/write.js +11 -1
- package/dist/lib/update.js +13 -35
- package/dist/lib/version.js +5 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-client.ts +0 -1
- 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/adapters/opencode-sandbox.ts +0 -5
- package/lib/agent-clients/adapters/traecli.ts +7 -8
- package/lib/agent-clients/config.ts +14 -144
- package/lib/agent-clients/reconcile.ts +1 -12
- package/lib/agent-clients/schema.ts +5 -4
- package/lib/agent-clients/tokens.ts +5 -18
- package/lib/decide.ts +12 -22
- package/lib/defaults.json +8 -0
- package/lib/git/workflow.ts +38 -1
- package/lib/init.ts +5 -2
- package/lib/internal/git-workflow.ts +7 -1
- package/lib/internal/platform-pr.ts +8 -1
- package/lib/internal/release-workflow.ts +0 -1
- package/lib/internal/task-delivery.ts +53 -0
- package/lib/internal/task-finalization.ts +27 -13
- package/lib/internal/task-ledger.ts +8 -2
- package/lib/internal/task-lifecycle.ts +33 -76
- package/lib/internal/task-orchestration.ts +33 -190
- package/lib/internal/task-validate.ts +3 -2
- package/lib/merge.ts +236 -120
- package/lib/platform/github-client.ts +8 -1
- package/lib/platform/pr-checks.ts +7 -5
- package/lib/platform/pr-summary.ts +10 -7
- package/lib/platform/pull-requests.ts +425 -87
- package/lib/platform/verification-required.ts +12 -2
- package/lib/platform/verification-sync.ts +9 -4
- package/lib/pr-review/evidence-grading.ts +7 -4
- package/lib/process-data/normalize.ts +2 -8
- package/lib/run/host.ts +63 -14
- package/lib/sandbox/agent-client-reconciler.ts +5 -15
- package/lib/sandbox/commands/create.ts +4 -4
- package/lib/sandbox/commands/enter.ts +24 -4
- package/lib/sandbox/config.ts +9 -14
- package/lib/sandbox/control/container-identity.ts +2 -2
- package/lib/sandbox/control/executor.ts +148 -51
- package/lib/sandbox/control/lifecycle.ts +59 -31
- package/lib/sandbox/control/protocol.ts +1 -3
- package/lib/sandbox/control/server.ts +15 -12
- package/lib/sandbox/control/state.ts +7 -7
- package/lib/sandbox/mounts.ts +4 -3
- package/lib/sandbox/recovery.ts +162 -34
- package/lib/sandbox/tools.ts +1 -1
- package/lib/sandbox/workspace-identity.ts +28 -0
- package/lib/sandbox/workspace-view.ts +14 -10
- package/lib/self-update.ts +341 -0
- package/lib/server/process-state.ts +2 -2
- package/lib/server/protocol.ts +5 -5
- package/lib/task/activity-log.ts +6 -1
- package/lib/task/artifact-lifecycle.ts +7 -5
- package/lib/task/codex-orchestration.ts +2 -1
- package/lib/task/command-options.ts +1 -2
- package/lib/task/commands/cat.ts +0 -4
- package/lib/task/commands/decisions.ts +80 -75
- package/lib/task/commands/files.ts +6 -6
- package/lib/task/commands/grep.ts +23 -51
- package/lib/task/commands/issue-body.ts +6 -6
- package/lib/task/commands/log.ts +22 -10
- package/lib/task/commands/show.ts +5 -6
- package/lib/task/commands/status.ts +8 -8
- package/lib/task/commit-intent.ts +137 -0
- package/lib/task/commit-operation.ts +161 -79
- package/lib/task/commit-policy.ts +1 -2
- package/lib/task/control-authority.ts +563 -0
- package/lib/task/create.ts +40 -10
- package/lib/task/current-contract.ts +49 -0
- package/lib/task/decision-details.ts +280 -0
- package/lib/task/decision-intents.ts +7 -2
- package/lib/task/delivery-target.ts +130 -0
- package/lib/task/delivery.ts +160 -0
- package/lib/task/events.ts +10 -5
- package/lib/task/human-override.ts +5 -2
- package/lib/task/index.ts +19 -20
- package/lib/task/ledger-intents.ts +6 -8
- package/lib/task/ledger.ts +17 -7
- package/lib/task/lifecycle.ts +113 -4
- package/lib/task/orchestration.ts +102 -80
- package/lib/task/pr-delivery-fact.ts +254 -0
- package/lib/task/review-artifacts.ts +53 -15
- package/lib/task/review-finalization.ts +21 -5
- package/lib/task/review-fingerprint.ts +10 -0
- package/lib/task/verification-engine.ts +78 -46
- package/lib/task/verification-review.ts +3 -1
- package/lib/task/workspace-snapshot.ts +4 -13
- package/lib/task/write.ts +20 -1
- package/lib/update.ts +14 -38
- package/lib/version.ts +6 -1
- package/package.json +1 -1
- package/templates/.agents/README.en.md +5 -3
- package/templates/.agents/README.zh-CN.md +5 -3
- package/templates/.agents/rules/README.en.md +4 -2
- package/templates/.agents/rules/README.zh-CN.md +4 -2
- package/templates/.agents/rules/compatibility-policy.en.md +5 -4
- package/templates/.agents/rules/compatibility-policy.zh-CN.md +5 -4
- 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/human-decision-context.en.md +9 -7
- package/templates/.agents/rules/human-decision-context.zh-CN.md +9 -7
- package/templates/.agents/rules/{issue-fields.github.en.md → issue-fields.en.md} +1 -1
- package/templates/.agents/rules/{issue-fields.github.zh-CN.md → issue-fields.zh-CN.md} +1 -1
- package/templates/.agents/rules/issue-pr-commands.en.md +5 -18
- package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +26 -5
- package/templates/.agents/rules/issue-sync.zh-CN.md +62 -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 +46 -0
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +46 -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/next-step-output.en.md +3 -3
- package/templates/.agents/rules/next-step-output.zh-CN.md +3 -3
- package/templates/.agents/rules/no-mid-flow-questions.en.md +1 -1
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +1 -1
- package/templates/.agents/rules/pr-sync.en.md +23 -14
- package/templates/.agents/rules/pr-sync.zh-CN.md +23 -2
- 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/review-handshake.en.md +1 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
- 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 +2 -3
- package/templates/.agents/rules/task-management.zh-CN.md +2 -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 +4 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +4 -3
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/cancel-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/check-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/close-codescan/SKILL.en.md +3 -3
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +3 -3
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/code-task/SKILL.en.md +8 -5
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +9 -6
- package/templates/.agents/skills/commit/SKILL.en.md +30 -10
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +6 -7
- 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/pr-summary-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
- 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 -6
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +7 -6
- 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 +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +13 -13
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +13 -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 +9 -11
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +9 -11
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +9 -4
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +9 -4
- package/templates/.agents/skills/create-pr/scripts/change-report.mjs +203 -0
- package/templates/.agents/skills/create-release-note/SKILL.en.md +5 -6
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +5 -6
- package/templates/.agents/skills/create-task/SKILL.en.md +2 -1
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +2 -1
- 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/import-issue/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-issue/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 +4 -3
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -3
- package/templates/.agents/skills/restore-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +8 -5
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +8 -5
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -0
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -0
- 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 +11 -8
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +11 -8
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -0
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/review-code/reference/migration-risks.en.md +2 -1
- package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +2 -1
- 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 +8 -5
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +8 -5
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -0
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -0
- 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 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +5 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +5 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +2 -2
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +144 -109
- package/templates/.agents/skills/watch-pr/SKILL.en.md +5 -5
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +5 -5
- package/templates/.agents/templates/task.en.md +6 -2
- package/templates/.agents/templates/task.zh-CN.md +6 -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/.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 -1
- package/templates/.claude/commands/commit.zh-CN.md +1 -1
- 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/run-manual-validation.en.md +1 -1
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
- package/templates/.claude/commands/run-task.en.md +1 -1
- package/templates/.claude/commands/run-task.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/{.trae/skills → .traecli/commands}/update-agent-infra.en.md +0 -1
- package/templates/{.trae/skills → .traecli/commands}/update-agent-infra.zh-CN.md +0 -1
- package/dist/lib/builtin-tuis.js +0 -40
- package/lib/builtin-tuis.ts +0 -56
- 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 -38
- package/templates/.agents/rules/release-commands.github.zh-CN.md +0 -38
- package/templates/.agents/rules/security-alerts.github.en.md +0 -43
- package/templates/.agents/rules/security-alerts.github.zh-CN.md +0 -43
package/lib/task/create.ts
CHANGED
|
@@ -2,8 +2,11 @@ import { createHash, randomUUID } from 'node:crypto';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
|
|
5
|
+
import { isValidAgentInfraVersion, VERSION } from '../version.ts';
|
|
5
6
|
import { loadShortIdByTaskId, mutateShortIdRegistry } from './short-id.ts';
|
|
6
7
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
8
|
+
import { readDeliveryDefaults, validateBaseRef, validateRemote } from './delivery-target.ts';
|
|
9
|
+
import { buildUnboundFact, encodePrDeliveryFact } from './pr-delivery-fact.ts';
|
|
7
10
|
|
|
8
11
|
const AGENTS = ['claude', 'codex', 'antigravity', 'opencode', 'cursor'] as const;
|
|
9
12
|
const TYPES = ['feature', 'bugfix', 'refactor', 'docs', 'chore'] as const;
|
|
@@ -29,6 +32,8 @@ type TaskCreateCandidateV1 = Readonly<{
|
|
|
29
32
|
priority: TaskPriority;
|
|
30
33
|
effort: TaskEffort;
|
|
31
34
|
description: string;
|
|
35
|
+
deliveryRemote?: string;
|
|
36
|
+
deliveryBaseRef?: string;
|
|
32
37
|
taskInput: Readonly<Record<typeof TASK_INPUT_KEYS[number], readonly string[]>>;
|
|
33
38
|
}>;
|
|
34
39
|
|
|
@@ -48,12 +53,14 @@ function payloadError(message: string): never {
|
|
|
48
53
|
throw new Error(`TASK_CREATE_PAYLOAD_INVALID: ${message}`);
|
|
49
54
|
}
|
|
50
55
|
|
|
51
|
-
function exactKeys(value: Record<string, unknown>, expected: readonly string[], label: string): void {
|
|
56
|
+
function exactKeys(value: Record<string, unknown>, expected: readonly string[], label: string, optional: readonly string[] = []): void {
|
|
52
57
|
const actual = Object.keys(value).sort();
|
|
53
58
|
const wanted = [...expected].sort();
|
|
54
|
-
|
|
59
|
+
const allowed = new Set([...expected, ...optional]);
|
|
60
|
+
if (actual.some((key) => !allowed.has(key))) {
|
|
55
61
|
payloadError(`${label} has unknown or missing fields`);
|
|
56
62
|
}
|
|
63
|
+
if (expected.some((key) => !actual.includes(key))) payloadError(`${label} has unknown or missing fields`);
|
|
57
64
|
}
|
|
58
65
|
|
|
59
66
|
function cleanString(value: unknown, label: string, maxBytes: number, oneLine = false): string {
|
|
@@ -77,7 +84,7 @@ function validateTaskCreateCandidate(value: unknown): TaskCreateCandidateV1 {
|
|
|
77
84
|
exactKeys(candidate, [
|
|
78
85
|
'version', 'idempotencyKey', 'agent', 'title', 'type', 'branchSlug',
|
|
79
86
|
'priority', 'effort', 'description', 'taskInput'
|
|
80
|
-
], 'candidate');
|
|
87
|
+
], 'candidate', ['deliveryRemote', 'deliveryBaseRef']);
|
|
81
88
|
if (candidate.version !== 1) payloadError('version must be 1');
|
|
82
89
|
const idempotencyKey = cleanString(candidate.idempotencyKey, 'idempotencyKey', 64, true);
|
|
83
90
|
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(idempotencyKey)) {
|
|
@@ -90,6 +97,14 @@ function validateTaskCreateCandidate(value: unknown): TaskCreateCandidateV1 {
|
|
|
90
97
|
payloadError('branchSlug must be lowercase ASCII kebab-case with length 3-64');
|
|
91
98
|
}
|
|
92
99
|
const description = cleanString(candidate.description, 'description', 8 * 1024);
|
|
100
|
+
const deliveryRemote = candidate.deliveryRemote === undefined
|
|
101
|
+
? undefined
|
|
102
|
+
: cleanString(candidate.deliveryRemote, 'deliveryRemote', 200, true);
|
|
103
|
+
const deliveryBaseRef = candidate.deliveryBaseRef === undefined
|
|
104
|
+
? undefined
|
|
105
|
+
: cleanString(candidate.deliveryBaseRef, 'deliveryBaseRef', 200, true);
|
|
106
|
+
if (deliveryRemote !== undefined && !validateRemote(deliveryRemote)) payloadError('deliveryRemote is invalid');
|
|
107
|
+
if (deliveryBaseRef !== undefined && !validateBaseRef(deliveryBaseRef)) payloadError('deliveryBaseRef is invalid');
|
|
93
108
|
if (!candidate.taskInput || typeof candidate.taskInput !== 'object' || Array.isArray(candidate.taskInput)) {
|
|
94
109
|
payloadError('taskInput must be an object');
|
|
95
110
|
}
|
|
@@ -110,6 +125,8 @@ function validateTaskCreateCandidate(value: unknown): TaskCreateCandidateV1 {
|
|
|
110
125
|
priority: enumValue(candidate.priority, PRIORITIES, 'priority'),
|
|
111
126
|
effort: enumValue(candidate.effort, EFFORTS, 'effort'),
|
|
112
127
|
description,
|
|
128
|
+
...(deliveryRemote === undefined ? {} : { deliveryRemote }),
|
|
129
|
+
...(deliveryBaseRef === undefined ? {} : { deliveryBaseRef }),
|
|
113
130
|
taskInput
|
|
114
131
|
};
|
|
115
132
|
}
|
|
@@ -126,6 +143,8 @@ function canonicalTaskCreateCandidate(candidate: TaskCreateCandidateV1): string
|
|
|
126
143
|
priority: valid.priority,
|
|
127
144
|
effort: valid.effort,
|
|
128
145
|
description: valid.description,
|
|
146
|
+
deliveryRemote: valid.deliveryRemote ?? null,
|
|
147
|
+
deliveryBaseRef: valid.deliveryBaseRef ?? null,
|
|
129
148
|
taskInput: Object.fromEntries(TASK_INPUT_KEYS.map((key) => [key, valid.taskInput[key]]))
|
|
130
149
|
});
|
|
131
150
|
}
|
|
@@ -169,6 +188,7 @@ function renderTask(params: Readonly<{
|
|
|
169
188
|
candidate: TaskCreateCandidateV1;
|
|
170
189
|
taskId: string;
|
|
171
190
|
project: string;
|
|
191
|
+
delivery: { remote: string; baseRef: string };
|
|
172
192
|
timestamp: string;
|
|
173
193
|
agentInfraVersion: string;
|
|
174
194
|
keyDigest: string;
|
|
@@ -193,7 +213,11 @@ function renderTask(params: Readonly<{
|
|
|
193
213
|
'target_date:',
|
|
194
214
|
'current_step: requirement-analysis',
|
|
195
215
|
`assigned_to: ${candidate.agent}`,
|
|
196
|
-
|
|
216
|
+
`pr_delivery_fact: ${JSON.stringify(encodePrDeliveryFact(buildUnboundFact()))}`,
|
|
217
|
+
`delivery_remote: ${candidate.deliveryRemote ?? params.delivery.remote}`,
|
|
218
|
+
`delivery_base_ref: ${candidate.deliveryBaseRef ?? params.delivery.baseRef}`,
|
|
219
|
+
'checkpoint_commit:',
|
|
220
|
+
'delivery_remote_head:',
|
|
197
221
|
`task_create_key_digest: ${params.keyDigest}`,
|
|
198
222
|
`task_create_candidate_digest: ${params.candidateDigest}`,
|
|
199
223
|
'---'
|
|
@@ -218,12 +242,14 @@ function renderTask(params: Readonly<{
|
|
|
218
242
|
return `${content.trimEnd()}\n`;
|
|
219
243
|
}
|
|
220
244
|
|
|
221
|
-
function readProject(repoRoot: string): string {
|
|
222
|
-
const config = JSON.parse(fs.readFileSync(path.join(repoRoot, '.agents', '.airc.json'), 'utf8')) as { project?: unknown };
|
|
245
|
+
function readProject(repoRoot: string): { project: string; delivery: { remote: string; baseRef: string } } {
|
|
246
|
+
const config = JSON.parse(fs.readFileSync(path.join(repoRoot, '.agents', '.airc.json'), 'utf8')) as { project?: unknown; delivery?: { remote?: unknown; baseRef?: unknown } };
|
|
223
247
|
if (typeof config.project !== 'string' || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(config.project)) {
|
|
224
248
|
throw new Error('TASK_CREATE_CONFIG_INVALID: project is missing or invalid');
|
|
225
249
|
}
|
|
226
|
-
|
|
250
|
+
const delivery = readDeliveryDefaults(repoRoot);
|
|
251
|
+
if (!delivery.ok) throw new Error(`TASK_CREATE_CONFIG_INVALID: ${delivery.message}`);
|
|
252
|
+
return { project: config.project, delivery: delivery.value };
|
|
227
253
|
}
|
|
228
254
|
|
|
229
255
|
function assertRealDirectory(directory: string, code: string): void {
|
|
@@ -294,13 +320,17 @@ function recoverPublishedTask(activeRoot: string, repoRoot: string, keyDigest: s
|
|
|
294
320
|
|
|
295
321
|
function createLocalTask(value: unknown, options: LocalTaskCreateOptions): LocalTaskCreateResult {
|
|
296
322
|
const candidate = validateTaskCreateCandidate(value);
|
|
323
|
+
const agentInfraVersion = options.agentInfraVersion ?? VERSION;
|
|
324
|
+
if (!isValidAgentInfraVersion(agentInfraVersion)) {
|
|
325
|
+
throw new Error('TASK_CREATE_VERSION_INVALID: agentInfraVersion must be a valid v-prefixed semver');
|
|
326
|
+
}
|
|
297
327
|
const repoRoot = fs.realpathSync.native(options.repoRoot);
|
|
298
328
|
const workspaceRoot = path.join(repoRoot, '.agents', 'workspace');
|
|
299
329
|
const activeRoot = path.join(workspaceRoot, 'active');
|
|
300
330
|
ensureRealDirectory(path.join(repoRoot, '.agents'), 'TASK_CREATE_WORKSPACE_INVALID');
|
|
301
331
|
ensureRealDirectory(workspaceRoot, 'TASK_CREATE_WORKSPACE_INVALID');
|
|
302
332
|
ensureRealDirectory(activeRoot, 'TASK_CREATE_WORKSPACE_INVALID');
|
|
303
|
-
const
|
|
333
|
+
const projectConfig = readProject(repoRoot);
|
|
304
334
|
const canonical = canonicalTaskCreateCandidate(candidate);
|
|
305
335
|
const candidateDigest = sha256(canonical);
|
|
306
336
|
const keyDigest = sha256(candidate.idempotencyKey);
|
|
@@ -341,8 +371,8 @@ function createLocalTask(value: unknown, options: LocalTaskCreateOptions): Local
|
|
|
341
371
|
const templateStat = fs.lstatSync(templatePath);
|
|
342
372
|
if (!templateStat.isFile() || templateStat.isSymbolicLink()) throw new Error('TASK_CREATE_TEMPLATE_INVALID');
|
|
343
373
|
const rendered = renderTask({
|
|
344
|
-
template: fs.readFileSync(templatePath, 'utf8'), candidate, taskId, project, timestamp,
|
|
345
|
-
agentInfraVersion
|
|
374
|
+
template: fs.readFileSync(templatePath, 'utf8'), candidate, taskId, project: projectConfig.project, delivery: projectConfig.delivery, timestamp,
|
|
375
|
+
agentInfraVersion, keyDigest, candidateDigest
|
|
346
376
|
});
|
|
347
377
|
const temporary = path.join(workspaceRoot, `.task-create.tmp.${process.pid}.${randomUUID()}`);
|
|
348
378
|
fs.mkdirSync(temporary, { mode: 0o700 });
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { isValidAgentInfraVersion } from '../version.ts';
|
|
2
|
+
import { parseTypedTaskFrontmatter } from './frontmatter.ts';
|
|
3
|
+
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, validateLedgerRows } from './ledger.ts';
|
|
4
|
+
import type { LedgerDocument } from './ledger.ts';
|
|
5
|
+
|
|
6
|
+
type CurrentTaskContractErrorCode = 'TASK_DOCUMENT_INVALID' | 'TASK_CURRENT_CONTRACT_INVALID';
|
|
7
|
+
type CurrentTaskContractResult =
|
|
8
|
+
| {
|
|
9
|
+
ok: true;
|
|
10
|
+
metadata: ReturnType<typeof parseTypedTaskFrontmatter>;
|
|
11
|
+
ledger: LedgerDocument;
|
|
12
|
+
}
|
|
13
|
+
| {
|
|
14
|
+
ok: false;
|
|
15
|
+
code: CurrentTaskContractErrorCode;
|
|
16
|
+
message: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function validateCurrentTaskContract(content: string): CurrentTaskContractResult {
|
|
20
|
+
let metadata: ReturnType<typeof parseTypedTaskFrontmatter>;
|
|
21
|
+
try {
|
|
22
|
+
metadata = parseTypedTaskFrontmatter(content);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
return { ok: false, code: 'TASK_DOCUMENT_INVALID', message: error instanceof Error ? error.message : String(error) };
|
|
25
|
+
}
|
|
26
|
+
if (!isValidAgentInfraVersion(metadata.agent_infra_version)) {
|
|
27
|
+
return {
|
|
28
|
+
ok: false,
|
|
29
|
+
code: 'TASK_CURRENT_CONTRACT_INVALID',
|
|
30
|
+
message: `agent_infra_version must be a valid v-prefixed semver (received ${String(metadata.agent_infra_version ?? 'missing')})`
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let ledger: LedgerDocument;
|
|
35
|
+
try {
|
|
36
|
+
ledger = parseLedgerDocument(content);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
return { ok: false, code: 'TASK_DOCUMENT_INVALID', message: error instanceof Error ? error.message : String(error) };
|
|
39
|
+
}
|
|
40
|
+
if (!ledger.present) {
|
|
41
|
+
return { ok: false, code: 'TASK_CURRENT_CONTRACT_INVALID', message: `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}` };
|
|
42
|
+
}
|
|
43
|
+
const invalid = validateLedgerRows(ledger.rows);
|
|
44
|
+
if (invalid) return { ok: false, code: 'TASK_CURRENT_CONTRACT_INVALID', message: `${invalid.code}: ${invalid.message}` };
|
|
45
|
+
return { ok: true, metadata, ledger };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { validateCurrentTaskContract };
|
|
49
|
+
export type { CurrentTaskContractErrorCode, CurrentTaskContractResult };
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
type SourceLine = {
|
|
2
|
+
start: number;
|
|
3
|
+
end: number;
|
|
4
|
+
text: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
type VisibleHeading = SourceLine & {
|
|
8
|
+
level: number;
|
|
9
|
+
text: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type VisibleAnchor = SourceLine & {
|
|
13
|
+
id: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type VisibleMarkdown = {
|
|
17
|
+
lines: SourceLine[];
|
|
18
|
+
headings: VisibleHeading[];
|
|
19
|
+
anchors: VisibleAnchor[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type DecisionDetailBlock = {
|
|
23
|
+
id: string;
|
|
24
|
+
heading: string;
|
|
25
|
+
canonical: boolean;
|
|
26
|
+
start: number;
|
|
27
|
+
end: number;
|
|
28
|
+
content: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type DecisionDetailResolution =
|
|
32
|
+
| { status: 'found'; block: DecisionDetailBlock }
|
|
33
|
+
| { status: 'missing'; reason: string }
|
|
34
|
+
| { status: 'ambiguous'; reason: string; candidates: DecisionDetailBlock[] };
|
|
35
|
+
|
|
36
|
+
type DecisionDetailDuplicate = {
|
|
37
|
+
id: string;
|
|
38
|
+
blocks: DecisionDetailBlock[];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
type DecisionDetailInspection =
|
|
42
|
+
| { ok: true; blocks: DecisionDetailBlock[] }
|
|
43
|
+
| {
|
|
44
|
+
ok: false;
|
|
45
|
+
code: 'DECISION_DETAIL_DUPLICATE';
|
|
46
|
+
message: string;
|
|
47
|
+
blocks: DecisionDetailBlock[];
|
|
48
|
+
duplicates: DecisionDetailDuplicate[];
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const DECISION_ID = /^(AN|PL|CD|HD)-[1-9]\d*(?=$|[\s::])/i;
|
|
52
|
+
const HEADING = /^ {0,3}(#{1,6})[ \t]+(.+?)\s*$/;
|
|
53
|
+
const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})(.*)$/;
|
|
54
|
+
const FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})\s*$/;
|
|
55
|
+
const ANCHOR = /^\s*<a\s+id=(['"])([^'"]+)\1\s*><\/a>\s*$/;
|
|
56
|
+
const FIELD = /^\s*[-*]\s+\*\*([^*]+)\*\*\s*[::]\s*/;
|
|
57
|
+
const OPTION_HEADING = /^(?:方案|Option)\s+[A-Z](?:\b|\s*[::])/i;
|
|
58
|
+
|
|
59
|
+
function sourceLines(content: string): SourceLine[] {
|
|
60
|
+
const lines: SourceLine[] = [];
|
|
61
|
+
let start = 0;
|
|
62
|
+
while (start < content.length) {
|
|
63
|
+
const newline = content.indexOf('\n', start);
|
|
64
|
+
const end = newline === -1 ? content.length : newline;
|
|
65
|
+
lines.push({ start, end, text: content.slice(start, end).replace(/\r$/, '') });
|
|
66
|
+
start = newline === -1 ? content.length : newline + 1;
|
|
67
|
+
}
|
|
68
|
+
return lines;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function scanVisibleMarkdown(content: string): VisibleMarkdown {
|
|
72
|
+
const lines = sourceLines(content);
|
|
73
|
+
const visible: SourceLine[] = [];
|
|
74
|
+
const headings: VisibleHeading[] = [];
|
|
75
|
+
const anchors: VisibleAnchor[] = [];
|
|
76
|
+
let fence: { character: '`' | '~'; length: number } | null = null;
|
|
77
|
+
|
|
78
|
+
for (const line of lines) {
|
|
79
|
+
if (fence) {
|
|
80
|
+
const close = line.text.match(FENCE_CLOSE);
|
|
81
|
+
if (close && close[1]![0] === fence.character && close[1]!.length >= fence.length) fence = null;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const open = line.text.match(FENCE_OPEN);
|
|
85
|
+
const marker = open?.[1];
|
|
86
|
+
const info = open?.[2] ?? '';
|
|
87
|
+
if (marker && !(marker[0] === '`' && info.includes('`'))) {
|
|
88
|
+
fence = { character: marker[0] as '`' | '~', length: marker.length };
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
visible.push(line);
|
|
92
|
+
const heading = line.text.match(HEADING);
|
|
93
|
+
if (heading) headings.push({ ...line, level: heading[1]!.length, text: heading[2]!.trim() });
|
|
94
|
+
const anchor = line.text.match(ANCHOR);
|
|
95
|
+
if (anchor) anchors.push({ ...line, id: anchor[2]! });
|
|
96
|
+
}
|
|
97
|
+
return { lines: visible, headings, anchors };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function parseDecisionDetailBlocks(content: string): DecisionDetailBlock[] {
|
|
101
|
+
const scanned = scanVisibleMarkdown(content);
|
|
102
|
+
const details = scanned.headings.filter((heading) => heading.level === 3 && DECISION_ID.test(heading.text));
|
|
103
|
+
return details.map((heading) => {
|
|
104
|
+
const next = scanned.headings.find((candidate) => (
|
|
105
|
+
candidate.start > heading.start && candidate.level <= 3
|
|
106
|
+
));
|
|
107
|
+
const end = next?.start ?? content.length;
|
|
108
|
+
return {
|
|
109
|
+
id: heading.text.match(DECISION_ID)![0]!,
|
|
110
|
+
heading: heading.text,
|
|
111
|
+
canonical: /\[needs-human-decision\]/i.test(heading.text),
|
|
112
|
+
start: heading.start,
|
|
113
|
+
end,
|
|
114
|
+
content: content.slice(heading.start, end).trim()
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function parseDecisionEvidence(evidence: string): { ok: true; artifact: string; anchor: string } | { ok: false } {
|
|
120
|
+
const separator = evidence.indexOf('#');
|
|
121
|
+
if (separator <= 0 || evidence.indexOf('#', separator + 1) !== -1) return { ok: false };
|
|
122
|
+
const artifact = evidence.slice(0, separator);
|
|
123
|
+
const anchor = evidence.slice(separator + 1);
|
|
124
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*\.md$/.test(artifact)) return { ok: false };
|
|
125
|
+
if (!anchor || /[\s\u0000-\u001f#]/.test(anchor)) return { ok: false };
|
|
126
|
+
return { ok: true, artifact, anchor };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function sameId(left: string, right: string): boolean {
|
|
130
|
+
return left.toUpperCase() === right.toUpperCase();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function resolveDecisionDetail(content: string, rowId: string, anchor: string): DecisionDetailResolution {
|
|
134
|
+
const scanned = scanVisibleMarkdown(content);
|
|
135
|
+
const blocks = parseDecisionDetailBlocks(content);
|
|
136
|
+
const explicit = scanned.anchors.filter((candidate) => candidate.id === anchor);
|
|
137
|
+
if (explicit.length > 1) {
|
|
138
|
+
return { status: 'ambiguous', reason: `evidence anchor '${anchor}' occurs more than once`, candidates: [] };
|
|
139
|
+
}
|
|
140
|
+
if (explicit.length === 1) {
|
|
141
|
+
const marker = explicit[0]!;
|
|
142
|
+
const followingLine = scanned.lines.find((line) => line.start > marker.end && line.text.trim());
|
|
143
|
+
const followingHeading = followingLine
|
|
144
|
+
? scanned.headings.find((heading) => heading.start === followingLine.start)
|
|
145
|
+
: undefined;
|
|
146
|
+
const candidate = followingHeading?.level === 3
|
|
147
|
+
? blocks.find((block) => block.start === followingHeading.start)
|
|
148
|
+
: undefined;
|
|
149
|
+
if (!candidate || !sameId(candidate.id, rowId) || !candidate.canonical) {
|
|
150
|
+
return { status: 'missing', reason: `evidence anchor '${anchor}' does not identify a canonical ${rowId} detail` };
|
|
151
|
+
}
|
|
152
|
+
return { status: 'found', block: candidate };
|
|
153
|
+
}
|
|
154
|
+
if (!sameId(anchor, rowId)) {
|
|
155
|
+
return { status: 'missing', reason: `evidence anchor '${anchor}' is not the ledger id '${rowId}'` };
|
|
156
|
+
}
|
|
157
|
+
const matches = blocks.filter((block) => sameId(block.id, rowId) && block.canonical);
|
|
158
|
+
if (matches.length === 0) return { status: 'missing', reason: `no canonical ${rowId} detail was found` };
|
|
159
|
+
if (matches.length > 1) {
|
|
160
|
+
return { status: 'ambiguous', reason: `more than one canonical ${rowId} detail was found`, candidates: matches };
|
|
161
|
+
}
|
|
162
|
+
return { status: 'found', block: matches[0]! };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function inspectDecisionDetailDuplicates(content: string): DecisionDetailInspection {
|
|
166
|
+
const blocks = parseDecisionDetailBlocks(content);
|
|
167
|
+
const grouped = new Map<string, DecisionDetailBlock[]>();
|
|
168
|
+
for (const block of blocks) {
|
|
169
|
+
const key = block.id.toUpperCase();
|
|
170
|
+
const group = grouped.get(key) ?? [];
|
|
171
|
+
group.push(block);
|
|
172
|
+
grouped.set(key, group);
|
|
173
|
+
}
|
|
174
|
+
const duplicates: DecisionDetailDuplicate[] = [];
|
|
175
|
+
for (const group of grouped.values()) {
|
|
176
|
+
if (group.length < 2) continue;
|
|
177
|
+
duplicates.push({ id: group[0]!.id, blocks: group });
|
|
178
|
+
}
|
|
179
|
+
if (duplicates.length === 0) return { ok: true, blocks };
|
|
180
|
+
const summary = duplicates.map((duplicate) => {
|
|
181
|
+
const types = duplicate.blocks.map((block) => block.canonical ? 'canonical' : 'informal').join(', ');
|
|
182
|
+
return `${duplicate.id} (${duplicate.blocks.length} blocks: ${types})`;
|
|
183
|
+
}).join(', ');
|
|
184
|
+
return {
|
|
185
|
+
ok: false,
|
|
186
|
+
code: 'DECISION_DETAIL_DUPLICATE',
|
|
187
|
+
message: `Duplicate decision detail ids: ${summary}`,
|
|
188
|
+
blocks,
|
|
189
|
+
duplicates
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
type FieldKey = 'decision' | 'why' | 'impact' | 'risk' | 'analogy' | 'recommend' | 'reason' | 'implementation';
|
|
194
|
+
|
|
195
|
+
const FIELD_KEYS: Record<string, FieldKey> = {
|
|
196
|
+
'为什么现在要决定': 'why', 'why decide now': 'why', 'why a decision is needed now': 'why',
|
|
197
|
+
'要决定什么': 'decision', 'what to decide': 'decision', 'what needs a decision': 'decision',
|
|
198
|
+
'会影响什么': 'impact', 'what it affects': 'impact', 'what this affects': 'impact',
|
|
199
|
+
'如果选错或暂不处理': 'risk', 'if wrong or postponed': 'risk',
|
|
200
|
+
'what could go wrong': 'risk',
|
|
201
|
+
'打个比方': 'analogy', '例子': 'analogy', 'example': 'analogy',
|
|
202
|
+
'建议选哪个': 'recommend', 'recommended option': 'recommend', 'recommended choice': 'recommend',
|
|
203
|
+
'为什么这样建议': 'reason', 'why this recommendation': 'reason', 'why this choice': 'reason',
|
|
204
|
+
'是否需要实现': 'implementation', 'implementation required': 'implementation'
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const TERMINAL_FIELD_KEYS = new Set<FieldKey>(['recommend', 'reason', 'implementation']);
|
|
208
|
+
|
|
209
|
+
function normalizeDecisionDetailForDisplay(block: DecisionDetailBlock): string {
|
|
210
|
+
const body = block.content;
|
|
211
|
+
const scanned = scanVisibleMarkdown(body);
|
|
212
|
+
const heading = scanned.headings.find((candidate) => candidate.level === 3);
|
|
213
|
+
if (!heading) return body.trim();
|
|
214
|
+
const options = scanned.headings.filter((candidate) => candidate.level === 4 && OPTION_HEADING.test(candidate.text));
|
|
215
|
+
const optionRanges = options.map((option, index) => ({
|
|
216
|
+
start: option.start,
|
|
217
|
+
end: options[index + 1]?.start
|
|
218
|
+
?? scanned.headings.find((candidate) => candidate.start > option.start && candidate.level <= 3)?.start
|
|
219
|
+
?? scanned.lines.find((line) => (
|
|
220
|
+
line.start > option.start
|
|
221
|
+
&& FIELD.test(line.text)
|
|
222
|
+
&& TERMINAL_FIELD_KEYS.has(FIELD_KEYS[line.text.match(FIELD)![1]!.trim().toLowerCase()] as FieldKey)
|
|
223
|
+
))?.start
|
|
224
|
+
?? body.length
|
|
225
|
+
}));
|
|
226
|
+
const topLines = scanned.lines.filter((line) => (
|
|
227
|
+
line.start > heading.end && !optionRanges.some((range) => line.start >= range.start && line.start < range.end)
|
|
228
|
+
));
|
|
229
|
+
const fields: { key: FieldKey | null; line: SourceLine }[] = [];
|
|
230
|
+
for (const line of topLines) {
|
|
231
|
+
const match = line.text.match(FIELD);
|
|
232
|
+
if (!match) continue;
|
|
233
|
+
const key = FIELD_KEYS[match[1]!.trim().toLowerCase()];
|
|
234
|
+
fields.push({ key: key ?? null, line });
|
|
235
|
+
}
|
|
236
|
+
if (fields.some((field) => field.key === null)) return body.trim();
|
|
237
|
+
const byKey = new Map<FieldKey, string>();
|
|
238
|
+
const selectedRanges: { start: number; end: number }[] = [];
|
|
239
|
+
for (let index = 0; index < fields.length; index += 1) {
|
|
240
|
+
const field = fields[index]!;
|
|
241
|
+
if (!field.key) continue;
|
|
242
|
+
const nextOption = optionRanges.find((range) => range.start > field.line.start)?.start ?? body.length;
|
|
243
|
+
const end = Math.min(fields[index + 1]?.line.start ?? body.length, nextOption);
|
|
244
|
+
const value = body.slice(field.line.start, end).trim();
|
|
245
|
+
byKey.set(field.key, value);
|
|
246
|
+
selectedRanges.push({ start: field.line.start, end });
|
|
247
|
+
}
|
|
248
|
+
selectedRanges.push(...optionRanges);
|
|
249
|
+
const required: FieldKey[] = ['decision', 'why', 'impact', 'recommend', 'reason'];
|
|
250
|
+
if (required.some((key) => !byKey.has(key)) || options.length < 2) return body.trim();
|
|
251
|
+
const sortedRanges = selectedRanges.sort((left, right) => left.start - right.start);
|
|
252
|
+
let cursor = heading.end;
|
|
253
|
+
for (const range of sortedRanges) {
|
|
254
|
+
if (/\S/.test(body.slice(cursor, range.start))) return body.trim();
|
|
255
|
+
cursor = Math.max(cursor, range.end);
|
|
256
|
+
}
|
|
257
|
+
if (/\S/.test(body.slice(cursor))) return body.trim();
|
|
258
|
+
const ordered: string[] = [
|
|
259
|
+
byKey.get('decision')!, byKey.get('why')!, byKey.get('impact')!
|
|
260
|
+
];
|
|
261
|
+
for (const optional of ['risk', 'analogy'] as const) if (byKey.has(optional)) ordered.push(byKey.get(optional)!);
|
|
262
|
+
ordered.push(...optionRanges.map((range) => body.slice(range.start, range.end).trim()));
|
|
263
|
+
ordered.push(byKey.get('recommend')!, byKey.get('reason')!);
|
|
264
|
+
if (byKey.has('implementation')) ordered.push(byKey.get('implementation')!);
|
|
265
|
+
return [body.slice(heading.start, heading.end).trim(), ...ordered].join('\n\n').trim();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export {
|
|
269
|
+
inspectDecisionDetailDuplicates,
|
|
270
|
+
normalizeDecisionDetailForDisplay,
|
|
271
|
+
parseDecisionDetailBlocks,
|
|
272
|
+
parseDecisionEvidence,
|
|
273
|
+
resolveDecisionDetail
|
|
274
|
+
};
|
|
275
|
+
export type {
|
|
276
|
+
DecisionDetailBlock,
|
|
277
|
+
DecisionDetailDuplicate,
|
|
278
|
+
DecisionDetailInspection,
|
|
279
|
+
DecisionDetailResolution
|
|
280
|
+
};
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
renderImplementationInputs,
|
|
11
11
|
selectDeclaredImplementationInput
|
|
12
12
|
} from './implementation-inputs.ts';
|
|
13
|
-
import { LEDGER_COLUMNS, LEDGER_HEADINGS,
|
|
13
|
+
import { LEDGER_COLUMNS, LEDGER_HEADINGS, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument } from './ledger.ts';
|
|
14
14
|
import type { LedgerRow } from './ledger.ts';
|
|
15
15
|
import { resolveTaskContext } from './resolve-ref.ts';
|
|
16
16
|
import { extractSection, extractSubSection, findSectionHeading } from './sections.ts';
|
|
@@ -90,7 +90,12 @@ function applyHumanDecision(request: HumanDecisionRequest, options: TaskWriteOpt
|
|
|
90
90
|
if (resolved.state !== 'active' && !stateOverride) return failed('TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
91
91
|
let content: string;
|
|
92
92
|
let rows: LedgerRow[];
|
|
93
|
-
try {
|
|
93
|
+
try {
|
|
94
|
+
content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
95
|
+
const ledger = parseLedgerDocument(content);
|
|
96
|
+
if (!ledger.present) return failed(LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, resolved.taskId);
|
|
97
|
+
rows = ledger.rows as LedgerRow[];
|
|
98
|
+
}
|
|
94
99
|
catch (error) { return failed('DECISION_DOCUMENT_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId); }
|
|
95
100
|
|
|
96
101
|
const direct = rows.filter((row) => row.id.toUpperCase() === request.selector.toUpperCase());
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
|
|
5
|
+
export type DeliveryTarget = Readonly<{
|
|
6
|
+
remote: string;
|
|
7
|
+
baseRef: string;
|
|
8
|
+
}>;
|
|
9
|
+
|
|
10
|
+
export type DeliveryTargetBindingInput = Readonly<{
|
|
11
|
+
defaults?: Partial<DeliveryTarget>;
|
|
12
|
+
existing?: Partial<DeliveryTarget>;
|
|
13
|
+
explicit?: Partial<DeliveryTarget>;
|
|
14
|
+
}>;
|
|
15
|
+
|
|
16
|
+
export type DeliveryTargetResult =
|
|
17
|
+
| { ok: true; value: DeliveryTarget }
|
|
18
|
+
| { ok: false; code: string; message: string };
|
|
19
|
+
|
|
20
|
+
export type DiffBaseResult =
|
|
21
|
+
| { ok: true; diffBase: string }
|
|
22
|
+
| { ok: false; code: string; message: string };
|
|
23
|
+
|
|
24
|
+
const REMOTE_RE = /^(?!-)(?!.*\.\.)(?!.*[~^:?*\\\s])[A-Za-z0-9._/-]+$/;
|
|
25
|
+
const REF_RE = /^(?!-)(?!.*\.\.)(?!.*(?:^|\/)\.)(?!.*[~^:?*\\\s])[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)*$/;
|
|
26
|
+
|
|
27
|
+
function validateRemote(value: unknown): value is string {
|
|
28
|
+
return typeof value === 'string' && value.length > 0 && REMOTE_RE.test(value);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function validateBaseRef(value: unknown): value is string {
|
|
32
|
+
return typeof value === 'string' && value.length > 0 && !value.startsWith('refs/') && REF_RE.test(value);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function invalid(field: string, value: unknown): DeliveryTargetResult {
|
|
36
|
+
return { ok: false, code: 'DELIVERY_TARGET_INVALID', message: `${field} is invalid: ${String(value ?? '')}` };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function bindDeliveryTarget(input: DeliveryTargetBindingInput): DeliveryTargetResult {
|
|
40
|
+
const remote = input.existing?.remote ?? input.explicit?.remote ?? input.defaults?.remote;
|
|
41
|
+
const baseRef = input.existing?.baseRef ?? input.explicit?.baseRef ?? input.defaults?.baseRef;
|
|
42
|
+
if (input.existing?.remote !== undefined && input.explicit?.remote !== undefined && input.existing.remote !== input.explicit.remote) {
|
|
43
|
+
return { ok: false, code: 'DELIVERY_TARGET_CONFLICT', message: 'Explicit delivery remote does not match the task binding' };
|
|
44
|
+
}
|
|
45
|
+
if (input.existing?.baseRef !== undefined && input.explicit?.baseRef !== undefined && input.existing.baseRef !== input.explicit.baseRef) {
|
|
46
|
+
return { ok: false, code: 'DELIVERY_TARGET_CONFLICT', message: 'Explicit delivery base ref does not match the task binding' };
|
|
47
|
+
}
|
|
48
|
+
if (!validateRemote(remote)) return invalid('delivery remote', remote);
|
|
49
|
+
if (!validateBaseRef(baseRef)) return invalid('delivery base ref', baseRef);
|
|
50
|
+
return { ok: true, value: { remote, baseRef } };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function readDeliveryDefaults(repoRoot: string): DeliveryTargetResult {
|
|
54
|
+
try {
|
|
55
|
+
const configPath = path.join(repoRoot, '.agents', '.airc.json');
|
|
56
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8')) as Record<string, unknown>;
|
|
57
|
+
if (!config.delivery || typeof config.delivery !== 'object' || Array.isArray(config.delivery)) {
|
|
58
|
+
return { ok: false, code: 'DELIVERY_CONFIG_INVALID', message: 'delivery config must declare remote and baseRef' };
|
|
59
|
+
}
|
|
60
|
+
const delivery = config.delivery as Record<string, unknown>;
|
|
61
|
+
if (typeof delivery.remote !== 'string' || typeof delivery.baseRef !== 'string') {
|
|
62
|
+
return { ok: false, code: 'DELIVERY_CONFIG_INVALID', message: 'delivery config must declare remote and baseRef' };
|
|
63
|
+
}
|
|
64
|
+
return bindDeliveryTarget({
|
|
65
|
+
defaults: {
|
|
66
|
+
remote: delivery.remote,
|
|
67
|
+
baseRef: delivery.baseRef
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return { ok: false, code: 'DELIVERY_CONFIG_INVALID', message: error instanceof Error ? error.message : String(error) };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function resolveDeliveryTarget(
|
|
76
|
+
repoRoot: string,
|
|
77
|
+
existing: Partial<DeliveryTarget> = {},
|
|
78
|
+
explicit: Partial<DeliveryTarget> = {}
|
|
79
|
+
): DeliveryTargetResult {
|
|
80
|
+
const defaults = readDeliveryDefaults(repoRoot);
|
|
81
|
+
if (!defaults.ok) return defaults;
|
|
82
|
+
return bindDeliveryTarget({ defaults: defaults.value, existing, explicit });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function gitText(repoRoot: string, args: readonly string[]): string {
|
|
86
|
+
return execFileSync('git', [...args], {
|
|
87
|
+
cwd: repoRoot,
|
|
88
|
+
encoding: 'utf8',
|
|
89
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
90
|
+
}).trim();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function resolveTargetHead(repoRoot: string, target: DeliveryTarget): DeliveryTargetResult & { head?: string } {
|
|
94
|
+
try {
|
|
95
|
+
const output = gitText(repoRoot, ['ls-remote', '--refs', target.remote, `refs/heads/${target.baseRef}`]);
|
|
96
|
+
const head = output.split(/\s+/)[0] ?? '';
|
|
97
|
+
if (!/^[a-f0-9]{40}$/i.test(head)) {
|
|
98
|
+
return { ok: false, code: 'DELIVERY_TARGET_UNAVAILABLE', message: `Delivery target ${target.remote}/${target.baseRef} is unavailable` };
|
|
99
|
+
}
|
|
100
|
+
try { gitText(repoRoot, ['cat-file', '-e', `${head}^{commit}`]); }
|
|
101
|
+
catch {
|
|
102
|
+
return { ok: false, code: 'DELIVERY_TARGET_OBJECT_MISSING', message: `Delivery target commit ${head} is not available locally` };
|
|
103
|
+
}
|
|
104
|
+
return { ok: true, value: target, head };
|
|
105
|
+
} catch (error) {
|
|
106
|
+
return { ok: false, code: 'DELIVERY_TARGET_UNAVAILABLE', message: error instanceof Error ? error.message : String(error) };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function resolveDiffBase(repoRoot: string, reviewedHead: string, targetHead: string): DiffBaseResult {
|
|
111
|
+
try {
|
|
112
|
+
const diffBase = gitText(repoRoot, ['merge-base', reviewedHead, targetHead]);
|
|
113
|
+
if (!/^[a-f0-9]{40}$/i.test(diffBase)) {
|
|
114
|
+
return { ok: false, code: 'DELIVERY_DIFF_BASE_UNAVAILABLE', message: 'Git returned an invalid diff base' };
|
|
115
|
+
}
|
|
116
|
+
return { ok: true, diffBase };
|
|
117
|
+
} catch (error) {
|
|
118
|
+
return { ok: false, code: 'DELIVERY_DIFF_BASE_UNAVAILABLE', message: error instanceof Error ? error.message : String(error) };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export {
|
|
123
|
+
bindDeliveryTarget,
|
|
124
|
+
readDeliveryDefaults,
|
|
125
|
+
resolveDeliveryTarget,
|
|
126
|
+
resolveTargetHead,
|
|
127
|
+
resolveDiffBase,
|
|
128
|
+
validateBaseRef,
|
|
129
|
+
validateRemote
|
|
130
|
+
};
|