gitspace 0.2.0-rc.20 → 0.2.0-rc.21
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/package.json +11 -6
- package/.claude/settings.local.json +0 -10
- package/.gitspace/bundle.json +0 -50
- package/.gitspace/events.json +0 -11
- package/.gitspace/processes.json +0 -23
- package/.gitspace/scripts/select/01-status.sh +0 -39
- package/.gitspace/scripts/setup/01-install-deps.sh +0 -12
- package/.gitspace/scripts/setup/02-typecheck.sh +0 -16
- package/AGENTS.md +0 -469
- package/CLAUDE.md +0 -1
- package/bun.lock +0 -794
- package/docs/CONNECTION.md +0 -623
- package/docs/GATEWAY-WORKER.md +0 -319
- package/docs/GETTING-STARTED.md +0 -448
- package/docs/GITSPACE-PLATFORM.md +0 -1819
- package/docs/INFRASTRUCTURE.md +0 -1347
- package/docs/PROTOCOL.md +0 -619
- package/docs/QUICKSTART.md +0 -183
- package/docs/RELAY.md +0 -327
- package/docs/REMOTE-DESIGN.md +0 -554
- package/docs/ROADMAP.md +0 -564
- package/docs/SITE_DOCS_FIGMA_MAKE.md +0 -1176
- package/docs/STACK-DESIGN.md +0 -588
- package/docs/UNIFIED_ARCHITECTURE.md +0 -138
- package/experiments/pty-benchmark.ts +0 -148
- package/experiments/pty-latency.ts +0 -100
- package/experiments/router/client.ts +0 -199
- package/experiments/router/protocol.ts +0 -74
- package/experiments/router/router.ts +0 -217
- package/experiments/router/session.ts +0 -180
- package/experiments/router/test.ts +0 -133
- package/experiments/socket-bandwidth.ts +0 -77
- package/homebrew/gitspace.rb +0 -45
- package/landing-page/ATTRIBUTIONS.md +0 -3
- package/landing-page/README.md +0 -11
- package/landing-page/bun.lock +0 -801
- package/landing-page/guidelines/Guidelines.md +0 -61
- package/landing-page/index.html +0 -37
- package/landing-page/package.json +0 -90
- package/landing-page/postcss.config.mjs +0 -15
- package/landing-page/public/_redirects +0 -1
- package/landing-page/public/favicon.png +0 -0
- package/landing-page/src/app/App.tsx +0 -53
- package/landing-page/src/app/components/figma/ImageWithFallback.tsx +0 -27
- package/landing-page/src/app/components/ui/accordion.tsx +0 -66
- package/landing-page/src/app/components/ui/alert-dialog.tsx +0 -157
- package/landing-page/src/app/components/ui/alert.tsx +0 -66
- package/landing-page/src/app/components/ui/aspect-ratio.tsx +0 -11
- package/landing-page/src/app/components/ui/avatar.tsx +0 -53
- package/landing-page/src/app/components/ui/badge.tsx +0 -46
- package/landing-page/src/app/components/ui/breadcrumb.tsx +0 -109
- package/landing-page/src/app/components/ui/button.tsx +0 -57
- package/landing-page/src/app/components/ui/calendar.tsx +0 -75
- package/landing-page/src/app/components/ui/card.tsx +0 -92
- package/landing-page/src/app/components/ui/carousel.tsx +0 -241
- package/landing-page/src/app/components/ui/chart.tsx +0 -353
- package/landing-page/src/app/components/ui/checkbox.tsx +0 -32
- package/landing-page/src/app/components/ui/collapsible.tsx +0 -33
- package/landing-page/src/app/components/ui/command.tsx +0 -177
- package/landing-page/src/app/components/ui/context-menu.tsx +0 -252
- package/landing-page/src/app/components/ui/dialog.tsx +0 -135
- package/landing-page/src/app/components/ui/drawer.tsx +0 -132
- package/landing-page/src/app/components/ui/dropdown-menu.tsx +0 -257
- package/landing-page/src/app/components/ui/form.tsx +0 -168
- package/landing-page/src/app/components/ui/hover-card.tsx +0 -44
- package/landing-page/src/app/components/ui/input-otp.tsx +0 -77
- package/landing-page/src/app/components/ui/input.tsx +0 -21
- package/landing-page/src/app/components/ui/label.tsx +0 -24
- package/landing-page/src/app/components/ui/menubar.tsx +0 -276
- package/landing-page/src/app/components/ui/navigation-menu.tsx +0 -168
- package/landing-page/src/app/components/ui/pagination.tsx +0 -127
- package/landing-page/src/app/components/ui/popover.tsx +0 -48
- package/landing-page/src/app/components/ui/progress.tsx +0 -31
- package/landing-page/src/app/components/ui/radio-group.tsx +0 -45
- package/landing-page/src/app/components/ui/resizable.tsx +0 -56
- package/landing-page/src/app/components/ui/scroll-area.tsx +0 -58
- package/landing-page/src/app/components/ui/select.tsx +0 -189
- package/landing-page/src/app/components/ui/separator.tsx +0 -28
- package/landing-page/src/app/components/ui/sheet.tsx +0 -139
- package/landing-page/src/app/components/ui/sidebar.tsx +0 -726
- package/landing-page/src/app/components/ui/skeleton.tsx +0 -13
- package/landing-page/src/app/components/ui/slider.tsx +0 -63
- package/landing-page/src/app/components/ui/sonner.tsx +0 -25
- package/landing-page/src/app/components/ui/switch.tsx +0 -31
- package/landing-page/src/app/components/ui/table.tsx +0 -116
- package/landing-page/src/app/components/ui/tabs.tsx +0 -66
- package/landing-page/src/app/components/ui/textarea.tsx +0 -18
- package/landing-page/src/app/components/ui/toggle-group.tsx +0 -73
- package/landing-page/src/app/components/ui/toggle.tsx +0 -47
- package/landing-page/src/app/components/ui/tooltip.tsx +0 -61
- package/landing-page/src/app/components/ui/use-mobile.ts +0 -21
- package/landing-page/src/app/components/ui/utils.ts +0 -6
- package/landing-page/src/components/docs/DocsContent.tsx +0 -801
- package/landing-page/src/components/docs/DocsSidebar.tsx +0 -90
- package/landing-page/src/components/landing/CTA.tsx +0 -59
- package/landing-page/src/components/landing/Comparison.tsx +0 -84
- package/landing-page/src/components/landing/FaultyTerminal.tsx +0 -424
- package/landing-page/src/components/landing/Features.tsx +0 -201
- package/landing-page/src/components/landing/Hero.tsx +0 -142
- package/landing-page/src/components/landing/Pricing.tsx +0 -140
- package/landing-page/src/components/landing/Roadmap.tsx +0 -86
- package/landing-page/src/components/landing/Security.tsx +0 -81
- package/landing-page/src/components/landing/TerminalWindow.tsx +0 -27
- package/landing-page/src/components/landing/UseCases.tsx +0 -55
- package/landing-page/src/components/landing/Workflow.tsx +0 -101
- package/landing-page/src/components/layout/DashboardNavbar.tsx +0 -37
- package/landing-page/src/components/layout/Footer.tsx +0 -55
- package/landing-page/src/components/layout/LandingNavbar.tsx +0 -82
- package/landing-page/src/components/ui/badge.tsx +0 -39
- package/landing-page/src/components/ui/breadcrumb.tsx +0 -115
- package/landing-page/src/components/ui/button.tsx +0 -57
- package/landing-page/src/components/ui/card.tsx +0 -79
- package/landing-page/src/components/ui/mock-terminal.tsx +0 -68
- package/landing-page/src/components/ui/separator.tsx +0 -28
- package/landing-page/src/lib/utils.ts +0 -6
- package/landing-page/src/main.tsx +0 -10
- package/landing-page/src/pages/Dashboard.tsx +0 -133
- package/landing-page/src/pages/DocsPage.tsx +0 -79
- package/landing-page/src/pages/LandingPage.tsx +0 -31
- package/landing-page/src/pages/TerminalView.tsx +0 -106
- package/landing-page/src/styles/fonts.css +0 -0
- package/landing-page/src/styles/index.css +0 -3
- package/landing-page/src/styles/tailwind.css +0 -4
- package/landing-page/src/styles/theme.css +0 -181
- package/landing-page/vite.config.ts +0 -19
- package/scripts/GHOSTTY_TAB_BUG.md +0 -106
- package/scripts/build.ts +0 -298
- package/scripts/migrate-secrets.ts +0 -77
- package/scripts/release.ts +0 -140
- package/scripts/sample-events.ts +0 -263
- package/scripts/test-tabs-minimal.ts +0 -68
- package/scripts/test-tabs-workaround.ts +0 -95
- package/scripts/test-tabs.ts +0 -171
- package/src/__tests__/test-utils.ts +0 -298
- package/src/app/input/__tests__/sessionCommands.test.ts +0 -40
- package/src/app/input/sessionCommands.ts +0 -94
- package/src/app/session/__tests__/useAttachController.test.ts +0 -229
- package/src/app/session/createSessionBackend.bun.ts +0 -76
- package/src/app/session/createSessionBackend.web.ts +0 -104
- package/src/app/session/types.ts +0 -16
- package/src/app/session/useAttachController.ts +0 -220
- package/src/app/session/useProcessActions.ts +0 -201
- package/src/app/session/useSessionClient.ts +0 -35
- package/src/app/session/useWorkspaceDeleteFlow.ts +0 -170
- package/src/app.tui.tsx +0 -2929
- package/src/app.web.tsx +0 -1454
- package/src/commands/__tests__/connect-key.test.ts +0 -10
- package/src/commands/__tests__/events.test.ts +0 -201
- package/src/commands/__tests__/notifications.test.ts +0 -349
- package/src/commands/__tests__/process.test.ts +0 -251
- package/src/commands/__tests__/serve-messages.test.ts +0 -190
- package/src/commands/__tests__/serve-process-hosting.test.ts +0 -63
- package/src/commands/access.ts +0 -298
- package/src/commands/add.ts +0 -455
- package/src/commands/auth.ts +0 -369
- package/src/commands/bundle.ts +0 -232
- package/src/commands/config.ts +0 -242
- package/src/commands/connect-key.ts +0 -1
- package/src/commands/connect.ts +0 -576
- package/src/commands/directory.ts +0 -16
- package/src/commands/events.ts +0 -157
- package/src/commands/host.ts +0 -566
- package/src/commands/identity.ts +0 -184
- package/src/commands/linear.ts +0 -717
- package/src/commands/list.ts +0 -181
- package/src/commands/migrate.ts +0 -52
- package/src/commands/notifications.ts +0 -351
- package/src/commands/process.ts +0 -104
- package/src/commands/relay.ts +0 -315
- package/src/commands/remove.ts +0 -279
- package/src/commands/review.ts +0 -787
- package/src/commands/serve.ts +0 -1946
- package/src/commands/share.ts +0 -451
- package/src/commands/status.ts +0 -125
- package/src/commands/switch.ts +0 -361
- package/src/commands/tmux.ts +0 -317
- package/src/components/DPad.web.tsx +0 -343
- package/src/components/DiffViewer.web.tsx +0 -1192
- package/src/components/Events.tsx +0 -137
- package/src/components/Events.tui.tsx +0 -129
- package/src/components/Events.web.tsx +0 -386
- package/src/components/FloatingControls.web.tsx +0 -112
- package/src/components/FloatingJogWheel.web.tsx +0 -240
- package/src/components/Flow.tsx +0 -458
- package/src/components/Flow.tui.tsx +0 -343
- package/src/components/Flow.web.tsx +0 -442
- package/src/components/Inbox.tsx +0 -448
- package/src/components/Inbox.tui.tsx +0 -262
- package/src/components/Inbox.web.tsx +0 -329
- package/src/components/MachineList.tsx +0 -187
- package/src/components/MachineList.tui.tsx +0 -161
- package/src/components/MachineList.web.tsx +0 -210
- package/src/components/NumPad.web.tsx +0 -270
- package/src/components/ProjectList.tsx +0 -175
- package/src/components/ProjectList.tui.tsx +0 -109
- package/src/components/ProjectList.web.tsx +0 -143
- package/src/components/ProjectOnboardingStep.ts +0 -23
- package/src/components/ProjectOnboardingStep.tui.tsx +0 -88
- package/src/components/ProjectOnboardingStep.web.tsx +0 -59
- package/src/components/RemoteMachineScreen.tui.tsx +0 -690
- package/src/components/ScriptTerminal.tui.tsx +0 -160
- package/src/components/ScriptTerminal.web.tsx +0 -89
- package/src/components/SessionTerminal.tui.tsx +0 -406
- package/src/components/SessionTerminal.web.tsx +0 -467
- package/src/components/SpacesBrowser.tsx +0 -540
- package/src/components/SpacesBrowser.tui.tsx +0 -258
- package/src/components/SpacesBrowser.web.tsx +0 -332
- package/src/components/TerminalControls.web.tsx +0 -464
- package/src/components/ThreadPanel.web.tsx +0 -798
- package/src/components/__tests__/SpacesBrowser.test.ts +0 -541
- package/src/components/__tests__/SpacesBrowser.tui.test.tsx +0 -249
- package/src/components/__tests__/script-terminal-buffer.tui.test.ts +0 -72
- package/src/components/index.ts +0 -105
- package/src/components/review-decision-colors.ts +0 -11
- package/src/components/script-terminal-buffer.tui.ts +0 -37
- package/src/components/session-terminal-page-navigation.ts +0 -48
- package/src/components/terminal-bracketed-paste.tui.test.ts +0 -43
- package/src/components/terminal-bracketed-paste.tui.ts +0 -46
- package/src/core/__tests__/access.test.ts +0 -240
- package/src/core/__tests__/bundle-refresh.test.ts +0 -567
- package/src/core/__tests__/bundle.test.ts +0 -209
- package/src/core/__tests__/github-review.test.ts +0 -781
- package/src/core/__tests__/project-lifecycle.test.ts +0 -137
- package/src/core/__tests__/workspace-lifecycle.test.ts +0 -159
- package/src/core/__tests__/workspace.test.ts +0 -149
- package/src/core/access.ts +0 -277
- package/src/core/bundle-refresh.ts +0 -1064
- package/src/core/bundle.ts +0 -326
- package/src/core/config.ts +0 -405
- package/src/core/git.ts +0 -768
- package/src/core/github-review.ts +0 -761
- package/src/core/github.ts +0 -151
- package/src/core/identity.ts +0 -631
- package/src/core/linear.ts +0 -403
- package/src/core/preferences-service.ts +0 -17
- package/src/core/project-catalog.ts +0 -52
- package/src/core/project-lifecycle.ts +0 -163
- package/src/core/review-executor.ts +0 -316
- package/src/core/review.ts +0 -407
- package/src/core/secret-runtime.ts +0 -167
- package/src/core/shell.ts +0 -117
- package/src/core/trusted-relays.ts +0 -315
- package/src/core/workspace-lifecycle.ts +0 -216
- package/src/core/workspace.ts +0 -363
- package/src/hooks/__tests__/useLocalSession.tui.test.ts +0 -557
- package/src/hooks/index.ts +0 -8
- package/src/hooks/index.tui.ts +0 -32
- package/src/hooks/useDaemonStatus.tui.ts +0 -174
- package/src/hooks/useLocalSession.tui.ts +0 -395
- package/src/hooks/useRelayConnection.web.ts +0 -54
- package/src/hooks/useRemoteMachines.tui.ts +0 -166
- package/src/hooks/useRemoteTerminal.tui.ts +0 -22
- package/src/hooks/useReview.web.ts +0 -248
- package/src/hooks/useTerminal.web.ts +0 -36
- package/src/hooks/useUserActivity.ts +0 -61
- package/src/hooks/useVisualViewport.web.ts +0 -104
- package/src/index.ts +0 -1376
- package/src/lib/events/__tests__/collector-filter.test.ts +0 -105
- package/src/lib/events/__tests__/store-query.test.ts +0 -103
- package/src/lib/events/collector.ts +0 -494
- package/src/lib/events/filters.ts +0 -26
- package/src/lib/events/index.ts +0 -11
- package/src/lib/events/indexer.ts +0 -14
- package/src/lib/events/paths.ts +0 -69
- package/src/lib/events/reader.ts +0 -212
- package/src/lib/events/store.ts +0 -141
- package/src/lib/invite.web.ts +0 -58
- package/src/lib/preferences-service.web.ts +0 -41
- package/src/lib/processes/__tests__/config.test.ts +0 -83
- package/src/lib/processes/__tests__/names.test.ts +0 -125
- package/src/lib/processes/__tests__/schema.test.ts +0 -208
- package/src/lib/processes/__tests__/watchdog.test.ts +0 -210
- package/src/lib/processes/autostart.ts +0 -16
- package/src/lib/processes/config.ts +0 -187
- package/src/lib/processes/control.ts +0 -53
- package/src/lib/processes/editor.ts +0 -32
- package/src/lib/processes/events-config.ts +0 -37
- package/src/lib/processes/index.ts +0 -14
- package/src/lib/processes/instances.ts +0 -20
- package/src/lib/processes/manager.ts +0 -131
- package/src/lib/processes/names.ts +0 -71
- package/src/lib/processes/registry.ts +0 -26
- package/src/lib/processes/runner.ts +0 -211
- package/src/lib/processes/scheduler.ts +0 -17
- package/src/lib/processes/schema.ts +0 -74
- package/src/lib/processes/session-list.ts +0 -15
- package/src/lib/processes/state.ts +0 -82
- package/src/lib/processes/watchdog.test.ts +0 -79
- package/src/lib/processes/watchdog.ts +0 -106
- package/src/lib/remote-session/__tests__/protocol.test.ts +0 -291
- package/src/lib/remote-session/index.ts +0 -7
- package/src/lib/remote-session/protocol.ts +0 -443
- package/src/lib/remote-session/session-handler.ts +0 -1298
- package/src/lib/remote-session/workspace-scanner.ts +0 -161
- package/src/lib/sonner.web.ts +0 -1
- package/src/lib/storage/identity-store.web.ts +0 -94
- package/src/lib/tmux-lite/README.md +0 -81
- package/src/lib/tmux-lite/cli.ts +0 -855
- package/src/lib/tmux-lite/crypto/__tests__/helpers/handshake-runner.ts +0 -349
- package/src/lib/tmux-lite/crypto/__tests__/helpers/mock-relay.ts +0 -291
- package/src/lib/tmux-lite/crypto/__tests__/helpers/test-identities.ts +0 -142
- package/src/lib/tmux-lite/crypto/__tests__/integration/authorization.integration.test.ts +0 -339
- package/src/lib/tmux-lite/crypto/__tests__/integration/e2e-communication.integration.test.ts +0 -477
- package/src/lib/tmux-lite/crypto/__tests__/integration/error-handling.integration.test.ts +0 -499
- package/src/lib/tmux-lite/crypto/__tests__/integration/handshake.integration.test.ts +0 -371
- package/src/lib/tmux-lite/crypto/__tests__/integration/security.integration.test.ts +0 -573
- package/src/lib/tmux-lite/crypto/access-control.test.ts +0 -512
- package/src/lib/tmux-lite/crypto/access-control.ts +0 -320
- package/src/lib/tmux-lite/crypto/frames.test.ts +0 -262
- package/src/lib/tmux-lite/crypto/frames.ts +0 -141
- package/src/lib/tmux-lite/crypto/handshake.ts +0 -894
- package/src/lib/tmux-lite/crypto/identity.test.ts +0 -220
- package/src/lib/tmux-lite/crypto/identity.ts +0 -286
- package/src/lib/tmux-lite/crypto/index.ts +0 -51
- package/src/lib/tmux-lite/crypto/invites.test.ts +0 -381
- package/src/lib/tmux-lite/crypto/invites.ts +0 -215
- package/src/lib/tmux-lite/crypto/keyexchange.ts +0 -435
- package/src/lib/tmux-lite/crypto/keys.test.ts +0 -58
- package/src/lib/tmux-lite/crypto/keys.ts +0 -47
- package/src/lib/tmux-lite/crypto/secretbox.test.ts +0 -169
- package/src/lib/tmux-lite/crypto/secretbox.ts +0 -124
- package/src/lib/tmux-lite/handshake-handler.ts +0 -451
- package/src/lib/tmux-lite/process-run.integration.test.ts +0 -266
- package/src/lib/tmux-lite/protocol.test.ts +0 -307
- package/src/lib/tmux-lite/protocol.ts +0 -291
- package/src/lib/tmux-lite/relay-client.ts +0 -506
- package/src/lib/tmux-lite/server-lifecycle.test.ts +0 -212
- package/src/lib/tmux-lite/server.ts +0 -1412
- package/src/lib/tmux-lite/shell-integration.sh +0 -37
- package/src/lib/tmux-lite/terminal-queries.test.ts +0 -54
- package/src/lib/tmux-lite/terminal-queries.ts +0 -49
- package/src/notifications/__tests__/useNotifications.test.ts +0 -739
- package/src/notifications/index.ts +0 -32
- package/src/notifications/policy.test.ts +0 -424
- package/src/notifications/policy.ts +0 -139
- package/src/notifications/types.ts +0 -82
- package/src/notifications/useNotifications.ts +0 -350
- package/src/pages/ReviewPage.web.tsx +0 -511
- package/src/preferences/index.ts +0 -1
- package/src/preferences/types.ts +0 -9
- package/src/relay/__tests__/e2e-flow.test.ts +0 -1284
- package/src/relay/__tests__/helpers/auth.ts +0 -354
- package/src/relay/__tests__/helpers/ports.ts +0 -51
- package/src/relay/__tests__/protocol-validation.test.ts +0 -265
- package/src/relay/authorization.ts +0 -303
- package/src/relay/embedded-assets.generated.d.ts +0 -15
- package/src/relay/identity.ts +0 -352
- package/src/relay/index.ts +0 -57
- package/src/relay/pipes.test.ts +0 -427
- package/src/relay/pipes.ts +0 -195
- package/src/relay/protocol.ts +0 -804
- package/src/relay/registries.test.ts +0 -437
- package/src/relay/registries.ts +0 -593
- package/src/relay/server.test.ts +0 -1323
- package/src/relay/server.ts +0 -1128
- package/src/relay/signing.ts +0 -238
- package/src/relay/types.ts +0 -69
- package/src/relay-client/__tests__/machine-directory-client.test.ts +0 -152
- package/src/relay-client/__tests__/useMachineDirectory.test.ts +0 -172
- package/src/relay-client/adapters/browser.ts +0 -27
- package/src/relay-client/adapters/node.ts +0 -29
- package/src/relay-client/index.ts +0 -33
- package/src/relay-client/machine-directory-client.ts +0 -244
- package/src/relay-client/useMachineDirectory.ts +0 -175
- package/src/serve/client-session-manager.ts +0 -635
- package/src/serve/daemon.ts +0 -497
- package/src/serve/pty-session.ts +0 -236
- package/src/serve/types.ts +0 -174
- package/src/session/__tests__/backend-manager.test.ts +0 -101
- package/src/session/__tests__/local-session-backend.test.ts +0 -1129
- package/src/session/__tests__/reducer.test.ts +0 -80
- package/src/session/__tests__/remote-session-backend.test.ts +0 -995
- package/src/session/__tests__/session-name.test.ts +0 -35
- package/src/session/__tests__/useBundleRefreshAttachFlow.test.ts +0 -431
- package/src/session/__tests__/useRemoteSessionClient.test.ts +0 -424
- package/src/session/__tests__/workspace-shell-hooks.integration.test.ts +0 -268
- package/src/session/__tests__/workspace-shell-hooks.test.ts +0 -24
- package/src/session/adapters/browser-remote.ts +0 -101
- package/src/session/adapters/node-remote.ts +0 -135
- package/src/session/backend-key.ts +0 -5
- package/src/session/backend-manager.ts +0 -80
- package/src/session/backend.ts +0 -93
- package/src/session/backends/local-session-backend.ts +0 -1119
- package/src/session/backends/remote-session-backend.ts +0 -1378
- package/src/session/crypto/__tests__/web-terminal.test.ts +0 -1158
- package/src/session/crypto/frames.web.ts +0 -205
- package/src/session/crypto/handshake.web.ts +0 -396
- package/src/session/crypto/identity.web.ts +0 -133
- package/src/session/crypto/keyexchange.web.ts +0 -246
- package/src/session/crypto/relay-signing.web.ts +0 -53
- package/src/session/events.ts +0 -38
- package/src/session/index.ts +0 -116
- package/src/session/reducer.ts +0 -274
- package/src/session/selectors.ts +0 -28
- package/src/session/session-name.ts +0 -50
- package/src/session/types.ts +0 -101
- package/src/session/useBundleRefreshAttachFlow.ts +0 -608
- package/src/session/useRemoteSessionClient.ts +0 -424
- package/src/session/useSessionEngine.ts +0 -432
- package/src/session/workspace-shell-hooks.ts +0 -35
- package/src/tui/__tests__/input-text.test.ts +0 -24
- package/src/tui/__tests__/local-terminal-sync.test.ts +0 -82
- package/src/tui/__tests__/session-terminal-page-navigation.test.ts +0 -94
- package/src/tui/app.tsx +0 -2
- package/src/tui/index.ts +0 -18
- package/src/tui/input-text.ts +0 -38
- package/src/tui/local-terminal-sync.ts +0 -41
- package/src/types/bundle-refresh.ts +0 -42
- package/src/types/bundle.ts +0 -130
- package/src/types/config.ts +0 -287
- package/src/types/errors.ts +0 -292
- package/src/types/events.ts +0 -91
- package/src/types/identity.ts +0 -284
- package/src/types/processes.ts +0 -45
- package/src/types/review.ts +0 -349
- package/src/types/script-phase.ts +0 -3
- package/src/types/workspace-fuzzy.ts +0 -49
- package/src/types/workspace.ts +0 -151
- package/src/utils/__tests__/onboarding.test.ts +0 -358
- package/src/utils/__tests__/run-scripts.test.ts +0 -535
- package/src/utils/__tests__/run-workspace-scripts.test.ts +0 -406
- package/src/utils/__tests__/workspace-setup.integration.test.ts +0 -633
- package/src/utils/__tests__/workspace-state.test.ts +0 -78
- package/src/utils/bun-socket-writer.ts +0 -80
- package/src/utils/clipboard.ts +0 -53
- package/src/utils/deps.test.ts +0 -31
- package/src/utils/deps.ts +0 -145
- package/src/utils/device.web.ts +0 -163
- package/src/utils/fuzzy-match.ts +0 -125
- package/src/utils/hostnames.ts +0 -43
- package/src/utils/hunk-header.ts +0 -17
- package/src/utils/id.ts +0 -9
- package/src/utils/logger.ts +0 -127
- package/src/utils/markdown.ts +0 -254
- package/src/utils/normalize-env-key.ts +0 -13
- package/src/utils/onboarding.ts +0 -279
- package/src/utils/prompts.ts +0 -176
- package/src/utils/run-commands.ts +0 -112
- package/src/utils/run-scripts.ts +0 -337
- package/src/utils/run-workspace-scripts.ts +0 -355
- package/src/utils/sanitize.test.ts +0 -149
- package/src/utils/sanitize.ts +0 -162
- package/src/utils/secrets.ts +0 -836
- package/src/utils/shell-escape.ts +0 -40
- package/src/utils/utf8.ts +0 -79
- package/src/utils/workspace-id.ts +0 -55
- package/src/utils/workspace-state.ts +0 -427
- package/src/version.generated.d.ts +0 -2
- package/todo-security.md +0 -92
- package/tsconfig.json +0 -29
- package/web/README.md +0 -73
- package/web/bun.lock +0 -675
- package/web/eslint.config.js +0 -23
- package/web/index.css +0 -249
- package/web/index.html +0 -16
- package/web/main.tsx +0 -10
- package/web/package.json +0 -39
- package/web/public/vite.svg +0 -1
- package/web/tsconfig.app.json +0 -35
- package/web/tsconfig.json +0 -7
- package/web/tsconfig.node.json +0 -26
- package/web/vite.config.ts +0 -39
- package/worker/bun.lock +0 -237
- package/worker/package.json +0 -22
- package/worker/schema.sql +0 -96
- package/worker/src/handlers/auth.ts +0 -451
- package/worker/src/handlers/subdomains.ts +0 -376
- package/worker/src/handlers/user.ts +0 -98
- package/worker/src/index.ts +0 -70
- package/worker/src/middleware/auth.ts +0 -152
- package/worker/src/services/cloudflare.ts +0 -609
- package/worker/src/types.ts +0 -96
- package/worker/tsconfig.json +0 -15
- package/worker/wrangler.toml +0 -26
package/src/core/git.ts
DELETED
|
@@ -1,768 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Git and worktree operations
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { exec } from 'child_process';
|
|
6
|
-
import { promisify } from 'util';
|
|
7
|
-
import { existsSync } from 'fs';
|
|
8
|
-
import { SpacesError } from '../types/errors.js';
|
|
9
|
-
import { logger } from '../utils/logger.js';
|
|
10
|
-
import { escapeShellArg } from '../utils/shell-escape.js';
|
|
11
|
-
import type { WorktreeInfo } from '../types/workspace.js';
|
|
12
|
-
import type { ReviewChangedFile } from '../types/review.js';
|
|
13
|
-
|
|
14
|
-
const execAsync = promisify(exec);
|
|
15
|
-
|
|
16
|
-
const BASE_REF_CACHE_TTL_MS = 60_000;
|
|
17
|
-
const BASE_REF_CACHE_MAX_ENTRIES = 256;
|
|
18
|
-
const comparableBaseRefCache = new Map<string, { baseRef: string; cachedAt: number }>();
|
|
19
|
-
const comparableBaseRefInflight = new Map<string, Promise<string>>();
|
|
20
|
-
|
|
21
|
-
function comparableBaseRefKey(workspacePath: string, baseBranch: string): string {
|
|
22
|
-
return `${workspacePath}::${baseBranch}`;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function pruneComparableBaseRefCache(now: number): void {
|
|
26
|
-
for (const [key, value] of comparableBaseRefCache.entries()) {
|
|
27
|
-
if (now - value.cachedAt >= BASE_REF_CACHE_TTL_MS) {
|
|
28
|
-
comparableBaseRefCache.delete(key);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const overflow = comparableBaseRefCache.size - BASE_REF_CACHE_MAX_ENTRIES;
|
|
33
|
-
if (overflow <= 0) {
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const sortedByAge = [...comparableBaseRefCache.entries()].sort(
|
|
38
|
-
(a, b) => a[1].cachedAt - b[1].cachedAt
|
|
39
|
-
);
|
|
40
|
-
for (let index = 0; index < overflow; index++) {
|
|
41
|
-
const entry = sortedByAge[index];
|
|
42
|
-
if (!entry) {
|
|
43
|
-
break;
|
|
44
|
-
}
|
|
45
|
-
comparableBaseRefCache.delete(entry[0]);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Get the default branch of a repository
|
|
51
|
-
*/
|
|
52
|
-
export async function getDefaultBranch(repoPath: string): Promise<string> {
|
|
53
|
-
try {
|
|
54
|
-
const { stdout } = await execAsync(
|
|
55
|
-
'git symbolic-ref refs/remotes/origin/HEAD',
|
|
56
|
-
{ cwd: repoPath }
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
// Extract branch name from refs/remotes/origin/main -> main
|
|
60
|
-
const branch = stdout.trim().replace('refs/remotes/origin/', '');
|
|
61
|
-
return branch;
|
|
62
|
-
} catch (error) {
|
|
63
|
-
// Fallback to 'main' if we can't determine
|
|
64
|
-
logger.debug(`Could not determine default branch, using 'main': ${error}`);
|
|
65
|
-
return 'main';
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Check if a branch exists on remote
|
|
71
|
-
*/
|
|
72
|
-
export async function checkRemoteBranch(
|
|
73
|
-
repoPath: string,
|
|
74
|
-
branchName: string
|
|
75
|
-
): Promise<boolean> {
|
|
76
|
-
try {
|
|
77
|
-
await execAsync(
|
|
78
|
-
`git ls-remote --exit-code --heads origin ${escapeShellArg(branchName)}`,
|
|
79
|
-
{ cwd: repoPath }
|
|
80
|
-
);
|
|
81
|
-
return true;
|
|
82
|
-
} catch {
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* List all remote branches from origin
|
|
89
|
-
* @param repoPath Path to the git repository
|
|
90
|
-
* @returns Array of branch names (without origin/ prefix)
|
|
91
|
-
*/
|
|
92
|
-
export async function listRemoteBranches(repoPath: string): Promise<string[]> {
|
|
93
|
-
try {
|
|
94
|
-
// Fetch latest from remote
|
|
95
|
-
await execAsync('git fetch --all --prune', { cwd: repoPath });
|
|
96
|
-
|
|
97
|
-
const { stdout } = await execAsync(
|
|
98
|
-
'git ls-remote --heads origin',
|
|
99
|
-
{ cwd: repoPath }
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
// Parse output: "hash\trefs/heads/branch-name"
|
|
103
|
-
const branches = stdout
|
|
104
|
-
.trim()
|
|
105
|
-
.split('\n')
|
|
106
|
-
.filter((line) => line.length > 0)
|
|
107
|
-
.map((line) => {
|
|
108
|
-
// Extract branch name from "hash\trefs/heads/branch-name"
|
|
109
|
-
const match = line.match(/refs\/heads\/(.+)$/);
|
|
110
|
-
return match ? match[1] : null;
|
|
111
|
-
})
|
|
112
|
-
.filter((branch): branch is string => branch !== null);
|
|
113
|
-
|
|
114
|
-
return branches;
|
|
115
|
-
} catch (error) {
|
|
116
|
-
throw new SpacesError(
|
|
117
|
-
`Failed to list remote branches: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
118
|
-
'SYSTEM_ERROR',
|
|
119
|
-
2
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Check if a branch exists locally
|
|
126
|
-
*/
|
|
127
|
-
export async function checkLocalBranch(
|
|
128
|
-
repoPath: string,
|
|
129
|
-
branchName: string
|
|
130
|
-
): Promise<boolean> {
|
|
131
|
-
try {
|
|
132
|
-
await execAsync(
|
|
133
|
-
`git show-ref --verify --quiet ${escapeShellArg(`refs/heads/${branchName}`)}`,
|
|
134
|
-
{ cwd: repoPath }
|
|
135
|
-
);
|
|
136
|
-
return true;
|
|
137
|
-
} catch {
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Options for creating a worktree
|
|
144
|
-
*/
|
|
145
|
-
export interface CreateWorktreeOptions {
|
|
146
|
-
/** Whether the branch exists on the remote */
|
|
147
|
-
existsRemotely?: boolean;
|
|
148
|
-
/** Callback to report progress (for TUI loading indicator) */
|
|
149
|
-
onProgress?: (message: string) => void;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Create a git worktree
|
|
154
|
-
*/
|
|
155
|
-
export async function createWorktree(
|
|
156
|
-
repoPath: string,
|
|
157
|
-
workspacePath: string,
|
|
158
|
-
branchName: string,
|
|
159
|
-
baseBranch: string,
|
|
160
|
-
existsRemotelyOrOptions?: boolean | CreateWorktreeOptions
|
|
161
|
-
): Promise<void> {
|
|
162
|
-
// Handle both old signature (boolean) and new signature (options object)
|
|
163
|
-
const options: CreateWorktreeOptions = typeof existsRemotelyOrOptions === 'boolean'
|
|
164
|
-
? { existsRemotely: existsRemotelyOrOptions }
|
|
165
|
-
: existsRemotelyOrOptions ?? {};
|
|
166
|
-
const { existsRemotely, onProgress } = options;
|
|
167
|
-
|
|
168
|
-
try {
|
|
169
|
-
// Check if worktree path already exists
|
|
170
|
-
if (existsSync(workspacePath)) {
|
|
171
|
-
throw new SpacesError(
|
|
172
|
-
`Worktree path already exists: ${workspacePath}`,
|
|
173
|
-
'USER_ERROR',
|
|
174
|
-
1
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Fetch latest changes
|
|
179
|
-
onProgress?.('Fetching latest changes...');
|
|
180
|
-
logger.debug('Fetching latest changes...');
|
|
181
|
-
await execAsync('git fetch --all --prune', { cwd: repoPath });
|
|
182
|
-
|
|
183
|
-
// Pull latest base branch
|
|
184
|
-
onProgress?.(`Updating ${baseBranch}...`);
|
|
185
|
-
try {
|
|
186
|
-
await execAsync(`git pull --ff-only origin ${escapeShellArg(baseBranch)}`, {
|
|
187
|
-
cwd: repoPath,
|
|
188
|
-
});
|
|
189
|
-
} catch (error) {
|
|
190
|
-
logger.debug(`Could not fast-forward ${baseBranch}: ${error}`);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Determine how to create the worktree
|
|
194
|
-
if (existsRemotely) {
|
|
195
|
-
// Branch exists on remote, create from remote branch
|
|
196
|
-
onProgress?.(`Creating worktree from ${branchName}...`);
|
|
197
|
-
logger.debug(`Creating worktree from remote branch: ${branchName}`);
|
|
198
|
-
await execAsync(
|
|
199
|
-
`git worktree add ${escapeShellArg(workspacePath)} -b ${escapeShellArg(branchName)} ${escapeShellArg(`origin/${branchName}`)}`,
|
|
200
|
-
{ cwd: repoPath }
|
|
201
|
-
);
|
|
202
|
-
} else if (await checkLocalBranch(repoPath, branchName)) {
|
|
203
|
-
// Branch exists locally, attach worktree to it
|
|
204
|
-
onProgress?.(`Creating worktree from ${branchName}...`);
|
|
205
|
-
logger.debug(`Creating worktree from local branch: ${branchName}`);
|
|
206
|
-
await execAsync(`git worktree add ${escapeShellArg(workspacePath)} ${escapeShellArg(branchName)}`, {
|
|
207
|
-
cwd: repoPath,
|
|
208
|
-
});
|
|
209
|
-
} else {
|
|
210
|
-
// Branch doesn't exist, create new from base
|
|
211
|
-
// Use --no-track to avoid setting upstream to baseBranch (user should push -u to set correct upstream)
|
|
212
|
-
onProgress?.(`Creating new branch ${branchName}...`);
|
|
213
|
-
logger.debug(`Creating new branch from ${baseBranch}: ${branchName}`);
|
|
214
|
-
await execAsync(
|
|
215
|
-
`git worktree add -b ${escapeShellArg(branchName)} ${escapeShellArg(workspacePath)} ${escapeShellArg(`origin/${baseBranch}`)} --no-track`,
|
|
216
|
-
{ cwd: repoPath }
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
} catch (error) {
|
|
220
|
-
if (error instanceof SpacesError) {
|
|
221
|
-
throw error;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
throw new SpacesError(
|
|
225
|
-
`Failed to create worktree: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
226
|
-
'SYSTEM_ERROR',
|
|
227
|
-
2
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Remove a git worktree
|
|
234
|
-
*/
|
|
235
|
-
export async function removeWorktree(
|
|
236
|
-
repoPath: string,
|
|
237
|
-
workspacePath: string,
|
|
238
|
-
force: boolean = false
|
|
239
|
-
): Promise<void> {
|
|
240
|
-
try {
|
|
241
|
-
const forceFlag = force ? '--force' : '';
|
|
242
|
-
await execAsync(`git worktree remove ${escapeShellArg(workspacePath)} ${forceFlag}`, {
|
|
243
|
-
cwd: repoPath,
|
|
244
|
-
});
|
|
245
|
-
} catch (error) {
|
|
246
|
-
throw new SpacesError(
|
|
247
|
-
`Failed to remove worktree: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
248
|
-
'SYSTEM_ERROR',
|
|
249
|
-
2
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Get information about a worktree
|
|
256
|
-
*/
|
|
257
|
-
export async function getWorktreeInfo(workspacePath: string): Promise<WorktreeInfo | null> {
|
|
258
|
-
try {
|
|
259
|
-
if (!existsSync(workspacePath)) {
|
|
260
|
-
return null;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// Get current branch
|
|
264
|
-
const { stdout: branchOutput } = await execAsync(
|
|
265
|
-
'git rev-parse --abbrev-ref HEAD',
|
|
266
|
-
{ cwd: workspacePath }
|
|
267
|
-
);
|
|
268
|
-
const branch = branchOutput.trim();
|
|
269
|
-
|
|
270
|
-
// Get commits ahead/behind
|
|
271
|
-
let ahead = 0;
|
|
272
|
-
let behind = 0;
|
|
273
|
-
try {
|
|
274
|
-
const { stdout: revListOutput } = await execAsync(
|
|
275
|
-
`git rev-list --left-right --count ${escapeShellArg(`HEAD...origin/${branch}`)}`,
|
|
276
|
-
{ cwd: workspacePath }
|
|
277
|
-
);
|
|
278
|
-
const [aheadStr, behindStr] = revListOutput.trim().split('\t');
|
|
279
|
-
ahead = parseInt(aheadStr, 10) || 0;
|
|
280
|
-
behind = parseInt(behindStr, 10) || 0;
|
|
281
|
-
} catch {
|
|
282
|
-
// Branch may not have remote tracking
|
|
283
|
-
logger.debug(`Could not get ahead/behind for ${branch}`);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Get uncommitted changes count
|
|
287
|
-
const { stdout: statusOutput } = await execAsync('git status --porcelain', {
|
|
288
|
-
cwd: workspacePath,
|
|
289
|
-
});
|
|
290
|
-
const uncommittedChanges = statusOutput
|
|
291
|
-
.trim()
|
|
292
|
-
.split('\n')
|
|
293
|
-
.filter((line) => line.length > 0).length;
|
|
294
|
-
|
|
295
|
-
// Get last commit info
|
|
296
|
-
const { stdout: lastCommitMsg } = await execAsync(
|
|
297
|
-
'git log -1 --pretty=format:"%s"',
|
|
298
|
-
{ cwd: workspacePath }
|
|
299
|
-
);
|
|
300
|
-
const { stdout: lastCommitDate } = await execAsync(
|
|
301
|
-
'git log -1 --pretty=format:"%aI"',
|
|
302
|
-
{ cwd: workspacePath }
|
|
303
|
-
);
|
|
304
|
-
|
|
305
|
-
const name = workspacePath.split('/').pop() || '';
|
|
306
|
-
|
|
307
|
-
return {
|
|
308
|
-
name,
|
|
309
|
-
path: workspacePath,
|
|
310
|
-
branch,
|
|
311
|
-
ahead,
|
|
312
|
-
behind,
|
|
313
|
-
uncommittedChanges,
|
|
314
|
-
lastCommit: lastCommitMsg.trim() || 'No commits',
|
|
315
|
-
lastCommitDate: lastCommitDate ? new Date(lastCommitDate) : new Date(),
|
|
316
|
-
};
|
|
317
|
-
} catch (error) {
|
|
318
|
-
logger.debug(`Failed to get worktree info for ${workspacePath}: ${error}`);
|
|
319
|
-
return null;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* Delete a local branch
|
|
325
|
-
*/
|
|
326
|
-
export async function deleteLocalBranch(
|
|
327
|
-
repoPath: string,
|
|
328
|
-
branchName: string,
|
|
329
|
-
force: boolean = false
|
|
330
|
-
): Promise<void> {
|
|
331
|
-
try {
|
|
332
|
-
const forceFlag = force ? '-D' : '-d';
|
|
333
|
-
await execAsync(`git branch ${forceFlag} ${escapeShellArg(branchName)}`, {
|
|
334
|
-
cwd: repoPath,
|
|
335
|
-
});
|
|
336
|
-
} catch (error) {
|
|
337
|
-
throw new SpacesError(
|
|
338
|
-
`Failed to delete branch ${branchName}: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
339
|
-
'SYSTEM_ERROR',
|
|
340
|
-
2
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Get the unified diff between a workspace branch and its base branch.
|
|
347
|
-
*
|
|
348
|
-
* Uses three-dot diff (`base...HEAD`) so we only see changes introduced by
|
|
349
|
-
* the workspace branch, not diverging changes on the base since the fork.
|
|
350
|
-
*
|
|
351
|
-
* @param workspacePath Path to the git worktree
|
|
352
|
-
* @param baseBranch The branch to diff against (e.g. 'main')
|
|
353
|
-
* @returns Unified diff string (empty string if no changes)
|
|
354
|
-
*/
|
|
355
|
-
export async function getWorkspaceDiff(
|
|
356
|
-
workspacePath: string,
|
|
357
|
-
baseBranch: string
|
|
358
|
-
): Promise<{ diff: string; baseBranch: string; headBranch: string }> {
|
|
359
|
-
try {
|
|
360
|
-
// Get the current HEAD branch name
|
|
361
|
-
const { stdout: headOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
|
|
362
|
-
cwd: workspacePath,
|
|
363
|
-
});
|
|
364
|
-
const headBranch = headOutput.trim();
|
|
365
|
-
|
|
366
|
-
const mergeBase = await resolveComparableBaseRef(workspacePath, baseBranch);
|
|
367
|
-
|
|
368
|
-
// Three-dot diff: changes on HEAD that are not in the selected base ref
|
|
369
|
-
const { stdout: diffOutput } = await execAsync(
|
|
370
|
-
`git diff ${escapeShellArg(mergeBase)}...HEAD`,
|
|
371
|
-
{ cwd: workspacePath, maxBuffer: 50 * 1024 * 1024 } // 50MB max
|
|
372
|
-
);
|
|
373
|
-
|
|
374
|
-
return {
|
|
375
|
-
diff: diffOutput,
|
|
376
|
-
baseBranch,
|
|
377
|
-
headBranch,
|
|
378
|
-
};
|
|
379
|
-
} catch (error) {
|
|
380
|
-
throw new SpacesError(
|
|
381
|
-
`Failed to get workspace diff: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
382
|
-
'SYSTEM_ERROR',
|
|
383
|
-
2
|
|
384
|
-
);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* List changed files in a workspace branch vs base branch.
|
|
390
|
-
*/
|
|
391
|
-
export async function getWorkspaceChangedFiles(
|
|
392
|
-
workspacePath: string,
|
|
393
|
-
baseBranch: string
|
|
394
|
-
): Promise<{ files: ReviewChangedFile[]; baseBranch: string; headBranch: string }> {
|
|
395
|
-
try {
|
|
396
|
-
const { stdout: headOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
|
|
397
|
-
cwd: workspacePath,
|
|
398
|
-
});
|
|
399
|
-
const headBranch = headOutput.trim();
|
|
400
|
-
|
|
401
|
-
const baseRef = await resolveComparableBaseRef(workspacePath, baseBranch);
|
|
402
|
-
const { stdout } = await execAsync(
|
|
403
|
-
`git diff --name-status -z --find-renames -M ${escapeShellArg(baseRef)}...HEAD`,
|
|
404
|
-
{ cwd: workspacePath, maxBuffer: 10 * 1024 * 1024 }
|
|
405
|
-
);
|
|
406
|
-
|
|
407
|
-
return {
|
|
408
|
-
files: parseChangedFilesFromNameStatusZ(stdout),
|
|
409
|
-
baseBranch,
|
|
410
|
-
headBranch,
|
|
411
|
-
};
|
|
412
|
-
} catch (error) {
|
|
413
|
-
throw new SpacesError(
|
|
414
|
-
`Failed to list changed files: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
415
|
-
'SYSTEM_ERROR',
|
|
416
|
-
2
|
|
417
|
-
);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* Get diff for a single file path in workspace vs base branch.
|
|
423
|
-
*/
|
|
424
|
-
export async function getWorkspaceFileDiff(
|
|
425
|
-
workspacePath: string,
|
|
426
|
-
baseBranch: string,
|
|
427
|
-
filePath: string,
|
|
428
|
-
prevFilePath?: string
|
|
429
|
-
): Promise<{ diff: string; baseBranch: string; headBranch: string }> {
|
|
430
|
-
try {
|
|
431
|
-
const { stdout: headOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
|
|
432
|
-
cwd: workspacePath,
|
|
433
|
-
});
|
|
434
|
-
const headBranch = headOutput.trim();
|
|
435
|
-
|
|
436
|
-
const baseRef = await resolveComparableBaseRef(workspacePath, baseBranch);
|
|
437
|
-
const pathSpec = prevFilePath
|
|
438
|
-
? `${escapeShellArg(prevFilePath)} ${escapeShellArg(filePath)}`
|
|
439
|
-
: escapeShellArg(filePath);
|
|
440
|
-
|
|
441
|
-
const { stdout } = await execAsync(
|
|
442
|
-
`git diff --patch --no-color --find-renames -M ${escapeShellArg(baseRef)}...HEAD -- ${pathSpec}`,
|
|
443
|
-
{ cwd: workspacePath, maxBuffer: 20 * 1024 * 1024 }
|
|
444
|
-
);
|
|
445
|
-
|
|
446
|
-
return {
|
|
447
|
-
diff: stdout,
|
|
448
|
-
baseBranch,
|
|
449
|
-
headBranch,
|
|
450
|
-
};
|
|
451
|
-
} catch (error) {
|
|
452
|
-
throw new SpacesError(
|
|
453
|
-
`Failed to get file diff: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
454
|
-
'SYSTEM_ERROR',
|
|
455
|
-
2
|
|
456
|
-
);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Read a file's old/new versions for the workspace diff base.
|
|
462
|
-
*
|
|
463
|
-
* Uses merge-base(<base-ref>, HEAD) for the old side so content aligns with
|
|
464
|
-
* three-dot diff semantics. For renames, pass prevFilePath for the old side.
|
|
465
|
-
*/
|
|
466
|
-
export async function getWorkspaceFileVersions(
|
|
467
|
-
workspacePath: string,
|
|
468
|
-
baseBranch: string,
|
|
469
|
-
filePath: string,
|
|
470
|
-
prevFilePath?: string
|
|
471
|
-
): Promise<{ oldContents: string | null; newContents: string | null }> {
|
|
472
|
-
try {
|
|
473
|
-
const baseRef = await resolveComparableBaseRef(workspacePath, baseBranch);
|
|
474
|
-
const mergeBaseCommit = await resolveMergeBaseCommit(workspacePath, baseRef);
|
|
475
|
-
|
|
476
|
-
const oldPath = prevFilePath ?? filePath;
|
|
477
|
-
const [oldContents, newContents] = await Promise.all([
|
|
478
|
-
readFileAtRevision(workspacePath, mergeBaseCommit, oldPath),
|
|
479
|
-
readFileAtRevision(workspacePath, 'HEAD', filePath),
|
|
480
|
-
]);
|
|
481
|
-
|
|
482
|
-
return { oldContents, newContents };
|
|
483
|
-
} catch (error) {
|
|
484
|
-
throw new SpacesError(
|
|
485
|
-
`Failed to read workspace file versions: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
486
|
-
'SYSTEM_ERROR',
|
|
487
|
-
2
|
|
488
|
-
);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
/**
|
|
493
|
-
* Read a file context range (or the full file when range omitted) on both
|
|
494
|
-
* old/base and new/head sides for on-demand diff expansion.
|
|
495
|
-
*/
|
|
496
|
-
export async function getWorkspaceFileContextRange(
|
|
497
|
-
workspacePath: string,
|
|
498
|
-
baseBranch: string,
|
|
499
|
-
filePath: string,
|
|
500
|
-
prevFilePath?: string,
|
|
501
|
-
range?: {
|
|
502
|
-
oldStart?: number;
|
|
503
|
-
oldEnd?: number;
|
|
504
|
-
newStart?: number;
|
|
505
|
-
newEnd?: number;
|
|
506
|
-
}
|
|
507
|
-
): Promise<{
|
|
508
|
-
oldStart: number;
|
|
509
|
-
oldLines: string[];
|
|
510
|
-
oldTotal: number;
|
|
511
|
-
newStart: number;
|
|
512
|
-
newLines: string[];
|
|
513
|
-
newTotal: number;
|
|
514
|
-
}> {
|
|
515
|
-
try {
|
|
516
|
-
const baseRef = await resolveComparableBaseRef(workspacePath, baseBranch);
|
|
517
|
-
const mergeBaseCommit = await resolveMergeBaseCommit(workspacePath, baseRef);
|
|
518
|
-
|
|
519
|
-
const oldPath = prevFilePath ?? filePath;
|
|
520
|
-
const [oldContents, newContents] = await Promise.all([
|
|
521
|
-
readFileAtRevision(workspacePath, mergeBaseCommit, oldPath),
|
|
522
|
-
readFileAtRevision(workspacePath, 'HEAD', filePath),
|
|
523
|
-
]);
|
|
524
|
-
|
|
525
|
-
const oldAllLines = splitFileIntoLines(oldContents ?? '');
|
|
526
|
-
const newAllLines = splitFileIntoLines(newContents ?? '');
|
|
527
|
-
|
|
528
|
-
const oldTotal = oldAllLines.length;
|
|
529
|
-
const newTotal = newAllLines.length;
|
|
530
|
-
|
|
531
|
-
const [oldStart, oldEnd] = normalizeRange(oldTotal, range?.oldStart, range?.oldEnd);
|
|
532
|
-
const [newStart, newEnd] = normalizeRange(newTotal, range?.newStart, range?.newEnd);
|
|
533
|
-
|
|
534
|
-
const oldLines = oldTotal === 0 ? [] : oldAllLines.slice(oldStart - 1, oldEnd);
|
|
535
|
-
const newLines = newTotal === 0 ? [] : newAllLines.slice(newStart - 1, newEnd);
|
|
536
|
-
|
|
537
|
-
return {
|
|
538
|
-
oldStart,
|
|
539
|
-
oldLines,
|
|
540
|
-
oldTotal,
|
|
541
|
-
newStart,
|
|
542
|
-
newLines,
|
|
543
|
-
newTotal,
|
|
544
|
-
};
|
|
545
|
-
} catch (error) {
|
|
546
|
-
throw new SpacesError(
|
|
547
|
-
`Failed to read file context range: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
548
|
-
'SYSTEM_ERROR',
|
|
549
|
-
2
|
|
550
|
-
);
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
async function resolveComparableBaseRef(
|
|
555
|
-
workspacePath: string,
|
|
556
|
-
baseBranch: string
|
|
557
|
-
): Promise<string> {
|
|
558
|
-
const now = Date.now();
|
|
559
|
-
pruneComparableBaseRefCache(now);
|
|
560
|
-
|
|
561
|
-
const cacheKey = comparableBaseRefKey(workspacePath, baseBranch);
|
|
562
|
-
const cached = comparableBaseRefCache.get(cacheKey);
|
|
563
|
-
if (cached && now - cached.cachedAt < BASE_REF_CACHE_TTL_MS) {
|
|
564
|
-
return cached.baseRef;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
const inFlight = comparableBaseRefInflight.get(cacheKey);
|
|
568
|
-
if (inFlight) {
|
|
569
|
-
return inFlight;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
const resolvePromise = (async () => {
|
|
573
|
-
// Best effort fetch. Keep short timeout to avoid hanging review requests.
|
|
574
|
-
try {
|
|
575
|
-
await execAsync(`git fetch origin ${escapeShellArg(baseBranch)} --quiet`, {
|
|
576
|
-
cwd: workspacePath,
|
|
577
|
-
timeout: 8000,
|
|
578
|
-
});
|
|
579
|
-
} catch {
|
|
580
|
-
logger.debug(`Could not fetch origin/${baseBranch}, using local refs`);
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
const candidates = [`origin/${baseBranch}`, baseBranch];
|
|
584
|
-
for (const candidate of candidates) {
|
|
585
|
-
try {
|
|
586
|
-
await execAsync(`git rev-parse --verify ${escapeShellArg(candidate)}`, {
|
|
587
|
-
cwd: workspacePath,
|
|
588
|
-
timeout: 5000,
|
|
589
|
-
});
|
|
590
|
-
|
|
591
|
-
comparableBaseRefCache.set(cacheKey, {
|
|
592
|
-
baseRef: candidate,
|
|
593
|
-
cachedAt: Date.now(),
|
|
594
|
-
});
|
|
595
|
-
pruneComparableBaseRefCache(Date.now());
|
|
596
|
-
return candidate;
|
|
597
|
-
} catch {
|
|
598
|
-
// Try next candidate
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
throw new Error(
|
|
603
|
-
`Cannot resolve base ref for "${baseBranch}". Fetch the branch or update project base branch config.`
|
|
604
|
-
);
|
|
605
|
-
})();
|
|
606
|
-
|
|
607
|
-
comparableBaseRefInflight.set(cacheKey, resolvePromise);
|
|
608
|
-
try {
|
|
609
|
-
return await resolvePromise;
|
|
610
|
-
} finally {
|
|
611
|
-
comparableBaseRefInflight.delete(cacheKey);
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
async function resolveMergeBaseCommit(
|
|
616
|
-
workspacePath: string,
|
|
617
|
-
baseRef: string
|
|
618
|
-
): Promise<string> {
|
|
619
|
-
const { stdout } = await execAsync(
|
|
620
|
-
`git merge-base ${escapeShellArg(baseRef)} HEAD`,
|
|
621
|
-
{ cwd: workspacePath, timeout: 5000 }
|
|
622
|
-
);
|
|
623
|
-
|
|
624
|
-
const mergeBaseCommit = stdout.trim();
|
|
625
|
-
if (!mergeBaseCommit) {
|
|
626
|
-
throw new Error(`Could not determine merge base for ${baseRef} and HEAD`);
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
return mergeBaseCommit;
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
async function readFileAtRevision(
|
|
633
|
-
workspacePath: string,
|
|
634
|
-
revision: string,
|
|
635
|
-
filePath: string
|
|
636
|
-
): Promise<string | null> {
|
|
637
|
-
const spec = `${revision}:${filePath}`;
|
|
638
|
-
|
|
639
|
-
try {
|
|
640
|
-
const { stdout } = await execAsync(`git show ${escapeShellArg(spec)}`, {
|
|
641
|
-
cwd: workspacePath,
|
|
642
|
-
maxBuffer: 20 * 1024 * 1024,
|
|
643
|
-
});
|
|
644
|
-
return stdout;
|
|
645
|
-
} catch (error) {
|
|
646
|
-
const err = error as { message?: string; stderr?: string };
|
|
647
|
-
const message = `${err.message ?? ''}\n${err.stderr ?? ''}`;
|
|
648
|
-
|
|
649
|
-
// Missing file at ref is expected for new/deleted/renamed files.
|
|
650
|
-
if (
|
|
651
|
-
/exists on disk, but not in/i.test(message) ||
|
|
652
|
-
/path .* does not exist in/i.test(message) ||
|
|
653
|
-
/path .* not in/i.test(message) ||
|
|
654
|
-
/invalid object name/i.test(message)
|
|
655
|
-
) {
|
|
656
|
-
return null;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
throw error;
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
function normalizeRange(total: number, start?: number, end?: number): [number, number] {
|
|
664
|
-
if (total <= 0) {
|
|
665
|
-
return [1, 0];
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
const normalizedStart =
|
|
669
|
-
typeof start === 'number' && Number.isFinite(start) ? clamp(Math.floor(start), 1, total) : 1;
|
|
670
|
-
const normalizedEnd =
|
|
671
|
-
typeof end === 'number' && Number.isFinite(end)
|
|
672
|
-
? clamp(Math.floor(end), normalizedStart, total)
|
|
673
|
-
: total;
|
|
674
|
-
|
|
675
|
-
return [normalizedStart, normalizedEnd];
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
function clamp(value: number, min: number, max: number): number {
|
|
679
|
-
if (value < min) return min;
|
|
680
|
-
if (value > max) return max;
|
|
681
|
-
return value;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
function splitFileIntoLines(contents: string): string[] {
|
|
685
|
-
if (contents.length === 0) {
|
|
686
|
-
return [];
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
const lines = contents.match(/[^\n]*\n|[^\n]+$/g);
|
|
690
|
-
return lines ?? [];
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
function parseChangedFilesFromNameStatusZ(stdout: string): ReviewChangedFile[] {
|
|
694
|
-
if (!stdout) {
|
|
695
|
-
return [];
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
const tokens = stdout.split('\0').filter((token) => token.length > 0);
|
|
699
|
-
const files: ReviewChangedFile[] = [];
|
|
700
|
-
|
|
701
|
-
let index = 0;
|
|
702
|
-
while (index < tokens.length) {
|
|
703
|
-
const statusToken = tokens[index++];
|
|
704
|
-
if (!statusToken) {
|
|
705
|
-
continue;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
const statusCode = statusToken[0];
|
|
709
|
-
if (statusCode === 'R' || statusCode === 'C') {
|
|
710
|
-
const prev = tokens[index++];
|
|
711
|
-
const next = tokens[index++];
|
|
712
|
-
if (!prev || !next) {
|
|
713
|
-
continue;
|
|
714
|
-
}
|
|
715
|
-
files.push({
|
|
716
|
-
filePath: next,
|
|
717
|
-
prevFilePath: prev,
|
|
718
|
-
changeType: statusCode === 'R' ? 'renamed' : 'copied',
|
|
719
|
-
});
|
|
720
|
-
continue;
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
const path = tokens[index++];
|
|
724
|
-
if (!path) {
|
|
725
|
-
continue;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
const changeType: ReviewChangedFile['changeType'] =
|
|
729
|
-
statusCode === 'A'
|
|
730
|
-
? 'new'
|
|
731
|
-
: statusCode === 'D'
|
|
732
|
-
? 'deleted'
|
|
733
|
-
: 'modified';
|
|
734
|
-
|
|
735
|
-
files.push({ filePath: path, changeType });
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
return files;
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
/**
|
|
742
|
-
* List all worktrees in a repository
|
|
743
|
-
*/
|
|
744
|
-
export async function listWorktrees(repoPath: string): Promise<string[]> {
|
|
745
|
-
try {
|
|
746
|
-
const { stdout } = await execAsync('git worktree list --porcelain', {
|
|
747
|
-
cwd: repoPath,
|
|
748
|
-
});
|
|
749
|
-
|
|
750
|
-
const worktrees: string[] = [];
|
|
751
|
-
const lines = stdout.trim().split('\n');
|
|
752
|
-
|
|
753
|
-
for (const line of lines) {
|
|
754
|
-
if (line.startsWith('worktree ')) {
|
|
755
|
-
const path = line.replace('worktree ', '');
|
|
756
|
-
worktrees.push(path);
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
return worktrees;
|
|
761
|
-
} catch (error) {
|
|
762
|
-
throw new SpacesError(
|
|
763
|
-
`Failed to list worktrees: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
764
|
-
'SYSTEM_ERROR',
|
|
765
|
-
2
|
|
766
|
-
);
|
|
767
|
-
}
|
|
768
|
-
}
|