this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix path decoding for hidden folders and deleted projects

Two issues fixed:

1. Hidden folder encoding: Claude encodes /. as -- (e.g., /.cache becomes
--cache). The decoder now handles this correctly.

2. Deleted project fallback: When the original project folder no longer
exists, filesystem probing fails. Now falls back to reading the cwd
from the session file itself, which always has the correct path.

This fixes projects like "pdf-to-markdown" being incorrectly decoded as
"markdown" when the folder /.cache/pdf-to-markdown was deleted.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

alice e97d9af3 5c8d284c

+78 -6
+78 -6
src/core/session-detector.ts
··· 1 1 import { join, dirname } from 'path'; 2 2 import { homedir } from 'os'; 3 - import { existsSync, readdirSync, statSync } from 'fs'; 3 + import { existsSync, readdirSync, statSync, readFileSync } from 'fs'; 4 4 import { createHash } from 'crypto'; 5 5 import { isFileProcessed } from './db'; 6 6 ··· 50 50 } 51 51 52 52 /** 53 + * Resolve a full encoded path by probing the filesystem. 54 + * Handles paths like "Users-sarah/.cache-pdf-to-markdown" where we need to find 55 + * where the base directory ends and the project name (with dashes) begins. 56 + * 57 + * If sessionFilePath is provided and filesystem probing fails, reads the session 58 + * file to extract the original cwd. 59 + */ 60 + function resolveFullPath(encodedPath: string, sessionFilePath?: string): string { 61 + const parts = encodedPath.split('-'); 62 + 63 + // Try interpretations from left to right 64 + // Build up the path, converting dashes to slashes until we find a directory 65 + // that contains the rest as a project folder 66 + for (let i = parts.length - 1; i >= 1; i--) { 67 + const baseParts = parts.slice(0, i); 68 + const projectParts = parts.slice(i); 69 + 70 + const basePath = '/' + baseParts.join('/'); 71 + const projectName = projectParts.join('-'); 72 + 73 + // Check if basePath exists and contains projectName 74 + const fullPath = `${basePath}/${projectName}`; 75 + if (existsSync(fullPath)) { 76 + return fullPath; 77 + } 78 + } 79 + 80 + // Filesystem probing failed - try reading cwd from session file 81 + if (sessionFilePath && existsSync(sessionFilePath)) { 82 + const cwd = extractCwdFromSessionFile(sessionFilePath); 83 + if (cwd) { 84 + return cwd; 85 + } 86 + } 87 + 88 + // Nothing found - just convert all dashes to slashes 89 + return '/' + encodedPath.replace(/-/g, '/'); 90 + } 91 + 92 + /** 93 + * Read the first few lines of a session file to extract the cwd. 94 + */ 95 + function extractCwdFromSessionFile(filePath: string): string | null { 96 + try { 97 + const content = readFileSync(filePath, 'utf-8'); 98 + const lines = content.split('\n').slice(0, 10); 99 + for (const line of lines) { 100 + if (!line.trim()) continue; 101 + try { 102 + const entry = JSON.parse(line); 103 + if (entry.cwd && typeof entry.cwd === 'string') { 104 + return entry.cwd; 105 + } 106 + } catch {} 107 + } 108 + } catch {} 109 + return null; 110 + } 111 + 112 + /** 53 113 * Try different interpretations of a dash-separated string by progressively 54 114 * replacing dashes with slashes from right to left. 55 115 * ··· 121 181 return name.replace(/^\d{4}-\d{2}-\d{2}-/, ''); 122 182 } 123 183 124 - export function decodeProjectFolder(folderName: string): { path: string; name: string } { 184 + export function decodeProjectFolder( 185 + folderName: string, 186 + sessionFilePath?: string 187 + ): { path: string; name: string } { 125 188 // Remove leading dash 126 189 const withoutLeading = folderName.slice(1); 127 190 ··· 142 205 decodedPath = resolveProjectPath(basePath, projectPart); 143 206 isTriesProject = true; 144 207 } else { 145 - // Fallback: just replace all dashes with slashes 146 - decodedPath = '/' + withoutLeading.replace(/-/g, '/'); 208 + // Fallback for paths outside src/a/ and src/tries/ 209 + // Handle hidden folders: -- encodes /. (e.g., /.cache, /.config) 210 + const withHiddenFolders = withoutLeading.replace(/--/g, '/.'); 211 + 212 + // Try to find where the base path ends and project name begins 213 + // by probing the filesystem progressively, with session file fallback 214 + decodedPath = resolveFullPath(withHiddenFolders, sessionFilePath); 147 215 } 148 216 149 217 // Special case: home directory should show as "~" ··· 213 281 const stat = statSync(folderPath); 214 282 if (!stat.isDirectory()) continue; 215 283 216 - const { path: projectPath, name: projectName } = decodeProjectFolder(folder); 217 - 218 284 // Find all .jsonl files in this project folder 219 285 const files = readdirSync(folderPath).filter((f) => f.endsWith('.jsonl')); 286 + if (files.length === 0) continue; 287 + 288 + // Use the first session file to help decode the project folder 289 + // (provides cwd fallback when the original directory no longer exists) 290 + const firstSessionPath = join(folderPath, files[0]); 291 + const { path: projectPath, name: projectName } = decodeProjectFolder(folder, firstSessionPath); 220 292 221 293 for (const file of files) { 222 294 const filePath = join(folderPath, file);