diff --git a/__tests__/stats.test.ts b/__tests__/stats.test.ts index 34599df..a9d22fc 100644 --- a/__tests__/stats.test.ts +++ b/__tests__/stats.test.ts @@ -157,9 +157,9 @@ describe("computeStats", () => { it("resolves session slug paths to real project paths", () => { const stats = computeStats(richData); - // -home-user-project-a should resolve to /home/user/project-a + // -home-user-project-a should resolve to /home/user/project-a, displayed as basename const projectNames = stats.topProjectStats.map((p) => p.name); - expect(projectNames).toContain("/home/user/project-a"); + expect(projectNames).toContain("project-a"); }); it("counts tool calls from session messages", () => { diff --git a/src/lib/stats.ts b/src/lib/stats.ts index d778c22..3be54e1 100644 --- a/src/lib/stats.ts +++ b/src/lib/stats.ts @@ -195,11 +195,13 @@ export function computeStats(data: ParsedData): ComputedStats { const projectSet = new Set(); const branchSet = new Set(); const projectActivity: Record = {}; - // Build slug→realPath from history so we can resolve session folder names + // Build slug→realPath from history so we can resolve session folder names. + // Always replace \, / and : so Windows drive letters (C:\ or C:/) slug correctly + // regardless of whether history.jsonl stores backslashes or forward slashes. + const pathToSlug = (p: string) => p.replaceAll("\\", "-").replaceAll("/", "-").replaceAll(":", "-"); for (const entry of history) { if (entry.project) { - const slug = entry.project.replaceAll("/", "-"); - slugToPath[slug] = entry.project; + slugToPath[pathToSlug(entry.project)] = entry.project; projectSet.add(entry.project); } } @@ -229,11 +231,15 @@ export function computeStats(data: ParsedData): ComputedStats { } } const topProjectStats = Array.from(projectSet) - .map((name) => ({ - name, - messages: projectActivity[name] || 0, - tokens: projectTokens[name] || 0, - sessions: projectSessions[name] || 0, + .map((fullPath) => ({ + name: + fullPath + .split(fullPath.includes("\\") ? "\\" : "/") + .filter(Boolean) + .at(-1) ?? fullPath, + messages: projectActivity[fullPath] || 0, + tokens: projectTokens[fullPath] || 0, + sessions: projectSessions[fullPath] || 0, })) .sort((a, b) => b.messages - a.messages) .slice(0, 5); @@ -434,15 +440,15 @@ function extractUsername(history: HistoryEntry[], sessions: { messages: { cwd?: for (const session of sessions) { for (const msg of session.messages) { if (msg.cwd) { - // /home/username/... or /Users/username/... - const match = msg.cwd.match(/^\/(?:home|Users)\/([^/]+)/); + // /home/username/... or /Users/username/... or C:\Users\username\... + const match = msg.cwd.match(/^(?:\/(?:home|Users)\/|[A-Za-z]:\\[Uu]sers\\)([^/\\]+)/); if (match) return match[1]; } } } for (const entry of history) { if (entry.project) { - const match = entry.project.match(/^\/(?:home|Users)\/([^/]+)/); + const match = entry.project.match(/^(?:\/(?:home|Users)\/|[A-Za-z]:\\[Uu]sers\\)([^/\\]+)/); if (match) return match[1]; } }