cuelo 0.10.52 → 0.10.53
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-path-routes-manifest.json +35 -35
- package/.next/build-manifest.json +4 -4
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +3 -3
- package/.next/react-loadable-manifest.json +549 -561
- package/.next/required-server-files.js +25 -25
- package/.next/required-server-files.json +25 -25
- package/.next/server/app/_global-error/page.js +3 -3
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js +2 -2
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/api/agent/[id]/account/route.js +1 -1
- package/.next/server/app/api/agent/[id]/account/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/agent/[id]/bash-output/route.js +1 -1
- package/.next/server/app/api/agent/[id]/bash-output/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/agent/[id]/events/route.js +2 -2
- package/.next/server/app/api/agent/[id]/events/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/agent/[id]/route.js +1 -1
- package/.next/server/app/api/agent/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/agent/new/route.js +1 -1
- package/.next/server/app/api/agent/new/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/agent/running/route.js +1 -1
- package/.next/server/app/api/agent/running/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/attachment-settings/route.js +1 -1
- package/.next/server/app/api/attachment-settings/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/attachments/[id]/transcription/route.js +2 -2
- package/.next/server/app/api/attachments/[id]/transcription/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/attachments/draft/route.js +1 -1
- package/.next/server/app/api/attachments/draft/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/attachments/route.js +2 -2
- package/.next/server/app/api/attachments/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/auth/all-providers/route.js +1 -1
- package/.next/server/app/api/auth/all-providers/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/auth/api-key/[provider]/route.js +1 -1
- package/.next/server/app/api/auth/api-key/[provider]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/auth/login/[provider]/route.js +1 -1
- package/.next/server/app/api/auth/login/[provider]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/auth/logout/[provider]/route.js +1 -1
- package/.next/server/app/api/auth/logout/[provider]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/auth/providers/route.js +1 -1
- package/.next/server/app/api/auth/providers/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/character-mode/route.js +1 -2
- package/.next/server/app/api/character-mode/route.js.nft.json +1 -1
- package/.next/server/app/api/character-mode/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/cwd/browse/route.js +1 -1
- package/.next/server/app/api/cwd/browse/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/cwd/validate/route.js +1 -1
- package/.next/server/app/api/cwd/validate/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/default-cwd/route.js +1 -1
- package/.next/server/app/api/default-cwd/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/display-settings/route.js +1 -1
- package/.next/server/app/api/display-settings/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/document-text/route.js +2 -2
- package/.next/server/app/api/document-text/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/experiments/route.js +1 -1
- package/.next/server/app/api/experiments/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/file-index/route.js +1 -1
- package/.next/server/app/api/file-index/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/files/[...path]/route.js +1 -1
- package/.next/server/app/api/files/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/git/diff/route.js +1 -1
- package/.next/server/app/api/git/diff/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/git/status/route.js +2 -2
- package/.next/server/app/api/git/status/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/github/projects/route.js +1 -1
- package/.next/server/app/api/github/projects/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/gpt6/handles/route.js +1 -1
- package/.next/server/app/api/gpt6/handles/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/gpt6/reply/route.js +1 -1
- package/.next/server/app/api/gpt6/reply/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/gpt6/token/route.js +1 -1
- package/.next/server/app/api/gpt6/token/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/gpt6/web6-session/route.js +1 -1
- package/.next/server/app/api/gpt6/web6-session/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/home/route.js +1 -1
- package/.next/server/app/api/home/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/live/call/[callId]/route.js +2 -2
- package/.next/server/app/api/live/call/[callId]/route.js.nft.json +1 -1
- package/.next/server/app/api/live/call/[callId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/live/events/route.js +2 -2
- package/.next/server/app/api/live/events/route.js.nft.json +1 -1
- package/.next/server/app/api/live/events/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/live/interpret/route.js +2 -2
- package/.next/server/app/api/live/interpret/route.js.nft.json +1 -1
- package/.next/server/app/api/live/interpret/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/live/offer/route.js +1 -1
- package/.next/server/app/api/live/offer/route.js.nft.json +1 -1
- package/.next/server/app/api/live/offer/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/live/voices/route.js +1 -1
- package/.next/server/app/api/live/voices/route.js.nft.json +1 -1
- package/.next/server/app/api/live/voices/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/mcp/route.js +2 -2
- package/.next/server/app/api/mcp/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/model-roles/route.js +1 -1
- package/.next/server/app/api/model-roles/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/models/route.js +1 -1
- package/.next/server/app/api/models/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/models-config/catalog/route.js +1 -1
- package/.next/server/app/api/models-config/catalog/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/models-config/discover/route.js +1 -1
- package/.next/server/app/api/models-config/discover/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/models-config/route.js +3 -3
- package/.next/server/app/api/models-config/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/models-config/test/route.js +1 -1
- package/.next/server/app/api/models-config/test/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/office-characters/[...path]/route.js +1 -2
- package/.next/server/app/api/office-characters/[...path]/route.js.nft.json +1 -1
- package/.next/server/app/api/office-characters/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/plugins/route.js +1 -1
- package/.next/server/app/api/plugins/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/project-docs/route.js +1 -1
- package/.next/server/app/api/project-docs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/project-trust/route.js +1 -1
- package/.next/server/app/api/project-trust/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/route.js +2 -2
- package/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/runs/[runId]/prelude/route.js +1 -1
- package/.next/server/app/api/runs/[runId]/prelude/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/runs/[runId]/route.js +1 -1
- package/.next/server/app/api/runs/[runId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/runs/[runId]/turns/route.js +1 -1
- package/.next/server/app/api/runs/[runId]/turns/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/runs/route.js +1 -1
- package/.next/server/app/api/runs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sessions/[id]/auto-name/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/auto-name/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sessions/[id]/context/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/context/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sessions/[id]/entries/[entryId]/thinking/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/entries/[entryId]/thinking/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/export/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sessions/[id]/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sessions/[id]/state/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/state/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sessions/route.js +1 -1
- package/.next/server/app/api/sessions/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sessions/search/route.js +1 -1
- package/.next/server/app/api/sessions/search/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/settings/route.js +1 -1
- package/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sidecars/[service]/[...path]/route.js +1 -1
- package/.next/server/app/api/sidecars/[service]/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/check/route.js +1 -1
- package/.next/server/app/api/skills/check/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/install/route.js +2 -2
- package/.next/server/app/api/skills/install/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/route.js +1 -1
- package/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/search/route.js +1 -1
- package/.next/server/app/api/skills/search/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/update/route.js +1 -1
- package/.next/server/app/api/skills/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/subagents/eta/route.js +1 -1
- package/.next/server/app/api/subagents/eta/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/theme/route.js +1 -1
- package/.next/server/app/api/theme/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/tool-image/route.js +1 -1
- package/.next/server/app/api/tool-image/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/update-maintenance/route.js +2 -2
- package/.next/server/app/api/update-maintenance/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/updates/route.js +1 -1
- package/.next/server/app/api/updates/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/web-access/recovery/route.js +1 -1
- package/.next/server/app/api/web-access/recovery/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/web-access/route.js +1 -1
- package/.next/server/app/api/web-access/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/web6/consults/route.js +1 -1
- package/.next/server/app/api/web6/consults/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/route.js +1 -1
- package/.next/server/app/api/worktrees/route_client-reference-manifest.js +1 -1
- package/.next/server/app/favicon.ico/route.js +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +4 -4
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/manifest.webmanifest/route.js +2 -2
- package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +1 -1
- package/.next/server/app/page.js +5 -5
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/recover/page.js +2 -2
- package/.next/server/app/recover/page_client-reference-manifest.js +1 -1
- package/.next/server/app/recover.html +1 -1
- package/.next/server/app/recover.rsc +3 -3
- package/.next/server/app/recover.segments/_full.segment.rsc +3 -3
- package/.next/server/app/recover.segments/_head.segment.rsc +1 -1
- package/.next/server/app/recover.segments/_index.segment.rsc +2 -2
- package/.next/server/app/recover.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/recover.segments/recover/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/recover.segments/recover.segment.rsc +1 -1
- package/.next/server/app-paths-manifest.json +35 -35
- package/.next/server/chunks/1008.js +2 -2
- package/.next/server/chunks/1966.js +2 -0
- package/.next/server/chunks/319.js +3 -3
- package/.next/server/chunks/6429.js +2 -2
- package/.next/server/chunks/6775.js +7 -0
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/middleware.js +9 -9
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/app/_not-found/page-041b64675adcdf82.js +1 -0
- package/.next/static/chunks/app/layout-3b1f8b74c7fc5e77.js +1 -0
- package/.next/static/chunks/app/{page-3640086c732eb87d.js → page-efae4fdcf84b7a14.js} +4 -4
- package/.next/static/chunks/app/recover/{page-c0c40b3c215078a4.js → page-2bcd6a351f3eda3d.js} +1 -1
- package/.next/static/chunks/{main-app-fbc4aa3c15d48613.js → main-app-ca46ab75db2b4e4b.js} +1 -1
- package/.next/static/chunks/main-dd4c9daba44bef88.js +5 -0
- package/.next/static/chunks/next/dist/client/components/builtin/global-error-dac2a58a3b9eb28e.js +1 -0
- package/.next/static/chunks/webpack-546c8221d1906945.js +1 -0
- package/.next/trace +107 -107
- package/.next/trace-build +1 -1
- package/.next/types/app/api/agent/[id]/account/route.ts +1 -1
- package/.next/types/app/api/agent/[id]/bash-output/route.ts +1 -1
- package/.next/types/app/api/agent/[id]/events/route.ts +1 -1
- package/.next/types/app/api/agent/[id]/route.ts +1 -1
- package/.next/types/app/api/agent/new/route.ts +1 -1
- package/.next/types/app/api/agent/running/route.ts +1 -1
- package/.next/types/app/api/attachment-settings/route.ts +1 -1
- package/.next/types/app/api/attachments/[id]/transcription/route.ts +1 -1
- package/.next/types/app/api/attachments/draft/route.ts +1 -1
- package/.next/types/app/api/attachments/route.ts +1 -1
- package/.next/types/app/api/auth/all-providers/route.ts +1 -1
- package/.next/types/app/api/auth/api-key/[provider]/route.ts +1 -1
- package/.next/types/app/api/auth/login/[provider]/route.ts +1 -1
- package/.next/types/app/api/auth/logout/[provider]/route.ts +1 -1
- package/.next/types/app/api/auth/providers/route.ts +1 -1
- package/.next/types/app/api/character-mode/route.ts +1 -1
- package/.next/types/app/api/cwd/browse/route.ts +1 -1
- package/.next/types/app/api/cwd/validate/route.ts +1 -1
- package/.next/types/app/api/default-cwd/route.ts +1 -1
- package/.next/types/app/api/display-settings/route.ts +1 -1
- package/.next/types/app/api/document-text/route.ts +1 -1
- package/.next/types/app/api/experiments/route.ts +1 -1
- package/.next/types/app/api/file-index/route.ts +1 -1
- package/.next/types/app/api/files/[...path]/route.ts +1 -1
- package/.next/types/app/api/git/diff/route.ts +1 -1
- package/.next/types/app/api/git/status/route.ts +1 -1
- package/.next/types/app/api/github/projects/route.ts +1 -1
- package/.next/types/app/api/gpt6/handles/route.ts +1 -1
- package/.next/types/app/api/gpt6/reply/route.ts +1 -1
- package/.next/types/app/api/gpt6/token/route.ts +1 -1
- package/.next/types/app/api/gpt6/web6-session/route.ts +1 -1
- package/.next/types/app/api/home/route.ts +1 -1
- package/.next/types/app/api/live/call/[callId]/route.ts +1 -1
- package/.next/types/app/api/live/events/route.ts +1 -1
- package/.next/types/app/api/live/interpret/route.ts +1 -1
- package/.next/types/app/api/live/offer/route.ts +1 -1
- package/.next/types/app/api/live/voices/route.ts +1 -1
- package/.next/types/app/api/mcp/route.ts +1 -1
- package/.next/types/app/api/model-roles/route.ts +1 -1
- package/.next/types/app/api/models/route.ts +1 -1
- package/.next/types/app/api/models-config/catalog/route.ts +1 -1
- package/.next/types/app/api/models-config/discover/route.ts +1 -1
- package/.next/types/app/api/models-config/route.ts +1 -1
- package/.next/types/app/api/models-config/test/route.ts +1 -1
- package/.next/types/app/api/office-characters/[...path]/route.ts +1 -1
- package/.next/types/app/api/plugins/route.ts +1 -1
- package/.next/types/app/api/project-docs/route.ts +1 -1
- package/.next/types/app/api/project-trust/route.ts +1 -1
- package/.next/types/app/api/projects/route.ts +1 -1
- package/.next/types/app/api/runs/[runId]/prelude/route.ts +1 -1
- package/.next/types/app/api/runs/[runId]/route.ts +1 -1
- package/.next/types/app/api/runs/[runId]/turns/route.ts +1 -1
- package/.next/types/app/api/runs/route.ts +1 -1
- package/.next/types/app/api/sessions/[id]/auto-name/route.ts +1 -1
- package/.next/types/app/api/sessions/[id]/context/route.ts +1 -1
- package/.next/types/app/api/sessions/[id]/entries/[entryId]/thinking/route.ts +1 -1
- package/.next/types/app/api/sessions/[id]/export/route.ts +1 -1
- package/.next/types/app/api/sessions/[id]/route.ts +1 -1
- package/.next/types/app/api/sessions/[id]/state/route.ts +1 -1
- package/.next/types/app/api/sessions/route.ts +1 -1
- package/.next/types/app/api/sessions/search/route.ts +1 -1
- package/.next/types/app/api/settings/route.ts +1 -1
- package/.next/types/app/api/sidecars/[service]/[...path]/route.ts +1 -1
- package/.next/types/app/api/skills/check/route.ts +1 -1
- package/.next/types/app/api/skills/install/route.ts +1 -1
- package/.next/types/app/api/skills/route.ts +1 -1
- package/.next/types/app/api/skills/search/route.ts +1 -1
- package/.next/types/app/api/skills/update/route.ts +1 -1
- package/.next/types/app/api/subagents/eta/route.ts +1 -1
- package/.next/types/app/api/theme/route.ts +1 -1
- package/.next/types/app/api/tool-image/route.ts +1 -1
- package/.next/types/app/api/update-maintenance/route.ts +1 -1
- package/.next/types/app/api/updates/route.ts +1 -1
- package/.next/types/app/api/web-access/recovery/route.ts +1 -1
- package/.next/types/app/api/web-access/route.ts +1 -1
- package/.next/types/app/api/web6/consults/route.ts +1 -1
- package/.next/types/app/api/worktrees/route.ts +1 -1
- package/.next/types/app/layout.ts +1 -1
- package/.next/types/app/page.ts +1 -1
- package/.next/types/app/recover/page.ts +1 -1
- package/LICENSE +22 -22
- package/README.md +273 -271
- package/Tools/CUELO_Setup/files/btw-server.js +346 -346
- package/Tools/CUELO_Setup/files/native-runtime-patch.js +238 -238
- package/Tools/CUELO_Setup/files/subagent-server.js +587 -587
- package/Tools/CUELO_Setup/files/usage-server.js +1057 -1057
- package/Tools/OMP_Global_Config/agent/AGENTS.md +144 -144
- package/Tools/OMP_Global_Config/agent/RULES.md +140 -140
- package/Tools/OMP_Global_Config/agent/TITLE_SYSTEM.md +17 -17
- package/Tools/OMP_Global_Config/agent/WATCHDOG.md +55 -55
- package/Tools/OMP_Global_Config/agent/agents/maker.md +413 -413
- package/Tools/OMP_Global_Config/agent/extensions/character-voice.ts +1035 -1035
- package/Tools/OMP_Global_Config/agent/extensions/command-guard/index.ts +1110 -1110
- package/Tools/OMP_Global_Config/agent/extensions/command-guard/interop-hint.ts +133 -133
- package/Tools/OMP_Global_Config/agent/extensions/command-guard/matcher.ts +2258 -2258
- package/Tools/OMP_Global_Config/agent/extensions/command-guard/powershell-syntax.ts +210 -210
- package/Tools/OMP_Global_Config/agent/extensions/command-guard/task-guard.ts +1137 -1121
- package/Tools/OMP_Global_Config/agent/extensions/cross-review.ts +132 -132
- package/Tools/OMP_Global_Config/agent/extensions/docmap-route.ts +133 -133
- package/Tools/OMP_Global_Config/agent/extensions/draft/markdown.ts +226 -226
- package/Tools/OMP_Global_Config/agent/extensions/draft/ooxml.ts +222 -222
- package/Tools/OMP_Global_Config/agent/extensions/draft/pdf.ts +63 -63
- package/Tools/OMP_Global_Config/agent/extensions/draft/zip.ts +73 -73
- package/Tools/OMP_Global_Config/agent/extensions/draft.ts +204 -204
- package/Tools/OMP_Global_Config/agent/extensions/edit-lessons.ts +159 -159
- package/Tools/OMP_Global_Config/agent/extensions/external-advice-check.ts +36 -36
- package/Tools/OMP_Global_Config/agent/extensions/gitnexus-autosync.ts +406 -406
- package/Tools/OMP_Global_Config/agent/extensions/image-question-router.ts +55 -55
- package/Tools/OMP_Global_Config/agent/extensions/jev-runtime.ts +2700 -2700
- package/Tools/OMP_Global_Config/agent/extensions/korean-reply-guard.ts +190 -190
- package/Tools/OMP_Global_Config/agent/extensions/lib/cross-review.ts +1208 -1208
- package/Tools/OMP_Global_Config/agent/extensions/lib/docmap-search.ts +612 -612
- package/Tools/OMP_Global_Config/agent/extensions/lib/job-observation.ts +182 -182
- package/Tools/OMP_Global_Config/agent/extensions/lib/maker-routing.ts +1332 -1332
- package/Tools/OMP_Global_Config/agent/extensions/lib/mcp-selection.ts +361 -361
- package/Tools/OMP_Global_Config/agent/extensions/lib/memory-excerpt.ts +5 -5
- package/Tools/OMP_Global_Config/agent/extensions/lib/native-judge.ts +113 -113
- package/Tools/OMP_Global_Config/agent/extensions/lib/prepared-task.ts +211 -211
- package/Tools/OMP_Global_Config/agent/extensions/lib/routing-ledger.ts +538 -538
- package/Tools/OMP_Global_Config/agent/extensions/lib/session-identity.ts +34 -34
- package/Tools/OMP_Global_Config/agent/extensions/lib/task-progress.ts +492 -492
- package/Tools/OMP_Global_Config/agent/extensions/mcp-selection.ts +301 -301
- package/Tools/OMP_Global_Config/agent/extensions/prompt-compact.ts +67 -67
- package/Tools/OMP_Global_Config/agent/extensions/report-chart-guard.ts +102 -102
- package/Tools/OMP_Global_Config/agent/extensions/skim.ts +332 -332
- package/Tools/OMP_Global_Config/agent/extensions/steering-reply-gate.ts +207 -207
- package/Tools/OMP_Global_Config/agent/extensions/todo-nudge.ts +80 -80
- package/Tools/OMP_Global_Config/agent/extensions/turn-end-guard.ts +421 -421
- package/Tools/OMP_Global_Config/agent/prompt-copies/AGENTS.en.md +147 -147
- package/Tools/OMP_Global_Config/agent/prompt-copies/RULES.en.md +49 -49
- package/Tools/OMP_Global_Config/agent/rules/docs-handoff.md +39 -39
- package/Tools/OMP_Global_Config/agent/rules/frontend.md +74 -74
- package/Tools/OMP_Global_Config/agent/rules/harness-policy.json +1224 -1224
- package/Tools/OMP_Global_Config/agent/rules/legacy-encoding.md +21 -21
- package/Tools/OMP_Global_Config/agent/rules/meta-harness.md +45 -45
- package/Tools/OMP_Global_Config/agent/rules/omp-harness.md +151 -151
- package/Tools/OMP_Global_Config/agent/rules/subagent.md +750 -750
- package/Tools/OMP_Global_Config/agent/rules/task-guard.md +62 -62
- package/Tools/OMP_Global_Config/agent/rules/verdict.md +210 -210
- package/Tools/OMP_Global_Config/agent/rules/web6-consult.md +83 -83
- package/Tools/OMP_Global_Config/agent/sop/_common.md +136 -136
- package/Tools/OMP_Global_Config/agent/sop/_writer.md +259 -259
- package/Tools/OMP_Global_Config/agent/sop/maker.md +9 -9
- package/Tools/OMP_Global_Config/agent/tools/git-finalizer/finalizer.ps1 +409 -409
- package/Tools/OMP_Global_Config/agent/tools/git-finalizer/index.ts +303 -303
- package/Tools/OMP_Global_Config/agent/tools/git-finalizer/sync-checkout.ts +184 -184
- package/Tools/OMP_Global_Config/agent/tools/git-finalizer/wsl-git.ts +96 -96
- package/Tools/OMP_Global_Config/agent/tools/windows-build/index.ts +387 -387
- package/Tools/OMP_Global_Config/agent/tools/windows-control/control.ps1 +200 -200
- package/Tools/OMP_Global_Config/agent/tools/windows-control/index.ts +255 -255
- package/Tools/OMP_Global_Config/notices/orchestrate.md +26 -26
- package/Tools/OMP_Global_Config/notices/ultrathink.md +9 -9
- package/Tools/OMP_Global_Config/notices/workflow.md +16 -16
- package/Tools/OMP_Global_Config/patches/apply-core-patch.mjs +18339 -18304
- package/Tools/OMP_Global_Config/patches/apply-notices.mjs +135 -135
- package/bin/cuelo-options.js +38 -38
- package/bin/cuelo.js +273 -273
- package/bin/docker-entrypoint.mjs +19 -19
- package/bin/password-prompt.js +100 -100
- package/bin/prepare-runtime.js +112 -112
- package/bin/runtime.js +134 -134
- package/bin/web-auth-store.d.ts +104 -104
- package/bin/web-auth-store.js +593 -593
- package/install.mjs +427 -427
- package/next.config.ts +104 -104
- package/package.json +141 -141
- package/public/audio/completion/voices.json +1393 -1393
- package/public/fonts/cuelo-display/NOTICE.txt +15 -15
- package/public/fonts/fonts.css +288 -288
- package/public/fonts/jetbrains-mono/OFL.txt +93 -93
- package/public/fonts/wanted-sans/OFL.txt +93 -93
- package/public/icons/catppuccin/LICENSE +22 -22
- package/public/icons/catppuccin/README.md +8 -8
- package/public/icons/catppuccin/latte/_file.svg +3 -3
- package/public/icons/catppuccin/latte/_folder.svg +3 -3
- package/public/icons/catppuccin/latte/_folder_open.svg +3 -3
- package/public/icons/catppuccin/latte/bash.svg +7 -7
- package/public/icons/catppuccin/latte/bun-lock.svg +8 -8
- package/public/icons/catppuccin/latte/config.svg +3 -3
- package/public/icons/catppuccin/latte/css.svg +6 -6
- package/public/icons/catppuccin/latte/database.svg +3 -3
- package/public/icons/catppuccin/latte/docker.svg +3 -3
- package/public/icons/catppuccin/latte/env.svg +3 -3
- package/public/icons/catppuccin/latte/eslint.svg +6 -6
- package/public/icons/catppuccin/latte/git.svg +6 -6
- package/public/icons/catppuccin/latte/go.svg +3 -3
- package/public/icons/catppuccin/latte/graphql.svg +3 -3
- package/public/icons/catppuccin/latte/html.svg +6 -6
- package/public/icons/catppuccin/latte/javascript-react.svg +8 -8
- package/public/icons/catppuccin/latte/javascript.svg +6 -6
- package/public/icons/catppuccin/latte/json.svg +3 -3
- package/public/icons/catppuccin/latte/lock.svg +3 -3
- package/public/icons/catppuccin/latte/markdown.svg +3 -3
- package/public/icons/catppuccin/latte/ms-word.svg +6 -6
- package/public/icons/catppuccin/latte/next.svg +6 -6
- package/public/icons/catppuccin/latte/npm-lock.svg +7 -7
- package/public/icons/catppuccin/latte/pdf.svg +3 -3
- package/public/icons/catppuccin/latte/python.svg +8 -8
- package/public/icons/catppuccin/latte/rust.svg +6 -6
- package/public/icons/catppuccin/latte/sass.svg +3 -3
- package/public/icons/catppuccin/latte/terraform.svg +3 -3
- package/public/icons/catppuccin/latte/toml.svg +3 -3
- package/public/icons/catppuccin/latte/typescript-react.svg +8 -8
- package/public/icons/catppuccin/latte/typescript.svg +6 -6
- package/public/icons/catppuccin/latte/yaml.svg +3 -3
- package/public/icons/catppuccin/mocha/_file.svg +3 -3
- package/public/icons/catppuccin/mocha/_folder.svg +3 -3
- package/public/icons/catppuccin/mocha/_folder_open.svg +3 -3
- package/public/icons/catppuccin/mocha/bash.svg +7 -7
- package/public/icons/catppuccin/mocha/bun-lock.svg +8 -8
- package/public/icons/catppuccin/mocha/config.svg +3 -3
- package/public/icons/catppuccin/mocha/css.svg +6 -6
- package/public/icons/catppuccin/mocha/database.svg +3 -3
- package/public/icons/catppuccin/mocha/docker.svg +3 -3
- package/public/icons/catppuccin/mocha/env.svg +3 -3
- package/public/icons/catppuccin/mocha/eslint.svg +6 -6
- package/public/icons/catppuccin/mocha/git.svg +6 -6
- package/public/icons/catppuccin/mocha/go.svg +3 -3
- package/public/icons/catppuccin/mocha/graphql.svg +3 -3
- package/public/icons/catppuccin/mocha/html.svg +6 -6
- package/public/icons/catppuccin/mocha/javascript-react.svg +8 -8
- package/public/icons/catppuccin/mocha/javascript.svg +6 -6
- package/public/icons/catppuccin/mocha/json.svg +3 -3
- package/public/icons/catppuccin/mocha/lock.svg +3 -3
- package/public/icons/catppuccin/mocha/markdown.svg +3 -3
- package/public/icons/catppuccin/mocha/ms-word.svg +6 -6
- package/public/icons/catppuccin/mocha/next.svg +6 -6
- package/public/icons/catppuccin/mocha/npm-lock.svg +7 -7
- package/public/icons/catppuccin/mocha/pdf.svg +3 -3
- package/public/icons/catppuccin/mocha/python.svg +8 -8
- package/public/icons/catppuccin/mocha/rust.svg +6 -6
- package/public/icons/catppuccin/mocha/sass.svg +3 -3
- package/public/icons/catppuccin/mocha/terraform.svg +3 -3
- package/public/icons/catppuccin/mocha/toml.svg +3 -3
- package/public/icons/catppuccin/mocha/typescript-react.svg +8 -8
- package/public/icons/catppuccin/mocha/typescript.svg +6 -6
- package/public/icons/catppuccin/mocha/yaml.svg +3 -3
- package/public/icons/cuelo-icon.svg +8 -8
- package/public/offline.html +145 -145
- package/public/sw.js +135 -135
- package/.next/server/chunks/205.js +0 -7
- package/.next/server/chunks/7738.js +0 -1
- package/.next/static/chunks/1966.b8f83f7ba376c962.js +0 -1
- package/.next/static/chunks/3899.3f761f7e3a944e8c.js +0 -1
- package/.next/static/chunks/app/_not-found/page-9e5295df095e2e41.js +0 -1
- package/.next/static/chunks/app/layout-8b29ba206a96135e.js +0 -1
- package/.next/static/chunks/main-1ddb405481ce21f6.js +0 -5
- package/.next/static/chunks/next/dist/client/components/builtin/global-error-50782c8c9c06c8b5.js +0 -1
- package/.next/static/chunks/webpack-7b514b9855bd748c.js +0 -1
- /package/.next/static/{dQA--5NX6NZt_A6pLZUde → -mDQEyWQ8VQsAfLpiACIP}/_buildManifest.js +0 -0
- /package/.next/static/{dQA--5NX6NZt_A6pLZUde → -mDQEyWQ8VQsAfLpiACIP}/_ssgManifest.js +0 -0
|
@@ -1,184 +1,184 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 검증·게시가 끝난 커밋을 같은 프로젝트의 다른 체크아웃(예: WSL 작업본 → Windows 사본)에 fast-forward로 반영하는 CLI.
|
|
3
|
-
*
|
|
4
|
-
* bun sync-checkout.ts --source <루트> --target <루트> --revision <40자 sha>
|
|
5
|
-
*
|
|
6
|
-
* 모델 도구가 아니다. 상시 동기화·스케줄러·훅 없이, 호출될 때 한 번만 일한다. 빌드·설치·배포는 하지 않는다.
|
|
7
|
-
* 대상 사본의 변경(tracked·staged·무시되지 않는 untracked)이나 갈라진/앞선 이력이 있으면 아무것도 바꾸지 않고 멈춘다.
|
|
8
|
-
* reset·stash·clean·force checkout·rebase·merge·잠금 삭제는 쓰지 않는다. 갱신은 `merge --ff-only <정확한 sha>` 하나다.
|
|
9
|
-
*/
|
|
10
|
-
import { execFile } from "node:child_process";
|
|
11
|
-
import { realpath } from "node:fs/promises";
|
|
12
|
-
import { resolve, sep } from "node:path";
|
|
13
|
-
import { fileURLToPath } from "node:url";
|
|
14
|
-
import { type GitExecutor, selectGitExecutor } from "./wsl-git";
|
|
15
|
-
|
|
16
|
-
export interface SyncCheckoutOptions {
|
|
17
|
-
source: string;
|
|
18
|
-
target: string;
|
|
19
|
-
revision: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export type SyncCheckoutResult =
|
|
23
|
-
| { ok: true; status: "updated" | "already-current"; revision: string; branch: string; previous: string }
|
|
24
|
-
| { ok: false; status: "blocked"; reason: string };
|
|
25
|
-
|
|
26
|
-
interface GitOutput {
|
|
27
|
-
code: number;
|
|
28
|
-
stdout: string;
|
|
29
|
-
stderr: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
class Blocked extends Error {}
|
|
33
|
-
|
|
34
|
-
function blocked(reason: string): never {
|
|
35
|
-
throw new Blocked(reason);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* git에 넘길 비대화형 환경. Windows git.exe는 interop에서 WSLENV에 이름이 든 변수만 받으므로(2026-10-11 실측: 이름이 없으면
|
|
40
|
-
* 빈 값) 두 변수를 WSLENV에 더한다. 기존 WSLENV 항목은 보존한다. 전역 환경은 바꾸지 않고 자식 프로세스에만 적용한다.
|
|
41
|
-
*/
|
|
42
|
-
function gitEnv(): NodeJS.ProcessEnv {
|
|
43
|
-
const names = (process.env.WSLENV ?? "").split(":").filter((entry) => entry.length > 0);
|
|
44
|
-
for (const name of ["GIT_TERMINAL_PROMPT", "GCM_INTERACTIVE"]) {
|
|
45
|
-
if (!names.some((entry) => entry.split("/")[0] === name)) names.push(name);
|
|
46
|
-
}
|
|
47
|
-
return { ...process.env, GIT_TERMINAL_PROMPT: "0", GCM_INTERACTIVE: "never", WSLENV: names.join(":") };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function runGit(executor: GitExecutor, cwd: string, args: string[]): Promise<GitOutput> {
|
|
51
|
-
const { promise, resolve, reject } = Promise.withResolvers<GitOutput>();
|
|
52
|
-
execFile(
|
|
53
|
-
executor.command,
|
|
54
|
-
args,
|
|
55
|
-
{ cwd, env: gitEnv(), maxBuffer: 16 * 1024 * 1024 },
|
|
56
|
-
(error, stdout, stderr) => {
|
|
57
|
-
if (error && typeof error.code !== "number") {
|
|
58
|
-
reject(error);
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
resolve({ code: typeof error?.code === "number" ? error.code : 0, stdout: String(stdout), stderr: String(stderr) });
|
|
62
|
-
},
|
|
63
|
-
);
|
|
64
|
-
return promise;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** 성공해야 하는 git 호출. 실패하면 stderr를 이유로 멈춘다. */
|
|
68
|
-
async function git(executor: GitExecutor, cwd: string, args: string[], what: string): Promise<string> {
|
|
69
|
-
const out = await runGit(executor, cwd, args);
|
|
70
|
-
if (out.code !== 0) blocked(`${what} 실패(git ${args[0]}, 종료 ${out.code}): ${out.stderr.trim() || out.stdout.trim()}`);
|
|
71
|
-
return out.stdout.trim();
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
interface Checkout {
|
|
75
|
-
executor: GitExecutor;
|
|
76
|
-
root: string;
|
|
77
|
-
branch: string;
|
|
78
|
-
origin: string;
|
|
79
|
-
head: string;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async function inspect(label: string, path: string): Promise<Checkout> {
|
|
83
|
-
const root = await realpath(resolve(path)).catch(() => blocked(`${label} 경로가 없다: ${path}`));
|
|
84
|
-
const executor = await selectGitExecutor(root);
|
|
85
|
-
const top = executor.toPosix(await git(executor, root, ["rev-parse", "--show-toplevel"], `${label} 루트 확인`));
|
|
86
|
-
const topReal = await realpath(top).catch(() => blocked(`${label} git 루트를 해석하지 못했다: ${top}`));
|
|
87
|
-
if (topReal !== root) blocked(`${label} 경로가 체크아웃 루트가 아니다: ${root} (루트 ${topReal})`);
|
|
88
|
-
const branchOut = await runGit(executor, root, ["symbolic-ref", "--short", "-q", "HEAD"]);
|
|
89
|
-
if (branchOut.code !== 0 || !branchOut.stdout.trim()) blocked(`${label}이 detached HEAD 상태다`);
|
|
90
|
-
const origin = await git(executor, root, ["remote", "get-url", "origin"], `${label} origin 확인`);
|
|
91
|
-
const head = await git(executor, root, ["rev-parse", "HEAD"], `${label} HEAD 확인`);
|
|
92
|
-
return { executor, root, branch: branchOut.stdout.trim(), origin, head };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* 네트워크 원격(`file://`을 뺀 스킴 URL, `[user@]host:path` SSH 형식)은 끝의 `/`와 `.git`을 떼고 비교한다.
|
|
97
|
-
* `https://github.com/o/r`와 `https://github.com/o/r.git`은 같은 저장소다.
|
|
98
|
-
* 로컬 경로·`file://` origin은 `r`과 `r.git`이 서로 다른 디렉터리일 수 있어 그대로 비교한다.
|
|
99
|
-
* SSH 형식의 host는 두 글자 이상이어서 `C:/…` 같은 드라이브 경로는 로컬로 남는다.
|
|
100
|
-
*/
|
|
101
|
-
export function sameOrigin(a: string, b: string): boolean {
|
|
102
|
-
const remote = /^(?!file:)[a-z][a-z0-9+.-]*:\/\/|^(?:[^/\\@:]+@)?[^/\\@:]{2,}:(?!\/\/)/i;
|
|
103
|
-
const norm = (url: string) => (remote.test(url) ? url.replace(/\/+$/, "").replace(/\.git$/, "") : url);
|
|
104
|
-
return norm(a) === norm(b);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async function run({ source, target, revision }: SyncCheckoutOptions): Promise<SyncCheckoutResult> {
|
|
108
|
-
if (!/^[0-9a-f]{40}$/.test(revision)) blocked("revision은 소문자 40자 전체 sha여야 한다");
|
|
109
|
-
const src = await inspect("source", source);
|
|
110
|
-
const dst = await inspect("target", target);
|
|
111
|
-
if (src.root === dst.root || src.root.startsWith(dst.root + sep) || dst.root.startsWith(src.root + sep)) {
|
|
112
|
-
blocked(`source와 target이 같거나 겹친다: ${src.root} / ${dst.root}`);
|
|
113
|
-
}
|
|
114
|
-
if (!sameOrigin(src.origin, dst.origin)) blocked("source와 target의 origin이 다르다");
|
|
115
|
-
if (src.branch !== dst.branch) blocked(`브랜치가 다르다: source ${src.branch} / target ${dst.branch}`);
|
|
116
|
-
if (src.head !== revision) blocked(`요청한 revision이 source HEAD(${src.head})와 다르다`);
|
|
117
|
-
|
|
118
|
-
const { executor, root, branch } = dst;
|
|
119
|
-
const dirty = await git(executor, root, ["status", "--porcelain", "--untracked-files=normal"], "target 상태 확인");
|
|
120
|
-
if (dirty) blocked(`target에 반영되지 않은 변경이 있어 건드리지 않는다:\n${dirty}`);
|
|
121
|
-
|
|
122
|
-
// target의 기존 origin 설정 그대로, 비대화형으로 그 브랜치 하나만 가져와 게시 여부를 확인한다.
|
|
123
|
-
await git(executor, root, ["fetch", "--no-tags", "origin", `refs/heads/${branch}`], "origin fetch");
|
|
124
|
-
const tip = await git(executor, root, ["rev-parse", "FETCH_HEAD"], "FETCH_HEAD 확인");
|
|
125
|
-
const known = await runGit(executor, root, ["cat-file", "-e", `${revision}^{commit}`]);
|
|
126
|
-
if (known.code !== 0) blocked(`origin/${branch}에서 ${revision} 커밋을 찾지 못했다(게시되지 않음)`);
|
|
127
|
-
const published = await runGit(executor, root, ["merge-base", "--is-ancestor", revision, tip]);
|
|
128
|
-
if (published.code !== 0) blocked(`${revision}은 origin/${branch}에 게시되지 않았다`);
|
|
129
|
-
|
|
130
|
-
const previous = dst.head;
|
|
131
|
-
if (previous === revision) return { ok: true, status: "already-current", revision, branch, previous };
|
|
132
|
-
|
|
133
|
-
const ff = await runGit(executor, root, ["merge-base", "--is-ancestor", previous, revision]);
|
|
134
|
-
if (ff.code !== 0) blocked(`target HEAD(${previous})가 ${revision}의 조상이 아니다(앞섰거나 갈라짐). 건드리지 않는다`);
|
|
135
|
-
|
|
136
|
-
// fetch 동안 target이 바뀌지 않았는지 갱신 직전에 다시 본다. 바뀌었으면 되돌리지 않고 멈춘다.
|
|
137
|
-
const nowHead = await git(executor, root, ["rev-parse", "HEAD"], "갱신 직전 HEAD 확인");
|
|
138
|
-
const nowDirty = await git(executor, root, ["status", "--porcelain", "--untracked-files=normal"], "갱신 직전 상태 확인");
|
|
139
|
-
if (nowHead !== previous || nowDirty) blocked("fetch 중 target이 바뀌어 갱신하지 않았다");
|
|
140
|
-
// 무시된 로컬 파일이 새로 추적되는 경로와 겹쳐도 덮어쓰지 않는다(git merge 기본값은 덮어쓴다).
|
|
141
|
-
await git(executor, root, ["merge", "--ff-only", "--no-overwrite-ignore", revision], "fast-forward");
|
|
142
|
-
const after = await git(executor, root, ["rev-parse", "HEAD"], "갱신 후 HEAD 확인");
|
|
143
|
-
if (after !== revision) blocked(`갱신 후 target HEAD(${after})가 요청 revision과 다르다`);
|
|
144
|
-
const left = await git(executor, root, ["status", "--porcelain", "--untracked-files=normal"], "갱신 후 상태 확인");
|
|
145
|
-
if (left) blocked(`갱신 후 target이 깨끗하지 않다:\n${left}`);
|
|
146
|
-
return { ok: true, status: "updated", revision, branch, previous };
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/** 막힘(Blocked)은 결과로 돌려주고, 예기치 못한 오류는 그대로 던진다. */
|
|
150
|
-
export async function syncCheckout(options: SyncCheckoutOptions): Promise<SyncCheckoutResult> {
|
|
151
|
-
try {
|
|
152
|
-
return await run(options);
|
|
153
|
-
} catch (error) {
|
|
154
|
-
if (error instanceof Blocked) return { ok: false, status: "blocked", reason: error.message };
|
|
155
|
-
throw error;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function parseArgs(argv: string[]): SyncCheckoutOptions {
|
|
160
|
-
const values: Record<string, string> = {};
|
|
161
|
-
for (let i = 0; i < argv.length; i += 2) {
|
|
162
|
-
const key = argv[i];
|
|
163
|
-
const value = argv[i + 1];
|
|
164
|
-
if (!["--source", "--target", "--revision"].includes(key) || value === undefined) {
|
|
165
|
-
throw new Error("usage: sync-checkout.ts --source <path> --target <path> --revision <sha>");
|
|
166
|
-
}
|
|
167
|
-
values[key.slice(2)] = value;
|
|
168
|
-
}
|
|
169
|
-
if (!values.source || !values.target || !values.revision) {
|
|
170
|
-
throw new Error("usage: sync-checkout.ts --source <path> --target <path> --revision <sha>");
|
|
171
|
-
}
|
|
172
|
-
return { source: values.source, target: values.target, revision: values.revision };
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
176
|
-
try {
|
|
177
|
-
const result = await syncCheckout(parseArgs(process.argv.slice(2)));
|
|
178
|
-
console.log(JSON.stringify(result));
|
|
179
|
-
process.exit(result.ok ? 0 : 1);
|
|
180
|
-
} catch (error) {
|
|
181
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
182
|
-
process.exit(2);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 검증·게시가 끝난 커밋을 같은 프로젝트의 다른 체크아웃(예: WSL 작업본 → Windows 사본)에 fast-forward로 반영하는 CLI.
|
|
3
|
+
*
|
|
4
|
+
* bun sync-checkout.ts --source <루트> --target <루트> --revision <40자 sha>
|
|
5
|
+
*
|
|
6
|
+
* 모델 도구가 아니다. 상시 동기화·스케줄러·훅 없이, 호출될 때 한 번만 일한다. 빌드·설치·배포는 하지 않는다.
|
|
7
|
+
* 대상 사본의 변경(tracked·staged·무시되지 않는 untracked)이나 갈라진/앞선 이력이 있으면 아무것도 바꾸지 않고 멈춘다.
|
|
8
|
+
* reset·stash·clean·force checkout·rebase·merge·잠금 삭제는 쓰지 않는다. 갱신은 `merge --ff-only <정확한 sha>` 하나다.
|
|
9
|
+
*/
|
|
10
|
+
import { execFile } from "node:child_process";
|
|
11
|
+
import { realpath } from "node:fs/promises";
|
|
12
|
+
import { resolve, sep } from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
import { type GitExecutor, selectGitExecutor } from "./wsl-git";
|
|
15
|
+
|
|
16
|
+
export interface SyncCheckoutOptions {
|
|
17
|
+
source: string;
|
|
18
|
+
target: string;
|
|
19
|
+
revision: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type SyncCheckoutResult =
|
|
23
|
+
| { ok: true; status: "updated" | "already-current"; revision: string; branch: string; previous: string }
|
|
24
|
+
| { ok: false; status: "blocked"; reason: string };
|
|
25
|
+
|
|
26
|
+
interface GitOutput {
|
|
27
|
+
code: number;
|
|
28
|
+
stdout: string;
|
|
29
|
+
stderr: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class Blocked extends Error {}
|
|
33
|
+
|
|
34
|
+
function blocked(reason: string): never {
|
|
35
|
+
throw new Blocked(reason);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* git에 넘길 비대화형 환경. Windows git.exe는 interop에서 WSLENV에 이름이 든 변수만 받으므로(2026-10-11 실측: 이름이 없으면
|
|
40
|
+
* 빈 값) 두 변수를 WSLENV에 더한다. 기존 WSLENV 항목은 보존한다. 전역 환경은 바꾸지 않고 자식 프로세스에만 적용한다.
|
|
41
|
+
*/
|
|
42
|
+
function gitEnv(): NodeJS.ProcessEnv {
|
|
43
|
+
const names = (process.env.WSLENV ?? "").split(":").filter((entry) => entry.length > 0);
|
|
44
|
+
for (const name of ["GIT_TERMINAL_PROMPT", "GCM_INTERACTIVE"]) {
|
|
45
|
+
if (!names.some((entry) => entry.split("/")[0] === name)) names.push(name);
|
|
46
|
+
}
|
|
47
|
+
return { ...process.env, GIT_TERMINAL_PROMPT: "0", GCM_INTERACTIVE: "never", WSLENV: names.join(":") };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function runGit(executor: GitExecutor, cwd: string, args: string[]): Promise<GitOutput> {
|
|
51
|
+
const { promise, resolve, reject } = Promise.withResolvers<GitOutput>();
|
|
52
|
+
execFile(
|
|
53
|
+
executor.command,
|
|
54
|
+
args,
|
|
55
|
+
{ cwd, env: gitEnv(), maxBuffer: 16 * 1024 * 1024 },
|
|
56
|
+
(error, stdout, stderr) => {
|
|
57
|
+
if (error && typeof error.code !== "number") {
|
|
58
|
+
reject(error);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
resolve({ code: typeof error?.code === "number" ? error.code : 0, stdout: String(stdout), stderr: String(stderr) });
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
return promise;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** 성공해야 하는 git 호출. 실패하면 stderr를 이유로 멈춘다. */
|
|
68
|
+
async function git(executor: GitExecutor, cwd: string, args: string[], what: string): Promise<string> {
|
|
69
|
+
const out = await runGit(executor, cwd, args);
|
|
70
|
+
if (out.code !== 0) blocked(`${what} 실패(git ${args[0]}, 종료 ${out.code}): ${out.stderr.trim() || out.stdout.trim()}`);
|
|
71
|
+
return out.stdout.trim();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface Checkout {
|
|
75
|
+
executor: GitExecutor;
|
|
76
|
+
root: string;
|
|
77
|
+
branch: string;
|
|
78
|
+
origin: string;
|
|
79
|
+
head: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function inspect(label: string, path: string): Promise<Checkout> {
|
|
83
|
+
const root = await realpath(resolve(path)).catch(() => blocked(`${label} 경로가 없다: ${path}`));
|
|
84
|
+
const executor = await selectGitExecutor(root);
|
|
85
|
+
const top = executor.toPosix(await git(executor, root, ["rev-parse", "--show-toplevel"], `${label} 루트 확인`));
|
|
86
|
+
const topReal = await realpath(top).catch(() => blocked(`${label} git 루트를 해석하지 못했다: ${top}`));
|
|
87
|
+
if (topReal !== root) blocked(`${label} 경로가 체크아웃 루트가 아니다: ${root} (루트 ${topReal})`);
|
|
88
|
+
const branchOut = await runGit(executor, root, ["symbolic-ref", "--short", "-q", "HEAD"]);
|
|
89
|
+
if (branchOut.code !== 0 || !branchOut.stdout.trim()) blocked(`${label}이 detached HEAD 상태다`);
|
|
90
|
+
const origin = await git(executor, root, ["remote", "get-url", "origin"], `${label} origin 확인`);
|
|
91
|
+
const head = await git(executor, root, ["rev-parse", "HEAD"], `${label} HEAD 확인`);
|
|
92
|
+
return { executor, root, branch: branchOut.stdout.trim(), origin, head };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 네트워크 원격(`file://`을 뺀 스킴 URL, `[user@]host:path` SSH 형식)은 끝의 `/`와 `.git`을 떼고 비교한다.
|
|
97
|
+
* `https://github.com/o/r`와 `https://github.com/o/r.git`은 같은 저장소다.
|
|
98
|
+
* 로컬 경로·`file://` origin은 `r`과 `r.git`이 서로 다른 디렉터리일 수 있어 그대로 비교한다.
|
|
99
|
+
* SSH 형식의 host는 두 글자 이상이어서 `C:/…` 같은 드라이브 경로는 로컬로 남는다.
|
|
100
|
+
*/
|
|
101
|
+
export function sameOrigin(a: string, b: string): boolean {
|
|
102
|
+
const remote = /^(?!file:)[a-z][a-z0-9+.-]*:\/\/|^(?:[^/\\@:]+@)?[^/\\@:]{2,}:(?!\/\/)/i;
|
|
103
|
+
const norm = (url: string) => (remote.test(url) ? url.replace(/\/+$/, "").replace(/\.git$/, "") : url);
|
|
104
|
+
return norm(a) === norm(b);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function run({ source, target, revision }: SyncCheckoutOptions): Promise<SyncCheckoutResult> {
|
|
108
|
+
if (!/^[0-9a-f]{40}$/.test(revision)) blocked("revision은 소문자 40자 전체 sha여야 한다");
|
|
109
|
+
const src = await inspect("source", source);
|
|
110
|
+
const dst = await inspect("target", target);
|
|
111
|
+
if (src.root === dst.root || src.root.startsWith(dst.root + sep) || dst.root.startsWith(src.root + sep)) {
|
|
112
|
+
blocked(`source와 target이 같거나 겹친다: ${src.root} / ${dst.root}`);
|
|
113
|
+
}
|
|
114
|
+
if (!sameOrigin(src.origin, dst.origin)) blocked("source와 target의 origin이 다르다");
|
|
115
|
+
if (src.branch !== dst.branch) blocked(`브랜치가 다르다: source ${src.branch} / target ${dst.branch}`);
|
|
116
|
+
if (src.head !== revision) blocked(`요청한 revision이 source HEAD(${src.head})와 다르다`);
|
|
117
|
+
|
|
118
|
+
const { executor, root, branch } = dst;
|
|
119
|
+
const dirty = await git(executor, root, ["status", "--porcelain", "--untracked-files=normal"], "target 상태 확인");
|
|
120
|
+
if (dirty) blocked(`target에 반영되지 않은 변경이 있어 건드리지 않는다:\n${dirty}`);
|
|
121
|
+
|
|
122
|
+
// target의 기존 origin 설정 그대로, 비대화형으로 그 브랜치 하나만 가져와 게시 여부를 확인한다.
|
|
123
|
+
await git(executor, root, ["fetch", "--no-tags", "origin", `refs/heads/${branch}`], "origin fetch");
|
|
124
|
+
const tip = await git(executor, root, ["rev-parse", "FETCH_HEAD"], "FETCH_HEAD 확인");
|
|
125
|
+
const known = await runGit(executor, root, ["cat-file", "-e", `${revision}^{commit}`]);
|
|
126
|
+
if (known.code !== 0) blocked(`origin/${branch}에서 ${revision} 커밋을 찾지 못했다(게시되지 않음)`);
|
|
127
|
+
const published = await runGit(executor, root, ["merge-base", "--is-ancestor", revision, tip]);
|
|
128
|
+
if (published.code !== 0) blocked(`${revision}은 origin/${branch}에 게시되지 않았다`);
|
|
129
|
+
|
|
130
|
+
const previous = dst.head;
|
|
131
|
+
if (previous === revision) return { ok: true, status: "already-current", revision, branch, previous };
|
|
132
|
+
|
|
133
|
+
const ff = await runGit(executor, root, ["merge-base", "--is-ancestor", previous, revision]);
|
|
134
|
+
if (ff.code !== 0) blocked(`target HEAD(${previous})가 ${revision}의 조상이 아니다(앞섰거나 갈라짐). 건드리지 않는다`);
|
|
135
|
+
|
|
136
|
+
// fetch 동안 target이 바뀌지 않았는지 갱신 직전에 다시 본다. 바뀌었으면 되돌리지 않고 멈춘다.
|
|
137
|
+
const nowHead = await git(executor, root, ["rev-parse", "HEAD"], "갱신 직전 HEAD 확인");
|
|
138
|
+
const nowDirty = await git(executor, root, ["status", "--porcelain", "--untracked-files=normal"], "갱신 직전 상태 확인");
|
|
139
|
+
if (nowHead !== previous || nowDirty) blocked("fetch 중 target이 바뀌어 갱신하지 않았다");
|
|
140
|
+
// 무시된 로컬 파일이 새로 추적되는 경로와 겹쳐도 덮어쓰지 않는다(git merge 기본값은 덮어쓴다).
|
|
141
|
+
await git(executor, root, ["merge", "--ff-only", "--no-overwrite-ignore", revision], "fast-forward");
|
|
142
|
+
const after = await git(executor, root, ["rev-parse", "HEAD"], "갱신 후 HEAD 확인");
|
|
143
|
+
if (after !== revision) blocked(`갱신 후 target HEAD(${after})가 요청 revision과 다르다`);
|
|
144
|
+
const left = await git(executor, root, ["status", "--porcelain", "--untracked-files=normal"], "갱신 후 상태 확인");
|
|
145
|
+
if (left) blocked(`갱신 후 target이 깨끗하지 않다:\n${left}`);
|
|
146
|
+
return { ok: true, status: "updated", revision, branch, previous };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** 막힘(Blocked)은 결과로 돌려주고, 예기치 못한 오류는 그대로 던진다. */
|
|
150
|
+
export async function syncCheckout(options: SyncCheckoutOptions): Promise<SyncCheckoutResult> {
|
|
151
|
+
try {
|
|
152
|
+
return await run(options);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (error instanceof Blocked) return { ok: false, status: "blocked", reason: error.message };
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function parseArgs(argv: string[]): SyncCheckoutOptions {
|
|
160
|
+
const values: Record<string, string> = {};
|
|
161
|
+
for (let i = 0; i < argv.length; i += 2) {
|
|
162
|
+
const key = argv[i];
|
|
163
|
+
const value = argv[i + 1];
|
|
164
|
+
if (!["--source", "--target", "--revision"].includes(key) || value === undefined) {
|
|
165
|
+
throw new Error("usage: sync-checkout.ts --source <path> --target <path> --revision <sha>");
|
|
166
|
+
}
|
|
167
|
+
values[key.slice(2)] = value;
|
|
168
|
+
}
|
|
169
|
+
if (!values.source || !values.target || !values.revision) {
|
|
170
|
+
throw new Error("usage: sync-checkout.ts --source <path> --target <path> --revision <sha>");
|
|
171
|
+
}
|
|
172
|
+
return { source: values.source, target: values.target, revision: values.revision };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
176
|
+
try {
|
|
177
|
+
const result = await syncCheckout(parseArgs(process.argv.slice(2)));
|
|
178
|
+
console.log(JSON.stringify(result));
|
|
179
|
+
process.exit(result.ok ? 0 : 1);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
182
|
+
process.exit(2);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -1,96 +1,96 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WSL에서 Windows 드라이브(`/mnt/<드라이브>/`) 위 저장소를 어느 git으로 다룰지 정하는 공용 규칙.
|
|
3
|
-
*
|
|
4
|
-
* Linux git은 9p로 파일마다 stat해 3,905개 파일 저장소의 `git status`가 4초 가까이 걸린다(2026-10-09 실측).
|
|
5
|
-
* 같은 저장소를 Windows `git.exe`(interop)에 맡기면 수십 ms다. 그래서 WSL + `/mnt/<드라이브>/` 경로면 Windows git을
|
|
6
|
-
* 쓴다. 단 하나는 예외다: WSL Linux git이 만든 linked worktree는 `.git` 파일에 `gitdir: /mnt/e/...` 같은 Linux 경로를
|
|
7
|
-
* 적어 Windows git이 따라가지 못한다. 그런 worktree는 느려도 Linux git이다.
|
|
8
|
-
*
|
|
9
|
-
* 이 파일은 `lib/wsl-git.ts`(Next 앱)와 같은 규칙의 짝이다. 하네스는 ~/.omp/agent로 복사돼 앱 번들과 분리되므로
|
|
10
|
-
* 한쪽이 다른 쪽을 import할 수 없다. 규칙을 바꿀 때는 두 파일을 함께 바꾼다(`lib/wsl-git.test.mjs`가 두 구현이
|
|
11
|
-
* 같은 판정을 내는지 확인한다).
|
|
12
|
-
*/
|
|
13
|
-
import { existsSync } from "node:fs";
|
|
14
|
-
import { readFile, stat } from "node:fs/promises";
|
|
15
|
-
import { delimiter, dirname, join, resolve } from "node:path";
|
|
16
|
-
|
|
17
|
-
export interface GitExecutor {
|
|
18
|
-
/** execFile에 넘길 실행 파일. */
|
|
19
|
-
command: "git" | "git.exe";
|
|
20
|
-
windows: boolean;
|
|
21
|
-
/**
|
|
22
|
-
* git이 출력한 절대 경로를 호출자가 쓰는 POSIX 경로로 바꾼다. Windows git은 `D:/repo/.git`처럼 쓰므로
|
|
23
|
-
* `/mnt/d/repo/.git`로 돌려놓아야 Linux 쪽 경로와 같은 기준으로 비교된다. Linux git이면 그대로다.
|
|
24
|
-
*/
|
|
25
|
-
toPosix(value: string): string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** WSL이고 `/mnt/<드라이브>` 아래(드라이브 루트 포함) 경로인가. */
|
|
29
|
-
export function isWindowsDrivePath(cwd: string): boolean {
|
|
30
|
-
return process.platform === "linux" && Boolean(process.env.WSL_DISTRO_NAME) && /^\/mnt\/[a-z](?:\/|$)/i.test(cwd);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** `D:/a/b`·`D:\a\b`를 `/mnt/d/a/b`로 바꾼다. 드라이브 문자로 시작하지 않으면 그대로 돌려준다. */
|
|
34
|
-
export function windowsPathToPosix(value: string): string {
|
|
35
|
-
const match = /^([A-Za-z]):[\\/]*(.*)$/.exec(value);
|
|
36
|
-
if (!match) return value;
|
|
37
|
-
const rest = match[2].replace(/\\/g, "/").replace(/\/+$/, "");
|
|
38
|
-
return rest ? `/mnt/${match[1].toLowerCase()}/${rest}` : `/mnt/${match[1].toLowerCase()}`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* 시작 디렉터리에서 위로 올라가 처음 만나는 `.git`이 `gitdir: /...`(POSIX 경로)를 가리키는 파일이면 true다.
|
|
43
|
-
* 2026-10-09 `.worktrees/dart-cron-order`에서 Windows git이 `fatal: not a git repository`로 끝났다.
|
|
44
|
-
*/
|
|
45
|
-
export async function hasPosixGitdirWorktreeFrom(startDirectory: string): Promise<boolean> {
|
|
46
|
-
let dir = resolve(startDirectory);
|
|
47
|
-
for (;;) {
|
|
48
|
-
const dotGit = join(dir, ".git");
|
|
49
|
-
const info = await stat(dotGit).catch(() => undefined);
|
|
50
|
-
if (info) return info.isFile() && /^gitdir:\s*\//m.test(await readFile(dotGit, "utf8"));
|
|
51
|
-
const parent = dirname(dir);
|
|
52
|
-
if (parent === dir) return false;
|
|
53
|
-
dir = parent;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** 대상 파일마다 위로 올라가 POSIX gitdir worktree에 속한 파일이 하나라도 있으면 true다. */
|
|
58
|
-
export async function hasPosixGitdirWorktree(cwd: string, files: string[]): Promise<boolean> {
|
|
59
|
-
for (const file of files) {
|
|
60
|
-
if (await hasPosixGitdirWorktreeFrom(dirname(resolve(cwd, file)))) return true;
|
|
61
|
-
}
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* WSL + `/mnt/<드라이브>/` + POSIX gitdir worktree가 아님 → Windows 쪽에서 다룬다. finalizer와 git 실행기가 같은 판정을 쓴다.
|
|
67
|
-
* `files`가 없으면 `cwd` 디렉터리 자체에서 위로 올라가며 보고, 있으면 파일마다 부모 디렉터리에서 올라간다.
|
|
68
|
-
*/
|
|
69
|
-
export async function usesWindowsDriveRepo(cwd: string, files?: string[]): Promise<boolean> {
|
|
70
|
-
if (!isWindowsDrivePath(cwd)) return false;
|
|
71
|
-
return !(await (files ? hasPosixGitdirWorktree(cwd, files) : hasPosixGitdirWorktreeFrom(cwd)));
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const LINUX_GIT: GitExecutor = { command: "git", windows: false, toPosix: (value) => value };
|
|
75
|
-
const WINDOWS_GIT: GitExecutor = { command: "git.exe", windows: true, toPosix: windowsPathToPosix };
|
|
76
|
-
|
|
77
|
-
let windowsGitProbe: { path: string; found: boolean } | undefined;
|
|
78
|
-
|
|
79
|
-
/** interop이 꺼져 PATH에 git.exe가 없으면 Linux git으로 남긴다. PATH가 바뀌면 다시 본다. */
|
|
80
|
-
function hasWindowsGit(): boolean {
|
|
81
|
-
const path = process.env.PATH ?? "";
|
|
82
|
-
if (windowsGitProbe?.path !== path) {
|
|
83
|
-
windowsGitProbe = { path, found: path.split(delimiter).some((dir) => dir && existsSync(join(dir, "git.exe"))) };
|
|
84
|
-
}
|
|
85
|
-
return windowsGitProbe.found;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* `directory`(git을 실행할 cwd)에서 쓸 git을 고른다. Windows git은 호출자의 cwd 옵션으로 실행한다.
|
|
90
|
-
* WSL interop이 `/mnt/<드라이브>` cwd를 Windows 경로로 넘기므로 경로 인자를 따로 바꿀 필요가 없다.
|
|
91
|
-
*/
|
|
92
|
-
export async function selectGitExecutor(directory: string): Promise<GitExecutor> {
|
|
93
|
-
if (!hasWindowsGit()) return LINUX_GIT;
|
|
94
|
-
// `.git` 파일을 읽지 못하는 등 판정을 못 하면 어느 git이든 열 수 있는 Linux git으로 남긴다.
|
|
95
|
-
return (await usesWindowsDriveRepo(directory).catch(() => false)) ? WINDOWS_GIT : LINUX_GIT;
|
|
96
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* WSL에서 Windows 드라이브(`/mnt/<드라이브>/`) 위 저장소를 어느 git으로 다룰지 정하는 공용 규칙.
|
|
3
|
+
*
|
|
4
|
+
* Linux git은 9p로 파일마다 stat해 3,905개 파일 저장소의 `git status`가 4초 가까이 걸린다(2026-10-09 실측).
|
|
5
|
+
* 같은 저장소를 Windows `git.exe`(interop)에 맡기면 수십 ms다. 그래서 WSL + `/mnt/<드라이브>/` 경로면 Windows git을
|
|
6
|
+
* 쓴다. 단 하나는 예외다: WSL Linux git이 만든 linked worktree는 `.git` 파일에 `gitdir: /mnt/e/...` 같은 Linux 경로를
|
|
7
|
+
* 적어 Windows git이 따라가지 못한다. 그런 worktree는 느려도 Linux git이다.
|
|
8
|
+
*
|
|
9
|
+
* 이 파일은 `lib/wsl-git.ts`(Next 앱)와 같은 규칙의 짝이다. 하네스는 ~/.omp/agent로 복사돼 앱 번들과 분리되므로
|
|
10
|
+
* 한쪽이 다른 쪽을 import할 수 없다. 규칙을 바꿀 때는 두 파일을 함께 바꾼다(`lib/wsl-git.test.mjs`가 두 구현이
|
|
11
|
+
* 같은 판정을 내는지 확인한다).
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync } from "node:fs";
|
|
14
|
+
import { readFile, stat } from "node:fs/promises";
|
|
15
|
+
import { delimiter, dirname, join, resolve } from "node:path";
|
|
16
|
+
|
|
17
|
+
export interface GitExecutor {
|
|
18
|
+
/** execFile에 넘길 실행 파일. */
|
|
19
|
+
command: "git" | "git.exe";
|
|
20
|
+
windows: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* git이 출력한 절대 경로를 호출자가 쓰는 POSIX 경로로 바꾼다. Windows git은 `D:/repo/.git`처럼 쓰므로
|
|
23
|
+
* `/mnt/d/repo/.git`로 돌려놓아야 Linux 쪽 경로와 같은 기준으로 비교된다. Linux git이면 그대로다.
|
|
24
|
+
*/
|
|
25
|
+
toPosix(value: string): string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** WSL이고 `/mnt/<드라이브>` 아래(드라이브 루트 포함) 경로인가. */
|
|
29
|
+
export function isWindowsDrivePath(cwd: string): boolean {
|
|
30
|
+
return process.platform === "linux" && Boolean(process.env.WSL_DISTRO_NAME) && /^\/mnt\/[a-z](?:\/|$)/i.test(cwd);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** `D:/a/b`·`D:\a\b`를 `/mnt/d/a/b`로 바꾼다. 드라이브 문자로 시작하지 않으면 그대로 돌려준다. */
|
|
34
|
+
export function windowsPathToPosix(value: string): string {
|
|
35
|
+
const match = /^([A-Za-z]):[\\/]*(.*)$/.exec(value);
|
|
36
|
+
if (!match) return value;
|
|
37
|
+
const rest = match[2].replace(/\\/g, "/").replace(/\/+$/, "");
|
|
38
|
+
return rest ? `/mnt/${match[1].toLowerCase()}/${rest}` : `/mnt/${match[1].toLowerCase()}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 시작 디렉터리에서 위로 올라가 처음 만나는 `.git`이 `gitdir: /...`(POSIX 경로)를 가리키는 파일이면 true다.
|
|
43
|
+
* 2026-10-09 `.worktrees/dart-cron-order`에서 Windows git이 `fatal: not a git repository`로 끝났다.
|
|
44
|
+
*/
|
|
45
|
+
export async function hasPosixGitdirWorktreeFrom(startDirectory: string): Promise<boolean> {
|
|
46
|
+
let dir = resolve(startDirectory);
|
|
47
|
+
for (;;) {
|
|
48
|
+
const dotGit = join(dir, ".git");
|
|
49
|
+
const info = await stat(dotGit).catch(() => undefined);
|
|
50
|
+
if (info) return info.isFile() && /^gitdir:\s*\//m.test(await readFile(dotGit, "utf8"));
|
|
51
|
+
const parent = dirname(dir);
|
|
52
|
+
if (parent === dir) return false;
|
|
53
|
+
dir = parent;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** 대상 파일마다 위로 올라가 POSIX gitdir worktree에 속한 파일이 하나라도 있으면 true다. */
|
|
58
|
+
export async function hasPosixGitdirWorktree(cwd: string, files: string[]): Promise<boolean> {
|
|
59
|
+
for (const file of files) {
|
|
60
|
+
if (await hasPosixGitdirWorktreeFrom(dirname(resolve(cwd, file)))) return true;
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* WSL + `/mnt/<드라이브>/` + POSIX gitdir worktree가 아님 → Windows 쪽에서 다룬다. finalizer와 git 실행기가 같은 판정을 쓴다.
|
|
67
|
+
* `files`가 없으면 `cwd` 디렉터리 자체에서 위로 올라가며 보고, 있으면 파일마다 부모 디렉터리에서 올라간다.
|
|
68
|
+
*/
|
|
69
|
+
export async function usesWindowsDriveRepo(cwd: string, files?: string[]): Promise<boolean> {
|
|
70
|
+
if (!isWindowsDrivePath(cwd)) return false;
|
|
71
|
+
return !(await (files ? hasPosixGitdirWorktree(cwd, files) : hasPosixGitdirWorktreeFrom(cwd)));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const LINUX_GIT: GitExecutor = { command: "git", windows: false, toPosix: (value) => value };
|
|
75
|
+
const WINDOWS_GIT: GitExecutor = { command: "git.exe", windows: true, toPosix: windowsPathToPosix };
|
|
76
|
+
|
|
77
|
+
let windowsGitProbe: { path: string; found: boolean } | undefined;
|
|
78
|
+
|
|
79
|
+
/** interop이 꺼져 PATH에 git.exe가 없으면 Linux git으로 남긴다. PATH가 바뀌면 다시 본다. */
|
|
80
|
+
function hasWindowsGit(): boolean {
|
|
81
|
+
const path = process.env.PATH ?? "";
|
|
82
|
+
if (windowsGitProbe?.path !== path) {
|
|
83
|
+
windowsGitProbe = { path, found: path.split(delimiter).some((dir) => dir && existsSync(join(dir, "git.exe"))) };
|
|
84
|
+
}
|
|
85
|
+
return windowsGitProbe.found;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* `directory`(git을 실행할 cwd)에서 쓸 git을 고른다. Windows git은 호출자의 cwd 옵션으로 실행한다.
|
|
90
|
+
* WSL interop이 `/mnt/<드라이브>` cwd를 Windows 경로로 넘기므로 경로 인자를 따로 바꿀 필요가 없다.
|
|
91
|
+
*/
|
|
92
|
+
export async function selectGitExecutor(directory: string): Promise<GitExecutor> {
|
|
93
|
+
if (!hasWindowsGit()) return LINUX_GIT;
|
|
94
|
+
// `.git` 파일을 읽지 못하는 등 판정을 못 하면 어느 git이든 열 수 있는 Linux git으로 남긴다.
|
|
95
|
+
return (await usesWindowsDriveRepo(directory).catch(() => false)) ? WINDOWS_GIT : LINUX_GIT;
|
|
96
|
+
}
|