@fitlab-ai/agent-infra 0.9.2 → 0.9.4
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 +12 -9
- package/README.zh-CN.md +12 -9
- package/bin/cli.ts +12 -0
- package/dist/bin/cli.js +14 -0
- package/dist/bin/internal-cli.js +134 -108
- package/dist/lib/agent-clients/adapter.js +137 -4
- package/dist/lib/agent-clients/adapters/antigravity-cli.js +64 -0
- package/dist/lib/{sandbox/credentials.js → agent-clients/adapters/claude-code-credentials.js} +4 -14
- package/dist/lib/agent-clients/adapters/claude-code-refresh.js +85 -0
- package/dist/lib/agent-clients/adapters/claude-code-sandbox.js +247 -0
- package/dist/lib/agent-clients/adapters/claude-code.js +27 -4
- package/dist/lib/agent-clients/adapters/codex.js +9 -0
- package/dist/lib/agent-clients/adapters/index.js +2 -2
- package/dist/lib/agent-clients/adapters/opencode-sandbox.js +108 -0
- package/dist/lib/agent-clients/adapters/opencode.js +24 -3
- package/dist/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
- package/dist/lib/agent-clients/config.js +42 -4
- package/dist/lib/agent-clients/next-steps.js +11 -2
- package/dist/lib/agent-clients/reconcile.js +2 -1
- package/dist/lib/agent-clients/registry.js +16 -2
- package/dist/lib/agent-clients/schema.js +18 -0
- package/dist/lib/agent-clients/tokens.js +49 -0
- package/dist/lib/agent-clients/types.js +1 -1
- package/dist/lib/defaults.json +1 -1
- package/dist/lib/git/workflow.js +54 -1
- package/dist/lib/internal/agent-client.js +61 -4
- package/dist/lib/internal/git-workflow.js +6 -1
- package/dist/lib/internal/platform-checks.js +3 -3
- package/dist/lib/internal/platform-comment.js +29 -2
- package/dist/lib/internal/platform-issue.js +7 -0
- package/dist/lib/internal/platform-pr-review.js +132 -0
- package/dist/lib/internal/platform-pr.js +20 -2
- package/dist/lib/internal/platform-release-notes.js +52 -16
- package/dist/lib/internal/pr-review-grade.js +140 -0
- package/dist/lib/internal/release-workflow.js +13 -6
- package/dist/lib/internal/sandbox-control.js +24 -0
- package/dist/lib/internal/task-activity.js +105 -0
- package/dist/lib/internal/task-event.js +17 -1
- package/dist/lib/internal/task-ledger.js +12 -3
- package/dist/lib/internal/task-lifecycle.js +26 -1
- package/dist/lib/internal/task-orchestration.js +167 -18
- package/dist/lib/internal/task-review.js +8 -3
- package/dist/lib/platform/adapters.js +8 -1
- package/dist/lib/platform/completion-backfill.js +122 -0
- package/dist/lib/platform/github-client.js +19 -2
- package/dist/lib/platform/issue-comments.js +3 -2
- package/dist/lib/platform/merged-pr-equivalence.js +115 -5
- package/dist/lib/platform/pr-checks.js +65 -42
- package/dist/lib/platform/pr-review.js +166 -0
- package/dist/lib/platform/pull-requests.js +371 -4
- package/dist/lib/platform/release-notes.js +85 -3
- package/dist/lib/platform/releases.js +13 -5
- package/dist/lib/pr-review/evidence-grading.js +229 -0
- package/dist/lib/process-data/index.js +239 -0
- package/dist/lib/process-data/normalize.js +198 -0
- package/dist/lib/process-data/privacy.js +33 -0
- package/dist/lib/process-data/reconcile.js +95 -0
- package/dist/lib/process-data/repair.js +58 -0
- package/dist/lib/process-data/sources.js +345 -0
- package/dist/lib/process-data/store.js +282 -0
- package/dist/lib/process-data/types.js +2 -0
- package/dist/lib/run/index.js +4 -3
- package/dist/lib/run/tui.js +10 -13
- package/dist/lib/sandbox/agent-client-reconciler.js +26 -2
- package/dist/lib/sandbox/capture.js +7 -2
- package/dist/lib/sandbox/commands/create.js +109 -248
- package/dist/lib/sandbox/commands/enter.js +15 -47
- package/dist/lib/sandbox/commands/list-running.js +16 -6
- package/dist/lib/sandbox/commands/ls.js +10 -6
- package/dist/lib/sandbox/commands/rebuild.js +4 -3
- package/dist/lib/sandbox/commands/refresh.js +4 -84
- package/dist/lib/sandbox/commands/rm.js +24 -3
- package/dist/lib/sandbox/commands/show.js +13 -4
- package/dist/lib/sandbox/commands/start.js +7 -4
- package/dist/lib/sandbox/config.js +2 -0
- package/dist/lib/sandbox/constants.js +6 -0
- package/dist/lib/sandbox/control/client.js +44 -0
- package/dist/lib/sandbox/control/protocol.js +35 -0
- package/dist/lib/sandbox/control/server.js +109 -0
- package/dist/lib/sandbox/dockerfile.js +36 -13
- package/dist/lib/sandbox/mounts.js +17 -4
- package/dist/lib/sandbox/recovery.js +128 -5
- package/dist/lib/sandbox/redaction.js +12 -0
- package/dist/lib/sandbox/runtimes/base.dockerfile +24 -74
- package/dist/lib/sandbox/workspace-identity.js +95 -0
- package/dist/lib/sandbox/workspace-view.js +103 -0
- package/dist/lib/task/activity-intent.js +307 -0
- package/dist/lib/task/artifact-lifecycle.js +2 -1
- package/dist/lib/task/commands/log.js +15 -14
- package/dist/lib/task/commit-finalization.js +267 -0
- package/dist/lib/task/commit-intent.js +165 -0
- package/dist/lib/task/decision-intents.js +31 -17
- package/dist/lib/task/delegation-receipts.js +25 -4
- package/dist/lib/task/events.js +67 -28
- package/dist/lib/task/finalization-artifacts.js +35 -0
- package/dist/lib/task/implementation-inputs.js +54 -7
- package/dist/lib/task/ledger-intents.js +35 -1
- package/dist/lib/task/lifecycle-execution.js +46 -0
- package/dist/lib/task/orchestration.js +753 -54
- package/dist/lib/task/resolve-ref.js +41 -1
- package/dist/lib/task/review-finalization.js +29 -9
- package/dist/lib/task/task-execution-lock.js +231 -0
- package/dist/lib/task/verification-checks.js +1 -1
- package/dist/lib/task/verification-engine.js +218 -2
- package/dist/lib/task/verification.js +4 -3
- package/dist/lib/task/workspace-snapshot.js +17 -5
- package/lib/agent-clients/adapter.ts +201 -4
- package/lib/agent-clients/adapters/antigravity-cli.ts +65 -0
- package/lib/{sandbox/credentials.ts → agent-clients/adapters/claude-code-credentials.ts} +3 -18
- package/lib/agent-clients/adapters/claude-code-refresh.ts +128 -0
- package/lib/agent-clients/adapters/claude-code-sandbox.ts +295 -0
- package/lib/agent-clients/adapters/claude-code.ts +31 -4
- package/lib/agent-clients/adapters/codex.ts +9 -0
- package/lib/agent-clients/adapters/index.ts +2 -2
- package/lib/agent-clients/adapters/opencode-sandbox.ts +136 -0
- package/lib/agent-clients/adapters/opencode.ts +27 -3
- package/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
- package/lib/agent-clients/config.ts +49 -4
- package/lib/agent-clients/next-steps.ts +15 -2
- package/lib/agent-clients/reconcile.ts +2 -1
- package/lib/agent-clients/registry.ts +19 -1
- package/lib/agent-clients/schema.ts +18 -0
- package/lib/agent-clients/tokens.ts +53 -0
- package/lib/agent-clients/types.ts +15 -1
- package/lib/defaults.json +1 -1
- package/lib/git/workflow.ts +66 -2
- package/lib/internal/agent-client.ts +59 -4
- package/lib/internal/git-workflow.ts +5 -1
- package/lib/internal/platform-checks.ts +4 -4
- package/lib/internal/platform-comment.ts +17 -2
- package/lib/internal/platform-issue.ts +4 -0
- package/lib/internal/platform-pr-review.ts +104 -0
- package/lib/internal/platform-pr.ts +14 -1
- package/lib/internal/platform-release-notes.ts +59 -16
- package/lib/internal/pr-review-grade.ts +130 -0
- package/lib/internal/release-workflow.ts +13 -6
- package/lib/internal/sandbox-control.ts +24 -0
- package/lib/internal/task-activity.ts +91 -0
- package/lib/internal/task-event.ts +9 -1
- package/lib/internal/task-ledger.ts +12 -3
- package/lib/internal/task-lifecycle.ts +26 -1
- package/lib/internal/task-orchestration.ts +123 -14
- package/lib/internal/task-review.ts +8 -3
- package/lib/platform/adapters.ts +29 -2
- package/lib/platform/completion-backfill.ts +134 -0
- package/lib/platform/github-client.ts +20 -2
- package/lib/platform/issue-comments.ts +3 -2
- package/lib/platform/merged-pr-equivalence.ts +127 -5
- package/lib/platform/pr-checks.ts +73 -46
- package/lib/platform/pr-review.ts +201 -0
- package/lib/platform/pull-requests.ts +444 -3
- package/lib/platform/release-notes.ts +100 -4
- package/lib/platform/releases.ts +16 -4
- package/lib/pr-review/evidence-grading.ts +318 -0
- package/lib/process-data/index.ts +224 -0
- package/lib/process-data/normalize.ts +202 -0
- package/lib/process-data/privacy.ts +41 -0
- package/lib/process-data/reconcile.ts +110 -0
- package/lib/process-data/repair.ts +64 -0
- package/lib/process-data/sources.ts +354 -0
- package/lib/process-data/store.ts +315 -0
- package/lib/process-data/types.ts +135 -0
- package/lib/run/index.ts +5 -3
- package/lib/run/tui.ts +14 -12
- package/lib/sandbox/agent-client-reconciler.ts +44 -2
- package/lib/sandbox/capture.ts +9 -2
- package/lib/sandbox/commands/create.ts +126 -280
- package/lib/sandbox/commands/enter.ts +13 -59
- package/lib/sandbox/commands/list-running.ts +23 -6
- package/lib/sandbox/commands/ls.ts +22 -6
- package/lib/sandbox/commands/rebuild.ts +4 -3
- package/lib/sandbox/commands/refresh.ts +4 -126
- package/lib/sandbox/commands/rm.ts +24 -3
- package/lib/sandbox/commands/show.ts +30 -4
- package/lib/sandbox/commands/start.ts +6 -4
- package/lib/sandbox/config.ts +4 -0
- package/lib/sandbox/constants.ts +8 -0
- package/lib/sandbox/control/client.ts +55 -0
- package/lib/sandbox/control/protocol.ts +75 -0
- package/lib/sandbox/control/server.ts +117 -0
- package/lib/sandbox/dockerfile.ts +66 -13
- package/lib/sandbox/mounts.ts +24 -4
- package/lib/sandbox/recovery.ts +163 -6
- package/lib/sandbox/redaction.ts +14 -0
- package/lib/sandbox/runtimes/base.dockerfile +24 -74
- package/lib/sandbox/tool-types.ts +16 -0
- package/lib/sandbox/workspace-identity.ts +123 -0
- package/lib/sandbox/workspace-view.ts +143 -0
- package/lib/server/server.schema.json +2 -2
- package/lib/task/activity-intent.ts +417 -0
- package/lib/task/artifact-lifecycle.ts +2 -1
- package/lib/task/commands/log.ts +15 -14
- package/lib/task/commit-finalization.ts +356 -0
- package/lib/task/commit-intent.ts +230 -0
- package/lib/task/decision-intents.ts +32 -12
- package/lib/task/delegation-receipts.ts +34 -5
- package/lib/task/events.ts +75 -30
- package/lib/task/finalization-artifacts.ts +58 -0
- package/lib/task/implementation-inputs.ts +62 -8
- package/lib/task/ledger-intents.ts +43 -3
- package/lib/task/lifecycle-execution.ts +106 -0
- package/lib/task/orchestration.ts +1019 -72
- package/lib/task/resolve-ref.ts +46 -1
- package/lib/task/review-finalization.ts +40 -9
- package/lib/task/task-execution-lock.ts +296 -0
- package/lib/task/verification-checks.ts +1 -1
- package/lib/task/verification-engine.ts +249 -2
- package/lib/task/verification.ts +5 -3
- package/lib/task/workspace-snapshot.ts +26 -5
- package/package.json +3 -3
- package/templates/.agents/QUICKSTART.en.md +25 -8
- package/templates/.agents/QUICKSTART.zh-CN.md +25 -8
- package/templates/.agents/README.en.md +10 -7
- package/templates/.agents/README.zh-CN.md +10 -7
- package/templates/.agents/hooks/lifecycle-delegation.js +15 -4
- package/templates/.agents/rules/README.en.md +1 -1
- package/templates/.agents/rules/README.zh-CN.md +1 -1
- package/templates/.agents/rules/create-issue.github.en.md +4 -2
- package/templates/.agents/rules/create-issue.github.zh-CN.md +4 -2
- package/templates/.agents/rules/human-decision-context.en.md +11 -2
- package/templates/.agents/rules/human-decision-context.zh-CN.md +11 -2
- package/templates/.agents/rules/issue-fields.github.en.md +3 -1
- package/templates/.agents/rules/issue-fields.github.zh-CN.md +3 -1
- package/templates/.agents/rules/issue-pr-commands.en.md +5 -3
- package/templates/.agents/rules/issue-pr-commands.github.en.md +5 -3
- package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +5 -3
- package/templates/.agents/rules/issue-pr-commands.zh-CN.md +5 -3
- package/templates/.agents/rules/issue-sync.github.en.md +7 -3
- package/templates/.agents/rules/issue-sync.github.zh-CN.md +8 -4
- package/templates/.agents/rules/lifecycle-orchestration.en.md +5 -2
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +5 -2
- package/templates/.agents/rules/milestone-inference.github.en.md +4 -2
- package/templates/.agents/rules/milestone-inference.github.zh-CN.md +4 -2
- package/templates/.agents/rules/next-step-output.en.md +3 -1
- package/templates/.agents/rules/next-step-output.zh-CN.md +3 -1
- package/templates/.agents/rules/no-mid-flow-questions.en.md +17 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +17 -0
- package/templates/.agents/rules/pr-checks-commands.en.md +3 -3
- package/templates/.agents/rules/pr-checks-commands.github.en.md +2 -2
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +3 -3
- package/templates/.agents/rules/pr-checks-commands.zh-CN.md +3 -3
- package/templates/.agents/rules/pr-sync.en.md +3 -1
- package/templates/.agents/rules/pr-sync.github.en.md +11 -1
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +11 -1
- package/templates/.agents/rules/pr-sync.zh-CN.md +3 -1
- package/templates/.agents/rules/release-commands.en.md +1 -1
- package/templates/.agents/rules/release-commands.github.en.md +12 -2
- package/templates/.agents/rules/release-commands.github.zh-CN.md +12 -2
- package/templates/.agents/rules/release-commands.zh-CN.md +1 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
- package/templates/.agents/rules/task-management.en.md +13 -0
- package/templates/.agents/rules/task-management.zh-CN.md +13 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +10 -7
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -7
- package/templates/.agents/skills/block-task/SKILL.en.md +7 -5
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +7 -5
- package/templates/.agents/skills/cancel-task/SKILL.en.md +6 -4
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +6 -4
- package/templates/.agents/skills/close-codescan/SKILL.en.md +3 -1
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +3 -1
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +10 -7
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +12 -9
- package/templates/.agents/skills/commit/SKILL.en.md +23 -11
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +29 -11
- package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +49 -0
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +55 -0
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
- 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 +4 -4
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +4 -4
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +6 -4
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +6 -4
- 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 +22 -7
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +22 -7
- package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +17 -0
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +17 -0
- package/templates/.agents/skills/create-pr/SKILL.en.md +7 -5
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -5
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +1 -1
- package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -16
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +17 -16
- package/templates/.agents/skills/create-task/SKILL.en.md +5 -3
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/create-task/reference/context-capture.en.md +34 -0
- package/templates/.agents/skills/create-task/reference/context-capture.zh-CN.md +34 -0
- package/templates/.agents/skills/import-issue/SKILL.en.md +5 -3
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/plan-task/SKILL.en.md +8 -5
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +8 -5
- package/templates/.agents/skills/post-release/SKILL.en.md +2 -0
- package/templates/.agents/skills/post-release/SKILL.zh-CN.md +2 -0
- package/templates/.agents/skills/release/SKILL.en.md +16 -10
- package/templates/.agents/skills/release/SKILL.zh-CN.md +16 -10
- package/templates/.agents/skills/restore-task/SKILL.en.md +4 -1
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +4 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +7 -4
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +7 -4
- 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/reference/output-templates.en.md +1 -1
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.en.md +7 -4
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +7 -4
- package/templates/.agents/skills/review-pr/SKILL.en.md +186 -0
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +186 -0
- package/templates/.agents/skills/review-pr/config/verify.en.json +37 -0
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +37 -0
- package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +65 -0
- package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +65 -0
- package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +18 -0
- package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +18 -0
- package/templates/.agents/skills/review-pr/reference/report-template.en.md +85 -0
- package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +85 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +8 -8
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/run-task/config/verify.en.json +2 -1
- package/templates/.agents/skills/run-task/config/verify.zh-CN.json +2 -1
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +20 -0
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +20 -0
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +98 -107
- package/templates/.agents/skills/watch-pr/SKILL.en.md +13 -13
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +13 -13
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +27 -6
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +26 -5
- package/templates/.agents/templates/task.en.md +2 -2
- package/templates/.agents/templates/task.zh-CN.md +2 -2
- package/templates/.agents/workflows/bug-fix.en.yaml +3 -3
- package/templates/.agents/workflows/bug-fix.zh-CN.yaml +3 -3
- package/templates/.agents/workflows/code-review.en.yaml +1 -1
- package/templates/.agents/workflows/code-review.zh-CN.yaml +1 -1
- package/templates/.agents/workflows/feature-development.en.yaml +4 -4
- package/templates/.agents/workflows/feature-development.zh-CN.yaml +4 -4
- package/templates/.agents/workflows/refactoring.en.yaml +3 -3
- package/templates/.agents/workflows/refactoring.zh-CN.yaml +3 -3
- package/templates/.claude/commands/post-release.en.md +1 -0
- package/templates/.claude/commands/post-release.zh-CN.md +1 -0
- package/templates/.claude/commands/review-pr.en.md +8 -0
- package/templates/.claude/commands/review-pr.zh-CN.md +8 -0
- package/templates/.claude/commands/watch-pr.en.md +1 -1
- package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
- package/templates/.claude/rules/tool-preferences.en.md +20 -0
- package/templates/.claude/rules/tool-preferences.zh-CN.md +20 -0
- package/templates/.opencode/commands/post-release.en.md +2 -0
- package/templates/.opencode/commands/post-release.zh-CN.md +2 -0
- package/templates/.opencode/commands/review-pr.en.md +11 -0
- package/templates/.opencode/commands/review-pr.zh-CN.md +11 -0
- package/templates/.opencode/commands/watch-pr.en.md +1 -1
- package/templates/.opencode/commands/watch-pr.zh-CN.md +1 -1
- package/dist/lib/agent-clients/adapters/gemini-cli.js +0 -64
- package/lib/agent-clients/adapters/gemini-cli.ts +0 -65
- package/templates/.gemini/commands/_project_/analyze-task.en.toml +0 -10
- package/templates/.gemini/commands/_project_/analyze-task.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/archive-tasks.en.toml +0 -10
- package/templates/.gemini/commands/_project_/archive-tasks.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/block-task.en.toml +0 -10
- package/templates/.gemini/commands/_project_/block-task.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/cancel-task.en.toml +0 -10
- package/templates/.gemini/commands/_project_/cancel-task.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/check-task.en.toml +0 -10
- package/templates/.gemini/commands/_project_/check-task.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/close-codescan.en.toml +0 -8
- package/templates/.gemini/commands/_project_/close-codescan.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/close-dependabot.en.toml +0 -8
- package/templates/.gemini/commands/_project_/close-dependabot.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/code-task.en.toml +0 -10
- package/templates/.gemini/commands/_project_/code-task.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/commit.en.toml +0 -10
- package/templates/.gemini/commands/_project_/commit.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/complete-manual-validation.en.toml +0 -10
- package/templates/.gemini/commands/_project_/complete-manual-validation.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/complete-task.en.toml +0 -10
- package/templates/.gemini/commands/_project_/complete-task.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/create-pr.en.toml +0 -10
- package/templates/.gemini/commands/_project_/create-pr.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/create-release-note.en.toml +0 -8
- package/templates/.gemini/commands/_project_/create-release-note.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/create-task.en.toml +0 -8
- package/templates/.gemini/commands/_project_/create-task.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/import-codescan.en.toml +0 -8
- package/templates/.gemini/commands/_project_/import-codescan.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/import-dependabot.en.toml +0 -8
- package/templates/.gemini/commands/_project_/import-dependabot.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/import-issue.en.toml +0 -8
- package/templates/.gemini/commands/_project_/import-issue.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/init-labels.en.toml +0 -8
- package/templates/.gemini/commands/_project_/init-labels.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/init-milestones.en.toml +0 -10
- package/templates/.gemini/commands/_project_/init-milestones.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/plan-task.en.toml +0 -10
- package/templates/.gemini/commands/_project_/plan-task.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/post-release.en.toml +0 -6
- package/templates/.gemini/commands/_project_/post-release.zh-CN.toml +0 -6
- package/templates/.gemini/commands/_project_/refine-title.en.toml +0 -8
- package/templates/.gemini/commands/_project_/refine-title.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/release.en.toml +0 -8
- package/templates/.gemini/commands/_project_/release.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/restore-task.en.toml +0 -8
- package/templates/.gemini/commands/_project_/restore-task.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/review-analysis.en.toml +0 -10
- package/templates/.gemini/commands/_project_/review-analysis.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/review-code.en.toml +0 -10
- package/templates/.gemini/commands/_project_/review-code.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/review-plan.en.toml +0 -10
- package/templates/.gemini/commands/_project_/review-plan.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/run-task.en.toml +0 -8
- package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/test-integration.en.toml +0 -6
- package/templates/.gemini/commands/_project_/test-integration.zh-CN.toml +0 -6
- package/templates/.gemini/commands/_project_/test.en.toml +0 -6
- package/templates/.gemini/commands/_project_/test.zh-CN.toml +0 -6
- package/templates/.gemini/commands/_project_/update-agent-infra.en.toml +0 -6
- package/templates/.gemini/commands/_project_/update-agent-infra.zh-CN.toml +0 -6
- package/templates/.gemini/commands/_project_/upgrade-dependency.en.toml +0 -8
- package/templates/.gemini/commands/_project_/upgrade-dependency.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/watch-pr.en.toml +0 -10
- package/templates/.gemini/commands/_project_/watch-pr.zh-CN.toml +0 -10
- package/templates/.gemini/settings.json +0 -3
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { locateActivityLog, appendActivityEntry, pairEntries } from './activity-log.ts';
|
|
6
|
+
import { commitIntentPath, digest, readCommitIntent } from './commit-intent.ts';
|
|
7
|
+
import type { CommitIntent } from './commit-intent.ts';
|
|
8
|
+
import { parseTypedTaskFrontmatter } from './frontmatter.ts';
|
|
9
|
+
import { parseReviewSummary } from './review-artifacts.ts';
|
|
10
|
+
import {
|
|
11
|
+
extractReviewBaseline,
|
|
12
|
+
extractReviewedSnapshotTree,
|
|
13
|
+
findAuthoritativeReviewCodeArtifact
|
|
14
|
+
} from './review-fingerprint.ts';
|
|
15
|
+
import type { TaskMutation } from './write.ts';
|
|
16
|
+
|
|
17
|
+
type CommitFinalizationDisposition =
|
|
18
|
+
| 'idle'
|
|
19
|
+
| 'prepared'
|
|
20
|
+
| 'recoverable'
|
|
21
|
+
| 'invalid'
|
|
22
|
+
| 'conflict'
|
|
23
|
+
| 'orphaned-start';
|
|
24
|
+
|
|
25
|
+
type CommitFinalizationCode =
|
|
26
|
+
| 'COMMIT_FINALIZATION_PENDING'
|
|
27
|
+
| 'COMMIT_FINALIZATION_EVIDENCE_MISSING'
|
|
28
|
+
| 'COMMIT_FINALIZATION_EVIDENCE_INVALID'
|
|
29
|
+
| 'COMMIT_FINALIZATION_CONFLICT';
|
|
30
|
+
|
|
31
|
+
type CommitFinalizationInspection = Readonly<{
|
|
32
|
+
disposition: CommitFinalizationDisposition;
|
|
33
|
+
code: CommitFinalizationCode | null;
|
|
34
|
+
message: string;
|
|
35
|
+
currentHead: string;
|
|
36
|
+
committedHead: string | null;
|
|
37
|
+
commitNote: string | null;
|
|
38
|
+
needsAnchor: boolean;
|
|
39
|
+
needsLog: boolean;
|
|
40
|
+
intent: CommitIntent | null;
|
|
41
|
+
intentDigest: string | null;
|
|
42
|
+
}>;
|
|
43
|
+
|
|
44
|
+
type CreatePrCommitGate = Readonly<{
|
|
45
|
+
allowed: boolean;
|
|
46
|
+
code: CommitFinalizationCode | null;
|
|
47
|
+
message: string;
|
|
48
|
+
disposition: CommitFinalizationDisposition;
|
|
49
|
+
action: 'continue' | 'rerun-commit' | 'rerun-review-code';
|
|
50
|
+
}>;
|
|
51
|
+
|
|
52
|
+
type CommitTaskFinalizationPlan = Readonly<{
|
|
53
|
+
mutations: readonly TaskMutation[];
|
|
54
|
+
commitNote: string;
|
|
55
|
+
}>;
|
|
56
|
+
|
|
57
|
+
const SHA_RE = /^[a-f0-9]{40,64}$/;
|
|
58
|
+
|
|
59
|
+
function git(repoRoot: string, args: string[]): string {
|
|
60
|
+
return execFileSync('git', args, {
|
|
61
|
+
cwd: repoRoot,
|
|
62
|
+
encoding: 'utf8',
|
|
63
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
64
|
+
}).trim();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function commitExists(repoRoot: string, value: string): boolean {
|
|
68
|
+
return SHA_RE.test(value) && spawnSync('git', ['cat-file', '-e', `${value}^{commit}`], {
|
|
69
|
+
cwd: repoRoot,
|
|
70
|
+
stdio: 'ignore'
|
|
71
|
+
}).status === 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isAncestor(repoRoot: string, ancestor: string, descendant: string): boolean {
|
|
75
|
+
return spawnSync('git', ['merge-base', '--is-ancestor', ancestor, descendant], {
|
|
76
|
+
cwd: repoRoot,
|
|
77
|
+
stdio: 'ignore'
|
|
78
|
+
}).status === 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function outcome(
|
|
82
|
+
disposition: CommitFinalizationDisposition,
|
|
83
|
+
overrides: Partial<CommitFinalizationInspection> = {}
|
|
84
|
+
): CommitFinalizationInspection {
|
|
85
|
+
return {
|
|
86
|
+
disposition,
|
|
87
|
+
code: null,
|
|
88
|
+
message: disposition,
|
|
89
|
+
currentHead: '',
|
|
90
|
+
committedHead: null,
|
|
91
|
+
commitNote: null,
|
|
92
|
+
needsAnchor: false,
|
|
93
|
+
needsLog: false,
|
|
94
|
+
intent: null,
|
|
95
|
+
intentDigest: null,
|
|
96
|
+
...overrides
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function invalid(
|
|
101
|
+
message: string,
|
|
102
|
+
currentHead = '',
|
|
103
|
+
code: CommitFinalizationCode = 'COMMIT_FINALIZATION_EVIDENCE_INVALID',
|
|
104
|
+
overrides: Partial<CommitFinalizationInspection> = {}
|
|
105
|
+
): CommitFinalizationInspection {
|
|
106
|
+
return outcome('invalid', { code, message, currentHead, ...overrides });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function inspectCommitFinalization(
|
|
110
|
+
taskDir: string,
|
|
111
|
+
repoRoot: string,
|
|
112
|
+
taskId: string
|
|
113
|
+
): CommitFinalizationInspection {
|
|
114
|
+
let currentHead: string;
|
|
115
|
+
let taskContent: string;
|
|
116
|
+
let metadata: ReturnType<typeof parseTypedTaskFrontmatter>;
|
|
117
|
+
try {
|
|
118
|
+
currentHead = git(repoRoot, ['rev-parse', 'HEAD']);
|
|
119
|
+
taskContent = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
|
|
120
|
+
metadata = parseTypedTaskFrontmatter(taskContent);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
return invalid(error instanceof Error ? error.message : String(error));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const activity = locateActivityLog(taskContent);
|
|
126
|
+
if (!activity) return invalid('task activity log is missing or ambiguous', currentHead);
|
|
127
|
+
const commitRows = pairEntries(activity.entries).filter((row) => row.step === 'Commit');
|
|
128
|
+
const openRows = commitRows.filter((row) => row.started !== '' && row.done === '');
|
|
129
|
+
const file = commitIntentPath(taskDir);
|
|
130
|
+
if (!fs.existsSync(file)) {
|
|
131
|
+
if (openRows.length > 0) {
|
|
132
|
+
return outcome('orphaned-start', {
|
|
133
|
+
code: 'COMMIT_FINALIZATION_EVIDENCE_MISSING',
|
|
134
|
+
message: 'an open Commit activity entry exists without a commit intent',
|
|
135
|
+
currentHead
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return outcome('idle', { message: 'no active commit finalization exists', currentHead });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let raw: string;
|
|
142
|
+
let intent: CommitIntent;
|
|
143
|
+
try {
|
|
144
|
+
raw = fs.readFileSync(file, 'utf8');
|
|
145
|
+
intent = readCommitIntent(taskDir, taskId);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
return invalid(error instanceof Error ? error.message : String(error), currentHead);
|
|
148
|
+
}
|
|
149
|
+
const intentDigest = digest(raw);
|
|
150
|
+
const shared = { currentHead, intent, intentDigest, committedHead: intent.committedHead };
|
|
151
|
+
|
|
152
|
+
if (!commitExists(repoRoot, intent.baselineHead)) {
|
|
153
|
+
return invalid('commit intent baseline is not a valid Git commit', currentHead, 'COMMIT_FINALIZATION_EVIDENCE_INVALID', shared);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (intent.phase === 'prepared') {
|
|
157
|
+
if (intent.committedHead !== null || intent.pushEvidence !== null) {
|
|
158
|
+
return invalid('prepared commit intent contains commit or push evidence', currentHead, 'COMMIT_FINALIZATION_EVIDENCE_INVALID', shared);
|
|
159
|
+
}
|
|
160
|
+
if (currentHead !== intent.baselineHead || openRows.length !== 1) {
|
|
161
|
+
return outcome('conflict', {
|
|
162
|
+
...shared,
|
|
163
|
+
code: 'COMMIT_FINALIZATION_CONFLICT',
|
|
164
|
+
message: 'prepared commit intent does not match HEAD and exactly one open Commit entry'
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return outcome('prepared', { ...shared, message: 'prepared commit intent can be safely retried' });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!intent.committedHead || !commitExists(repoRoot, intent.committedHead)) {
|
|
171
|
+
return invalid('committed intent head is missing or invalid', currentHead, 'COMMIT_FINALIZATION_EVIDENCE_INVALID', shared);
|
|
172
|
+
}
|
|
173
|
+
if (intent.phase === 'committed' && intent.pushEvidence !== null) {
|
|
174
|
+
return invalid('committed intent contains premature push evidence', currentHead, 'COMMIT_FINALIZATION_EVIDENCE_INVALID', shared);
|
|
175
|
+
}
|
|
176
|
+
if (
|
|
177
|
+
intent.phase === 'pushed'
|
|
178
|
+
&& (
|
|
179
|
+
intent.pushEvidence === null
|
|
180
|
+
|| intent.pushEvidence.head !== intent.committedHead
|
|
181
|
+
|| !intent.pushEvidence.remote
|
|
182
|
+
|| !intent.pushEvidence.ref
|
|
183
|
+
)
|
|
184
|
+
) {
|
|
185
|
+
return invalid('pushed intent evidence is missing or inconsistent', currentHead, 'COMMIT_FINALIZATION_EVIDENCE_INVALID', shared);
|
|
186
|
+
}
|
|
187
|
+
if (currentHead !== intent.committedHead || !isAncestor(repoRoot, intent.baselineHead, intent.committedHead)) {
|
|
188
|
+
return outcome('conflict', {
|
|
189
|
+
...shared,
|
|
190
|
+
code: 'COMMIT_FINALIZATION_CONFLICT',
|
|
191
|
+
message: 'repository HEAD or ancestry conflicts with the commit intent'
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const review = findAuthoritativeReviewCodeArtifact(taskDir);
|
|
196
|
+
let needsAnchor = false;
|
|
197
|
+
if (review.ok && review.path) {
|
|
198
|
+
let reviewContent: string;
|
|
199
|
+
try {
|
|
200
|
+
reviewContent = fs.readFileSync(review.path, 'utf8');
|
|
201
|
+
} catch (error) {
|
|
202
|
+
return invalid(error instanceof Error ? error.message : String(error), currentHead, 'COMMIT_FINALIZATION_EVIDENCE_MISSING', shared);
|
|
203
|
+
}
|
|
204
|
+
const summary = parseReviewSummary(reviewContent);
|
|
205
|
+
const reviewBaseline = extractReviewBaseline(reviewContent);
|
|
206
|
+
const reviewTree = extractReviewedSnapshotTree(reviewContent);
|
|
207
|
+
if (!summary.ok || summary.summary.verdict !== 'Approved' || !commitExists(repoRoot, reviewBaseline) || !SHA_RE.test(reviewTree)) {
|
|
208
|
+
return invalid('authoritative review-code evidence is not a valid approved snapshot', currentHead, 'COMMIT_FINALIZATION_EVIDENCE_INVALID', shared);
|
|
209
|
+
}
|
|
210
|
+
const committedTree = git(repoRoot, ['rev-parse', `${intent.committedHead}^{tree}`]);
|
|
211
|
+
if (reviewBaseline !== intent.baselineHead || reviewTree !== committedTree) {
|
|
212
|
+
return outcome('conflict', {
|
|
213
|
+
...shared,
|
|
214
|
+
code: 'COMMIT_FINALIZATION_CONFLICT',
|
|
215
|
+
message: 'review baseline or snapshot tree conflicts with the committed intent'
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const anchor = String(metadata.last_reviewed_commit ?? '').trim();
|
|
220
|
+
if (anchor && anchor !== intent.committedHead) {
|
|
221
|
+
return outcome('conflict', {
|
|
222
|
+
...shared,
|
|
223
|
+
code: 'COMMIT_FINALIZATION_CONFLICT',
|
|
224
|
+
message: 'task review anchor conflicts with the committed intent'
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
needsAnchor = anchor === '';
|
|
228
|
+
}
|
|
229
|
+
const commitNote = git(repoRoot, ['show', '-s', '--format=%h%x20%s', intent.committedHead]);
|
|
230
|
+
const matchingDone = commitRows.filter((row) => row.done !== '' && row.note === commitNote);
|
|
231
|
+
const needsLog = matchingDone.length === 0 && openRows.length === 1;
|
|
232
|
+
if (!(needsLog || (matchingDone.length === 1 && openRows.length === 0))) {
|
|
233
|
+
return outcome('conflict', {
|
|
234
|
+
...shared,
|
|
235
|
+
code: 'COMMIT_FINALIZATION_CONFLICT',
|
|
236
|
+
message: 'Commit activity identity is missing, duplicated, or ambiguous',
|
|
237
|
+
commitNote
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
return outcome('recoverable', {
|
|
241
|
+
...shared,
|
|
242
|
+
message: 'commit finalization can be recovered',
|
|
243
|
+
commitNote,
|
|
244
|
+
needsAnchor,
|
|
245
|
+
needsLog
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function planCommitTaskFinalization(
|
|
250
|
+
taskDir: string,
|
|
251
|
+
inspection: CommitFinalizationInspection,
|
|
252
|
+
agent: string,
|
|
253
|
+
timestamp: string
|
|
254
|
+
): CommitTaskFinalizationPlan {
|
|
255
|
+
if (inspection.disposition !== 'recoverable' || !inspection.committedHead || !inspection.commitNote) {
|
|
256
|
+
throw new Error('commit finalization task mutations require a recoverable inspection');
|
|
257
|
+
}
|
|
258
|
+
const taskContent = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
|
|
259
|
+
const frontmatter: Record<string, string> = { assigned_to: agent };
|
|
260
|
+
if (inspection.needsAnchor) frontmatter.last_reviewed_commit = inspection.committedHead;
|
|
261
|
+
const mutations: TaskMutation[] = [{
|
|
262
|
+
kind: 'frontmatter',
|
|
263
|
+
set: frontmatter
|
|
264
|
+
}];
|
|
265
|
+
if (inspection.needsLog) {
|
|
266
|
+
const activity = locateActivityLog(taskContent);
|
|
267
|
+
if (!activity) throw new Error('task activity log is missing or ambiguous');
|
|
268
|
+
mutations.push({
|
|
269
|
+
kind: 'section',
|
|
270
|
+
aliases: ['活动日志', 'Activity Log'],
|
|
271
|
+
heading: activity.heading,
|
|
272
|
+
body: appendActivityEntry(activity, {
|
|
273
|
+
time: timestamp,
|
|
274
|
+
step: 'Commit',
|
|
275
|
+
agent,
|
|
276
|
+
note: inspection.commitNote
|
|
277
|
+
})
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
return { mutations, commitNote: inspection.commitNote };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function inspectCreatePrCommitGate(taskDir: string, repoRoot: string, taskId: string): CreatePrCommitGate {
|
|
284
|
+
const lifecycle = inspectCommitFinalization(taskDir, repoRoot, taskId);
|
|
285
|
+
if (lifecycle.disposition !== 'idle') {
|
|
286
|
+
const pending = lifecycle.disposition === 'prepared' || lifecycle.disposition === 'recoverable';
|
|
287
|
+
return {
|
|
288
|
+
allowed: false,
|
|
289
|
+
code: pending ? 'COMMIT_FINALIZATION_PENDING' : lifecycle.code ?? 'COMMIT_FINALIZATION_CONFLICT',
|
|
290
|
+
message: pending ? 'commit finalization must complete before creating a pull request' : lifecycle.message,
|
|
291
|
+
disposition: lifecycle.disposition,
|
|
292
|
+
action: 'rerun-commit'
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const review = findAuthoritativeReviewCodeArtifact(taskDir);
|
|
297
|
+
if (!review.ok || !review.path) {
|
|
298
|
+
return { allowed: true, code: null, message: 'no review-code history requires an anchor', disposition: 'idle', action: 'continue' };
|
|
299
|
+
}
|
|
300
|
+
try {
|
|
301
|
+
const content = fs.readFileSync(review.path, 'utf8');
|
|
302
|
+
const summary = parseReviewSummary(content);
|
|
303
|
+
if (!summary.ok || summary.summary.verdict !== 'Approved') {
|
|
304
|
+
return {
|
|
305
|
+
allowed: false,
|
|
306
|
+
code: 'COMMIT_FINALIZATION_EVIDENCE_INVALID',
|
|
307
|
+
message: 'latest authoritative review-code is not approved',
|
|
308
|
+
disposition: 'idle',
|
|
309
|
+
action: 'rerun-review-code'
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
const task = parseTypedTaskFrontmatter(fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8'));
|
|
313
|
+
const currentHead = git(repoRoot, ['rev-parse', 'HEAD']);
|
|
314
|
+
const anchor = String(task.last_reviewed_commit ?? '').trim();
|
|
315
|
+
if (!anchor || !commitExists(repoRoot, anchor)) {
|
|
316
|
+
return {
|
|
317
|
+
allowed: false,
|
|
318
|
+
code: 'COMMIT_FINALIZATION_EVIDENCE_MISSING',
|
|
319
|
+
message: 'approved review snapshot is not anchored to a Git commit',
|
|
320
|
+
disposition: 'idle',
|
|
321
|
+
action: 'rerun-commit'
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
if (anchor !== currentHead || extractReviewedSnapshotTree(content) !== git(repoRoot, ['rev-parse', 'HEAD^{tree}'])) {
|
|
325
|
+
return {
|
|
326
|
+
allowed: false,
|
|
327
|
+
code: 'COMMIT_FINALIZATION_CONFLICT',
|
|
328
|
+
message: 'approved review anchor or tree does not match repository HEAD',
|
|
329
|
+
disposition: 'idle',
|
|
330
|
+
action: 'rerun-review-code'
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
return { allowed: true, code: null, message: 'commit finalization evidence is complete', disposition: 'idle', action: 'continue' };
|
|
334
|
+
} catch (error) {
|
|
335
|
+
return {
|
|
336
|
+
allowed: false,
|
|
337
|
+
code: 'COMMIT_FINALIZATION_EVIDENCE_INVALID',
|
|
338
|
+
message: error instanceof Error ? error.message : String(error),
|
|
339
|
+
disposition: 'idle',
|
|
340
|
+
action: 'rerun-review-code'
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export {
|
|
346
|
+
inspectCommitFinalization,
|
|
347
|
+
inspectCreatePrCommitGate,
|
|
348
|
+
planCommitTaskFinalization
|
|
349
|
+
};
|
|
350
|
+
export type {
|
|
351
|
+
CommitFinalizationCode,
|
|
352
|
+
CommitFinalizationDisposition,
|
|
353
|
+
CommitFinalizationInspection,
|
|
354
|
+
CommitTaskFinalizationPlan,
|
|
355
|
+
CreatePrCommitGate
|
|
356
|
+
};
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { createHash, randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import type { DelegationReceipt } from './delegation-receipts.ts';
|
|
6
|
+
|
|
7
|
+
type CommitIntentPhase = 'prepared' | 'committed' | 'pushed';
|
|
8
|
+
type PushEvidence = Readonly<{ remote: string; ref: string; head: string }>;
|
|
9
|
+
type CommitIntentOrchestration = Readonly<{
|
|
10
|
+
runId: string;
|
|
11
|
+
receiptId: string;
|
|
12
|
+
authorizationIssuedAt: string;
|
|
13
|
+
sourceRunDigest: string;
|
|
14
|
+
plannedRunDigest: string;
|
|
15
|
+
completionUpdatedAt: string;
|
|
16
|
+
plannedReceipt: DelegationReceipt;
|
|
17
|
+
}>;
|
|
18
|
+
type CommitIntent = Readonly<{
|
|
19
|
+
schemaVersion: 1;
|
|
20
|
+
taskId: string;
|
|
21
|
+
mode: 'standalone' | 'orchestrated';
|
|
22
|
+
phase: CommitIntentPhase;
|
|
23
|
+
tokenDigest: string;
|
|
24
|
+
baselineHead: string;
|
|
25
|
+
committedHead: string | null;
|
|
26
|
+
pushEvidence: PushEvidence | null;
|
|
27
|
+
orchestration: CommitIntentOrchestration | null;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
updatedAt: string;
|
|
30
|
+
}>;
|
|
31
|
+
type CommitIntentInput = Omit<CommitIntent, 'schemaVersion' | 'tokenDigest'>;
|
|
32
|
+
|
|
33
|
+
class CommitIntentError extends Error {
|
|
34
|
+
readonly code: string;
|
|
35
|
+
|
|
36
|
+
constructor(code: string, message: string) {
|
|
37
|
+
super(message);
|
|
38
|
+
this.name = 'CommitIntentError';
|
|
39
|
+
this.code = code;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function commitIntentPath(taskDir: string): string {
|
|
44
|
+
return path.join(taskDir, 'commit-intent.json');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function digest(value: string | Buffer): string {
|
|
48
|
+
return createHash('sha256').update(value).digest('hex');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function serialize(value: unknown): string {
|
|
52
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function atomicWrite(file: string, value: CommitIntent): void {
|
|
56
|
+
const temp = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
57
|
+
try {
|
|
58
|
+
fs.writeFileSync(temp, serialize(value), { encoding: 'utf8', flag: 'wx', mode: 0o600 });
|
|
59
|
+
const handle = fs.openSync(temp, 'r+');
|
|
60
|
+
try {
|
|
61
|
+
fs.fsyncSync(handle);
|
|
62
|
+
} finally {
|
|
63
|
+
fs.closeSync(handle);
|
|
64
|
+
}
|
|
65
|
+
fs.renameSync(temp, file);
|
|
66
|
+
fs.chmodSync(file, 0o600);
|
|
67
|
+
try {
|
|
68
|
+
const directory = fs.openSync(path.dirname(file), 'r');
|
|
69
|
+
try {
|
|
70
|
+
fs.fsyncSync(directory);
|
|
71
|
+
} finally {
|
|
72
|
+
fs.closeSync(directory);
|
|
73
|
+
}
|
|
74
|
+
} catch (error) {
|
|
75
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
76
|
+
if (!['EACCES', 'EISDIR', 'EINVAL', 'EPERM'].includes(code ?? '')) throw error;
|
|
77
|
+
}
|
|
78
|
+
} finally {
|
|
79
|
+
if (fs.existsSync(temp)) fs.unlinkSync(temp);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
84
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function keysEqual(value: Record<string, unknown>, expected: readonly string[]): boolean {
|
|
88
|
+
return Object.keys(value).sort().join('\0') === [...expected].sort().join('\0');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function validPushEvidence(value: unknown): value is PushEvidence | null {
|
|
92
|
+
return value === null || (
|
|
93
|
+
isRecord(value)
|
|
94
|
+
&& keysEqual(value, ['remote', 'ref', 'head'])
|
|
95
|
+
&& ['remote', 'ref', 'head'].every((key) => typeof value[key] === 'string' && value[key] !== '')
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function validOrchestration(value: unknown): value is CommitIntentOrchestration | null {
|
|
100
|
+
if (value === null) return true;
|
|
101
|
+
if (!isRecord(value) || !keysEqual(value, [
|
|
102
|
+
'runId', 'receiptId', 'authorizationIssuedAt', 'sourceRunDigest', 'plannedRunDigest',
|
|
103
|
+
'completionUpdatedAt', 'plannedReceipt'
|
|
104
|
+
])) return false;
|
|
105
|
+
return [
|
|
106
|
+
'runId', 'receiptId', 'authorizationIssuedAt', 'sourceRunDigest', 'plannedRunDigest', 'completionUpdatedAt'
|
|
107
|
+
].every((key) => typeof value[key] === 'string' && value[key] !== '') && isRecord(value.plannedReceipt);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function parseIntent(raw: string, taskId: string): CommitIntent {
|
|
111
|
+
let value: unknown;
|
|
112
|
+
try {
|
|
113
|
+
value = JSON.parse(raw);
|
|
114
|
+
} catch {
|
|
115
|
+
throw new CommitIntentError('ORCHESTRATION_COMMIT_INTENT_INVALID', 'commit intent is not valid JSON');
|
|
116
|
+
}
|
|
117
|
+
if (!isRecord(value) || !keysEqual(value, [
|
|
118
|
+
'schemaVersion', 'taskId', 'mode', 'phase', 'tokenDigest', 'baselineHead', 'committedHead',
|
|
119
|
+
'pushEvidence', 'orchestration', 'createdAt', 'updatedAt'
|
|
120
|
+
])) {
|
|
121
|
+
throw new CommitIntentError('ORCHESTRATION_COMMIT_INTENT_INVALID', 'commit intent has an invalid schema');
|
|
122
|
+
}
|
|
123
|
+
if (
|
|
124
|
+
value.schemaVersion !== 1
|
|
125
|
+
|| value.taskId !== taskId
|
|
126
|
+
|| !['standalone', 'orchestrated'].includes(String(value.mode))
|
|
127
|
+
|| !['prepared', 'committed', 'pushed'].includes(String(value.phase))
|
|
128
|
+
|| typeof value.tokenDigest !== 'string'
|
|
129
|
+
|| !/^[a-f0-9]{64}$/.test(value.tokenDigest)
|
|
130
|
+
|| typeof value.baselineHead !== 'string'
|
|
131
|
+
|| (value.committedHead !== null && typeof value.committedHead !== 'string')
|
|
132
|
+
|| !validPushEvidence(value.pushEvidence)
|
|
133
|
+
|| !validOrchestration(value.orchestration)
|
|
134
|
+
|| typeof value.createdAt !== 'string'
|
|
135
|
+
|| typeof value.updatedAt !== 'string'
|
|
136
|
+
|| (value.mode === 'standalone') !== (value.orchestration === null)
|
|
137
|
+
) {
|
|
138
|
+
throw new CommitIntentError('ORCHESTRATION_COMMIT_INTENT_INVALID', 'commit intent has invalid field values');
|
|
139
|
+
}
|
|
140
|
+
return value as CommitIntent;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function tokenMatches(actualDigest: string, token: string): boolean {
|
|
144
|
+
const actual = Buffer.from(actualDigest, 'hex');
|
|
145
|
+
const supplied = Buffer.from(digest(token), 'hex');
|
|
146
|
+
return actual.length === supplied.length && timingSafeEqual(actual, supplied);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function readCommitIntent(taskDir: string, taskId: string, token?: string): CommitIntent {
|
|
150
|
+
const file = commitIntentPath(taskDir);
|
|
151
|
+
const intent = parseIntent(fs.readFileSync(file, 'utf8'), taskId);
|
|
152
|
+
if (token !== undefined && !tokenMatches(intent.tokenDigest, token)) {
|
|
153
|
+
throw new CommitIntentError('ORCHESTRATION_COMMIT_INTENT_TOKEN_MISMATCH', 'commit intent token does not match');
|
|
154
|
+
}
|
|
155
|
+
return intent;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function createCommitIntent(
|
|
159
|
+
taskDir: string,
|
|
160
|
+
input: CommitIntentInput,
|
|
161
|
+
options: Readonly<{ token?: () => string }> = {}
|
|
162
|
+
): Readonly<{ intent: CommitIntent; token: string }> {
|
|
163
|
+
const file = commitIntentPath(taskDir);
|
|
164
|
+
if (fs.existsSync(file)) {
|
|
165
|
+
throw new CommitIntentError('ORCHESTRATION_COMMIT_INTENT_BUSY', 'an active commit intent already exists');
|
|
166
|
+
}
|
|
167
|
+
const token = (options.token ?? (() => randomBytes(32).toString('base64url')))();
|
|
168
|
+
const intent: CommitIntent = Object.freeze({ schemaVersion: 1, ...input, tokenDigest: digest(token) });
|
|
169
|
+
atomicWrite(file, intent);
|
|
170
|
+
return { intent, token };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function updateCommitIntent(
|
|
174
|
+
taskDir: string,
|
|
175
|
+
taskId: string,
|
|
176
|
+
token: string,
|
|
177
|
+
updates: Partial<Pick<CommitIntent, 'phase' | 'committedHead' | 'pushEvidence' | 'updatedAt'>>
|
|
178
|
+
): CommitIntent {
|
|
179
|
+
const current = readCommitIntent(taskDir, taskId, token);
|
|
180
|
+
const nextPhase = updates.phase ?? current.phase;
|
|
181
|
+
const transitions: Record<CommitIntentPhase, readonly CommitIntentPhase[]> = {
|
|
182
|
+
prepared: ['prepared', 'committed', 'pushed'],
|
|
183
|
+
committed: ['committed', 'pushed'],
|
|
184
|
+
pushed: ['pushed']
|
|
185
|
+
};
|
|
186
|
+
if (!transitions[current.phase].includes(nextPhase)) {
|
|
187
|
+
throw new CommitIntentError('ORCHESTRATION_COMMIT_INTENT_STATE_INVALID', `cannot move commit intent from ${current.phase} to ${nextPhase}`);
|
|
188
|
+
}
|
|
189
|
+
const updated = Object.freeze({ ...current, ...updates, phase: nextPhase });
|
|
190
|
+
atomicWrite(commitIntentPath(taskDir), updated);
|
|
191
|
+
return updated;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function removeCommitIntent(taskDir: string, taskId: string, token: string): void {
|
|
195
|
+
readCommitIntent(taskDir, taskId, token);
|
|
196
|
+
fs.unlinkSync(commitIntentPath(taskDir));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function removeCommitIntentByDigest(taskDir: string, taskId: string, expectedDigest: string): void {
|
|
200
|
+
const file = commitIntentPath(taskDir);
|
|
201
|
+
const raw = fs.readFileSync(file);
|
|
202
|
+
parseIntent(raw.toString('utf8'), taskId);
|
|
203
|
+
const actual = digest(raw);
|
|
204
|
+
const expected = Buffer.from(expectedDigest, 'hex');
|
|
205
|
+
const observed = Buffer.from(actual, 'hex');
|
|
206
|
+
if (
|
|
207
|
+
!/^[a-f0-9]{64}$/.test(expectedDigest)
|
|
208
|
+
|| expected.length !== observed.length
|
|
209
|
+
|| !timingSafeEqual(expected, observed)
|
|
210
|
+
) {
|
|
211
|
+
throw new CommitIntentError(
|
|
212
|
+
'ORCHESTRATION_COMMIT_RECOVERY_REQUIRED',
|
|
213
|
+
'commit intent changed after recovery inspection'
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
fs.unlinkSync(file);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export {
|
|
220
|
+
CommitIntentError,
|
|
221
|
+
commitIntentPath,
|
|
222
|
+
createCommitIntent,
|
|
223
|
+
digest,
|
|
224
|
+
readCommitIntent,
|
|
225
|
+
removeCommitIntent,
|
|
226
|
+
removeCommitIntentByDigest,
|
|
227
|
+
serialize,
|
|
228
|
+
updateCommitIntent
|
|
229
|
+
};
|
|
230
|
+
export type { CommitIntent, CommitIntentInput, CommitIntentOrchestration, CommitIntentPhase, PushEvidence };
|
|
@@ -4,9 +4,11 @@ import { appendActivityEntry, locateActivityLog } from './activity-log.ts';
|
|
|
4
4
|
import { isDecisionItem, listDecisionItems, selectDecisionItem } from './decision-items.ts';
|
|
5
5
|
import {
|
|
6
6
|
createImplementationInput,
|
|
7
|
+
finalizeImplementationInput,
|
|
7
8
|
IMPLEMENTATION_INPUT_ALIASES,
|
|
8
9
|
parseImplementationInputs,
|
|
9
|
-
renderImplementationInputs
|
|
10
|
+
renderImplementationInputs,
|
|
11
|
+
selectDeclaredImplementationInput
|
|
10
12
|
} from './implementation-inputs.ts';
|
|
11
13
|
import { LEDGER_COLUMNS, LEDGER_HEADINGS, parseLedger } from './ledger.ts';
|
|
12
14
|
import type { LedgerRow } from './ledger.ts';
|
|
@@ -73,7 +75,7 @@ function existingDecision(
|
|
|
73
75
|
? { recordId, implementationInputId: null }
|
|
74
76
|
: { conflict: '--needs-implementation is only valid for code-stage decisions' };
|
|
75
77
|
const inputs = parseImplementationInputs(content).rows.filter((input) => input.ledgerId === row.id && input.decisionEvidence === row.evidence);
|
|
76
|
-
if (inputs.length !== 1 || inputs[0]!.needsImplementation !== needsImplementation) {
|
|
78
|
+
if (inputs.length !== 1 || (needsImplementation !== undefined && inputs[0]!.needsImplementation !== needsImplementation)) {
|
|
77
79
|
return { conflict: `implementation intent for '${row.id}' conflicts with the existing decision` };
|
|
78
80
|
}
|
|
79
81
|
return { recordId, implementationInputId: inputs[0]!.id };
|
|
@@ -106,9 +108,28 @@ function applyHumanDecision(request: HumanDecisionRequest, options: TaskWriteOpt
|
|
|
106
108
|
return failed('DECISION_TARGET_INVALID', message, resolved.taskId);
|
|
107
109
|
}
|
|
108
110
|
const row = selected.row;
|
|
109
|
-
if (row.stage === 'code' && request.needsImplementation === undefined) return failed('DECISION_PAYLOAD_INVALID', 'code-stage decisions require --needs-implementation true|false', resolved.taskId, row.id);
|
|
110
111
|
if (row.stage !== 'code' && request.needsImplementation !== undefined) return failed('DECISION_PAYLOAD_INVALID', '--needs-implementation is only valid for code-stage decisions', resolved.taskId, row.id);
|
|
111
112
|
|
|
113
|
+
let parsedInputs: ReturnType<typeof parseImplementationInputs> | null = null;
|
|
114
|
+
let declaredInput: ReturnType<typeof selectDeclaredImplementationInput> = null;
|
|
115
|
+
let effectiveNeedsImplementation = request.needsImplementation;
|
|
116
|
+
if (row.stage === 'code') {
|
|
117
|
+
try {
|
|
118
|
+
parsedInputs = parseImplementationInputs(content);
|
|
119
|
+
declaredInput = selectDeclaredImplementationInput(parsedInputs.rows, row.id, row.evidence);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
return failed('DECISION_DOCUMENT_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId, row.id);
|
|
122
|
+
}
|
|
123
|
+
if (declaredInput) {
|
|
124
|
+
if (request.needsImplementation !== undefined && request.needsImplementation !== declaredInput.needsImplementation) {
|
|
125
|
+
return failed('DECISION_CONFLICT', `implementation intent for '${row.id}' conflicts with its declaration`, resolved.taskId, row.id);
|
|
126
|
+
}
|
|
127
|
+
effectiveNeedsImplementation = declaredInput.needsImplementation;
|
|
128
|
+
} else if (effectiveNeedsImplementation === undefined) {
|
|
129
|
+
return failed('DECISION_PAYLOAD_INVALID', 'code-stage decisions require --needs-implementation true|false when no implementation intent was declared', resolved.taskId, row.id);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
112
133
|
let metadata;
|
|
113
134
|
try { metadata = (options.metadataProvider ?? captureTaskWriteMetadata)(); }
|
|
114
135
|
catch (error) { return failed('METADATA_CAPTURE_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId, row.id); }
|
|
@@ -132,18 +153,17 @@ function applyHumanDecision(request: HumanDecisionRequest, options: TaskWriteOpt
|
|
|
132
153
|
];
|
|
133
154
|
let implementationInputId: string | null = null;
|
|
134
155
|
if (row.stage === 'code') {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
implementationInputId = input.id;
|
|
156
|
+
const nextInputs = declaredInput
|
|
157
|
+
? finalizeImplementationInput(parsedInputs!.rows, declaredInput.id, evidence, metadata.timestamp)
|
|
158
|
+
: [...parsedInputs!.rows, createImplementationInput(parsedInputs!.rows, {
|
|
159
|
+
ledgerId: row.id, decisionEvidence: evidence,
|
|
160
|
+
needsImplementation: effectiveNeedsImplementation!, decidedAt: metadata.timestamp
|
|
161
|
+
})];
|
|
162
|
+
implementationInputId = declaredInput?.id ?? nextInputs[nextInputs.length - 1]!.id;
|
|
143
163
|
mutations.push({
|
|
144
164
|
kind: 'section', aliases: IMPLEMENTATION_INPUT_ALIASES,
|
|
145
165
|
heading: findSectionHeading(content, [...IMPLEMENTATION_INPUT_ALIASES]),
|
|
146
|
-
body: renderImplementationInputs(
|
|
166
|
+
body: renderImplementationInputs(nextInputs)
|
|
147
167
|
});
|
|
148
168
|
}
|
|
149
169
|
mutations.push({
|