diff --git a/packages/server/src/server/project-group-key.test.ts b/packages/server/src/server/project-group-key.test.ts index 6be6120e5..a7f79dcd2 100644 --- a/packages/server/src/server/project-group-key.test.ts +++ b/packages/server/src/server/project-group-key.test.ts @@ -29,6 +29,19 @@ describe("deriveProjectGroupKey", () => { ).toBe("remote:git.example.com/repo"); }); + test("accepts an SCP-style remote without a username", () => { + const rootPath = path.resolve("repo"); + + expect( + deriveProjectGroupKey({ + rootPath, + remoteUrl: "github.com:getpaseo/paseo.git", + worktreeRoot: rootPath, + mainRepoRoot: null, + }), + ).toBe("remote:github.com/getpaseo/paseo"); + }); + test("includes the selected path within a repository", () => { const worktreeRoot = path.resolve("repo"); const rootPath = path.join(worktreeRoot, "packages", "app"); diff --git a/packages/server/src/server/project-group-key.ts b/packages/server/src/server/project-group-key.ts index 81dbfa7db..7f554a178 100644 --- a/packages/server/src/server/project-group-key.ts +++ b/packages/server/src/server/project-group-key.ts @@ -38,7 +38,10 @@ function deriveRemoteProjectGroupKey(remoteUrl: string | null): string | null { let host: string | null = null; let remotePath: string | null = null; - const scpLike = trimmed.match(/^[^@]+@([^:]+):(.+)$/); + const scpLike = + !trimmed.includes("://") && !/^[A-Za-z]:[\\/]/.test(trimmed) + ? trimmed.match(/^(?:[^@/:]+@)?([^/:]+):(.+)$/) + : null; if (scpLike) { host = scpLike[1] ?? null; remotePath = scpLike[2] ?? null; diff --git a/packages/server/src/server/workspace-registry-bootstrap-legacy.ts b/packages/server/src/server/workspace-registry-bootstrap-legacy.ts index c3bf8736f..e1cea95ad 100644 --- a/packages/server/src/server/workspace-registry-bootstrap-legacy.ts +++ b/packages/server/src/server/workspace-registry-bootstrap-legacy.ts @@ -1,4 +1,4 @@ -import { basename, resolve } from "node:path"; +import { basename, isAbsolute, relative, resolve, sep } from "node:path"; import type { ProjectCheckoutLitePayload } from "@getpaseo/protocol/messages"; @@ -75,7 +75,20 @@ function deriveProjectRootPath(input: { cwd: string; checkout: ProjectCheckoutLitePayload; }): string { - return input.checkout.isGit && input.checkout.mainRepoRoot - ? input.checkout.mainRepoRoot - : input.cwd; + if (!input.checkout.isGit || !input.checkout.mainRepoRoot) return input.cwd; + const worktreeRoot = input.checkout.worktreeRoot + ? parseGitRevParsePath(input.checkout.worktreeRoot) + : null; + if (!worktreeRoot) return input.checkout.mainRepoRoot; + const selectedPath = relative(resolve(worktreeRoot), resolve(input.cwd)); + if ( + !selectedPath || + selectedPath === "." || + selectedPath === ".." || + selectedPath.startsWith(`..${sep}`) || + isAbsolute(selectedPath) + ) { + return input.checkout.mainRepoRoot; + } + return resolve(input.checkout.mainRepoRoot, selectedPath); } diff --git a/packages/server/src/server/workspace-registry-bootstrap.test.ts b/packages/server/src/server/workspace-registry-bootstrap.test.ts index 033fdc6dc..f4c3a1ca1 100644 --- a/packages/server/src/server/workspace-registry-bootstrap.test.ts +++ b/packages/server/src/server/workspace-registry-bootstrap.test.ts @@ -378,8 +378,8 @@ describe("bootstrapWorkspaceRegistries", () => { }); test("keeps legacy agents in different monorepo subprojects separate", async () => { - const appProject = path.join(GIT_PROJECT, "packages", "app"); - const serverProject = path.join(GIT_PROJECT, "packages", "server"); + const appProject = path.join(GIT_WORKTREE, "packages", "app"); + const serverProject = path.join(GIT_WORKTREE, "packages", "server"); mkdirSync(appProject, { recursive: true }); mkdirSync(serverProject, { recursive: true }); workspaceGitService = createNoopWorkspaceGitService({ @@ -388,9 +388,9 @@ describe("bootstrapWorkspaceRegistries", () => { isGit: true, currentBranch: "main", remoteUrl: "git@github.com:acme/legacy-project.git", - worktreeRoot: GIT_PROJECT, + worktreeRoot: GIT_WORKTREE, isPaseoOwnedWorktree: false, - mainRepoRoot: null, + mainRepoRoot: GIT_PROJECT, }), }); await agentStorage.initialize(); @@ -428,16 +428,22 @@ describe("bootstrapWorkspaceRegistries", () => { expect( (await projectRegistry.list()) - .map((project) => ({ projectId: project.projectId, displayName: project.displayName })) + .map((project) => ({ + projectId: project.projectId, + displayName: project.displayName, + rootPath: project.rootPath, + })) .sort((left, right) => left.projectId.localeCompare(right.projectId)), ).toEqual([ { projectId: "remote:github.com/acme/legacy-project#subdir:packages/app", displayName: "app", + rootPath: path.join(GIT_PROJECT, "packages", "app"), }, { projectId: "remote:github.com/acme/legacy-project#subdir:packages/server", displayName: "server", + rootPath: path.join(GIT_PROJECT, "packages", "server"), }, ]); expect(