(
repoPath: string
)
| 43 | // Claude Code: ~/.claude/projects/<encoded-path>/<sessionId>.jsonl |
| 44 | // ------------------------------------------------------------------- |
| 45 | async function extractFromClaudeCode( |
| 46 | repoPath: string |
| 47 | ): Promise<ExtractedContext | null> { |
| 48 | const home = os.homedir(); |
| 49 | const claudeDir = path.join(home, ".claude", "projects"); |
| 50 | |
| 51 | if (!fs.existsSync(claudeDir)) return null; |
| 52 | |
| 53 | // Find the project folder matching this repo path |
| 54 | // Claude encodes paths by replacing / with - |
| 55 | const encodedPath = repoPath.replace(/\//g, "-"); |
| 56 | const projectDirs = fs.readdirSync(claudeDir); |
| 57 | const matchingDir = projectDirs.find((d) => encodedPath.endsWith(d) || d.endsWith(encodedPath.slice(1))); |
| 58 | |
| 59 | if (!matchingDir) return null; |
| 60 | |
| 61 | const projectPath = path.join(claudeDir, matchingDir); |
| 62 | |
| 63 | // 1. Try memory files first (most structured) |
| 64 | const memoryDir = path.join(projectPath, "memory"); |
| 65 | if (fs.existsSync(memoryDir)) { |
| 66 | const memoryResult = parseClaudeMemory(memoryDir); |
| 67 | if (memoryResult) return memoryResult; |
| 68 | } |
| 69 | |
| 70 | // 2. Parse the most recent session JSONL |
| 71 | const jsonlFiles = fs |
| 72 | .readdirSync(projectPath) |
| 73 | .filter((f) => f.endsWith(".jsonl")) |
| 74 | .map((f) => ({ |
| 75 | name: f, |
| 76 | mtime: fs.statSync(path.join(projectPath, f)).mtime.getTime(), |
| 77 | })) |
| 78 | .sort((a, b) => b.mtime - a.mtime); |
| 79 | |
| 80 | if (jsonlFiles.length === 0) return null; |
| 81 | |
| 82 | const latestSession = path.join(projectPath, jsonlFiles[0].name); |
| 83 | return await parseClaudeSession(latestSession); |
| 84 | } |
| 85 | |
| 86 | function parseClaudeMemory(memoryDir: string): ExtractedContext | null { |
| 87 | const files = fs.readdirSync(memoryDir).filter((f) => f.endsWith(".md")); |
nothing calls this directly
no test coverage detected