Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions src/helpers/session-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,46 @@ import { readdirSync, statSync } from "node:fs";
import { homedir } from "node:os";
import { basename, join } from "node:path";

import type { EditorTarget } from "./init-project";
import { isWSL, toWindowsPath } from "./platform";

/**
* Encode a project root path into the directory name used by Claude/Cursor
* for storing session files under `~/.claude/projects/` or `~/.cursor/projects/`.
*
* Replaces path separators (`\`, `/`), drive-letter colons (`:`), and dots (`.`)
* with dashes (`-`) to match the encoding Claude Code and Cursor use internally.
* Replaces path separators (`\`, `/`) and dots (`.`) with dashes (`-`).
* Drive-letter colons (`:`) are handled per-tool: Claude Code replaces them
* with dashes while Cursor strips them entirely.
*
* Examples:
* Examples (target = "claude", the default):
* - `/home/user/project` → `-home-user-project`
* - `C:\Users\user\project` → `C--Users-user-project`
* - `E:\foo\.claude\worktrees\x` → `E--foo--claude-worktrees-x`
*
* Examples (target = "cursor"):
* - `/home/user/project` → `-home-user-project`
* - `C:\Users\user\project` → `C-Users-user-project`
* - `E:\foo\.claude\worktrees\x` → `E-foo--claude-worktrees-x`
*
* In WSL, converts to the Windows path first so the encoded name matches
* what the Windows-side editor uses.
*/
export async function encodeProjectPath(projectRoot: string): Promise<string> {
export async function encodeProjectPath(
projectRoot: string,
target?: EditorTarget
): Promise<string> {
let raw = projectRoot;
if (isWSL()) {
const winPath = await toWindowsPath(projectRoot);
if (winPath) {
raw = winPath;
}
}
const colonReplacement = target === "cursor" ? "" : "-";
return raw
.replaceAll("\\", "-")
.replaceAll("/", "-")
.replaceAll(":", "-")
.replaceAll(":", colonReplacement)
.replaceAll(".", "-");
}

Expand Down Expand Up @@ -177,7 +188,10 @@ export async function readCursorSession(
options?: ReadCursorSessionOptions
): Promise<CursorSessionResult> {
const limit = options?.maxEntries ?? 200;
const encodedPath = await encodeProjectPath(projectRoot ?? process.cwd());
const encodedPath = await encodeProjectPath(
projectRoot ?? process.cwd(),
"cursor"
);
const transcriptsDir = join(
homedir(),
".cursor",
Expand Down
2 changes: 1 addition & 1 deletion tests/helpers/session-context-cursor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe("readCursorSession", () => {
const encodedProject = projectRoot
.replaceAll("/", "-")
.replaceAll("\\", "-")
.replaceAll(":", "-")
.replaceAll(":", "")
.replaceAll(".", "-");
let transcriptsDir: string;

Expand Down
28 changes: 28 additions & 0 deletions tests/helpers/session-context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,34 @@ describe("encodeProjectPath", () => {
)
).toBe("E--archgate-cli--claude-worktrees-fancy-prancing-sedgewick");
});

test("cursor target strips colons instead of replacing with dashes", async () => {
expect(await encodeProjectPath("C:\\Users\\user\\project", "cursor")).toBe(
"C-Users-user-project"
);
});

test("cursor target handles mixed slashes", async () => {
expect(await encodeProjectPath("C:\\Users/user\\project", "cursor")).toBe(
"C-Users-user-project"
);
});

test("cursor target encodes Windows worktree path", async () => {
expect(
await encodeProjectPath(
"E:\\archgate\\cli\\.claude\\worktrees\\fancy-prancing-sedgewick",
"cursor"
)
).toBe("E-archgate-cli--claude-worktrees-fancy-prancing-sedgewick");
});

test("cursor target produces same result as default for Unix paths", async () => {
const unixPath = "/home/user/project";
expect(await encodeProjectPath(unixPath, "cursor")).toBe(
await encodeProjectPath(unixPath)
);
});
});

describe("readClaudeCodeSession", () => {
Expand Down
Loading