commandmate 0.22.1 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/app-build-manifest.json +86 -86
- package/.next/app-path-routes-manifest.json +17 -17
- package/.next/build-manifest.json +2 -2
- package/.next/prerender-manifest.json +9 -9
- package/.next/required-server-files.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/api/app/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/app/update-check/route.js +1 -1
- package/.next/server/app/api/app/update-check/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/conversation/route.js +1 -1
- package/.next/server/app/api/assistant/conversation/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/conversation/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/current-output/route.js +1 -1
- package/.next/server/app/api/assistant/current-output/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/current-output/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/messages/route.js +1 -1
- package/.next/server/app/api/assistant/messages/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/messages/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/session/route.js +1 -1
- package/.next/server/app/api/assistant/session/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/session/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/start/route.js +1 -1
- package/.next/server/app/api/assistant/start/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/start/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/terminal/route.js +3 -3
- package/.next/server/app/api/assistant/terminal/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/terminal/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/tools/route.js +1 -1
- package/.next/server/app/api/assistant/tools/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/tools/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/auth/status/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/daily-summary/route.js +1 -1
- package/.next/server/app/api/daily-summary/route.js.nft.json +1 -1
- package/.next/server/app/api/daily-summary/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/daily-summary/status/route.js +1 -1
- package/.next/server/app/api/daily-summary/status/route.js.nft.json +1 -1
- package/.next/server/app/api/daily-summary/status/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/external-apps/[id]/health/route.js +1 -1
- package/.next/server/app/api/external-apps/[id]/health/route.js.nft.json +1 -1
- package/.next/server/app/api/external-apps/[id]/health/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/external-apps/[id]/route.js +1 -1
- package/.next/server/app/api/external-apps/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/external-apps/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/external-apps/route.js +1 -1
- package/.next/server/app/api/external-apps/route.js.nft.json +1 -1
- package/.next/server/app/api/external-apps/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/fs/browse/route.js +1 -1
- package/.next/server/app/api/fs/browse/route.js.nft.json +1 -1
- package/.next/server/app/api/fs/browse/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/fs/recent-paths/route.js +1 -1
- package/.next/server/app/api/fs/recent-paths/route.js.nft.json +1 -1
- package/.next/server/app/api/fs/recent-paths/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/hooks/agent-event/route.js +1 -1
- package/.next/server/app/api/hooks/agent-event/route.js.nft.json +1 -1
- package/.next/server/app/api/hooks/agent-event/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/hooks/claude-done/route.js +1 -1
- package/.next/server/app/api/hooks/claude-done/route.js.nft.json +1 -1
- package/.next/server/app/api/hooks/claude-done/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/hooks/permission-request/route.js +1 -1
- package/.next/server/app/api/hooks/permission-request/route.js.nft.json +1 -1
- package/.next/server/app/api/hooks/permission-request/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/metrics/vibe/route.js +1 -1
- package/.next/server/app/api/metrics/vibe/route.js.nft.json +1 -1
- package/.next/server/app/api/metrics/vibe/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/ollama/models/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/push/subscriptions/route.js +1 -1
- package/.next/server/app/api/push/subscriptions/route.js.nft.json +1 -1
- package/.next/server/app/api/push/subscriptions/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/push/vapid/route.js +1 -1
- package/.next/server/app/api/push/vapid/route.js.nft.json +1 -1
- package/.next/server/app/api/push/vapid/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/[id]/route.js +1 -1
- package/.next/server/app/api/repositories/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/[id]/todos/[todoId]/route.js +1 -1
- package/.next/server/app/api/repositories/[id]/todos/[todoId]/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/[id]/todos/route.js +1 -1
- package/.next/server/app/api/repositories/[id]/todos/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/[id]/todos/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route.js +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/clone/route.js +1 -1
- package/.next/server/app/api/repositories/clone/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/clone/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/excluded/route.js +1 -1
- package/.next/server/app/api/repositories/excluded/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/excluded/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/restore/route.js +1 -1
- package/.next/server/app/api/repositories/restore/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/restore/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/route.js +1 -1
- package/.next/server/app/api/repositories/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/scan/route.js +1 -1
- package/.next/server/app/api/repositories/scan/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/scan/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/sync/route.js +1 -1
- package/.next/server/app/api/repositories/sync/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/sync/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/validate-path/route.js +1 -1
- package/.next/server/app/api/repositories/validate-path/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/validate-path/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sidebar/group-order/route.js +1 -1
- package/.next/server/app/api/sidebar/group-order/route.js.nft.json +1 -1
- package/.next/server/app/api/sidebar/group-order/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/installations/route.js +1 -1
- package/.next/server/app/api/skills/installations/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/installations/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/operations/route.js +1 -1
- package/.next/server/app/api/skills/operations/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/operations/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/reindex/route.js +1 -1
- package/.next/server/app/api/skills/reindex/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/reindex/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/slash-commands/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/tasks/[taskId]/cancel/route.js +1 -4
- package/.next/server/app/api/tasks/[taskId]/cancel/route.js.nft.json +1 -1
- package/.next/server/app/api/tasks/[taskId]/cancel/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/tasks/[taskId]/route.js +1 -4
- package/.next/server/app/api/tasks/[taskId]/route.js.nft.json +1 -1
- package/.next/server/app/api/tasks/[taskId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/templates/[id]/route.js +1 -1
- package/.next/server/app/api/templates/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/templates/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/templates/route.js +1 -1
- package/.next/server/app/api/templates/route.js.nft.json +1 -1
- package/.next/server/app/api/templates/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/todos/route.js +1 -1
- package/.next/server/app/api/todos/route.js.nft.json +1 -1
- package/.next/server/app/api/todos/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/verification/runs/[runId]/route.js +1 -1
- package/.next/server/app/api/verification/runs/[runId]/route.js.nft.json +1 -1
- package/.next/server/app/api/verification/runs/[runId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/verification/runs/route.js +1 -1
- package/.next/server/app/api/verification/runs/route.js.nft.json +1 -1
- package/.next/server/app/api/verification/runs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/capture/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/capture/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/capture/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/cli-tool/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/cli-tool/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/cli-tool/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/cmate/schedules/route.js +2 -2
- package/.next/server/app/api/worktrees/[id]/cmate/schedules/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/cmate/schedules/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/execution-logs/route.js +2 -2
- package/.next/server/app/api/worktrees/[id]/execution-logs/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/execution-logs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/files/[...path]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/files/[...path]/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/files/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/branch/create/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/branch/create/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/git/branch/create/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/branch/delete/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/branch/delete/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/git/branch/delete/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/branches/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/branches/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/git/branches/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/checkout/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/checkout/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/git/checkout/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/commit/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/commit/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/git/commit/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/diff/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/diff/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/git/diff/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/fetch/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/fetch/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/git/fetch/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/log/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/log/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/git/log/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/pull/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/pull/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/git/pull/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/push/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/push/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/git/push/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/reset/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/reset/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/git/reset/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/revert/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/revert/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/git/revert/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/show/[commitHash]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/show/[commitHash]/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/git/show/[commitHash]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stage/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stage/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stage/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/staged/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/staged/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/git/staged/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/[index]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/[index]/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/[index]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/apply/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/apply/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/apply/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/pop/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/pop/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/pop/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/push/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/push/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/push/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/status/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/status/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/git/status/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/unstage/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/unstage/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/git/unstage/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/working-diff/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/working-diff/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/git/working-diff/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/[filename]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/[filename]/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/[filename]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/marp-render/route.js +3 -3
- package/.next/server/app/api/worktrees/[id]/marp-render/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/marp-render/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/messages/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/messages/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/messages/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route.js +2 -2
- package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/active/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/active/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/active/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/route.js +3 -3
- package/.next/server/app/api/worktrees/[id]/schedules/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/search/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/search/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/search/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/install/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/install/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/install/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/plan/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/plan/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/plan/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall-plan/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall-plan/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall-plan/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update-plan/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update-plan/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update-plan/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/slash-commands/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/slash-commands/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/slash-commands/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/special-keys/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/special-keys/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/special-keys/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/start-polling/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/start-polling/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/start-polling/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tasks/route.js +2 -2
- package/.next/server/app/api/worktrees/[id]/tasks/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/tasks/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/terminal/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/terminal/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/terminal/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/timers/history/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/timers/history/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/timers/history/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/timers/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/timers/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/timers/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/todos/[todoId]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/todos/[todoId]/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/todos/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/todos/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/todos/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/[...path]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/[...path]/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/upload/[...path]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/upload/[...path]/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/upload/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/route.js +1 -4
- package/.next/server/app/api/worktrees/[id]/verify/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/viewed/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/viewed/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/viewed/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/route.js +1 -1
- package/.next/server/app/api/worktrees/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/route_client-reference-manifest.js +1 -1
- package/.next/server/app/chat/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +1 -1
- package/.next/server/app/more/page_client-reference-manifest.js +1 -1
- package/.next/server/app/offline/page_client-reference-manifest.js +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/proxy/[...path]/route.js +1 -1
- package/.next/server/app/proxy/[...path]/route.js.nft.json +1 -1
- package/.next/server/app/proxy/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/repositories/page_client-reference-manifest.js +1 -1
- package/.next/server/app/review/page_client-reference-manifest.js +1 -1
- package/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
- package/.next/server/app/skills/[skillId]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/skills/installed/page_client-reference-manifest.js +1 -1
- package/.next/server/app/skills/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/files/[...path]/page.js +1 -1
- package/.next/server/app/worktrees/[id]/files/[...path]/page.js.nft.json +1 -1
- package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/page.js +2 -2
- package/.next/server/app/worktrees/[id]/page.js.nft.json +1 -1
- package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/terminal/page.js +1 -1
- package/.next/server/app/worktrees/[id]/terminal/page.js.nft.json +1 -1
- package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +17 -17
- package/.next/server/chunks/1029.js +1 -1
- package/.next/server/chunks/1068.js +1 -1
- package/.next/server/chunks/2119.js +1 -1
- package/.next/server/chunks/2404.js +1 -1
- package/.next/server/chunks/2445.js +1 -1
- package/.next/server/chunks/2546.js +1 -1
- package/.next/server/chunks/3241.js +1 -0
- package/.next/server/chunks/3417.js +1 -1
- package/.next/server/chunks/406.js +10 -6
- package/.next/server/chunks/4444.js +1 -1
- package/.next/server/chunks/5959.js +4 -5
- package/.next/server/chunks/5998.js +1 -12
- package/.next/server/chunks/6130.js +1 -1
- package/.next/server/chunks/6274.js +3 -3
- package/.next/server/chunks/6401.js +1 -1
- package/.next/server/chunks/6781.js +1 -0
- package/.next/server/chunks/7458.js +6 -0
- package/.next/server/chunks/{3753.js → 7970.js} +57 -32
- package/.next/server/chunks/8536.js +1 -1
- package/.next/server/chunks/8807.js +180 -127
- package/.next/server/chunks/9058.js +1 -1
- package/.next/server/chunks/9067.js +1 -1
- package/.next/server/chunks/9360.js +1 -1
- package/.next/server/chunks/9687.js +2 -2
- package/.next/server/middleware-manifest.json +5 -5
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/server/src/middleware.js.map +1 -1
- package/.next/static/chunks/{6488-63dacfbab082ca8d.js → 6488-95ba9d24a76f0cc5.js} +1 -1
- package/.next/static/chunks/app/worktrees/[id]/{page-c1be93f2595d0803.js → page-966a451ae7b0903a.js} +1 -1
- package/.next/trace +12 -12
- package/dist/cli/docs/agent-operations.d.ts +1 -1
- package/dist/cli/docs/agent-operations.d.ts.map +1 -1
- package/dist/cli/docs/agent-operations.js +5 -1
- package/dist/cli/utils/api-client.d.ts.map +1 -1
- package/dist/cli/utils/api-client.js +9 -2
- package/dist/cli/utils/server-url.d.ts +23 -0
- package/dist/cli/utils/server-url.d.ts.map +1 -1
- package/dist/cli/utils/server-url.js +33 -0
- package/dist/server/src/config/log-config.js +10 -1
- package/dist/server/src/config/safe-directory.js +103 -0
- package/dist/server/src/config/system-directories.js +74 -9
- package/dist/server/src/lib/cli-tools/antigravity.js +19 -2
- package/dist/server/src/lib/cli-tools/codex.js +95 -1
- package/dist/server/src/lib/cli-tools/copilot.js +30 -1
- package/dist/server/src/lib/cli-tools/gemini.js +28 -2
- package/dist/server/src/lib/cli-tools/opencode.js +51 -2
- package/dist/server/src/lib/git/git-default-branch.js +59 -0
- package/dist/server/src/lib/hooks/agent-event-service.js +205 -0
- package/dist/server/src/lib/hooks/agent-event-types.js +7 -86
- package/dist/server/src/lib/hooks/hook-settings-generator.js +26 -11
- package/dist/server/src/lib/hooks/permission-decision-service.js +269 -0
- package/dist/server/src/lib/hooks/sources/abstain.js +78 -0
- package/dist/server/src/lib/hooks/sources/antigravity/hooks-config.js +372 -0
- package/dist/server/src/lib/hooks/sources/antigravity/source.js +225 -0
- package/dist/server/src/lib/hooks/sources/antigravity/tool-id.js +16 -0
- package/dist/server/src/lib/hooks/sources/claude/source.js +143 -0
- package/dist/server/src/lib/hooks/sources/claude/tool-id.js +18 -0
- package/dist/server/src/lib/hooks/sources/codex/hooks-config.js +465 -0
- package/dist/server/src/lib/hooks/sources/codex/source.js +138 -0
- package/dist/server/src/lib/hooks/sources/codex/tool-id.js +15 -0
- package/dist/server/src/lib/hooks/sources/copilot/hook-settings.js +473 -0
- package/dist/server/src/lib/hooks/sources/copilot/source.js +210 -0
- package/dist/server/src/lib/hooks/sources/copilot/tool-id.js +15 -0
- package/dist/server/src/lib/hooks/sources/define-source.js +169 -0
- package/dist/server/src/lib/hooks/sources/event-mapper.js +157 -0
- package/dist/server/src/lib/hooks/sources/gemini/event-vocabulary.js +102 -0
- package/dist/server/src/lib/hooks/sources/gemini/settings-generator.js +315 -0
- package/dist/server/src/lib/hooks/sources/gemini/shared-config-tree.js +103 -0
- package/dist/server/src/lib/hooks/sources/gemini/source.js +151 -0
- package/dist/server/src/lib/hooks/sources/gemini/tool-id.js +17 -0
- package/dist/server/src/lib/hooks/sources/hook-event-vocabulary.js +111 -0
- package/dist/server/src/lib/hooks/sources/index.js +62 -0
- package/dist/server/src/lib/hooks/sources/legacy-relay.js +84 -0
- package/dist/server/src/lib/hooks/sources/opencode/client.js +299 -0
- package/dist/server/src/lib/hooks/sources/opencode/ingest.js +261 -0
- package/dist/server/src/lib/hooks/sources/opencode/mappers.js +154 -0
- package/dist/server/src/lib/hooks/sources/opencode/payloads.js +189 -0
- package/dist/server/src/lib/hooks/sources/opencode/ports.js +295 -0
- package/dist/server/src/lib/hooks/sources/opencode/runtime.js +159 -0
- package/dist/server/src/lib/hooks/sources/opencode/source.js +250 -0
- package/dist/server/src/lib/hooks/sources/opencode/subscription.js +422 -0
- package/dist/server/src/lib/hooks/sources/opencode/tool-id.js +14 -0
- package/dist/server/src/lib/hooks/sources/opencode/turn-gate.js +193 -0
- package/dist/server/src/lib/hooks/sources/pending-decisions.js +127 -0
- package/dist/server/src/lib/hooks/sources/registry.js +111 -0
- package/dist/server/src/lib/hooks/sources/types.js +46 -0
- package/dist/server/src/lib/session/agent-session-lifecycle.js +82 -0
- package/dist/server/src/lib/session/claude-session.js +10 -3
- package/dist/server/src/lib/tasks/contract-message.js +175 -0
- package/dist/server/src/lib/verification/env-clean-gate.js +320 -0
- package/dist/server/src/lib/verification/env-snapshot.js +474 -0
- package/dist/server/src/lib/verification/gate-runner.js +802 -0
- package/dist/server/src/lib/verification/scope-gate.js +402 -0
- package/dist/server/src/lib/verification/verify-config.js +281 -0
- package/package.json +1 -1
- package/scripts/hooks/cmate-agent-event.sh +73 -3
- package/.next/server/chunks/8856.js +0 -4
- /package/.next/static/{d4vh4O18WEhXs4WbdShIs → wH6K6QNPHSdATFkVoIcy3}/_buildManifest.js +0 -0
- /package/.next/static/{d4vh4O18WEhXs4WbdShIs → wH6K6QNPHSdATFkVoIcy3}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,802 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Verification gate execution engine (Issue #1543, Phase 1-3).
|
|
4
|
+
*
|
|
5
|
+
* Runs the gates declared in `.commandmate/verify.yaml` inside a worktree and
|
|
6
|
+
* records every verdict in `verification_runs` / `verification_gate_results`,
|
|
7
|
+
* so "the agent said it was done" and "the work passes the repository's own
|
|
8
|
+
* checks" stop being the same claim.
|
|
9
|
+
*
|
|
10
|
+
* Promoted from the Phase 0 shell runner
|
|
11
|
+
* (`.claude/skills/cmate-verify/scripts/verify-run.sh`), which established the
|
|
12
|
+
* two disciplines this module exists to preserve:
|
|
13
|
+
*
|
|
14
|
+
* 1. The verdict is the process exit code, read directly. Nothing is piped:
|
|
15
|
+
* `cmd | grep` reports grep's status, so a failing gate reads as a pass.
|
|
16
|
+
* 2. A failing gate does not end the run. All gates execute so one report
|
|
17
|
+
* lists every problem instead of revealing them one round-trip at a time.
|
|
18
|
+
*
|
|
19
|
+
* Server-only: spawns processes and reads the filesystem.
|
|
20
|
+
*
|
|
21
|
+
* @module lib/verification/gate-runner
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.VerificationConflictError = exports.MAX_CONCURRENT_VERIFICATIONS = exports.CONFIG_GATE_ID = exports.ENV_CLEAN_GATE_ID = exports.SCOPE_GATE_ID = exports.WORK_EVIDENCE_GATE_ID = void 0;
|
|
25
|
+
exports.waitForVerification = waitForVerification;
|
|
26
|
+
exports.startVerification = startVerification;
|
|
27
|
+
const child_process_1 = require("child_process");
|
|
28
|
+
const fs_1 = require("fs");
|
|
29
|
+
const db_instance_1 = require("../../lib/db/db-instance");
|
|
30
|
+
const db_1 = require("../../lib/db");
|
|
31
|
+
// Past the barrel, as `agent-event-service` does for `getActiveTaskForInstance`:
|
|
32
|
+
// this selector exists for the verification runner alone and re-exporting it
|
|
33
|
+
// would invite the callers that must keep using `getActiveTask` (Auto-Yes,
|
|
34
|
+
// prompt events) to reach for the wider set by accident.
|
|
35
|
+
const tasks_db_1 = require("../../lib/db/tasks-db");
|
|
36
|
+
const git_default_branch_1 = require("../../lib/git/git-default-branch");
|
|
37
|
+
const logger_1 = require("../../lib/logger");
|
|
38
|
+
const contract_message_1 = require("../../lib/tasks/contract-message");
|
|
39
|
+
const task_transition_service_1 = require("../../lib/tasks/task-transition-service");
|
|
40
|
+
const env_clean_gate_1 = require("./env-clean-gate");
|
|
41
|
+
const env_snapshot_1 = require("./env-snapshot");
|
|
42
|
+
const scope_gate_1 = require("./scope-gate");
|
|
43
|
+
const verify_config_1 = require("./verify-config");
|
|
44
|
+
Object.defineProperty(exports, "ENV_CLEAN_GATE_ID", { enumerable: true, get: function () { return verify_config_1.ENV_CLEAN_GATE_ID; } });
|
|
45
|
+
Object.defineProperty(exports, "SCOPE_GATE_ID", { enumerable: true, get: function () { return verify_config_1.SCOPE_GATE_ID; } });
|
|
46
|
+
Object.defineProperty(exports, "WORK_EVIDENCE_GATE_ID", { enumerable: true, get: function () { return verify_config_1.WORK_EVIDENCE_GATE_ID; } });
|
|
47
|
+
const logger = (0, logger_1.createLogger)('lib/verification/gate-runner');
|
|
48
|
+
/**
|
|
49
|
+
* Pseudo-gate used to carry a config-load failure into the run record.
|
|
50
|
+
*
|
|
51
|
+
* `verification_runs` has no message column, so without this row a run that
|
|
52
|
+
* died on a missing or malformed verify.yaml would be an `error` status with
|
|
53
|
+
* no stated cause — the caller would have to guess. It is only ever written on
|
|
54
|
+
* the path where zero real gates run, so it cannot collide with a user gate.
|
|
55
|
+
*/
|
|
56
|
+
exports.CONFIG_GATE_ID = 'config';
|
|
57
|
+
/**
|
|
58
|
+
* What the built-in gates record in `command`.
|
|
59
|
+
*
|
|
60
|
+
* Neither runs a shell command, but the column is what a reader consults to see
|
|
61
|
+
* what a gate did, so it names the plumbing instead of being left null.
|
|
62
|
+
*/
|
|
63
|
+
const WORK_EVIDENCE_GATE_COMMAND = 'git merge-base / rev-list / status --porcelain (excluding contract files)';
|
|
64
|
+
const SCOPE_GATE_COMMAND = 'git diff --name-only / status --porcelain × contract scope';
|
|
65
|
+
const ENV_CLEAN_GATE_COMMAND = 'lsof -iTCP -sTCP:LISTEN / tmux list-sessions / $HOME / ~/.commandmate × task-start snapshot';
|
|
66
|
+
/**
|
|
67
|
+
* Concurrent runs allowed process-wide.
|
|
68
|
+
*
|
|
69
|
+
* Gates are whole test suites and builds; letting every worktree start one at
|
|
70
|
+
* once turns a verification request into a fork bomb on a developer laptop.
|
|
71
|
+
* Excess runs queue rather than fail: the run row already exists and its
|
|
72
|
+
* caller has the id, so a queued run is honest about what will happen.
|
|
73
|
+
*/
|
|
74
|
+
exports.MAX_CONCURRENT_VERIFICATIONS = 2;
|
|
75
|
+
/** Grace period between SIGTERM and SIGKILL for a gate that overran. */
|
|
76
|
+
const SIGKILL_GRACE_MS = 5000;
|
|
77
|
+
/** A run is already in flight for this worktree. Surfaces as HTTP 409. */
|
|
78
|
+
class VerificationConflictError extends Error {
|
|
79
|
+
worktreeId;
|
|
80
|
+
runningRunId;
|
|
81
|
+
constructor(worktreeId, runningRunId) {
|
|
82
|
+
super(`Verification run ${runningRunId} is already running for worktree '${worktreeId}'`);
|
|
83
|
+
this.name = 'VerificationConflictError';
|
|
84
|
+
this.worktreeId = worktreeId;
|
|
85
|
+
this.runningRunId = runningRunId;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
exports.VerificationConflictError = VerificationConflictError;
|
|
89
|
+
/**
|
|
90
|
+
* A gate that did not execute (Issue #1625).
|
|
91
|
+
*
|
|
92
|
+
* `started_at` is NOT NULL in the schema and a null `finished_at` already means
|
|
93
|
+
* "still open", so there is no way to spell "no interval" — a zero-length
|
|
94
|
+
* window at the instant the decision was made is recorded instead. It is also
|
|
95
|
+
* the truth: nothing ran, and `duration_ms` has always said 0 for these rows.
|
|
96
|
+
*/
|
|
97
|
+
function notRun(logTail) {
|
|
98
|
+
return {
|
|
99
|
+
status: 'skipped',
|
|
100
|
+
exitCode: null,
|
|
101
|
+
startedAt: Date.now(),
|
|
102
|
+
durationMs: 0,
|
|
103
|
+
logTail,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
// =============================================================================
|
|
107
|
+
// Concurrency
|
|
108
|
+
// =============================================================================
|
|
109
|
+
let activeVerifications = 0;
|
|
110
|
+
const slotWaiters = [];
|
|
111
|
+
function acquireSlot() {
|
|
112
|
+
if (activeVerifications < exports.MAX_CONCURRENT_VERIFICATIONS) {
|
|
113
|
+
activeVerifications += 1;
|
|
114
|
+
return Promise.resolve();
|
|
115
|
+
}
|
|
116
|
+
return new Promise((resolve) => {
|
|
117
|
+
slotWaiters.push(resolve);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function releaseSlot() {
|
|
121
|
+
const next = slotWaiters.shift();
|
|
122
|
+
if (next) {
|
|
123
|
+
// Hand the slot straight over instead of decrementing and re-incrementing:
|
|
124
|
+
// a gap between the two would let a third run slip past the limit.
|
|
125
|
+
next();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
activeVerifications -= 1;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* In-flight runs, so callers (tests, and the CLI in Phase 1-4) can await a run
|
|
132
|
+
* that {@link startVerification} deliberately did not wait for.
|
|
133
|
+
*/
|
|
134
|
+
const inFlight = new Map();
|
|
135
|
+
/**
|
|
136
|
+
* Resolve when `runId` finishes, with its final status.
|
|
137
|
+
*
|
|
138
|
+
* Returns null when the run is not in flight — either it never started or it
|
|
139
|
+
* already finished, in which case the database holds the answer.
|
|
140
|
+
*/
|
|
141
|
+
function waitForVerification(runId) {
|
|
142
|
+
return inFlight.get(runId) ?? Promise.resolve(null);
|
|
143
|
+
}
|
|
144
|
+
// =============================================================================
|
|
145
|
+
// Command execution
|
|
146
|
+
// =============================================================================
|
|
147
|
+
/**
|
|
148
|
+
* Keep exactly the last `maxBytes` of output.
|
|
149
|
+
*
|
|
150
|
+
* A failing suite's useful part is its tail (the summary), and log_tail is
|
|
151
|
+
* stored per gate, so an unbounded buffer would put a full `npm test` log in
|
|
152
|
+
* SQLite for every run.
|
|
153
|
+
*
|
|
154
|
+
* Evicting whole chunks is not enough: a stream that writes 4KB and then a
|
|
155
|
+
* newline would leave the newline as the entire "tail". The head chunk is
|
|
156
|
+
* trimmed in place instead, so what is retained never depends on where the
|
|
157
|
+
* operating system happened to split the writes.
|
|
158
|
+
*/
|
|
159
|
+
class TailBuffer {
|
|
160
|
+
maxBytes;
|
|
161
|
+
chunks = [];
|
|
162
|
+
size = 0;
|
|
163
|
+
constructor(maxBytes) {
|
|
164
|
+
this.maxBytes = maxBytes;
|
|
165
|
+
}
|
|
166
|
+
append(chunk) {
|
|
167
|
+
if (this.maxBytes <= 0 || chunk.length === 0)
|
|
168
|
+
return;
|
|
169
|
+
this.chunks.push(chunk);
|
|
170
|
+
this.size += chunk.length;
|
|
171
|
+
// Drop head chunks that are entirely outside the window...
|
|
172
|
+
while (this.chunks.length > 0 && this.size - this.chunks[0].length >= this.maxBytes) {
|
|
173
|
+
this.size -= this.chunks.shift().length;
|
|
174
|
+
}
|
|
175
|
+
// ...then trim the one chunk that straddles its edge.
|
|
176
|
+
if (this.size > this.maxBytes) {
|
|
177
|
+
const excess = this.size - this.maxBytes;
|
|
178
|
+
this.chunks[0] = this.chunks[0].subarray(excess);
|
|
179
|
+
this.size -= excess;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
toString() {
|
|
183
|
+
return this.chunks.length === 0 ? '' : Buffer.concat(this.chunks).toString('utf8');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Run one shell command and report its exit code.
|
|
188
|
+
*
|
|
189
|
+
* `shell: true` so a gate can be written the way a developer would type it
|
|
190
|
+
* (`npm run lint`), and `detached: true` so the timeout can signal the whole
|
|
191
|
+
* process group — killing only the shell leaves the `npm`/`vitest` grandchildren
|
|
192
|
+
* running and holding the worktree.
|
|
193
|
+
*/
|
|
194
|
+
function runCommand(command, cwd, timeoutSec, maxLogTailBytes) {
|
|
195
|
+
return new Promise((resolve) => {
|
|
196
|
+
const startedAt = Date.now();
|
|
197
|
+
const tail = new TailBuffer(maxLogTailBytes);
|
|
198
|
+
let settled = false;
|
|
199
|
+
let timedOut = false;
|
|
200
|
+
let graceTimer;
|
|
201
|
+
const child = (0, child_process_1.spawn)(command, {
|
|
202
|
+
shell: true,
|
|
203
|
+
cwd,
|
|
204
|
+
// CI=true matches how the same commands run in the pipeline; vitest picks
|
|
205
|
+
// a different fileParallelism otherwise, and a gate that passes here but
|
|
206
|
+
// fails in CI is worse than no gate.
|
|
207
|
+
env: { ...process.env, CI: 'true' },
|
|
208
|
+
detached: true,
|
|
209
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
210
|
+
});
|
|
211
|
+
// stdout and stderr share one buffer: gate output is read as a transcript,
|
|
212
|
+
// and interleaving is what makes an error attributable to the step above it.
|
|
213
|
+
child.stdout?.on('data', (chunk) => tail.append(chunk));
|
|
214
|
+
child.stderr?.on('data', (chunk) => tail.append(chunk));
|
|
215
|
+
const signalGroup = (signal) => {
|
|
216
|
+
if (child.pid === undefined)
|
|
217
|
+
return;
|
|
218
|
+
try {
|
|
219
|
+
process.kill(-child.pid, signal);
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// The group is already gone, or this platform refused the negative pid.
|
|
223
|
+
try {
|
|
224
|
+
child.kill(signal);
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
// Nothing left to signal.
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
// Only ever called from an event handler, so the killTimer binding below is
|
|
232
|
+
// initialised by the time this runs.
|
|
233
|
+
const finish = (outcome) => {
|
|
234
|
+
if (settled)
|
|
235
|
+
return;
|
|
236
|
+
settled = true;
|
|
237
|
+
clearTimeout(killTimer);
|
|
238
|
+
if (graceTimer)
|
|
239
|
+
clearTimeout(graceTimer);
|
|
240
|
+
resolve({ ...outcome, startedAt, durationMs: Date.now() - startedAt });
|
|
241
|
+
};
|
|
242
|
+
const killTimer = setTimeout(() => {
|
|
243
|
+
timedOut = true;
|
|
244
|
+
signalGroup('SIGTERM');
|
|
245
|
+
graceTimer = setTimeout(() => signalGroup('SIGKILL'), SIGKILL_GRACE_MS);
|
|
246
|
+
graceTimer.unref?.();
|
|
247
|
+
}, timeoutSec * 1000);
|
|
248
|
+
killTimer.unref?.();
|
|
249
|
+
child.on('error', (error) => {
|
|
250
|
+
finish({
|
|
251
|
+
status: 'error',
|
|
252
|
+
exitCode: null,
|
|
253
|
+
logTail: `${tail.toString()}\n[gate could not be spawned: ${error.message}]`.trim(),
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
child.on('close', (code, signal) => {
|
|
257
|
+
if (timedOut) {
|
|
258
|
+
finish({
|
|
259
|
+
status: 'timeout',
|
|
260
|
+
// A timed-out gate has no verdict of its own; recording the signal's
|
|
261
|
+
// exit code would look like the command decided something.
|
|
262
|
+
exitCode: null,
|
|
263
|
+
logTail: `${tail.toString()}\n[gate exceeded ${timeoutSec}s and was terminated (${signal ?? 'exited'})]`.trim(),
|
|
264
|
+
});
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
finish({
|
|
268
|
+
status: code === 0 ? 'passed' : 'failed',
|
|
269
|
+
exitCode: code,
|
|
270
|
+
logTail: tail.toString() || null,
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
/** Run a git plumbing command. No shell: every argument here is code-supplied. */
|
|
276
|
+
function runGit(args, cwd) {
|
|
277
|
+
return new Promise((resolve) => {
|
|
278
|
+
let stdout = '';
|
|
279
|
+
const child = (0, child_process_1.spawn)('git', args, { cwd, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
280
|
+
child.stdout?.on('data', (chunk) => {
|
|
281
|
+
stdout += chunk.toString('utf8');
|
|
282
|
+
});
|
|
283
|
+
child.on('error', () => resolve({ code: null, stdout: '' }));
|
|
284
|
+
child.on('close', (code) => resolve({ code, stdout }));
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
// =============================================================================
|
|
288
|
+
// Built-in work-evidence gate
|
|
289
|
+
// =============================================================================
|
|
290
|
+
/**
|
|
291
|
+
* Decide whether the worktree contains work at all.
|
|
292
|
+
*
|
|
293
|
+
* Commits ahead of `baseRef` or a dirty tree both count. Neither means the
|
|
294
|
+
* agent reported completion without touching anything, which the run records
|
|
295
|
+
* as `not_started` rather than letting empty work collect a row of passing
|
|
296
|
+
* gates — the precise failure `verify-completion.sh` was written to catch.
|
|
297
|
+
*
|
|
298
|
+
* Contract files are evidence of the *orchestrator*, not of the agent, so
|
|
299
|
+
* neither side counts them (see {@link CONTRACT_DIR_PREFIX}).
|
|
300
|
+
*/
|
|
301
|
+
async function evaluateWorkEvidence(worktreePath, baseRef, requireCommit) {
|
|
302
|
+
const startedAt = Date.now();
|
|
303
|
+
const done = (status, logTail, exitCode) => ({
|
|
304
|
+
status,
|
|
305
|
+
exitCode,
|
|
306
|
+
startedAt,
|
|
307
|
+
durationMs: Date.now() - startedAt,
|
|
308
|
+
logTail,
|
|
309
|
+
});
|
|
310
|
+
if (!baseRef) {
|
|
311
|
+
return done('error', 'work-evidence: no base ref. Set options.baseRef in ' +
|
|
312
|
+
`${verify_config_1.VERIFY_CONFIG_RELATIVE_PATH}; origin/HEAD did not resolve a default branch.`, null);
|
|
313
|
+
}
|
|
314
|
+
const mergeBase = await runGit(['merge-base', baseRef, 'HEAD'], worktreePath);
|
|
315
|
+
if (mergeBase.code !== 0 || mergeBase.stdout.trim() === '') {
|
|
316
|
+
return done('error', `work-evidence: 'git merge-base ${baseRef} HEAD' failed; base ref is unreachable from this worktree.`, null);
|
|
317
|
+
}
|
|
318
|
+
const base = mergeBase.stdout.trim();
|
|
319
|
+
// `:(top)` is an explicit "everything from the repository root" so the
|
|
320
|
+
// pathspec is not exclusions alone, and it anchors both patterns at the root
|
|
321
|
+
// rather than at cwd. A setup commit that only carries the contract must not
|
|
322
|
+
// read as a commit's worth of work.
|
|
323
|
+
const revList = await runGit([
|
|
324
|
+
'rev-list',
|
|
325
|
+
'--count',
|
|
326
|
+
`${base}..HEAD`,
|
|
327
|
+
'--',
|
|
328
|
+
':(top)',
|
|
329
|
+
`:(exclude,top)${scope_gate_1.CONTRACT_DIR_PREFIX}`,
|
|
330
|
+
], worktreePath);
|
|
331
|
+
if (revList.code !== 0) {
|
|
332
|
+
return done('error', `work-evidence: 'git rev-list --count ${base}..HEAD' failed.`, null);
|
|
333
|
+
}
|
|
334
|
+
const commitCount = Number.parseInt(revList.stdout.trim(), 10);
|
|
335
|
+
// -z -uall for the same reasons scope-gate uses them: the human format
|
|
336
|
+
// C-quotes paths with spaces and joins renames with ` -> `, and the default
|
|
337
|
+
// untracked mode collapses a fresh `.commandmate/tasks/` to one directory
|
|
338
|
+
// entry — all three make a per-path exclusion judge something that is not a
|
|
339
|
+
// path. An entry counts as work when any of its paths is not a contract file,
|
|
340
|
+
// so renaming a contract into real work is still a change.
|
|
341
|
+
const porcelain = await runGit(['status', '--porcelain', '-z', '--untracked-files=all'], worktreePath);
|
|
342
|
+
if (porcelain.code !== 0) {
|
|
343
|
+
return done('error', "work-evidence: 'git status --porcelain' failed.", null);
|
|
344
|
+
}
|
|
345
|
+
const uncommittedCount = (0, scope_gate_1.parsePorcelainEntries)(porcelain.stdout).filter((paths) => paths.some((path) => !(0, scope_gate_1.isContractPath)(path))).length;
|
|
346
|
+
const summary = `work-evidence: baseRef=${baseRef} commits=${commitCount} uncommitted=${uncommittedCount}` +
|
|
347
|
+
(requireCommit.required ? ' requireCommit=true' : '') +
|
|
348
|
+
' (contract files excluded)';
|
|
349
|
+
if (!Number.isFinite(commitCount) || (commitCount === 0 && uncommittedCount === 0)) {
|
|
350
|
+
return done('failed', `${summary}\nNo commits and no uncommitted changes: nothing to verify.`, 1);
|
|
351
|
+
}
|
|
352
|
+
// Issue #1628 (D-4): `commits=0 uncommitted=1` passing is what let a task
|
|
353
|
+
// contract that says "未 commit の作業は未完了とみなされる" still end in
|
|
354
|
+
// `RESULT passed` over work that was never committed. Opt-in, and off by
|
|
355
|
+
// default so the gate keeps answering "is there work here" for everyone else.
|
|
356
|
+
// Two declarations can switch it on — `options.requireCommit` per repository
|
|
357
|
+
// and `success.requireCommit` per delegation (#1642) — so the reason names
|
|
358
|
+
// whichever ones actually did.
|
|
359
|
+
if (requireCommit.required && commitCount === 0) {
|
|
360
|
+
return done('failed', `${summary}\n${requireCommit.sources.join(' and ')} requires a commit: ` +
|
|
361
|
+
'uncommitted changes are not work evidence, commit them.', 1);
|
|
362
|
+
}
|
|
363
|
+
return done('passed', summary, 0);
|
|
364
|
+
}
|
|
365
|
+
// =============================================================================
|
|
366
|
+
// Run orchestration
|
|
367
|
+
// =============================================================================
|
|
368
|
+
function sameRealPath(a, b) {
|
|
369
|
+
try {
|
|
370
|
+
return (0, fs_1.realpathSync)(a) === (0, fs_1.realpathSync)(b);
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
// An unresolvable path cannot be proven to be the primary checkout, and the
|
|
374
|
+
// gates would fail on it anyway.
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Resolve the ref work-evidence diffs against.
|
|
380
|
+
*
|
|
381
|
+
* An explicit `options.baseRef` wins. Otherwise origin/HEAD names the default
|
|
382
|
+
* branch, and the remote-tracking ref is used rather than the local branch:
|
|
383
|
+
* a linked worktree frequently has no local checkout of `main`.
|
|
384
|
+
*/
|
|
385
|
+
async function resolveBaseRef(config, worktreePath) {
|
|
386
|
+
if (config.options.baseRef)
|
|
387
|
+
return config.options.baseRef;
|
|
388
|
+
const defaultBranch = await (0, git_default_branch_1.resolveDefaultBranchName)(worktreePath);
|
|
389
|
+
return defaultBranch ? `origin/${defaultBranch}` : null;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Resolve `gateIds` against the config.
|
|
393
|
+
*
|
|
394
|
+
* Returns a message instead of a selection when the request names a gate that
|
|
395
|
+
* does not exist, or selects nothing at all. Both would otherwise produce a run
|
|
396
|
+
* with zero gate results, which {@link aggregateRunStatus} would have to call
|
|
397
|
+
* `passed` — a green verdict from having checked nothing.
|
|
398
|
+
*
|
|
399
|
+
* @param requireEnvClean whether a declaration switched env-clean on. It is
|
|
400
|
+
* ORed with an explicit request rather than replacing it, so `--gates
|
|
401
|
+
* env-clean` still works with the switch off (and honestly reports
|
|
402
|
+
* UNKNOWN, because no baseline was recorded), and a delegation that
|
|
403
|
+
* declared the requirement cannot lose it by naming a narrower gate list.
|
|
404
|
+
*/
|
|
405
|
+
function selectGates(config, gateIds, requireEnvClean) {
|
|
406
|
+
if (!gateIds) {
|
|
407
|
+
return {
|
|
408
|
+
runWorkEvidence: true,
|
|
409
|
+
scope: 'implicit',
|
|
410
|
+
runEnvClean: requireEnvClean,
|
|
411
|
+
gates: config.gates,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
const known = new Set([
|
|
415
|
+
verify_config_1.WORK_EVIDENCE_GATE_ID,
|
|
416
|
+
verify_config_1.SCOPE_GATE_ID,
|
|
417
|
+
verify_config_1.ENV_CLEAN_GATE_ID,
|
|
418
|
+
...config.gates.map((g) => g.id),
|
|
419
|
+
]);
|
|
420
|
+
const unknown = gateIds.filter((id) => !known.has(id));
|
|
421
|
+
if (unknown.length > 0) {
|
|
422
|
+
return `Unknown gate id(s): ${unknown.join(', ')}. Declared gates: ${[...known].join(', ')}.`;
|
|
423
|
+
}
|
|
424
|
+
const requested = new Set(gateIds);
|
|
425
|
+
const selection = {
|
|
426
|
+
runWorkEvidence: requested.has(verify_config_1.WORK_EVIDENCE_GATE_ID),
|
|
427
|
+
scope: requested.has(verify_config_1.SCOPE_GATE_ID) ? 'explicit' : 'off',
|
|
428
|
+
runEnvClean: requested.has(verify_config_1.ENV_CLEAN_GATE_ID) || requireEnvClean,
|
|
429
|
+
gates: config.gates.filter((gate) => requested.has(gate.id)),
|
|
430
|
+
};
|
|
431
|
+
if (!selection.runWorkEvidence &&
|
|
432
|
+
selection.scope === 'off' &&
|
|
433
|
+
!selection.runEnvClean &&
|
|
434
|
+
selection.gates.length === 0) {
|
|
435
|
+
return 'gateIds selected no gates; a run with no gates has nothing to report.';
|
|
436
|
+
}
|
|
437
|
+
return selection;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Collapse gate verdicts into the run verdict.
|
|
441
|
+
*
|
|
442
|
+
* A `skipped` gate blocks `passed` on purpose. The primary-checkout guard skips
|
|
443
|
+
* exactly the gates that would have judged the work, so reporting `passed`
|
|
444
|
+
* would turn "we declined to check" into "we checked and it was fine" — the
|
|
445
|
+
* inversion `skipped` exists to prevent.
|
|
446
|
+
*/
|
|
447
|
+
function aggregateRunStatus(gateStatuses) {
|
|
448
|
+
if (gateStatuses.some((s) => s === 'failed' || s === 'timeout' || s === 'error'))
|
|
449
|
+
return 'failed';
|
|
450
|
+
if (gateStatuses.some((s) => s === 'skipped'))
|
|
451
|
+
return 'error';
|
|
452
|
+
return 'passed';
|
|
453
|
+
}
|
|
454
|
+
async function executeRun(db, runId, worktreePath, config, selection, baseRef, task, detachedContract, envClean) {
|
|
455
|
+
const { maxLogTailBytes, skipInPrimaryCheckout } = config.options;
|
|
456
|
+
const { runWorkEvidence, gates } = selection;
|
|
457
|
+
// ORed with the repository-wide switch rather than replacing it (#1642): a
|
|
458
|
+
// delegation may tighten the rule, never relax one the repository declared.
|
|
459
|
+
const requireCommit = (0, contract_message_1.resolveRequireCommit)(task?.contract ?? null, config);
|
|
460
|
+
/**
|
|
461
|
+
* Close an open gate row with the interval its evaluator measured.
|
|
462
|
+
*
|
|
463
|
+
* `finishedAt` is derived from the same clock reading `durationMs` was
|
|
464
|
+
* measured against, so the stored pair is that measurement rather than a
|
|
465
|
+
* second, later reading (#1625).
|
|
466
|
+
*/
|
|
467
|
+
const close = (rowId, outcome) => {
|
|
468
|
+
(0, db_1.finishGateResult)(db, rowId, {
|
|
469
|
+
status: outcome.status,
|
|
470
|
+
exitCode: outcome.exitCode,
|
|
471
|
+
durationMs: outcome.durationMs,
|
|
472
|
+
logTail: outcome.logTail,
|
|
473
|
+
executionWindow: {
|
|
474
|
+
startedAt: outcome.startedAt,
|
|
475
|
+
finishedAt: outcome.startedAt + outcome.durationMs,
|
|
476
|
+
},
|
|
477
|
+
});
|
|
478
|
+
return outcome;
|
|
479
|
+
};
|
|
480
|
+
/**
|
|
481
|
+
* Open the gate's row, run it, then close it.
|
|
482
|
+
*
|
|
483
|
+
* The row exists *before* the gate does anything, so a process that dies
|
|
484
|
+
* mid-gate leaves a `running` row naming what it died in — which is what
|
|
485
|
+
* {@link reconcileOrphanVerificationRuns} closes on the next start. Opening
|
|
486
|
+
* and closing back to back (what this used to do) left that loop with nothing
|
|
487
|
+
* to find, and an interrupted gate with no record at all.
|
|
488
|
+
*/
|
|
489
|
+
const runGate = async (gateId, command, evaluate) => {
|
|
490
|
+
const row = (0, db_1.createGateResult)(db, runId, { gateId, command });
|
|
491
|
+
return close(row.id, await evaluate());
|
|
492
|
+
};
|
|
493
|
+
/**
|
|
494
|
+
* Record a gate that was never entered.
|
|
495
|
+
*
|
|
496
|
+
* Opened and closed on the spot: there is no execution to leave a `running`
|
|
497
|
+
* row around, and the row carries {@link notRun}'s zero-length window.
|
|
498
|
+
*/
|
|
499
|
+
const recordNotRun = (gateId, command, reason) => {
|
|
500
|
+
const outcome = notRun(reason);
|
|
501
|
+
const row = (0, db_1.createGateResult)(db, runId, { gateId, command });
|
|
502
|
+
return close(row.id, outcome);
|
|
503
|
+
};
|
|
504
|
+
const statuses = [];
|
|
505
|
+
if (runWorkEvidence) {
|
|
506
|
+
const outcome = await runGate(verify_config_1.WORK_EVIDENCE_GATE_ID, WORK_EVIDENCE_GATE_COMMAND, () => evaluateWorkEvidence(worktreePath, baseRef, requireCommit));
|
|
507
|
+
if (outcome.status !== 'passed') {
|
|
508
|
+
// Nothing was produced, so every command gate below would be judging the
|
|
509
|
+
// base commit. Record them as skipped so the run shows what was not run.
|
|
510
|
+
const reason = `skipped: the ${verify_config_1.WORK_EVIDENCE_GATE_ID} gate did not pass.`;
|
|
511
|
+
if (selection.scope !== 'off') {
|
|
512
|
+
recordNotRun(verify_config_1.SCOPE_GATE_ID, SCOPE_GATE_COMMAND, reason);
|
|
513
|
+
}
|
|
514
|
+
if (selection.runEnvClean) {
|
|
515
|
+
recordNotRun(verify_config_1.ENV_CLEAN_GATE_ID, ENV_CLEAN_GATE_COMMAND, reason);
|
|
516
|
+
}
|
|
517
|
+
for (const gate of gates) {
|
|
518
|
+
recordNotRun(gate.id, gate.command, reason);
|
|
519
|
+
}
|
|
520
|
+
return outcome.status === 'failed' ? 'not_started' : 'error';
|
|
521
|
+
}
|
|
522
|
+
statuses.push(outcome.status);
|
|
523
|
+
}
|
|
524
|
+
if (selection.scope !== 'off') {
|
|
525
|
+
// A detached contract is decided before the gate would run, so its row is
|
|
526
|
+
// recorded rather than executed.
|
|
527
|
+
const outcome = detachedContract
|
|
528
|
+
? recordNotRun(verify_config_1.SCOPE_GATE_ID, SCOPE_GATE_COMMAND, (0, scope_gate_1.scopeSkipDetachedContract)(detachedContract.id, detachedContract.status))
|
|
529
|
+
: await runGate(verify_config_1.SCOPE_GATE_ID, SCOPE_GATE_COMMAND, () => (0, scope_gate_1.evaluateScope)(worktreePath, task?.contract.scope ?? null, task?.contract.success.requireScopeClean ?? false, baseRef, task?.contractPath ?? null));
|
|
530
|
+
// A skipped scope gate in the default selection is usually not a declined
|
|
531
|
+
// check: no contract declared a scope, so there was no assertion to test.
|
|
532
|
+
// Counting it would turn every contract-less run into `error` (see
|
|
533
|
+
// aggregateRunStatus). It *is* a declined check when the caller asked for
|
|
534
|
+
// scope by name, and when a contract exists that this run could not attach
|
|
535
|
+
// to — that second case is how a run reported `passed` while the scope it
|
|
536
|
+
// was supposed to judge went unexamined (#1620).
|
|
537
|
+
const declined = selection.scope === 'explicit' || detachedContract !== null;
|
|
538
|
+
if (outcome.status !== 'skipped' || declined) {
|
|
539
|
+
statuses.push(outcome.status);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
if (selection.runEnvClean) {
|
|
543
|
+
// Measured before the command gates, not after: the pair of snapshots spans
|
|
544
|
+
// the agent's working window, and the gates below are the repository's own
|
|
545
|
+
// declared commands. A `test:e2e` gate that starts a server would otherwise
|
|
546
|
+
// be reported as the agent leaking one.
|
|
547
|
+
const outcome = await runGate(verify_config_1.ENV_CLEAN_GATE_ID, ENV_CLEAN_GATE_COMMAND, () => (0, env_clean_gate_1.evaluateEnvClean)({
|
|
548
|
+
worktreeId: envClean.worktreeId,
|
|
549
|
+
worktreePath,
|
|
550
|
+
taskId: envClean.taskId,
|
|
551
|
+
baseline: envClean.baseline,
|
|
552
|
+
sources: envClean.decision.sources,
|
|
553
|
+
}));
|
|
554
|
+
statuses.push(outcome.status);
|
|
555
|
+
}
|
|
556
|
+
const isPrimaryCheckout = skipInPrimaryCheckout && sameRealPath(worktreePath, process.cwd());
|
|
557
|
+
for (const gate of gates) {
|
|
558
|
+
if (isPrimaryCheckout) {
|
|
559
|
+
// The server process runs out of this directory. A `build` here replaces
|
|
560
|
+
// the chunks the running app is serving mid-flight and breaks the live UI
|
|
561
|
+
// — observed twice before this guard existed.
|
|
562
|
+
statuses.push(recordNotRun(gate.id, gate.command, 'skipped: worktreePath is the server process working directory and ' +
|
|
563
|
+
'options.skipInPrimaryCheckout is true.').status);
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
const outcome = await runGate(gate.id, gate.command, () => runCommand(gate.command, worktreePath, gate.timeoutSec, maxLogTailBytes));
|
|
567
|
+
statuses.push(outcome.status);
|
|
568
|
+
}
|
|
569
|
+
return aggregateRunStatus(statuses);
|
|
570
|
+
}
|
|
571
|
+
// =============================================================================
|
|
572
|
+
// Task contracts (#1545)
|
|
573
|
+
// =============================================================================
|
|
574
|
+
/**
|
|
575
|
+
* Translate a run verdict into the event that closes the task.
|
|
576
|
+
*
|
|
577
|
+
* `error` maps to `verify_failed`: no verdict was reached, and a task that could
|
|
578
|
+
* not be verified is not a task that passed. Leaving it in `verifying` forever
|
|
579
|
+
* would be a worse lie than calling it failed — the reason is recorded in the
|
|
580
|
+
* run's `config` gate log_tail.
|
|
581
|
+
*/
|
|
582
|
+
function taskEventForRunStatus(status) {
|
|
583
|
+
switch (status) {
|
|
584
|
+
case 'passed':
|
|
585
|
+
return 'verify_passed';
|
|
586
|
+
case 'not_started':
|
|
587
|
+
return 'verify_not_started';
|
|
588
|
+
case 'cancelled':
|
|
589
|
+
return 'cancel';
|
|
590
|
+
default:
|
|
591
|
+
return 'verify_failed';
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Raise a task event, tolerating a task that vanished mid-run.
|
|
596
|
+
*
|
|
597
|
+
* A deleted worktree takes its tasks with it; the run's own record is already
|
|
598
|
+
* written by then, so failing here would lose a verdict that exists.
|
|
599
|
+
*
|
|
600
|
+
* Whether the event is allowed is the state machine's decision, not this
|
|
601
|
+
* module's — a run against an already-`succeeded` task is refused there, and the
|
|
602
|
+
* refusal is recorded in `task_events` instead of vanishing.
|
|
603
|
+
*/
|
|
604
|
+
function recordTaskTransition(db, taskId, event, runId) {
|
|
605
|
+
try {
|
|
606
|
+
(0, task_transition_service_1.applyTaskEvent)(db, taskId, event, runId === undefined ? undefined : { runId });
|
|
607
|
+
}
|
|
608
|
+
catch (error) {
|
|
609
|
+
logger.warn('task-transition-failed', {
|
|
610
|
+
taskId,
|
|
611
|
+
event,
|
|
612
|
+
error: error instanceof Error ? error.message : String(error),
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Resolve the task this run belongs to.
|
|
618
|
+
*
|
|
619
|
+
* An explicit `taskId` wins even when the row is gone — the caller asserted the
|
|
620
|
+
* attribution and the run should record it — so a missing row yields no task to
|
|
621
|
+
* transition, not an error. It also wins over the task's status: naming a task
|
|
622
|
+
* is the caller saying "this run is about that contract", which is what lets a
|
|
623
|
+
* `wait --verify` still judge a contract whose agent already verified itself
|
|
624
|
+
* and closed the task (#1620).
|
|
625
|
+
*/
|
|
626
|
+
function resolveTask(db, input) {
|
|
627
|
+
if (input.taskId !== undefined) {
|
|
628
|
+
return (0, db_1.getTask)(db, input.taskId);
|
|
629
|
+
}
|
|
630
|
+
return (0, tasks_db_1.getVerifiableTask)(db, input.worktreeId);
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* A contract this worktree has that the run could not be attached to.
|
|
634
|
+
*
|
|
635
|
+
* The scope gate skips whenever no task resolves, and that skip is forgiven so
|
|
636
|
+
* a repository without contracts is not permanently `error`. The forgiveness
|
|
637
|
+
* was load-bearing for a case it was never meant to cover: once an agent's own
|
|
638
|
+
* `commandmate verify` moved its task to `succeeded`, the orchestrator's run
|
|
639
|
+
* resolved nothing, skipped scope, and reported `passed` — a green verdict on a
|
|
640
|
+
* declaration nothing had read (#1620).
|
|
641
|
+
*
|
|
642
|
+
* Reported only when the contract actually asked for a clean scope: a contract
|
|
643
|
+
* with `requireScopeClean: false` would have skipped while fully attached, so
|
|
644
|
+
* nothing was declined by losing it.
|
|
645
|
+
*
|
|
646
|
+
* @returns the closed task, or null when there is genuinely nothing to judge
|
|
647
|
+
*/
|
|
648
|
+
function findDetachedContract(db, input, task) {
|
|
649
|
+
// A resolved task, or a caller that named one, is an attributed run.
|
|
650
|
+
if (task !== null || input.taskId !== undefined)
|
|
651
|
+
return null;
|
|
652
|
+
const [latest] = (0, db_1.listTasks)(db, input.worktreeId, 1);
|
|
653
|
+
// `pending` is not detached: nothing was sent, so no run can be about it yet.
|
|
654
|
+
// Anything resolvable would already have come back from resolveTask.
|
|
655
|
+
if (!latest || latest.status === 'pending')
|
|
656
|
+
return null;
|
|
657
|
+
return latest.contract.success.requireScopeClean ? latest : null;
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Open a verification run and execute it in the background.
|
|
661
|
+
*
|
|
662
|
+
* Returns as soon as the run row exists, so an HTTP caller gets an id it can
|
|
663
|
+
* poll instead of holding a connection open for the length of a test suite.
|
|
664
|
+
* Use {@link waitForVerification} to await the verdict.
|
|
665
|
+
*
|
|
666
|
+
* @throws VerificationConflictError when the worktree already has an open run
|
|
667
|
+
*/
|
|
668
|
+
async function startVerification(input) {
|
|
669
|
+
const db = (0, db_instance_1.getDbInstance)();
|
|
670
|
+
const existing = (0, db_1.getRunningVerificationRun)(db, input.worktreeId);
|
|
671
|
+
if (existing) {
|
|
672
|
+
throw new VerificationConflictError(input.worktreeId, existing.id);
|
|
673
|
+
}
|
|
674
|
+
const task = resolveTask(db, input);
|
|
675
|
+
// Deliberately not folded into `taskId` below: the run did not judge this
|
|
676
|
+
// contract, and recording it would put a verdict-less run in that task's
|
|
677
|
+
// history. It is named in the scope gate's log_tail instead.
|
|
678
|
+
const detachedContract = findDetachedContract(db, input, task);
|
|
679
|
+
const taskId = input.taskId ?? task?.id ?? null;
|
|
680
|
+
// An explicit gateIds always wins: `verify --gates lint` must mean lint even
|
|
681
|
+
// when the contract asks for more.
|
|
682
|
+
const gateIds = input.gateIds ?? (task ? (0, contract_message_1.resolveContractGateIds)(task.contract) ?? undefined : undefined);
|
|
683
|
+
let config = null;
|
|
684
|
+
let configFailure = null;
|
|
685
|
+
try {
|
|
686
|
+
config = (0, verify_config_1.loadVerifyConfig)(input.worktreePath);
|
|
687
|
+
if (!config) {
|
|
688
|
+
configFailure =
|
|
689
|
+
`${verify_config_1.VERIFY_CONFIG_RELATIVE_PATH} not found in ${input.worktreePath}. ` +
|
|
690
|
+
'Declare the repository verification gates there before running verification.';
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
catch (error) {
|
|
694
|
+
configFailure =
|
|
695
|
+
error instanceof verify_config_1.VerifyConfigError
|
|
696
|
+
? error.message
|
|
697
|
+
: `Failed to read ${verify_config_1.VERIFY_CONFIG_RELATIVE_PATH}: ${error.message}`;
|
|
698
|
+
}
|
|
699
|
+
// Resolved before gate selection because it can add a gate to it (#1740).
|
|
700
|
+
const envCleanDecision = (0, env_clean_gate_1.resolveRequireEnvClean)(task?.contract ?? null, config);
|
|
701
|
+
let selection = null;
|
|
702
|
+
if (config && !configFailure) {
|
|
703
|
+
const selected = selectGates(config, gateIds, envCleanDecision.required);
|
|
704
|
+
if (typeof selected === 'string') {
|
|
705
|
+
configFailure = selected;
|
|
706
|
+
}
|
|
707
|
+
else {
|
|
708
|
+
selection = selected;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
const baseRef = config ? await resolveBaseRef(config, input.worktreePath) : null;
|
|
712
|
+
const run = (0, db_1.createVerificationRun)(db, {
|
|
713
|
+
worktreeId: input.worktreeId,
|
|
714
|
+
trigger: input.trigger,
|
|
715
|
+
instanceId: input.instanceId ?? null,
|
|
716
|
+
taskId,
|
|
717
|
+
baseRef,
|
|
718
|
+
});
|
|
719
|
+
// Whether this run may move the task is the state machine's call (#1548).
|
|
720
|
+
// It refuses `verify_started` from `succeeded`/`cancelled`, so an unrelated
|
|
721
|
+
// manual run still cannot walk back a recorded verdict — while a `failed`
|
|
722
|
+
// task, which a retry legitimately reopens, is no longer excluded by a status
|
|
723
|
+
// check that could not tell the two apart.
|
|
724
|
+
const trackedTask = task;
|
|
725
|
+
if (!config || !selection) {
|
|
726
|
+
// Closed synchronously: there is no work to schedule, and leaving the row
|
|
727
|
+
// `running` would block the next request for this worktree on a run that
|
|
728
|
+
// will never move.
|
|
729
|
+
// Recorded, not run: this row carries a message about a config that could
|
|
730
|
+
// not be loaded, so like any gate that never executed it gets a zero-length
|
|
731
|
+
// window at the moment the failure was recorded (#1625).
|
|
732
|
+
const recordedAt = Date.now();
|
|
733
|
+
const row = (0, db_1.createGateResult)(db, run.id, {
|
|
734
|
+
gateId: exports.CONFIG_GATE_ID,
|
|
735
|
+
command: verify_config_1.VERIFY_CONFIG_RELATIVE_PATH,
|
|
736
|
+
});
|
|
737
|
+
(0, db_1.finishGateResult)(db, row.id, {
|
|
738
|
+
status: 'error',
|
|
739
|
+
exitCode: null,
|
|
740
|
+
durationMs: 0,
|
|
741
|
+
logTail: configFailure,
|
|
742
|
+
executionWindow: { startedAt: recordedAt, finishedAt: recordedAt },
|
|
743
|
+
});
|
|
744
|
+
(0, db_1.finishVerificationRun)(db, run.id, 'error');
|
|
745
|
+
logger.warn('verification-config-unusable', { runId: run.id, worktreeId: input.worktreeId });
|
|
746
|
+
if (trackedTask) {
|
|
747
|
+
// The run opened and immediately errored, so the task passes through
|
|
748
|
+
// `verifying` the same way it would for gates that ran: an unusable config
|
|
749
|
+
// means this task can never be shown to have passed, and going straight to
|
|
750
|
+
// `verify_failed` would be a transition the machine has no rule for.
|
|
751
|
+
recordTaskTransition(db, trackedTask.id, 'verify_started', run.id);
|
|
752
|
+
recordTaskTransition(db, trackedTask.id, 'verify_failed', run.id);
|
|
753
|
+
}
|
|
754
|
+
return { runId: run.id };
|
|
755
|
+
}
|
|
756
|
+
if (trackedTask) {
|
|
757
|
+
recordTaskTransition(db, trackedTask.id, 'verify_started', run.id);
|
|
758
|
+
}
|
|
759
|
+
const resolvedConfig = config;
|
|
760
|
+
const resolvedSelection = selection;
|
|
761
|
+
// Read once, up front: the baseline is a file, and a run that took minutes
|
|
762
|
+
// must be judged against the snapshot the task started from, not against
|
|
763
|
+
// whatever is on disk when the gate finally executes.
|
|
764
|
+
const envCleanContext = {
|
|
765
|
+
worktreeId: input.worktreeId,
|
|
766
|
+
taskId,
|
|
767
|
+
baseline: resolvedSelection.runEnvClean && taskId ? (0, env_snapshot_1.loadEnvSnapshot)(taskId) : null,
|
|
768
|
+
decision: envCleanDecision,
|
|
769
|
+
};
|
|
770
|
+
const completion = (async () => {
|
|
771
|
+
await acquireSlot();
|
|
772
|
+
let terminalStatus = 'error';
|
|
773
|
+
try {
|
|
774
|
+
const status = await executeRun(db, run.id, input.worktreePath, resolvedConfig, resolvedSelection, baseRef, task, detachedContract, envCleanContext);
|
|
775
|
+
terminalStatus = status;
|
|
776
|
+
(0, db_1.finishVerificationRun)(db, run.id, status);
|
|
777
|
+
return status;
|
|
778
|
+
}
|
|
779
|
+
catch (error) {
|
|
780
|
+
logger.error('verification-run-crashed', {
|
|
781
|
+
runId: run.id,
|
|
782
|
+
error: error instanceof Error ? error.message : String(error),
|
|
783
|
+
});
|
|
784
|
+
try {
|
|
785
|
+
(0, db_1.finishVerificationRun)(db, run.id, 'error');
|
|
786
|
+
}
|
|
787
|
+
catch {
|
|
788
|
+
// The row is gone (worktree deleted mid-run); nothing left to close.
|
|
789
|
+
}
|
|
790
|
+
return 'error';
|
|
791
|
+
}
|
|
792
|
+
finally {
|
|
793
|
+
if (trackedTask) {
|
|
794
|
+
recordTaskTransition(db, trackedTask.id, taskEventForRunStatus(terminalStatus), run.id);
|
|
795
|
+
}
|
|
796
|
+
releaseSlot();
|
|
797
|
+
inFlight.delete(run.id);
|
|
798
|
+
}
|
|
799
|
+
})();
|
|
800
|
+
inFlight.set(run.id, completion);
|
|
801
|
+
return { runId: run.id };
|
|
802
|
+
}
|