automagik-forge 0.1.13 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +143 -447
- package/dist/linux-x64/automagik-forge-mcp.zip +0 -0
- package/{npx-cli/automagik-forge-0.0.55.tgz → dist/linux-x64/automagik-forge.zip} +0 -0
- package/package.json +13 -23
- package/.cargo/config.toml +0 -13
- package/.claude/commands/commit.md +0 -376
- package/.claude/commands/prompt.md +0 -871
- package/.env.example +0 -20
- package/.github/actions/setup-node/action.yml +0 -29
- package/.github/images/automagik-logo.png +0 -0
- package/.github/workflows/pre-release.yml +0 -470
- package/.github/workflows/publish.yml +0 -145
- package/.github/workflows/test.yml +0 -63
- package/.mcp.json +0 -57
- package/AGENT.md +0 -40
- package/CLAUDE.md +0 -40
- package/CODE-OF-CONDUCT.md +0 -89
- package/Cargo.toml +0 -19
- package/Dockerfile +0 -43
- package/LICENSE +0 -201
- package/Makefile +0 -97
- package/backend/.sqlx/query-01b7e2bac1261d8be3d03c03df3e5220590da6c31c77f161074fc62752d63881.json +0 -12
- package/backend/.sqlx/query-03f2b02ba6dc5ea2b3cf6b1004caea0ad6bcc10ebd63f441d321a389f026e263.json +0 -12
- package/backend/.sqlx/query-0923b77d137a29fc54d399a873ff15fc4af894490bc65a4d344a7575cb0d8643.json +0 -12
- package/backend/.sqlx/query-0f808bcdb63c5f180836e448dd64c435c51758b2fc54a52ce9e67495b1ab200e.json +0 -68
- package/backend/.sqlx/query-1268afe9ca849daa6722e3df7ca8e9e61f0d37052e782bb5452ab8e1018d9b63.json +0 -12
- package/backend/.sqlx/query-1b082630a9622f8667ee7a9aba2c2d3176019a68c6bb83d33008594821415a57.json +0 -12
- package/backend/.sqlx/query-1c7b06ba1e112abf6b945a2ff08a0b40ec23f3738c2e7399f067b558cf8d490e.json +0 -12
- package/backend/.sqlx/query-1f619f01f46859a64ded531dd0ef61abacfe62e758abe7030a6aa745140b95ca.json +0 -104
- package/backend/.sqlx/query-1fca1ce14b4b20205364cd1f1f45ebe1d2e30cd745e59e189d56487b5639dfbb.json +0 -12
- package/backend/.sqlx/query-212828320e8d871ab9d83705a040b23bcf0393dc7252177fc539a74657f578ef.json +0 -32
- package/backend/.sqlx/query-290ce5c152be8d36e58ff42570f9157beb07ab9e77a03ec6fc30b4f56f9b8f6b.json +0 -56
- package/backend/.sqlx/query-2b471d2c2e8ffbe0cd42d2a91b814c0d79f9d09200f147e3cea33ba4ce673c8a.json +0 -68
- package/backend/.sqlx/query-354a48c705bb9bb2048c1b7f10fcb714e23f9db82b7a4ea6932486197b2ede6a.json +0 -92
- package/backend/.sqlx/query-36c9e3dd10648e94b949db5c91a774ecb1e10a899ef95da74066eccedca4d8b2.json +0 -12
- package/backend/.sqlx/query-36e4ba7bbd81b402d5a20b6005755eafbb174c8dda442081823406ac32809a94.json +0 -56
- package/backend/.sqlx/query-3a5b3c98a55ca183ab20c74708e3d7e579dda37972c059e7515c4ceee4bd8dd3.json +0 -62
- package/backend/.sqlx/query-3d0a1cabf2a52e9d90cdfd29c509ca89aeb448d0c1d2446c65cd43db40735e86.json +0 -62
- package/backend/.sqlx/query-3d6bd16fbce59efe30b7f67ea342e0e4ea6d1432389c02468ad79f1f742d4031.json +0 -56
- package/backend/.sqlx/query-4049ca413b285a05aca6b25385e9c8185575f01e9069e4e8581aa45d713f612f.json +0 -32
- package/backend/.sqlx/query-412bacd3477d86369082e90f52240407abce436cb81292d42b2dbe1e5c18eea1.json +0 -104
- package/backend/.sqlx/query-417a8b1ff4e51de82aea0159a3b97932224dc325b23476cb84153d690227fd8b.json +0 -62
- package/backend/.sqlx/query-461cc1b0bb6fd909afc9dd2246e8526b3771cfbb0b22ae4b5d17b51af587b9e2.json +0 -56
- package/backend/.sqlx/query-58408c7a8cdeeda0bef359f1f9bd91299a339dc2b191462fc58c9736a56d5227.json +0 -92
- package/backend/.sqlx/query-5a886026d75d515c01f347cc203c8d99dd04c61dc468e2e4c5aa548436d13834.json +0 -62
- package/backend/.sqlx/query-5b902137b11022d2e1a5c4f6a9c83fec1a856c6a710aff831abd2382ede76b43.json +0 -12
- package/backend/.sqlx/query-5ed1238e52e59bb5f76c0f153fd99a14093f7ce2585bf9843585608f17ec575b.json +0 -104
- package/backend/.sqlx/query-6e8b860b14decfc2227dc57213f38442943d3fbef5c8418fd6b634c6e0f5e2ea.json +0 -104
- package/backend/.sqlx/query-6ec414276994c4ccb2433eaa5b1b342168557d17ddf5a52dac84cb1b59b9de8f.json +0 -68
- package/backend/.sqlx/query-6ecfa16d0cf825aacf233544b5baf151e9adfdca26c226ad71020d291fd802d5.json +0 -62
- package/backend/.sqlx/query-72509d252c39fce77520aa816cb2acbc1fb35dc2605e7be893610599b2427f2e.json +0 -62
- package/backend/.sqlx/query-75239b2da188f749707d77f3c1544332ca70db3d6d6743b2601dc0d167536437.json +0 -62
- package/backend/.sqlx/query-83d10e29f8478aff33434f9ac67068e013b888b953a2657e2bb72a6f619d04f2.json +0 -50
- package/backend/.sqlx/query-8610803360ea18b9b9d078a6981ea56abfbfe84e6354fc1d5ae4c622e01410ed.json +0 -68
- package/backend/.sqlx/query-86d03eb70eef39c59296416867f2ee66c9f7cd8b7f961fbda2f89fc0a1c442c2.json +0 -12
- package/backend/.sqlx/query-87d0feb5a6b442bad9c60068ea7569599cc6fc91a0e2692ecb42e93b03201b9d.json +0 -68
- package/backend/.sqlx/query-8a67b3b3337248f06a57bdf8a908f7ef23177431eaed82dc08c94c3e5944340e.json +0 -12
- package/backend/.sqlx/query-8f01ebd64bdcde6a090479f14810d73ba23020e76fd70854ac57f2da251702c3.json +0 -12
- package/backend/.sqlx/query-90fd607fcb2dca72239ff25e618e21e174b195991eaa33722cbf5f76da84cfab.json +0 -62
- package/backend/.sqlx/query-92e8bdbcd80c5ff3db7a35cf79492048803ef305cbdef0d0a1fe5dc881ca8c71.json +0 -104
- package/backend/.sqlx/query-93a1605f90e9672dad29b472b6ad85fa9a55ea3ffa5abcb8724b09d61be254ca.json +0 -20
- package/backend/.sqlx/query-9472c8fb477958167f5fae40b85ac44252468c5226b2cdd7770f027332eed6d7.json +0 -104
- package/backend/.sqlx/query-96036c4f9e0f48bdc5a4a4588f0c5f288ac7aaa5425cac40fc33f337e1a351f2.json +0 -56
- package/backend/.sqlx/query-9edb2c01e91fd0f0fe7b56e988c7ae0393150f50be3f419a981e035c0121dfc7.json +0 -104
- package/backend/.sqlx/query-a157cf00616f703bfba21927f1eb1c9eec2a81c02da15f66efdba0b6c375de1b.json +0 -26
- package/backend/.sqlx/query-a31fff84f3b8e532fd1160447d89d700f06ae08821fee00c9a5b60492b05259c.json +0 -62
- package/backend/.sqlx/query-a5ba908419fb3e456bdd2daca41ba06cc3212ffffb8520fc7dbbcc8b60ada314.json +0 -12
- package/backend/.sqlx/query-a6d2961718dbc3b1a925e549f49a159c561bef58c105529275f274b27e2eba5b.json +0 -104
- package/backend/.sqlx/query-a9e93d5b09b29faf66e387e4d7596a792d81e75c4d3726e83c2963e8d7c9b56f.json +0 -104
- package/backend/.sqlx/query-ac5247c8d7fb86e4650c4b0eb9420031614c831b7b085083bac20c1af314c538.json +0 -12
- package/backend/.sqlx/query-afef9467be74c411c4cb119a8b2b1aea53049877dfc30cc60b486134ba4b4c9f.json +0 -68
- package/backend/.sqlx/query-b2b2c6b4d0b1a347b5c4cb63c3a46a265d4db53be9554989a814b069d0af82f2.json +0 -62
- package/backend/.sqlx/query-c50d2ff0b12e5bcc81e371089ee2d007e233e7db93aefba4fef08e7aa68f5ab7.json +0 -20
- package/backend/.sqlx/query-c614e6056b244ca07f1b9d44e7edc9d5819225c6f8d9e077070c6e518a17f50b.json +0 -12
- package/backend/.sqlx/query-c67259be8bf4ee0cfd32167b2aa3b7fe9192809181a8171bf1c2d6df731967ae.json +0 -12
- package/backend/.sqlx/query-d2d0a1b985ebbca6a2b3e882a221a219f3199890fa640afc946ef1a792d6d8de.json +0 -12
- package/backend/.sqlx/query-d30aa5786757f32bf2b9c5fe51a45e506c71c28c5994e430d9b0546adb15ffa2.json +0 -20
- package/backend/.sqlx/query-d3b9ea1de1576af71b312924ce7f4ea8ae5dbe2ac138ea3b4470f2d5cd734846.json +0 -12
- package/backend/.sqlx/query-ed8456646fa69ddd412441955f06ff22bfb790f29466450735e0b8bb1bc4ec94.json +0 -12
- package/backend/Cargo.toml +0 -71
- package/backend/build.rs +0 -32
- package/backend/migrations/20250617183714_init.sql +0 -44
- package/backend/migrations/20250620212427_execution_processes.sql +0 -25
- package/backend/migrations/20250620214100_remove_stdout_stderr_from_task_attempts.sql +0 -28
- package/backend/migrations/20250621120000_relate_activities_to_execution_processes.sql +0 -23
- package/backend/migrations/20250623120000_executor_sessions.sql +0 -17
- package/backend/migrations/20250623130000_add_executor_type_to_execution_processes.sql +0 -4
- package/backend/migrations/20250625000000_add_dev_script_to_projects.sql +0 -4
- package/backend/migrations/20250701000000_add_branch_to_task_attempts.sql +0 -2
- package/backend/migrations/20250701000001_add_pr_tracking_to_task_attempts.sql +0 -5
- package/backend/migrations/20250701120000_add_assistant_message_to_executor_sessions.sql +0 -2
- package/backend/migrations/20250708000000_add_base_branch_to_task_attempts.sql +0 -2
- package/backend/migrations/20250709000000_add_worktree_deleted_flag.sql +0 -2
- package/backend/migrations/20250710000000_add_setup_completion.sql +0 -3
- package/backend/migrations/20250715154859_add_task_templates.sql +0 -25
- package/backend/migrations/20250716143725_add_default_templates.sql +0 -174
- package/backend/migrations/20250716161432_update_executor_names_to_kebab_case.sql +0 -20
- package/backend/migrations/20250716170000_add_parent_task_to_tasks.sql +0 -7
- package/backend/migrations/20250717000000_drop_task_attempt_activities.sql +0 -9
- package/backend/migrations/20250719000000_add_cleanup_script_to_projects.sql +0 -2
- package/backend/migrations/20250720000000_add_cleanupscript_to_process_type_constraint.sql +0 -25
- package/backend/migrations/20250723000000_add_wish_to_tasks.sql +0 -7
- package/backend/migrations/20250724000000_remove_unique_wish_constraint.sql +0 -5
- package/backend/scripts/toast-notification.ps1 +0 -23
- package/backend/sounds/abstract-sound1.wav +0 -0
- package/backend/sounds/abstract-sound2.wav +0 -0
- package/backend/sounds/abstract-sound3.wav +0 -0
- package/backend/sounds/abstract-sound4.wav +0 -0
- package/backend/sounds/cow-mooing.wav +0 -0
- package/backend/sounds/phone-vibration.wav +0 -0
- package/backend/sounds/rooster.wav +0 -0
- package/backend/src/app_state.rs +0 -218
- package/backend/src/bin/generate_types.rs +0 -189
- package/backend/src/bin/mcp_task_server.rs +0 -191
- package/backend/src/execution_monitor.rs +0 -1193
- package/backend/src/executor.rs +0 -1053
- package/backend/src/executors/amp.rs +0 -697
- package/backend/src/executors/ccr.rs +0 -91
- package/backend/src/executors/charm_opencode.rs +0 -113
- package/backend/src/executors/claude.rs +0 -887
- package/backend/src/executors/cleanup_script.rs +0 -124
- package/backend/src/executors/dev_server.rs +0 -53
- package/backend/src/executors/echo.rs +0 -79
- package/backend/src/executors/gemini/config.rs +0 -67
- package/backend/src/executors/gemini/streaming.rs +0 -363
- package/backend/src/executors/gemini.rs +0 -765
- package/backend/src/executors/mod.rs +0 -23
- package/backend/src/executors/opencode_ai.rs +0 -113
- package/backend/src/executors/setup_script.rs +0 -130
- package/backend/src/executors/sst_opencode/filter.rs +0 -184
- package/backend/src/executors/sst_opencode/tools.rs +0 -139
- package/backend/src/executors/sst_opencode.rs +0 -756
- package/backend/src/lib.rs +0 -45
- package/backend/src/main.rs +0 -324
- package/backend/src/mcp/mod.rs +0 -1
- package/backend/src/mcp/task_server.rs +0 -850
- package/backend/src/middleware/mod.rs +0 -3
- package/backend/src/middleware/model_loaders.rs +0 -242
- package/backend/src/models/api_response.rs +0 -36
- package/backend/src/models/config.rs +0 -375
- package/backend/src/models/execution_process.rs +0 -430
- package/backend/src/models/executor_session.rs +0 -225
- package/backend/src/models/mod.rs +0 -12
- package/backend/src/models/project.rs +0 -356
- package/backend/src/models/task.rs +0 -345
- package/backend/src/models/task_attempt.rs +0 -1214
- package/backend/src/models/task_template.rs +0 -146
- package/backend/src/openapi.rs +0 -93
- package/backend/src/routes/auth.rs +0 -297
- package/backend/src/routes/config.rs +0 -385
- package/backend/src/routes/filesystem.rs +0 -228
- package/backend/src/routes/health.rs +0 -16
- package/backend/src/routes/mod.rs +0 -9
- package/backend/src/routes/projects.rs +0 -562
- package/backend/src/routes/stream.rs +0 -244
- package/backend/src/routes/task_attempts.rs +0 -1172
- package/backend/src/routes/task_templates.rs +0 -229
- package/backend/src/routes/tasks.rs +0 -353
- package/backend/src/services/analytics.rs +0 -216
- package/backend/src/services/git_service.rs +0 -1321
- package/backend/src/services/github_service.rs +0 -307
- package/backend/src/services/mod.rs +0 -13
- package/backend/src/services/notification_service.rs +0 -263
- package/backend/src/services/pr_monitor.rs +0 -214
- package/backend/src/services/process_service.rs +0 -940
- package/backend/src/utils/path.rs +0 -96
- package/backend/src/utils/shell.rs +0 -19
- package/backend/src/utils/text.rs +0 -24
- package/backend/src/utils/worktree_manager.rs +0 -578
- package/backend/src/utils.rs +0 -125
- package/backend/test.db +0 -0
- package/build-npm-package.sh +0 -61
- package/dev_assets_seed/config.json +0 -19
- package/frontend/.eslintrc.json +0 -25
- package/frontend/.prettierrc.json +0 -8
- package/frontend/components.json +0 -17
- package/frontend/index.html +0 -19
- package/frontend/package-lock.json +0 -7321
- package/frontend/package.json +0 -61
- package/frontend/postcss.config.js +0 -6
- package/frontend/public/android-chrome-192x192.png +0 -0
- package/frontend/public/android-chrome-512x512.png +0 -0
- package/frontend/public/apple-touch-icon.png +0 -0
- package/frontend/public/automagik-forge-logo-dark.svg +0 -3
- package/frontend/public/automagik-forge-logo.svg +0 -3
- package/frontend/public/automagik-forge-screenshot-overview.png +0 -0
- package/frontend/public/favicon-16x16.png +0 -0
- package/frontend/public/favicon-32x32.png +0 -0
- package/frontend/public/favicon.ico +0 -0
- package/frontend/public/site.webmanifest +0 -1
- package/frontend/public/viba-kanban-favicon.png +0 -0
- package/frontend/src/App.tsx +0 -157
- package/frontend/src/components/DisclaimerDialog.tsx +0 -106
- package/frontend/src/components/GitHubLoginDialog.tsx +0 -314
- package/frontend/src/components/OnboardingDialog.tsx +0 -185
- package/frontend/src/components/PrivacyOptInDialog.tsx +0 -130
- package/frontend/src/components/ProvidePatDialog.tsx +0 -98
- package/frontend/src/components/TaskTemplateManager.tsx +0 -336
- package/frontend/src/components/config-provider.tsx +0 -119
- package/frontend/src/components/context/TaskDetailsContextProvider.tsx +0 -470
- package/frontend/src/components/context/taskDetailsContext.ts +0 -125
- package/frontend/src/components/keyboard-shortcuts-demo.tsx +0 -35
- package/frontend/src/components/layout/navbar.tsx +0 -86
- package/frontend/src/components/logo.tsx +0 -44
- package/frontend/src/components/projects/ProjectCard.tsx +0 -155
- package/frontend/src/components/projects/project-detail.tsx +0 -251
- package/frontend/src/components/projects/project-form-fields.tsx +0 -238
- package/frontend/src/components/projects/project-form.tsx +0 -301
- package/frontend/src/components/projects/project-list.tsx +0 -200
- package/frontend/src/components/projects/projects-page.tsx +0 -20
- package/frontend/src/components/tasks/BranchSelector.tsx +0 -169
- package/frontend/src/components/tasks/DeleteFileConfirmationDialog.tsx +0 -94
- package/frontend/src/components/tasks/EditorSelectionDialog.tsx +0 -119
- package/frontend/src/components/tasks/TaskCard.tsx +0 -154
- package/frontend/src/components/tasks/TaskDetails/CollapsibleToolbar.tsx +0 -33
- package/frontend/src/components/tasks/TaskDetails/DiffCard.tsx +0 -109
- package/frontend/src/components/tasks/TaskDetails/DiffChunkSection.tsx +0 -135
- package/frontend/src/components/tasks/TaskDetails/DiffFile.tsx +0 -296
- package/frontend/src/components/tasks/TaskDetails/DiffTab.tsx +0 -32
- package/frontend/src/components/tasks/TaskDetails/DisplayConversationEntry.tsx +0 -392
- package/frontend/src/components/tasks/TaskDetails/LogsTab/Conversation.tsx +0 -256
- package/frontend/src/components/tasks/TaskDetails/LogsTab/ConversationEntry.tsx +0 -56
- package/frontend/src/components/tasks/TaskDetails/LogsTab/NormalizedConversationViewer.tsx +0 -92
- package/frontend/src/components/tasks/TaskDetails/LogsTab/Prompt.tsx +0 -22
- package/frontend/src/components/tasks/TaskDetails/LogsTab/SetupScriptRunning.tsx +0 -49
- package/frontend/src/components/tasks/TaskDetails/LogsTab.tsx +0 -186
- package/frontend/src/components/tasks/TaskDetails/ProcessesTab.tsx +0 -288
- package/frontend/src/components/tasks/TaskDetails/RelatedTasksTab.tsx +0 -216
- package/frontend/src/components/tasks/TaskDetails/TabNavigation.tsx +0 -93
- package/frontend/src/components/tasks/TaskDetailsHeader.tsx +0 -169
- package/frontend/src/components/tasks/TaskDetailsPanel.tsx +0 -126
- package/frontend/src/components/tasks/TaskDetailsToolbar.tsx +0 -302
- package/frontend/src/components/tasks/TaskFollowUpSection.tsx +0 -130
- package/frontend/src/components/tasks/TaskFormDialog.tsx +0 -400
- package/frontend/src/components/tasks/TaskKanbanBoard.tsx +0 -180
- package/frontend/src/components/tasks/Toolbar/CreateAttempt.tsx +0 -259
- package/frontend/src/components/tasks/Toolbar/CreatePRDialog.tsx +0 -243
- package/frontend/src/components/tasks/Toolbar/CurrentAttempt.tsx +0 -899
- package/frontend/src/components/tasks/index.ts +0 -2
- package/frontend/src/components/theme-provider.tsx +0 -82
- package/frontend/src/components/theme-toggle.tsx +0 -36
- package/frontend/src/components/ui/alert.tsx +0 -59
- package/frontend/src/components/ui/auto-expanding-textarea.tsx +0 -70
- package/frontend/src/components/ui/badge.tsx +0 -36
- package/frontend/src/components/ui/button.tsx +0 -56
- package/frontend/src/components/ui/card.tsx +0 -86
- package/frontend/src/components/ui/checkbox.tsx +0 -44
- package/frontend/src/components/ui/chip.tsx +0 -25
- package/frontend/src/components/ui/dialog.tsx +0 -124
- package/frontend/src/components/ui/dropdown-menu.tsx +0 -198
- package/frontend/src/components/ui/file-search-textarea.tsx +0 -292
- package/frontend/src/components/ui/folder-picker.tsx +0 -279
- package/frontend/src/components/ui/input.tsx +0 -25
- package/frontend/src/components/ui/label.tsx +0 -24
- package/frontend/src/components/ui/loader.tsx +0 -26
- package/frontend/src/components/ui/markdown-renderer.tsx +0 -75
- package/frontend/src/components/ui/select.tsx +0 -160
- package/frontend/src/components/ui/separator.tsx +0 -31
- package/frontend/src/components/ui/shadcn-io/kanban/index.tsx +0 -185
- package/frontend/src/components/ui/table.tsx +0 -117
- package/frontend/src/components/ui/tabs.tsx +0 -53
- package/frontend/src/components/ui/textarea.tsx +0 -22
- package/frontend/src/components/ui/tooltip.tsx +0 -28
- package/frontend/src/hooks/useNormalizedConversation.ts +0 -440
- package/frontend/src/index.css +0 -225
- package/frontend/src/lib/api.ts +0 -630
- package/frontend/src/lib/keyboard-shortcuts.ts +0 -266
- package/frontend/src/lib/responsive-config.ts +0 -70
- package/frontend/src/lib/types.ts +0 -39
- package/frontend/src/lib/utils.ts +0 -10
- package/frontend/src/main.tsx +0 -50
- package/frontend/src/pages/McpServers.tsx +0 -418
- package/frontend/src/pages/Settings.tsx +0 -610
- package/frontend/src/pages/project-tasks.tsx +0 -575
- package/frontend/src/pages/projects.tsx +0 -18
- package/frontend/src/vite-env.d.ts +0 -1
- package/frontend/tailwind.config.js +0 -125
- package/frontend/tsconfig.json +0 -26
- package/frontend/tsconfig.node.json +0 -10
- package/frontend/vite.config.ts +0 -33
- package/npx-cli/README.md +0 -159
- package/npx-cli/automagik-forge-0.1.0.tgz +0 -0
- package/npx-cli/automagik-forge-0.1.10.tgz +0 -0
- package/npx-cli/package.json +0 -17
- package/npx-cli/vibe-kanban-0.0.55.tgz +0 -0
- package/pnpm-workspace.yaml +0 -2
- package/rust-toolchain.toml +0 -11
- package/rustfmt.toml +0 -3
- package/scripts/load-env.js +0 -43
- package/scripts/mcp_test.js +0 -374
- package/scripts/prepare-db.js +0 -45
- package/scripts/setup-dev-environment.js +0 -274
- package/scripts/start-mcp-sse.js +0 -70
- package/scripts/test-debug.js +0 -32
- package/scripts/test-mcp-sse.js +0 -138
- package/scripts/test-simple.js +0 -44
- package/scripts/test-wish-final.js +0 -179
- package/scripts/test-wish-system.js +0 -221
- package/shared/types.ts +0 -182
- package/test-npm-package.sh +0 -42
- /package/{npx-cli/bin → bin}/cli.js +0 -0
@@ -1,578 +0,0 @@
|
|
1
|
-
use std::{
|
2
|
-
collections::HashMap,
|
3
|
-
path::{Path, PathBuf},
|
4
|
-
sync::{Arc, Mutex},
|
5
|
-
};
|
6
|
-
|
7
|
-
use git2::{Error as GitError, Repository, WorktreeAddOptions};
|
8
|
-
use tracing::{debug, info, warn};
|
9
|
-
|
10
|
-
// Global synchronization for worktree creation to prevent race conditions
|
11
|
-
lazy_static::lazy_static! {
|
12
|
-
static ref WORKTREE_CREATION_LOCKS: Arc<Mutex<HashMap<String, Arc<tokio::sync::Mutex<()>>>>> =
|
13
|
-
Arc::new(Mutex::new(HashMap::new()));
|
14
|
-
}
|
15
|
-
|
16
|
-
pub struct WorktreeManager;
|
17
|
-
|
18
|
-
impl WorktreeManager {
|
19
|
-
/// Ensure worktree exists, recreating if necessary with proper synchronization
|
20
|
-
/// This is the main entry point for ensuring a worktree exists and prevents race conditions
|
21
|
-
pub async fn ensure_worktree_exists(
|
22
|
-
repo_path: String,
|
23
|
-
branch_name: String,
|
24
|
-
worktree_path: PathBuf,
|
25
|
-
) -> Result<(), GitError> {
|
26
|
-
let path_str = worktree_path.to_string_lossy().to_string();
|
27
|
-
|
28
|
-
// Get or create a lock for this specific worktree path
|
29
|
-
let lock = {
|
30
|
-
let mut locks = WORKTREE_CREATION_LOCKS.lock().unwrap();
|
31
|
-
locks
|
32
|
-
.entry(path_str.clone())
|
33
|
-
.or_insert_with(|| Arc::new(tokio::sync::Mutex::new(())))
|
34
|
-
.clone()
|
35
|
-
};
|
36
|
-
|
37
|
-
// Acquire the lock for this specific worktree path
|
38
|
-
let _guard = lock.lock().await;
|
39
|
-
|
40
|
-
// Check if worktree already exists and is properly set up
|
41
|
-
if Self::is_worktree_properly_set_up(&repo_path, &worktree_path).await? {
|
42
|
-
debug!("Worktree already properly set up at path: {}", path_str);
|
43
|
-
return Ok(());
|
44
|
-
}
|
45
|
-
|
46
|
-
// If worktree doesn't exist or isn't properly set up, recreate it
|
47
|
-
info!("Worktree needs recreation at path: {}", path_str);
|
48
|
-
Self::recreate_worktree_internal(repo_path, branch_name, worktree_path).await
|
49
|
-
}
|
50
|
-
|
51
|
-
/// Internal worktree recreation function (always recreates)
|
52
|
-
async fn recreate_worktree_internal(
|
53
|
-
repo_path: String,
|
54
|
-
branch_name: String,
|
55
|
-
worktree_path: PathBuf,
|
56
|
-
) -> Result<(), GitError> {
|
57
|
-
let path_str = worktree_path.to_string_lossy().to_string();
|
58
|
-
let branch_name_owned = branch_name.to_string();
|
59
|
-
let worktree_path_owned = worktree_path.to_path_buf();
|
60
|
-
|
61
|
-
// Use the provided repo path
|
62
|
-
let git_repo_path = repo_path;
|
63
|
-
|
64
|
-
// Get the worktree name for metadata operations
|
65
|
-
let worktree_name = worktree_path
|
66
|
-
.file_name()
|
67
|
-
.and_then(|n| n.to_str())
|
68
|
-
.ok_or_else(|| GitError::from_str("Invalid worktree path"))?
|
69
|
-
.to_string();
|
70
|
-
|
71
|
-
info!(
|
72
|
-
"Creating worktree {} at path {}",
|
73
|
-
branch_name_owned, path_str
|
74
|
-
);
|
75
|
-
|
76
|
-
// Step 1: Comprehensive cleanup of existing worktree and metadata (non-blocking)
|
77
|
-
Self::comprehensive_worktree_cleanup_async(
|
78
|
-
&git_repo_path,
|
79
|
-
&worktree_path_owned,
|
80
|
-
&worktree_name,
|
81
|
-
)
|
82
|
-
.await?;
|
83
|
-
|
84
|
-
// Step 2: Ensure parent directory exists (non-blocking)
|
85
|
-
if let Some(parent) = worktree_path_owned.parent() {
|
86
|
-
let parent_path = parent.to_path_buf();
|
87
|
-
tokio::task::spawn_blocking(move || std::fs::create_dir_all(&parent_path))
|
88
|
-
.await
|
89
|
-
.map_err(|e| GitError::from_str(&format!("Task join error: {}", e)))?
|
90
|
-
.map_err(|e| {
|
91
|
-
GitError::from_str(&format!("Failed to create parent directory: {}", e))
|
92
|
-
})?;
|
93
|
-
}
|
94
|
-
|
95
|
-
// Step 3: Create the worktree with retry logic for metadata conflicts (non-blocking)
|
96
|
-
Self::create_worktree_with_retry(
|
97
|
-
&git_repo_path,
|
98
|
-
&branch_name_owned,
|
99
|
-
&worktree_path_owned,
|
100
|
-
&worktree_name,
|
101
|
-
&path_str,
|
102
|
-
)
|
103
|
-
.await
|
104
|
-
}
|
105
|
-
|
106
|
-
/// Check if a worktree is properly set up (filesystem + git metadata)
|
107
|
-
async fn is_worktree_properly_set_up(
|
108
|
-
repo_path: &str,
|
109
|
-
worktree_path: &Path,
|
110
|
-
) -> Result<bool, GitError> {
|
111
|
-
let repo_path = repo_path.to_string();
|
112
|
-
let worktree_path = worktree_path.to_path_buf();
|
113
|
-
|
114
|
-
tokio::task::spawn_blocking(move || {
|
115
|
-
// Check 1: Filesystem path must exist
|
116
|
-
if !worktree_path.exists() {
|
117
|
-
return Ok(false);
|
118
|
-
}
|
119
|
-
|
120
|
-
// Check 2: Worktree must be registered in git metadata using find_worktree
|
121
|
-
let repo = Repository::open(&repo_path)?;
|
122
|
-
let worktree_name = worktree_path
|
123
|
-
.file_name()
|
124
|
-
.and_then(|n| n.to_str())
|
125
|
-
.ok_or_else(|| GitError::from_str("Invalid worktree path"))?;
|
126
|
-
|
127
|
-
// Try to find the worktree - if it exists and is valid, we're good
|
128
|
-
match repo.find_worktree(worktree_name) {
|
129
|
-
Ok(_) => Ok(true),
|
130
|
-
Err(_) => Ok(false),
|
131
|
-
}
|
132
|
-
})
|
133
|
-
.await
|
134
|
-
.map_err(|e| GitError::from_str(&format!("Task join error: {}", e)))?
|
135
|
-
}
|
136
|
-
|
137
|
-
/// Try to remove a worktree registration from git
|
138
|
-
fn try_remove_worktree(repo: &Repository, worktree_name: &str) -> Result<(), GitError> {
|
139
|
-
let worktrees = repo.worktrees()?;
|
140
|
-
|
141
|
-
for name in worktrees.iter().flatten() {
|
142
|
-
if name == worktree_name {
|
143
|
-
let worktree = repo.find_worktree(name)?;
|
144
|
-
worktree.prune(None)?;
|
145
|
-
debug!("Successfully removed worktree registration: {}", name);
|
146
|
-
return Ok(());
|
147
|
-
}
|
148
|
-
}
|
149
|
-
|
150
|
-
debug!("Worktree {} not found in git worktrees list", worktree_name);
|
151
|
-
Ok(())
|
152
|
-
}
|
153
|
-
|
154
|
-
/// Comprehensive cleanup of worktree path and metadata to prevent "path exists" errors (blocking)
|
155
|
-
fn comprehensive_worktree_cleanup(
|
156
|
-
repo: &Repository,
|
157
|
-
worktree_path: &Path,
|
158
|
-
worktree_name: &str,
|
159
|
-
) -> Result<(), GitError> {
|
160
|
-
debug!("Performing cleanup for worktree: {}", worktree_name);
|
161
|
-
|
162
|
-
let git_repo_path = Self::get_git_repo_path(repo)?;
|
163
|
-
|
164
|
-
// Step 1: Always try to remove worktree registration first (this may fail if not registered)
|
165
|
-
if let Err(e) = Self::try_remove_worktree(repo, worktree_name) {
|
166
|
-
debug!(
|
167
|
-
"Worktree registration removal failed or not found (non-fatal): {}",
|
168
|
-
e
|
169
|
-
);
|
170
|
-
}
|
171
|
-
|
172
|
-
// Step 2: Always force cleanup metadata directory (proactive cleanup)
|
173
|
-
if let Err(e) = Self::force_cleanup_worktree_metadata(&git_repo_path, worktree_name) {
|
174
|
-
debug!("Metadata cleanup failed (non-fatal): {}", e);
|
175
|
-
}
|
176
|
-
|
177
|
-
// Step 3: Clean up physical worktree directory if it exists
|
178
|
-
if worktree_path.exists() {
|
179
|
-
debug!(
|
180
|
-
"Removing existing worktree directory: {}",
|
181
|
-
worktree_path.display()
|
182
|
-
);
|
183
|
-
std::fs::remove_dir_all(worktree_path).map_err(|e| {
|
184
|
-
GitError::from_str(&format!(
|
185
|
-
"Failed to remove existing directory {}: {}",
|
186
|
-
worktree_path.display(),
|
187
|
-
e
|
188
|
-
))
|
189
|
-
})?;
|
190
|
-
}
|
191
|
-
|
192
|
-
debug!(
|
193
|
-
"Comprehensive cleanup completed for worktree: {}",
|
194
|
-
worktree_name
|
195
|
-
);
|
196
|
-
Ok(())
|
197
|
-
}
|
198
|
-
|
199
|
-
/// Async version of comprehensive cleanup to avoid blocking the main runtime
|
200
|
-
async fn comprehensive_worktree_cleanup_async(
|
201
|
-
git_repo_path: &str,
|
202
|
-
worktree_path: &Path,
|
203
|
-
worktree_name: &str,
|
204
|
-
) -> Result<(), GitError> {
|
205
|
-
let git_repo_path_owned = git_repo_path.to_string();
|
206
|
-
let worktree_path_owned = worktree_path.to_path_buf();
|
207
|
-
let worktree_name_owned = worktree_name.to_string();
|
208
|
-
|
209
|
-
// First, try to open the repository to see if it exists
|
210
|
-
let repo_result = tokio::task::spawn_blocking({
|
211
|
-
let git_repo_path = git_repo_path_owned.clone();
|
212
|
-
move || Repository::open(&git_repo_path)
|
213
|
-
})
|
214
|
-
.await;
|
215
|
-
|
216
|
-
match repo_result {
|
217
|
-
Ok(Ok(repo)) => {
|
218
|
-
// Repository exists, perform comprehensive cleanup
|
219
|
-
tokio::task::spawn_blocking(move || {
|
220
|
-
Self::comprehensive_worktree_cleanup(
|
221
|
-
&repo,
|
222
|
-
&worktree_path_owned,
|
223
|
-
&worktree_name_owned,
|
224
|
-
)
|
225
|
-
})
|
226
|
-
.await
|
227
|
-
.map_err(|e| GitError::from_str(&format!("Task join error: {}", e)))?
|
228
|
-
}
|
229
|
-
Ok(Err(e)) => {
|
230
|
-
// Repository doesn't exist (likely deleted project), fall back to simple cleanup
|
231
|
-
debug!(
|
232
|
-
"Failed to open repository at {}: {}. Falling back to simple cleanup for worktree at {}",
|
233
|
-
git_repo_path_owned, e, worktree_path_owned.display()
|
234
|
-
);
|
235
|
-
Self::simple_worktree_cleanup(&worktree_path_owned).await?;
|
236
|
-
Ok(())
|
237
|
-
}
|
238
|
-
Err(e) => Err(GitError::from_str(&format!("Task join error: {}", e))),
|
239
|
-
}
|
240
|
-
}
|
241
|
-
|
242
|
-
/// Create worktree with retry logic in non-blocking manner
|
243
|
-
async fn create_worktree_with_retry(
|
244
|
-
git_repo_path: &str,
|
245
|
-
branch_name: &str,
|
246
|
-
worktree_path: &Path,
|
247
|
-
worktree_name: &str,
|
248
|
-
path_str: &str,
|
249
|
-
) -> Result<(), GitError> {
|
250
|
-
let git_repo_path = git_repo_path.to_string();
|
251
|
-
let branch_name = branch_name.to_string();
|
252
|
-
let worktree_path = worktree_path.to_path_buf();
|
253
|
-
let worktree_name = worktree_name.to_string();
|
254
|
-
let path_str = path_str.to_string();
|
255
|
-
|
256
|
-
tokio::task::spawn_blocking(move || {
|
257
|
-
// Open repository in blocking context
|
258
|
-
let repo = Repository::open(&git_repo_path)
|
259
|
-
.map_err(|e| GitError::from_str(&format!("Failed to open repository: {}", e)))?;
|
260
|
-
|
261
|
-
// Find the branch reference using the branch name
|
262
|
-
let branch_ref = repo
|
263
|
-
.find_branch(&branch_name, git2::BranchType::Local)
|
264
|
-
.map_err(|e| {
|
265
|
-
GitError::from_str(&format!("Branch '{}' not found: {}", branch_name, e))
|
266
|
-
})?
|
267
|
-
.into_reference();
|
268
|
-
|
269
|
-
// Create worktree options
|
270
|
-
let mut worktree_opts = WorktreeAddOptions::new();
|
271
|
-
worktree_opts.reference(Some(&branch_ref));
|
272
|
-
|
273
|
-
match repo.worktree(&branch_name, &worktree_path, Some(&worktree_opts)) {
|
274
|
-
Ok(_) => {
|
275
|
-
// Verify the worktree was actually created
|
276
|
-
if !worktree_path.exists() {
|
277
|
-
return Err(GitError::from_str(&format!(
|
278
|
-
"Worktree creation reported success but path {} does not exist",
|
279
|
-
path_str
|
280
|
-
)));
|
281
|
-
}
|
282
|
-
|
283
|
-
info!(
|
284
|
-
"Successfully created worktree {} at {}",
|
285
|
-
branch_name, path_str
|
286
|
-
);
|
287
|
-
|
288
|
-
// Fix commondir for Windows/WSL compatibility
|
289
|
-
if let Err(e) = Self::fix_worktree_commondir_for_windows_wsl(
|
290
|
-
Path::new(&git_repo_path),
|
291
|
-
&worktree_name,
|
292
|
-
) {
|
293
|
-
warn!("Failed to fix worktree commondir for Windows/WSL: {}", e);
|
294
|
-
}
|
295
|
-
|
296
|
-
Ok(())
|
297
|
-
}
|
298
|
-
Err(e) if e.code() == git2::ErrorCode::Exists => {
|
299
|
-
// Handle the specific "directory exists" error for metadata
|
300
|
-
debug!(
|
301
|
-
"Worktree metadata directory exists, attempting force cleanup: {}",
|
302
|
-
e
|
303
|
-
);
|
304
|
-
|
305
|
-
// Force cleanup metadata and try one more time
|
306
|
-
Self::force_cleanup_worktree_metadata(&git_repo_path, &worktree_name).map_err(
|
307
|
-
|e| {
|
308
|
-
GitError::from_str(&format!(
|
309
|
-
"Failed to cleanup worktree metadata: {}",
|
310
|
-
e
|
311
|
-
))
|
312
|
-
},
|
313
|
-
)?;
|
314
|
-
|
315
|
-
// Try again after cleanup
|
316
|
-
match repo.worktree(&branch_name, &worktree_path, Some(&worktree_opts)) {
|
317
|
-
Ok(_) => {
|
318
|
-
if !worktree_path.exists() {
|
319
|
-
return Err(GitError::from_str(&format!(
|
320
|
-
"Worktree creation reported success but path {} does not exist",
|
321
|
-
path_str
|
322
|
-
)));
|
323
|
-
}
|
324
|
-
|
325
|
-
info!(
|
326
|
-
"Successfully created worktree {} at {} after metadata cleanup",
|
327
|
-
branch_name, path_str
|
328
|
-
);
|
329
|
-
|
330
|
-
// Fix commondir for Windows/WSL compatibility
|
331
|
-
if let Err(e) = Self::fix_worktree_commondir_for_windows_wsl(
|
332
|
-
Path::new(&git_repo_path),
|
333
|
-
&worktree_name,
|
334
|
-
) {
|
335
|
-
warn!("Failed to fix worktree commondir for Windows/WSL: {}", e);
|
336
|
-
}
|
337
|
-
|
338
|
-
Ok(())
|
339
|
-
}
|
340
|
-
Err(retry_error) => {
|
341
|
-
debug!(
|
342
|
-
"Worktree creation failed even after metadata cleanup: {}",
|
343
|
-
retry_error
|
344
|
-
);
|
345
|
-
Err(retry_error)
|
346
|
-
}
|
347
|
-
}
|
348
|
-
}
|
349
|
-
Err(e) => Err(e),
|
350
|
-
}
|
351
|
-
})
|
352
|
-
.await
|
353
|
-
.map_err(|e| GitError::from_str(&format!("Task join error: {}", e)))?
|
354
|
-
}
|
355
|
-
|
356
|
-
/// Get the git repository path
|
357
|
-
fn get_git_repo_path(repo: &Repository) -> Result<String, GitError> {
|
358
|
-
repo.workdir()
|
359
|
-
.ok_or_else(|| GitError::from_str("Repository has no working directory"))?
|
360
|
-
.to_str()
|
361
|
-
.ok_or_else(|| GitError::from_str("Repository path is not valid UTF-8"))
|
362
|
-
.map(|s| s.to_string())
|
363
|
-
}
|
364
|
-
|
365
|
-
/// Force cleanup worktree metadata directory
|
366
|
-
fn force_cleanup_worktree_metadata(
|
367
|
-
git_repo_path: &str,
|
368
|
-
worktree_name: &str,
|
369
|
-
) -> Result<(), std::io::Error> {
|
370
|
-
let git_worktree_metadata_path = Path::new(git_repo_path)
|
371
|
-
.join(".git")
|
372
|
-
.join("worktrees")
|
373
|
-
.join(worktree_name);
|
374
|
-
|
375
|
-
if git_worktree_metadata_path.exists() {
|
376
|
-
debug!(
|
377
|
-
"Force removing git worktree metadata: {}",
|
378
|
-
git_worktree_metadata_path.display()
|
379
|
-
);
|
380
|
-
std::fs::remove_dir_all(&git_worktree_metadata_path)?;
|
381
|
-
}
|
382
|
-
|
383
|
-
Ok(())
|
384
|
-
}
|
385
|
-
|
386
|
-
/// Clean up a worktree path and its git metadata (non-blocking)
|
387
|
-
/// If git_repo_path is None, attempts to infer it from the worktree itself
|
388
|
-
pub async fn cleanup_worktree(
|
389
|
-
worktree_path: &Path,
|
390
|
-
git_repo_path: Option<&str>,
|
391
|
-
) -> Result<(), GitError> {
|
392
|
-
let path_str = worktree_path.to_string_lossy().to_string();
|
393
|
-
|
394
|
-
// Get the same lock to ensure we don't interfere with creation
|
395
|
-
let lock = {
|
396
|
-
let mut locks = WORKTREE_CREATION_LOCKS.lock().unwrap();
|
397
|
-
locks
|
398
|
-
.entry(path_str.clone())
|
399
|
-
.or_insert_with(|| Arc::new(tokio::sync::Mutex::new(())))
|
400
|
-
.clone()
|
401
|
-
};
|
402
|
-
|
403
|
-
let _guard = lock.lock().await;
|
404
|
-
|
405
|
-
if let Some(worktree_name) = worktree_path.file_name().and_then(|n| n.to_str()) {
|
406
|
-
// Try to determine the git repo path if not provided
|
407
|
-
let resolved_repo_path = if let Some(repo_path) = git_repo_path {
|
408
|
-
Some(repo_path.to_string())
|
409
|
-
} else {
|
410
|
-
Self::infer_git_repo_path(worktree_path).await
|
411
|
-
};
|
412
|
-
|
413
|
-
if let Some(repo_path) = resolved_repo_path {
|
414
|
-
Self::comprehensive_worktree_cleanup_async(
|
415
|
-
&repo_path,
|
416
|
-
worktree_path,
|
417
|
-
worktree_name,
|
418
|
-
)
|
419
|
-
.await?;
|
420
|
-
} else {
|
421
|
-
// Can't determine repo path, just clean up the worktree directory
|
422
|
-
debug!(
|
423
|
-
"Cannot determine git repo path for worktree {}, performing simple cleanup",
|
424
|
-
path_str
|
425
|
-
);
|
426
|
-
Self::simple_worktree_cleanup(worktree_path).await?;
|
427
|
-
}
|
428
|
-
} else {
|
429
|
-
return Err(GitError::from_str(
|
430
|
-
"Invalid worktree path, cannot determine name",
|
431
|
-
));
|
432
|
-
}
|
433
|
-
|
434
|
-
Ok(())
|
435
|
-
}
|
436
|
-
|
437
|
-
/// Try to infer the git repository path from a worktree
|
438
|
-
async fn infer_git_repo_path(worktree_path: &Path) -> Option<String> {
|
439
|
-
// Try using git rev-parse --git-common-dir from within the worktree
|
440
|
-
let worktree_path_owned = worktree_path.to_path_buf();
|
441
|
-
|
442
|
-
tokio::task::spawn_blocking(move || {
|
443
|
-
let (shell_cmd, shell_arg) = crate::utils::shell::get_shell_command();
|
444
|
-
let git_command = "git rev-parse --git-common-dir";
|
445
|
-
|
446
|
-
let output = std::process::Command::new(shell_cmd)
|
447
|
-
.args([shell_arg, git_command])
|
448
|
-
.current_dir(&worktree_path_owned)
|
449
|
-
.output()
|
450
|
-
.ok()?;
|
451
|
-
|
452
|
-
if output.status.success() {
|
453
|
-
let git_common_dir = String::from_utf8(output.stdout).ok()?.trim().to_string();
|
454
|
-
|
455
|
-
// git-common-dir gives us the path to the .git directory
|
456
|
-
// We need the working directory (parent of .git)
|
457
|
-
let git_dir_path = std::path::Path::new(&git_common_dir);
|
458
|
-
if git_dir_path.file_name() == Some(std::ffi::OsStr::new(".git")) {
|
459
|
-
git_dir_path.parent()?.to_str().map(|s| s.to_string())
|
460
|
-
} else {
|
461
|
-
// In case of bare repo or unusual setup, use the git-common-dir as is
|
462
|
-
Some(git_common_dir)
|
463
|
-
}
|
464
|
-
} else {
|
465
|
-
None
|
466
|
-
}
|
467
|
-
})
|
468
|
-
.await
|
469
|
-
.ok()
|
470
|
-
.flatten()
|
471
|
-
}
|
472
|
-
|
473
|
-
/// Simple worktree cleanup when we can't determine the main repo
|
474
|
-
async fn simple_worktree_cleanup(worktree_path: &Path) -> Result<(), GitError> {
|
475
|
-
let worktree_path_owned = worktree_path.to_path_buf();
|
476
|
-
|
477
|
-
tokio::task::spawn_blocking(move || {
|
478
|
-
if worktree_path_owned.exists() {
|
479
|
-
std::fs::remove_dir_all(&worktree_path_owned).map_err(|e| {
|
480
|
-
GitError::from_str(&format!(
|
481
|
-
"Failed to remove worktree directory {}: {}",
|
482
|
-
worktree_path_owned.display(),
|
483
|
-
e
|
484
|
-
))
|
485
|
-
})?;
|
486
|
-
info!(
|
487
|
-
"Removed worktree directory: {}",
|
488
|
-
worktree_path_owned.display()
|
489
|
-
);
|
490
|
-
}
|
491
|
-
Ok(())
|
492
|
-
})
|
493
|
-
.await
|
494
|
-
.map_err(|e| GitError::from_str(&format!("Task join error: {}", e)))?
|
495
|
-
}
|
496
|
-
|
497
|
-
/// Rewrite worktree's commondir file to use relative paths for WSL compatibility
|
498
|
-
///
|
499
|
-
/// This fixes Git repository corruption in WSL environments where git2/libgit2 creates
|
500
|
-
/// worktrees with absolute WSL paths (/mnt/c/...) that Windows Git cannot understand.
|
501
|
-
/// Git CLI creates relative paths (../../..) which work across both environments.
|
502
|
-
///
|
503
|
-
/// References:
|
504
|
-
/// - Git 2.48+ native support: https://git-scm.com/docs/git-config/2.48.0#Documentation/git-config.txt-worktreeuseRelativePaths
|
505
|
-
/// - WSL worktree absolute path issue: https://github.com/git-ecosystem/git-credential-manager/issues/1789
|
506
|
-
pub fn fix_worktree_commondir_for_windows_wsl(
|
507
|
-
git_repo_path: &Path,
|
508
|
-
worktree_name: &str,
|
509
|
-
) -> Result<(), std::io::Error> {
|
510
|
-
if !cfg!(target_os = "linux") || !crate::utils::is_wsl2() {
|
511
|
-
debug!("Skipping commondir fix for non-WSL2 environment");
|
512
|
-
return Ok(());
|
513
|
-
}
|
514
|
-
|
515
|
-
let commondir_path = git_repo_path
|
516
|
-
.join(".git")
|
517
|
-
.join("worktrees")
|
518
|
-
.join(worktree_name)
|
519
|
-
.join("commondir");
|
520
|
-
|
521
|
-
if !commondir_path.exists() {
|
522
|
-
debug!(
|
523
|
-
"commondir file does not exist: {}",
|
524
|
-
commondir_path.display()
|
525
|
-
);
|
526
|
-
return Ok(());
|
527
|
-
}
|
528
|
-
|
529
|
-
// Read current commondir content
|
530
|
-
let current_content = std::fs::read_to_string(&commondir_path)?.trim().to_string();
|
531
|
-
|
532
|
-
debug!("Current commondir content: {}", current_content);
|
533
|
-
|
534
|
-
// Skip if already relative
|
535
|
-
if !Path::new(¤t_content).is_absolute() {
|
536
|
-
debug!("commondir already contains relative path, skipping");
|
537
|
-
return Ok(());
|
538
|
-
}
|
539
|
-
|
540
|
-
// Calculate relative path from worktree metadata dir to repo .git dir
|
541
|
-
let metadata_dir = commondir_path.parent().unwrap();
|
542
|
-
let target_git_dir = Path::new(¤t_content);
|
543
|
-
|
544
|
-
if let Some(relative_path) = pathdiff::diff_paths(target_git_dir, metadata_dir) {
|
545
|
-
let relative_path_str = relative_path.to_string_lossy();
|
546
|
-
|
547
|
-
// Safety check: ensure the relative path resolves to the same absolute path
|
548
|
-
let resolved_path = metadata_dir.join(&relative_path);
|
549
|
-
if let (Ok(resolved_canonical), Ok(target_canonical)) =
|
550
|
-
(resolved_path.canonicalize(), target_git_dir.canonicalize())
|
551
|
-
{
|
552
|
-
if resolved_canonical == target_canonical {
|
553
|
-
// Write the relative path
|
554
|
-
std::fs::write(&commondir_path, format!("{}\n", relative_path_str))?;
|
555
|
-
info!(
|
556
|
-
"Rewrote commondir to relative path: {} -> {}",
|
557
|
-
current_content, relative_path_str
|
558
|
-
);
|
559
|
-
} else {
|
560
|
-
warn!(
|
561
|
-
"Safety check failed: relative path {} does not resolve to same target",
|
562
|
-
relative_path_str
|
563
|
-
);
|
564
|
-
}
|
565
|
-
} else {
|
566
|
-
warn!("Failed to canonicalize paths for safety check");
|
567
|
-
}
|
568
|
-
} else {
|
569
|
-
warn!(
|
570
|
-
"Failed to calculate relative path from {} to {}",
|
571
|
-
metadata_dir.display(),
|
572
|
-
target_git_dir.display()
|
573
|
-
);
|
574
|
-
}
|
575
|
-
|
576
|
-
Ok(())
|
577
|
-
}
|
578
|
-
}
|
package/backend/src/utils.rs
DELETED
@@ -1,125 +0,0 @@
|
|
1
|
-
use std::{env, sync::OnceLock};
|
2
|
-
|
3
|
-
pub mod path;
|
4
|
-
pub mod shell;
|
5
|
-
pub mod text;
|
6
|
-
pub mod worktree_manager;
|
7
|
-
|
8
|
-
const PROJECT_ROOT: &str = env!("CARGO_MANIFEST_DIR");
|
9
|
-
|
10
|
-
/// Cache for WSL2 detection result
|
11
|
-
static WSL2_CACHE: OnceLock<bool> = OnceLock::new();
|
12
|
-
|
13
|
-
/// Check if running in WSL2 (cached)
|
14
|
-
pub fn is_wsl2() -> bool {
|
15
|
-
*WSL2_CACHE.get_or_init(|| {
|
16
|
-
// Check for WSL environment variables
|
17
|
-
if std::env::var("WSL_DISTRO_NAME").is_ok() || std::env::var("WSLENV").is_ok() {
|
18
|
-
tracing::debug!("WSL2 detected via environment variables");
|
19
|
-
return true;
|
20
|
-
}
|
21
|
-
|
22
|
-
// Check /proc/version for WSL2 signature
|
23
|
-
if let Ok(version) = std::fs::read_to_string("/proc/version") {
|
24
|
-
if version.contains("WSL2") || version.contains("microsoft") {
|
25
|
-
tracing::debug!("WSL2 detected via /proc/version");
|
26
|
-
return true;
|
27
|
-
}
|
28
|
-
}
|
29
|
-
|
30
|
-
tracing::debug!("WSL2 not detected");
|
31
|
-
false
|
32
|
-
})
|
33
|
-
}
|
34
|
-
|
35
|
-
pub fn asset_dir() -> std::path::PathBuf {
|
36
|
-
if cfg!(debug_assertions) {
|
37
|
-
std::path::PathBuf::from(PROJECT_ROOT).join("../dev_assets")
|
38
|
-
} else if cfg!(target_os = "windows") {
|
39
|
-
dirs::data_dir()
|
40
|
-
.expect("Could not find data directory")
|
41
|
-
.join("automagik-forge")
|
42
|
-
} else {
|
43
|
-
dirs::home_dir()
|
44
|
-
.expect("Could not find home directory")
|
45
|
-
.join(".automagik-forge")
|
46
|
-
}
|
47
|
-
|
48
|
-
// ✔ Linux/macOS → ~/.automagik-forge
|
49
|
-
// ✔ Windows → %APPDATA%\automagik-forge
|
50
|
-
}
|
51
|
-
|
52
|
-
pub fn config_path() -> std::path::PathBuf {
|
53
|
-
asset_dir().join("config.json")
|
54
|
-
}
|
55
|
-
|
56
|
-
pub fn cache_dir() -> std::path::PathBuf {
|
57
|
-
if cfg!(debug_assertions) {
|
58
|
-
std::path::PathBuf::from(PROJECT_ROOT).join("../dev_assets/.cache")
|
59
|
-
} else if cfg!(target_os = "windows") {
|
60
|
-
dirs::cache_dir()
|
61
|
-
.expect("Could not find cache directory")
|
62
|
-
.join("automagik-forge")
|
63
|
-
} else {
|
64
|
-
dirs::home_dir()
|
65
|
-
.expect("Could not find home directory")
|
66
|
-
.join(".automagik-forge")
|
67
|
-
.join("cache")
|
68
|
-
}
|
69
|
-
|
70
|
-
// ✔ Linux/macOS → ~/.automagik-forge/cache
|
71
|
-
// ✔ Windows → %LOCALAPPDATA%\automagik-forge
|
72
|
-
}
|
73
|
-
|
74
|
-
/// Get or create cached PowerShell script file
|
75
|
-
pub async fn get_powershell_script(
|
76
|
-
) -> Result<std::path::PathBuf, Box<dyn std::error::Error + Send + Sync>> {
|
77
|
-
use std::io::Write;
|
78
|
-
|
79
|
-
let cache_dir = cache_dir();
|
80
|
-
let script_path = cache_dir.join("toast-notification.ps1");
|
81
|
-
|
82
|
-
// Check if cached file already exists and is valid
|
83
|
-
if script_path.exists() {
|
84
|
-
// Verify file has content (basic validation)
|
85
|
-
if let Ok(metadata) = std::fs::metadata(&script_path) {
|
86
|
-
if metadata.len() > 0 {
|
87
|
-
return Ok(script_path);
|
88
|
-
}
|
89
|
-
}
|
90
|
-
}
|
91
|
-
|
92
|
-
// File doesn't exist or is invalid, create it
|
93
|
-
let script_content = crate::ScriptAssets::get("toast-notification.ps1")
|
94
|
-
.ok_or("Embedded PowerShell script not found: toast-notification.ps1")?
|
95
|
-
.data;
|
96
|
-
|
97
|
-
// Ensure cache directory exists
|
98
|
-
std::fs::create_dir_all(&cache_dir)
|
99
|
-
.map_err(|e| format!("Failed to create cache directory: {}", e))?;
|
100
|
-
|
101
|
-
let mut file = std::fs::File::create(&script_path)
|
102
|
-
.map_err(|e| format!("Failed to create PowerShell script file: {}", e))?;
|
103
|
-
|
104
|
-
file.write_all(&script_content)
|
105
|
-
.map_err(|e| format!("Failed to write PowerShell script data: {}", e))?;
|
106
|
-
|
107
|
-
drop(file); // Ensure file is closed
|
108
|
-
|
109
|
-
Ok(script_path)
|
110
|
-
}
|
111
|
-
|
112
|
-
/// Open URL in browser with WSL2 support
|
113
|
-
pub async fn open_browser(url: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
114
|
-
if is_wsl2() {
|
115
|
-
// In WSL2, use PowerShell to open the browser
|
116
|
-
tokio::process::Command::new("powershell.exe")
|
117
|
-
.arg("-Command")
|
118
|
-
.arg(format!("Start-Process '{}'", url))
|
119
|
-
.spawn()?;
|
120
|
-
Ok(())
|
121
|
-
} else {
|
122
|
-
// Use the standard open crate for other platforms
|
123
|
-
open::that(url).map_err(|e| e.into())
|
124
|
-
}
|
125
|
-
}
|