commandmate 0.22.2 → 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 +108 -108
- package/.next/app-path-routes-manifest.json +21 -21
- package/.next/build-manifest.json +2 -2
- package/.next/prerender-manifest.json +12 -12
- 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 +21 -21
- 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-53a3e1d78e4fe8f7.js → 6488-95ba9d24a76f0cc5.js} +1 -1
- package/.next/static/chunks/app/worktrees/[id]/{page-6dc0b8c4e808ea3e.js → page-966a451ae7b0903a.js} +1 -1
- package/.next/trace +12 -12
- 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/{XpKfn5x6H-mtTaXI9yNmb → wH6K6QNPHSdATFkVoIcy3}/_buildManifest.js +0 -0
- /package/.next/static/{XpKfn5x6H-mtTaXI9yNmb → wH6K6QNPHSdATFkVoIcy3}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Built-in `scope` gate: reconcile what actually changed in the worktree against
|
|
4
|
+
* the contract's `scope.allow` / `scope.deny` (Issue #1546, Phase 2-2).
|
|
5
|
+
*
|
|
6
|
+
* Canonical spec: docs/design/task-contract.md §2.2
|
|
7
|
+
*
|
|
8
|
+
* A parallel worker that edits a shared config file, or quietly rewrites a
|
|
9
|
+
* module another worker owns, produces a green run and a broken integration.
|
|
10
|
+
* The contract already declares which paths the task may touch; this gate is
|
|
11
|
+
* what makes that declaration something other than a comment.
|
|
12
|
+
*
|
|
13
|
+
* Promoted from `.claude/skills/orchestrate-monitor/scripts/verify-scope.sh`.
|
|
14
|
+
* That script is a *grep* guard over one file rather than a diff-versus-glob
|
|
15
|
+
* check, so what carries over is not its logic but the two ways it false-reported
|
|
16
|
+
* (both recorded in feedback_orchestrate_monitor_started_guard):
|
|
17
|
+
*
|
|
18
|
+
* 1. It matched a forbidden pattern that appeared only in explanatory prose.
|
|
19
|
+
* The analogue here is judging text instead of paths, so nothing in this
|
|
20
|
+
* module ever greps content: the file set comes from git plumbing, and the
|
|
21
|
+
* verdict comes from matching whole paths.
|
|
22
|
+
* 2. Its `grep -c ... || echo 0` idiom emitted a two-line "0\n0" that the
|
|
23
|
+
* caller read as a count. The analogue is git output that parses into
|
|
24
|
+
* phantom entries, so every git call here is `-z`: the human formats quote
|
|
25
|
+
* and arrow-join paths, and splitting those on whitespace invents files
|
|
26
|
+
* that do not exist.
|
|
27
|
+
*
|
|
28
|
+
* Server-only: spawns git and is called from the verification engine.
|
|
29
|
+
*
|
|
30
|
+
* @module lib/verification/scope-gate
|
|
31
|
+
*/
|
|
32
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
+
exports.SCOPE_SKIP_NOT_REQUIRED = exports.SCOPE_SKIP_NO_CONTRACT = exports.ScopeMatcher = exports.CONTRACT_DIR_PREFIX = exports.ALWAYS_ALLOWED_PREFIX = exports.SCOPE_ALLOW_GUIDANCE = exports.MAX_REPORTED_VIOLATIONS = void 0;
|
|
34
|
+
exports.isContractPath = isContractPath;
|
|
35
|
+
exports.parsePorcelainEntries = parsePorcelainEntries;
|
|
36
|
+
exports.collectChangedPaths = collectChangedPaths;
|
|
37
|
+
exports.scopeSkipDetachedContract = scopeSkipDetachedContract;
|
|
38
|
+
exports.evaluateScope = evaluateScope;
|
|
39
|
+
const child_process_1 = require("child_process");
|
|
40
|
+
/**
|
|
41
|
+
* Violations listed in `log_tail` before the rest are summarised as a count.
|
|
42
|
+
*
|
|
43
|
+
* A worker that ran in the wrong directory can put thousands of paths out of
|
|
44
|
+
* scope, and log_tail is stored per gate in SQLite.
|
|
45
|
+
*/
|
|
46
|
+
exports.MAX_REPORTED_VIOLATIONS = 100;
|
|
47
|
+
/**
|
|
48
|
+
* Actionable coda appended to every scope failure report (#1683).
|
|
49
|
+
*
|
|
50
|
+
* The violating paths alone say *what* failed but not what to do about it —
|
|
51
|
+
* #1678 B-2 is a worker structurally unable to pass because a lockfile was
|
|
52
|
+
* missing from the issue's target-file list, and nothing in the output said so.
|
|
53
|
+
* The wording answers both directions a violation can resolve: widen the
|
|
54
|
+
* declared scope when the diff is intended, revert when it is not. Re-sending
|
|
55
|
+
* is named because scope is judged from the contract's send-time snapshot, so
|
|
56
|
+
* editing the YAML in place changes nothing.
|
|
57
|
+
*/
|
|
58
|
+
exports.SCOPE_ALLOW_GUIDANCE = "To allow this diff, add the paths above to the contract's scope.allow (the " +
|
|
59
|
+
"issue's target-file list) and re-send the task (`send --contract`) — scope " +
|
|
60
|
+
'is judged from the send-time snapshot, so editing the contract file alone ' +
|
|
61
|
+
'changes nothing. A path matching deny: is a deliberate prohibition; revert ' +
|
|
62
|
+
'it instead.';
|
|
63
|
+
/**
|
|
64
|
+
* Paths that are in scope no matter what the contract says.
|
|
65
|
+
*
|
|
66
|
+
* The contract file lives here, so a contract whose own `allow` list forgot to
|
|
67
|
+
* mention `.commandmate/` would fail the moment it was committed — the scope
|
|
68
|
+
* declaration would make its own storage a violation. The exemption covers the
|
|
69
|
+
* `allow` requirement only; an explicit `deny` still applies, because a deny is
|
|
70
|
+
* a deliberate prohibition and silently ignoring one is worse than the accident
|
|
71
|
+
* this exemption prevents.
|
|
72
|
+
*/
|
|
73
|
+
exports.ALWAYS_ALLOWED_PREFIX = '.commandmate/';
|
|
74
|
+
/**
|
|
75
|
+
* Execution contracts, dropped from both gates' change sets entirely (#1580).
|
|
76
|
+
*
|
|
77
|
+
* An orchestrator writes `.commandmate/tasks/<task>.yaml` into a worktree and
|
|
78
|
+
* sends immediately, without committing it. Counting that file as change makes
|
|
79
|
+
* an agent that did nothing indistinguishable from one that did something:
|
|
80
|
+
* work-evidence sees a dirty tree and passes, and the run that should have been
|
|
81
|
+
* `not_started` collects a row of green gates instead.
|
|
82
|
+
*
|
|
83
|
+
* Dropping it is tamper-safe because the contract is snapshotted into
|
|
84
|
+
* `tasks.contract_json` at send time (src/lib/db/tasks-db.ts), so editing the
|
|
85
|
+
* file afterwards cannot change what the run is judged against.
|
|
86
|
+
* `.commandmate/verify.yaml` has no such snapshot — the gates are re-read from
|
|
87
|
+
* the file on every run — so it stays in the change set, where a contract's
|
|
88
|
+
* explicit `deny` can still catch an agent weakening its own gates.
|
|
89
|
+
*/
|
|
90
|
+
exports.CONTRACT_DIR_PREFIX = '.commandmate/tasks/';
|
|
91
|
+
/** Whether git reported a path that {@link CONTRACT_DIR_PREFIX} excludes. */
|
|
92
|
+
function isContractPath(path) {
|
|
93
|
+
return path.startsWith(exports.CONTRACT_DIR_PREFIX);
|
|
94
|
+
}
|
|
95
|
+
// =============================================================================
|
|
96
|
+
// Glob matching
|
|
97
|
+
// =============================================================================
|
|
98
|
+
/**
|
|
99
|
+
* Translate a scope glob into an anchored regular expression.
|
|
100
|
+
*
|
|
101
|
+
* Deliberately a small, documented subset rather than a glob library. The
|
|
102
|
+
* semantics are fixed in docs/design/task-contract.md §2.2:
|
|
103
|
+
*
|
|
104
|
+
* - `**` as a whole path segment crosses directory boundaries, including zero of
|
|
105
|
+
* them: `a/**` matches `a/b` and `a/b/c`, `**\/*.ts` matches `x.ts`.
|
|
106
|
+
* - `*` and `?` never cross `/`. Leading dots are ordinary characters, so
|
|
107
|
+
* `.github/**` and `*.md` behave the way they read.
|
|
108
|
+
* - `{a,b}` is alternation, nestable. Unbalanced braces are literal, as in a
|
|
109
|
+
* shell — `src/{a` is a path with a brace in it, not a syntax error.
|
|
110
|
+
* - `[` and `]` are **literal**. This repository routes through
|
|
111
|
+
* `src/app/proxy/[...path]/`, and treating those as character classes would
|
|
112
|
+
* make the pattern that names them match nothing at all — a silent no-match is
|
|
113
|
+
* the failure mode this gate exists to avoid.
|
|
114
|
+
* - A pattern that matches a directory also matches everything beneath it, so
|
|
115
|
+
* `src/lib`, `src/lib/` and `src/lib/**` all mean the same thing. Without this
|
|
116
|
+
* rule, `allow: ["src/lib/verification"]` — the way people write a directory —
|
|
117
|
+
* would put every file inside it out of scope.
|
|
118
|
+
*/
|
|
119
|
+
function globToRegExp(rawPattern) {
|
|
120
|
+
// A trailing slash carries no information once the rule above applies.
|
|
121
|
+
const pattern = rawPattern.replace(/\/+$/, '');
|
|
122
|
+
const bracesBalanced = hasBalancedBraces(pattern);
|
|
123
|
+
let source = '';
|
|
124
|
+
let depth = 0;
|
|
125
|
+
let i = 0;
|
|
126
|
+
while (i < pattern.length) {
|
|
127
|
+
const char = pattern[i];
|
|
128
|
+
if (char === '*') {
|
|
129
|
+
let end = i;
|
|
130
|
+
while (pattern[end] === '*')
|
|
131
|
+
end += 1;
|
|
132
|
+
const isGlobstar = end - i > 1;
|
|
133
|
+
const atSegmentStart = i === 0 || pattern[i - 1] === '/';
|
|
134
|
+
if (isGlobstar && atSegmentStart && pattern[end] === '/') {
|
|
135
|
+
// Consume the following slash too: `a/**/b` has to match `a/b`.
|
|
136
|
+
source += '(?:[^/]*/)*';
|
|
137
|
+
i = end + 1;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (isGlobstar && atSegmentStart && end === pattern.length) {
|
|
141
|
+
source += '.*';
|
|
142
|
+
i = end;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
// `**` that is not a whole segment (`a**b`) cannot mean "cross
|
|
146
|
+
// directories" without also matching `a/x/b`, which the pattern does not
|
|
147
|
+
// say. Collapse it to a single-segment wildcard.
|
|
148
|
+
source += '[^/]*';
|
|
149
|
+
i = end;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (char === '?') {
|
|
153
|
+
source += '[^/]';
|
|
154
|
+
i += 1;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (bracesBalanced && char === '{') {
|
|
158
|
+
depth += 1;
|
|
159
|
+
source += '(?:';
|
|
160
|
+
i += 1;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (bracesBalanced && char === '}' && depth > 0) {
|
|
164
|
+
depth -= 1;
|
|
165
|
+
source += ')';
|
|
166
|
+
i += 1;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (bracesBalanced && char === ',' && depth > 0) {
|
|
170
|
+
source += '|';
|
|
171
|
+
i += 1;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
source += char.replace(/[.*+?^${}()|[\]\\]/, '\\$&');
|
|
175
|
+
i += 1;
|
|
176
|
+
}
|
|
177
|
+
return new RegExp(`^${source}(?:/.*)?$`);
|
|
178
|
+
}
|
|
179
|
+
function hasBalancedBraces(pattern) {
|
|
180
|
+
let depth = 0;
|
|
181
|
+
for (const char of pattern) {
|
|
182
|
+
if (char === '{')
|
|
183
|
+
depth += 1;
|
|
184
|
+
else if (char === '}') {
|
|
185
|
+
depth -= 1;
|
|
186
|
+
if (depth < 0)
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return depth === 0;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* A scope declaration compiled once per run.
|
|
194
|
+
*
|
|
195
|
+
* Compiling per pattern per file would rebuild the same regexes for every one of
|
|
196
|
+
* potentially thousands of changed paths.
|
|
197
|
+
*/
|
|
198
|
+
class ScopeMatcher {
|
|
199
|
+
contractPath;
|
|
200
|
+
allow;
|
|
201
|
+
deny;
|
|
202
|
+
/**
|
|
203
|
+
* @param contractPath the contract's own path, exempted alongside
|
|
204
|
+
* {@link ALWAYS_ALLOWED_PREFIX} for contracts kept outside `.commandmate/`
|
|
205
|
+
*/
|
|
206
|
+
constructor(scope, contractPath = null) {
|
|
207
|
+
this.contractPath = contractPath;
|
|
208
|
+
this.allow = scope.allow.map(globToRegExp);
|
|
209
|
+
this.deny = scope.deny.map(globToRegExp);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Whether `path` is a scope violation.
|
|
213
|
+
*
|
|
214
|
+
* `path` is a repository-root-relative POSIX path as git reports it.
|
|
215
|
+
*/
|
|
216
|
+
isViolation(path) {
|
|
217
|
+
if (this.deny.some((pattern) => pattern.test(path)))
|
|
218
|
+
return true;
|
|
219
|
+
if (path.startsWith(exports.ALWAYS_ALLOWED_PREFIX) || path === this.contractPath)
|
|
220
|
+
return false;
|
|
221
|
+
return !this.allow.some((pattern) => pattern.test(path));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
exports.ScopeMatcher = ScopeMatcher;
|
|
225
|
+
// =============================================================================
|
|
226
|
+
// Changed-path collection
|
|
227
|
+
// =============================================================================
|
|
228
|
+
/** Run a git plumbing command. No shell: every argument here is code-supplied. */
|
|
229
|
+
function runGit(args, cwd) {
|
|
230
|
+
return new Promise((resolve) => {
|
|
231
|
+
let stdout = '';
|
|
232
|
+
let stderr = '';
|
|
233
|
+
const child = (0, child_process_1.spawn)('git', args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
234
|
+
child.stdout?.on('data', (chunk) => {
|
|
235
|
+
stdout += chunk.toString('utf8');
|
|
236
|
+
});
|
|
237
|
+
child.stderr?.on('data', (chunk) => {
|
|
238
|
+
stderr += chunk.toString('utf8');
|
|
239
|
+
});
|
|
240
|
+
child.on('error', (error) => resolve({ code: null, stdout: '', stderr: error.message }));
|
|
241
|
+
child.on('close', (code) => resolve({ code, stdout, stderr }));
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
function splitNul(output) {
|
|
245
|
+
return output.split('\0').filter((field) => field !== '');
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Paths in `git status --porcelain -z -uall` output.
|
|
249
|
+
*
|
|
250
|
+
* Measured format (git 2.49): every record is `XY<space><path>NUL`, and a
|
|
251
|
+
* rename or copy appends the original path as the *next* NUL field —
|
|
252
|
+
* `R new.txt\0old.txt\0`. Note that this is the reverse of the human
|
|
253
|
+
* `old -> new` rendering, and that `-z` writes paths verbatim while the human
|
|
254
|
+
* format C-quotes anything containing a space. Both paths of a rename are
|
|
255
|
+
* returned: moving a file *out of* a permitted directory is a change to that
|
|
256
|
+
* directory, and judging only the destination would miss it.
|
|
257
|
+
*
|
|
258
|
+
* Grouped per entry rather than flattened because work-evidence counts entries,
|
|
259
|
+
* and a rename that moved a contract file into real work has to stay countable
|
|
260
|
+
* as one change (#1580).
|
|
261
|
+
*/
|
|
262
|
+
function parsePorcelainEntries(output) {
|
|
263
|
+
const fields = output.split('\0');
|
|
264
|
+
const entries = [];
|
|
265
|
+
let i = 0;
|
|
266
|
+
while (i < fields.length) {
|
|
267
|
+
const entry = fields[i];
|
|
268
|
+
i += 1;
|
|
269
|
+
// "XY " plus at least one path character.
|
|
270
|
+
if (entry.length < 4)
|
|
271
|
+
continue;
|
|
272
|
+
const paths = [entry.slice(3)];
|
|
273
|
+
const [x, y] = entry;
|
|
274
|
+
if (x === 'R' || x === 'C' || y === 'R' || y === 'C') {
|
|
275
|
+
const original = fields[i];
|
|
276
|
+
i += 1;
|
|
277
|
+
if (original)
|
|
278
|
+
paths.push(original);
|
|
279
|
+
}
|
|
280
|
+
entries.push(paths);
|
|
281
|
+
}
|
|
282
|
+
return entries;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Every path this worktree changed relative to `baseRef`.
|
|
286
|
+
*
|
|
287
|
+
* The union of committed and uncommitted change is deliberate: looking only at
|
|
288
|
+
* commits misses a deviation the agent has not committed yet, and looking only
|
|
289
|
+
* at the working tree misses everything it did commit.
|
|
290
|
+
*
|
|
291
|
+
* @returns the paths, or a message describing which git call could not answer
|
|
292
|
+
*/
|
|
293
|
+
async function collectChangedPaths(worktreePath, baseRef) {
|
|
294
|
+
const mergeBase = await runGit(['merge-base', baseRef, 'HEAD'], worktreePath);
|
|
295
|
+
if (mergeBase.code !== 0 || mergeBase.stdout.trim() === '') {
|
|
296
|
+
return {
|
|
297
|
+
error: `'git merge-base ${baseRef} HEAD' failed; base ref is unreachable from this worktree.` +
|
|
298
|
+
(mergeBase.stderr.trim() ? `\n${mergeBase.stderr.trim()}` : ''),
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
const base = mergeBase.stdout.trim();
|
|
302
|
+
// --no-renames so a committed rename reports both paths (--name-only alone
|
|
303
|
+
// reports the destination only, once rename detection folds the pair).
|
|
304
|
+
const diff = await runGit(['diff', '--name-only', '-z', '--no-renames', base, 'HEAD'], worktreePath);
|
|
305
|
+
if (diff.code !== 0) {
|
|
306
|
+
return {
|
|
307
|
+
error: `'git diff --name-only ${base} HEAD' failed.` +
|
|
308
|
+
(diff.stderr.trim() ? `\n${diff.stderr.trim()}` : ''),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
// -uall: without it a new directory collapses to a single `?? dir/` entry, and
|
|
312
|
+
// the gate would judge a directory name instead of the files inside it.
|
|
313
|
+
const status = await runGit(['status', '--porcelain', '-z', '--untracked-files=all'], worktreePath);
|
|
314
|
+
if (status.code !== 0) {
|
|
315
|
+
return {
|
|
316
|
+
error: "'git status --porcelain' failed." +
|
|
317
|
+
(status.stderr.trim() ? `\n${status.stderr.trim()}` : ''),
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
// Both sides are filtered, not just the working tree: an orchestrator may
|
|
321
|
+
// also have committed the contract as a setup commit (#1580).
|
|
322
|
+
const paths = new Set([...splitNul(diff.stdout), ...parsePorcelainEntries(status.stdout).flat()].filter((path) => !isContractPath(path)));
|
|
323
|
+
return { paths: [...paths].sort(), mergeBase: base };
|
|
324
|
+
}
|
|
325
|
+
// =============================================================================
|
|
326
|
+
// Gate evaluation
|
|
327
|
+
// =============================================================================
|
|
328
|
+
function formatPatterns(patterns) {
|
|
329
|
+
return patterns.length > 0 ? patterns.join(', ') : '(none)';
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Reasons the gate has nothing to judge, phrased for `log_tail`.
|
|
333
|
+
*
|
|
334
|
+
* A skip is recorded rather than omitted: a run with no scope row at all is
|
|
335
|
+
* indistinguishable from a run predating the gate, and "we looked and no
|
|
336
|
+
* contract declared a scope" is the answer the reader needs.
|
|
337
|
+
*/
|
|
338
|
+
exports.SCOPE_SKIP_NO_CONTRACT = 'scope: no contract is attached to this run, so no scope is declared to check.';
|
|
339
|
+
exports.SCOPE_SKIP_NOT_REQUIRED = 'scope: the contract sets success.requireScopeClean: false.';
|
|
340
|
+
/**
|
|
341
|
+
* The skip that is *not* harmless: a contract exists and this run could not be
|
|
342
|
+
* attached to it (#1620).
|
|
343
|
+
*
|
|
344
|
+
* Worded apart from {@link SCOPE_SKIP_NO_CONTRACT} on purpose. Both are skips,
|
|
345
|
+
* but only one of them means "there was nothing to judge" — reporting the other
|
|
346
|
+
* with the same sentence is what let a run whose scope was never checked read
|
|
347
|
+
* as a repository that simply does not use contracts. The task id is named so
|
|
348
|
+
* the reader can go and look at what was not judged.
|
|
349
|
+
*/
|
|
350
|
+
function scopeSkipDetachedContract(taskId, status) {
|
|
351
|
+
return (`scope: task ${taskId} declares a scope for this worktree, but it is ${status} and ` +
|
|
352
|
+
'this run was not attached to it, so its scope was NOT judged. ' +
|
|
353
|
+
'Name the task when starting the run (`wait --verify` does this automatically).');
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Judge the worktree's changes against a contract's scope.
|
|
357
|
+
*
|
|
358
|
+
* @param scope declared scope, or null when no contract is attached to the run
|
|
359
|
+
* @param requireScopeClean the contract's `success.requireScopeClean`
|
|
360
|
+
* @param baseRef ref the change set is computed against; null when unresolved
|
|
361
|
+
* @param contractPath the contract's own path, always exempt from `allow`
|
|
362
|
+
*/
|
|
363
|
+
async function evaluateScope(worktreePath, scope, requireScopeClean, baseRef, contractPath = null) {
|
|
364
|
+
const startedAt = Date.now();
|
|
365
|
+
const done = (status, logTail, exitCode) => ({
|
|
366
|
+
status,
|
|
367
|
+
exitCode,
|
|
368
|
+
startedAt,
|
|
369
|
+
durationMs: Date.now() - startedAt,
|
|
370
|
+
logTail,
|
|
371
|
+
});
|
|
372
|
+
if (!scope)
|
|
373
|
+
return done('skipped', exports.SCOPE_SKIP_NO_CONTRACT, null);
|
|
374
|
+
if (!requireScopeClean)
|
|
375
|
+
return done('skipped', exports.SCOPE_SKIP_NOT_REQUIRED, null);
|
|
376
|
+
if (!baseRef) {
|
|
377
|
+
return done('error', 'scope: no base ref, so there is nothing to diff against. Set options.baseRef in ' +
|
|
378
|
+
'.commandmate/verify.yaml; origin/HEAD did not resolve a default branch.', null);
|
|
379
|
+
}
|
|
380
|
+
const changed = await collectChangedPaths(worktreePath, baseRef);
|
|
381
|
+
if ('error' in changed)
|
|
382
|
+
return done('error', `scope: ${changed.error}`, null);
|
|
383
|
+
const matcher = new ScopeMatcher(scope, contractPath);
|
|
384
|
+
const violations = changed.paths.filter((path) => matcher.isViolation(path));
|
|
385
|
+
const summary = `scope: baseRef=${baseRef} changed=${changed.paths.length} violations=${violations.length}\n` +
|
|
386
|
+
`allow: ${formatPatterns(scope.allow)}\ndeny: ${formatPatterns(scope.deny)}`;
|
|
387
|
+
if (violations.length === 0) {
|
|
388
|
+
// An empty change set passes: work-evidence is the gate that judges "nothing
|
|
389
|
+
// happened", and failing here too would report one problem as two.
|
|
390
|
+
return done('passed', summary, 0);
|
|
391
|
+
}
|
|
392
|
+
const listed = violations.slice(0, exports.MAX_REPORTED_VIOLATIONS);
|
|
393
|
+
const remainder = violations.length - listed.length;
|
|
394
|
+
const report = [
|
|
395
|
+
summary,
|
|
396
|
+
'out of scope:',
|
|
397
|
+
...listed.map((path) => ` - ${path}`),
|
|
398
|
+
...(remainder > 0 ? [` ... and ${remainder} more`] : []),
|
|
399
|
+
exports.SCOPE_ALLOW_GUIDANCE,
|
|
400
|
+
].join('\n');
|
|
401
|
+
return done('failed', report, 1);
|
|
402
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Loader and validator for `.commandmate/verify.yaml`.
|
|
4
|
+
*
|
|
5
|
+
* Canonical spec: docs/design/verification-config.md
|
|
6
|
+
* Phase 0 reference implementation: .claude/skills/cmate-verify/scripts/verify-run.sh
|
|
7
|
+
*
|
|
8
|
+
* Server-only: reads from disk, so import this from API routes / CLI only.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.VerifyConfigError = exports.DEFAULT_MAX_LOG_TAIL_BYTES = exports.DEFAULT_TIMEOUT_SEC = exports.VERIFY_CONFIG_RELATIVE_PATH = exports.RESERVED_GATE_IDS = exports.ENV_CLEAN_GATE_ID = exports.SCOPE_GATE_ID = exports.WORK_EVIDENCE_GATE_ID = void 0;
|
|
12
|
+
exports.loadVerifyConfig = loadVerifyConfig;
|
|
13
|
+
const fs_1 = require("fs");
|
|
14
|
+
const path_1 = require("path");
|
|
15
|
+
const yaml_1 = require("yaml");
|
|
16
|
+
/**
|
|
17
|
+
* Built-in gate that answers "is there any work here to verify?".
|
|
18
|
+
*
|
|
19
|
+
* Declared here rather than in the runner so modules that only reason *about*
|
|
20
|
+
* gate selection (task contracts) can name it without importing the engine.
|
|
21
|
+
*/
|
|
22
|
+
exports.WORK_EVIDENCE_GATE_ID = 'work-evidence';
|
|
23
|
+
/**
|
|
24
|
+
* Built-in gate that reconciles the changed files against the contract's
|
|
25
|
+
* `scope.allow` / `scope.deny` (#1546). Declared alongside work-evidence for the
|
|
26
|
+
* same reason: `lib/tasks/contract-message.ts` has to name it while resolving a
|
|
27
|
+
* contract's gate list, and must not pull the engine in to do so.
|
|
28
|
+
*/
|
|
29
|
+
exports.SCOPE_GATE_ID = 'scope';
|
|
30
|
+
/**
|
|
31
|
+
* Built-in gate that reconciles the *machine* against the snapshot taken when
|
|
32
|
+
* the task was created (#1740): listening CommandMate servers, `mcbd-*` tmux
|
|
33
|
+
* sessions, `$HOME` and `~/.commandmate`. `scope` answers what a delegation
|
|
34
|
+
* changed inside the repository; this answers what it changed outside one.
|
|
35
|
+
*/
|
|
36
|
+
exports.ENV_CLEAN_GATE_ID = 'env-clean';
|
|
37
|
+
/** Gate IDs reserved for built-in gates; using one in `gates` is a config error. */
|
|
38
|
+
exports.RESERVED_GATE_IDS = [
|
|
39
|
+
exports.WORK_EVIDENCE_GATE_ID,
|
|
40
|
+
exports.SCOPE_GATE_ID,
|
|
41
|
+
exports.ENV_CLEAN_GATE_ID,
|
|
42
|
+
];
|
|
43
|
+
exports.VERIFY_CONFIG_RELATIVE_PATH = '.commandmate/verify.yaml';
|
|
44
|
+
exports.DEFAULT_TIMEOUT_SEC = 600;
|
|
45
|
+
exports.DEFAULT_MAX_LOG_TAIL_BYTES = 8192;
|
|
46
|
+
const MIN_TIMEOUT_SEC = 1;
|
|
47
|
+
const MAX_TIMEOUT_SEC = 7200;
|
|
48
|
+
const MAX_LOG_TAIL_BYTES = 1048576;
|
|
49
|
+
const GATE_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,31}$/;
|
|
50
|
+
const TOP_LEVEL_KEYS = ['version', 'gates', 'options'];
|
|
51
|
+
const GATE_KEYS = ['id', 'command', 'timeoutSec'];
|
|
52
|
+
const OPTION_KEYS = [
|
|
53
|
+
'baseRef',
|
|
54
|
+
'skipInPrimaryCheckout',
|
|
55
|
+
'maxLogTailBytes',
|
|
56
|
+
'requireCommit',
|
|
57
|
+
'requireEnvClean',
|
|
58
|
+
];
|
|
59
|
+
class VerifyConfigError extends Error {
|
|
60
|
+
issues;
|
|
61
|
+
constructor(issues) {
|
|
62
|
+
super(`invalid verify config:\n${issues.map((issue) => ` - ${issue}`).join('\n')}`);
|
|
63
|
+
this.name = 'VerifyConfigError';
|
|
64
|
+
this.issues = issues;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
exports.VerifyConfigError = VerifyConfigError;
|
|
68
|
+
function isMapping(value) {
|
|
69
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The bash reference parser strips outer quotes before comparing, so `timeoutSec: "900"`
|
|
73
|
+
* means 900 there. Accept the same spelling, but only an exact decimal literal.
|
|
74
|
+
*/
|
|
75
|
+
function asInteger(value) {
|
|
76
|
+
if (typeof value === 'number')
|
|
77
|
+
return Number.isInteger(value) ? value : null;
|
|
78
|
+
if (typeof value === 'string' && /^-?\d+$/.test(value))
|
|
79
|
+
return Number(value);
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
function asBoolean(value) {
|
|
83
|
+
if (typeof value === 'boolean')
|
|
84
|
+
return value;
|
|
85
|
+
if (value === 'true')
|
|
86
|
+
return true;
|
|
87
|
+
if (value === 'false')
|
|
88
|
+
return false;
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
function describe(value) {
|
|
92
|
+
if (value === undefined)
|
|
93
|
+
return 'nothing';
|
|
94
|
+
if (value === null)
|
|
95
|
+
return 'null';
|
|
96
|
+
if (typeof value === 'string')
|
|
97
|
+
return JSON.stringify(value);
|
|
98
|
+
if (Array.isArray(value))
|
|
99
|
+
return 'a list';
|
|
100
|
+
if (typeof value === 'object')
|
|
101
|
+
return 'a mapping';
|
|
102
|
+
return String(value);
|
|
103
|
+
}
|
|
104
|
+
function collectUnknownKeys(mapping, allowed, at, issues) {
|
|
105
|
+
for (const key of Object.keys(mapping)) {
|
|
106
|
+
if (!allowed.includes(key)) {
|
|
107
|
+
issues.push(`${at}: unknown key "${key}" (v1 is a closed set)`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function validateGates(value, issues) {
|
|
112
|
+
if (value === undefined || value === null) {
|
|
113
|
+
issues.push('gates: required, at least one gate must be defined');
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
if (!Array.isArray(value)) {
|
|
117
|
+
issues.push(`gates: must be a list (got ${describe(value)})`);
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
if (value.length === 0) {
|
|
121
|
+
issues.push('gates: at least one gate must be defined');
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
const gates = [];
|
|
125
|
+
const seenIds = new Set();
|
|
126
|
+
value.forEach((entry, index) => {
|
|
127
|
+
const at = `gates[${index}]`;
|
|
128
|
+
const issuesBefore = issues.length;
|
|
129
|
+
if (!isMapping(entry)) {
|
|
130
|
+
issues.push(`${at}: must be a mapping (got ${describe(entry)})`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
collectUnknownKeys(entry, GATE_KEYS, at, issues);
|
|
134
|
+
if (typeof entry.id !== 'string' || entry.id === '') {
|
|
135
|
+
issues.push(`${at}.id: required, must be a non-empty string (got ${describe(entry.id)})`);
|
|
136
|
+
}
|
|
137
|
+
else if (!GATE_ID_PATTERN.test(entry.id)) {
|
|
138
|
+
issues.push(`${at}.id: "${entry.id}" must match ${GATE_ID_PATTERN.source}`);
|
|
139
|
+
}
|
|
140
|
+
else if (exports.RESERVED_GATE_IDS.includes(entry.id)) {
|
|
141
|
+
issues.push(`${at}.id: "${entry.id}" is reserved for a built-in gate`);
|
|
142
|
+
}
|
|
143
|
+
else if (seenIds.has(entry.id)) {
|
|
144
|
+
issues.push(`${at}.id: duplicate gate id "${entry.id}"`);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
seenIds.add(entry.id);
|
|
148
|
+
}
|
|
149
|
+
if (typeof entry.command !== 'string' || entry.command.trim() === '') {
|
|
150
|
+
issues.push(`${at}.command: required, must be a non-empty string (got ${describe(entry.command)})`);
|
|
151
|
+
}
|
|
152
|
+
let timeoutSec = exports.DEFAULT_TIMEOUT_SEC;
|
|
153
|
+
if (entry.timeoutSec !== undefined) {
|
|
154
|
+
const parsed = asInteger(entry.timeoutSec);
|
|
155
|
+
if (parsed === null) {
|
|
156
|
+
issues.push(`${at}.timeoutSec: must be an integer (got ${describe(entry.timeoutSec)})`);
|
|
157
|
+
}
|
|
158
|
+
else if (parsed < MIN_TIMEOUT_SEC || parsed > MAX_TIMEOUT_SEC) {
|
|
159
|
+
issues.push(`${at}.timeoutSec: must be ${MIN_TIMEOUT_SEC}..${MAX_TIMEOUT_SEC} (got ${parsed})`);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
timeoutSec = parsed;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (issues.length === issuesBefore) {
|
|
166
|
+
gates.push({ id: entry.id, command: entry.command, timeoutSec });
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
return gates;
|
|
170
|
+
}
|
|
171
|
+
function validateOptions(value, issues) {
|
|
172
|
+
const options = {
|
|
173
|
+
baseRef: null,
|
|
174
|
+
skipInPrimaryCheckout: true,
|
|
175
|
+
maxLogTailBytes: exports.DEFAULT_MAX_LOG_TAIL_BYTES,
|
|
176
|
+
requireCommit: false,
|
|
177
|
+
requireEnvClean: false,
|
|
178
|
+
};
|
|
179
|
+
// A childless `options:` key parses as null and means "all defaults".
|
|
180
|
+
if (value === undefined || value === null)
|
|
181
|
+
return options;
|
|
182
|
+
if (!isMapping(value)) {
|
|
183
|
+
issues.push(`options: must be a mapping (got ${describe(value)})`);
|
|
184
|
+
return options;
|
|
185
|
+
}
|
|
186
|
+
collectUnknownKeys(value, OPTION_KEYS, 'options', issues);
|
|
187
|
+
if (value.baseRef !== undefined) {
|
|
188
|
+
if (typeof value.baseRef !== 'string' || value.baseRef.trim() === '') {
|
|
189
|
+
issues.push(`options.baseRef: must be a non-empty string (got ${describe(value.baseRef)})`);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
options.baseRef = value.baseRef;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (value.skipInPrimaryCheckout !== undefined) {
|
|
196
|
+
const parsed = asBoolean(value.skipInPrimaryCheckout);
|
|
197
|
+
if (parsed === null) {
|
|
198
|
+
issues.push(`options.skipInPrimaryCheckout: must be true or false (got ${describe(value.skipInPrimaryCheckout)})`);
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
options.skipInPrimaryCheckout = parsed;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (value.requireCommit !== undefined) {
|
|
205
|
+
const parsed = asBoolean(value.requireCommit);
|
|
206
|
+
if (parsed === null) {
|
|
207
|
+
issues.push(`options.requireCommit: must be true or false (got ${describe(value.requireCommit)})`);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
options.requireCommit = parsed;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (value.requireEnvClean !== undefined) {
|
|
214
|
+
const parsed = asBoolean(value.requireEnvClean);
|
|
215
|
+
if (parsed === null) {
|
|
216
|
+
issues.push(`options.requireEnvClean: must be true or false (got ${describe(value.requireEnvClean)})`);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
options.requireEnvClean = parsed;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (value.maxLogTailBytes !== undefined) {
|
|
223
|
+
const parsed = asInteger(value.maxLogTailBytes);
|
|
224
|
+
if (parsed === null) {
|
|
225
|
+
issues.push(`options.maxLogTailBytes: must be an integer (got ${describe(value.maxLogTailBytes)})`);
|
|
226
|
+
}
|
|
227
|
+
else if (parsed < 0 || parsed > MAX_LOG_TAIL_BYTES) {
|
|
228
|
+
issues.push(`options.maxLogTailBytes: must be 0..${MAX_LOG_TAIL_BYTES} (got ${parsed})`);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
options.maxLogTailBytes = parsed;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return options;
|
|
235
|
+
}
|
|
236
|
+
function parseVerifyConfig(raw, configPath) {
|
|
237
|
+
let document;
|
|
238
|
+
try {
|
|
239
|
+
document = (0, yaml_1.parse)(raw);
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
throw new VerifyConfigError([`${configPath}: YAML parse error: ${error.message}`]);
|
|
243
|
+
}
|
|
244
|
+
if (!isMapping(document)) {
|
|
245
|
+
throw new VerifyConfigError([
|
|
246
|
+
`${configPath}: expected a YAML mapping at the top level (got ${describe(document)})`,
|
|
247
|
+
]);
|
|
248
|
+
}
|
|
249
|
+
const issues = [];
|
|
250
|
+
collectUnknownKeys(document, TOP_LEVEL_KEYS, 'top level', issues);
|
|
251
|
+
if (!('version' in document)) {
|
|
252
|
+
issues.push('version: required, v1 configs must declare "version: 1"');
|
|
253
|
+
}
|
|
254
|
+
else if (asInteger(document.version) !== 1) {
|
|
255
|
+
issues.push(`version: must be 1 (got ${describe(document.version)})`);
|
|
256
|
+
}
|
|
257
|
+
const gates = validateGates(document.gates, issues);
|
|
258
|
+
const options = validateOptions(document.options, issues);
|
|
259
|
+
if (issues.length > 0)
|
|
260
|
+
throw new VerifyConfigError(issues);
|
|
261
|
+
return { version: 1, gates, options };
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Read and validate `<repoPath>/.commandmate/verify.yaml`.
|
|
265
|
+
*
|
|
266
|
+
* @returns the validated config, or null when the file does not exist
|
|
267
|
+
* @throws VerifyConfigError with every violation collected in `issues`
|
|
268
|
+
*/
|
|
269
|
+
function loadVerifyConfig(repoPath) {
|
|
270
|
+
const configPath = (0, path_1.join)(repoPath, exports.VERIFY_CONFIG_RELATIVE_PATH);
|
|
271
|
+
let raw;
|
|
272
|
+
try {
|
|
273
|
+
raw = (0, fs_1.readFileSync)(configPath, 'utf8');
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
if (error.code === 'ENOENT')
|
|
277
|
+
return null;
|
|
278
|
+
throw new VerifyConfigError([`${configPath}: ${error.message}`]);
|
|
279
|
+
}
|
|
280
|
+
return parseVerifyConfig(raw, configPath);
|
|
281
|
+
}
|
package/package.json
CHANGED