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
25 changes: 20 additions & 5 deletions src/helpers/session-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,33 @@ import { basename, join } from "node:path";
import { isWSL, toWindowsPath } from "./platform";

/**
* Encode a project root path into the directory name used by Claude/Cursor.
* In WSL, converts to Windows path first so the encoded name matches
* what Windows-side Claude/Cursor uses.
* 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.
*
* Examples:
* - `/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> {
let raw = projectRoot;
if (isWSL()) {
const winPath = await toWindowsPath(projectRoot);
if (winPath) {
return winPath.replaceAll("\\", "-").replaceAll("/", "-");
raw = winPath;
}
}
return projectRoot.replaceAll("\\", "-").replaceAll("/", "-");
return raw
.replaceAll("\\", "-")
.replaceAll("/", "-")
.replaceAll(":", "-")
.replaceAll(".", "-");
}

const RELEVANT_TYPES = new Set(["user", "assistant"]);
Expand Down
6 changes: 5 additions & 1 deletion tests/helpers/session-context-cursor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ describe("readCursorSession", () => {
// homedir() caching on Linux doesn't break the tests.
const uniqueId = `test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
const projectRoot = `/__archgate_cursor_test_${uniqueId}`;
const encodedProject = projectRoot.replaceAll("/", "-");
const encodedProject = projectRoot
.replaceAll("/", "-")
.replaceAll("\\", "-")
.replaceAll(":", "-")
.replaceAll(".", "-");
let transcriptsDir: string;

beforeEach(() => {
Expand Down
26 changes: 22 additions & 4 deletions tests/helpers/session-context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,31 @@ describe("encodeProjectPath", () => {
expect(await encodeProjectPath("/a//b")).toBe("-a--b");
});

test("replaces backslashes with dashes (Windows paths)", async () => {
test("replaces backslashes and colons with dashes (Windows paths)", async () => {
expect(await encodeProjectPath("C:\\Users\\user\\project")).toBe(
"C:-Users-user-project"
"C--Users-user-project"
);
});

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

test("replaces dots with dashes", async () => {
expect(await encodeProjectPath("/home/user/.config/project")).toBe(
"-home-user--config-project"
);
});

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

describe("readClaudeCodeSession", () => {
Expand All @@ -62,7 +76,11 @@ describe("readClaudeCodeSession", () => {
// homedir() caching on Linux doesn't break the tests.
const uniqueId = `test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
const projectRoot = `/__archgate_test_${uniqueId}`;
const encodedProject = projectRoot.replaceAll("/", "-");
const encodedProject = projectRoot
.replaceAll("/", "-")
.replaceAll("\\", "-")
.replaceAll(":", "-")
.replaceAll(".", "-");
let projectsDir: string;

beforeEach(() => {
Expand Down
Loading