Skip to content

Commit 1bdc75e

Browse files
committed
Enhance project directory handling by integrating extractProjectDirectory and clearProjectDirectoryCache functions. Adjust git route handlers to utilize the new directory extraction logic for improved project path resolution.
1 parent c5e3bd0 commit 1bdc75e

File tree

5 files changed

+182
-131
lines changed

5 files changed

+182
-131
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "claude-code-ui",
3-
"version": "1.1.1",
3+
"version": "1.1.3",
44
"description": "A web-based UI for Claude Code CLI",
55
"main": "server/index.js",
66
"scripts": {

server/index.js

Lines changed: 11 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const os = require('os');
3030
const pty = require('node-pty');
3131
const fetch = require('node-fetch');
3232

33-
const { getProjects, getSessions, getSessionMessages, renameProject, deleteSession, deleteProject, addProjectManually } = require('./projects');
33+
const { getProjects, getSessions, getSessionMessages, renameProject, deleteSession, deleteProject, addProjectManually, extractProjectDirectory, clearProjectDirectoryCache } = require('./projects');
3434
const { spawnClaude, abortClaudeSession } = require('./claude-cli');
3535
const gitRoutes = require('./routes/git');
3636

@@ -76,6 +76,9 @@ function setupProjectsWatcher() {
7676
debounceTimer = setTimeout(async () => {
7777
try {
7878

79+
// Clear project directory cache when files change
80+
clearProjectDirectoryCache();
81+
7982
// Get updated projects list
8083
const updatedProjects = await getProjects();
8184

@@ -372,47 +375,15 @@ app.get('/api/projects/:projectName/files', async (req, res) => {
372375
try {
373376

374377
const fs = require('fs').promises;
375-
const projectPath = path.join(process.env.HOME, '.claude', 'projects', req.params.projectName);
376-
377-
// Try different methods to get the actual project path
378-
let actualPath = projectPath;
379378

379+
// Use extractProjectDirectory to get the actual project path
380+
let actualPath;
380381
try {
381-
// First try to read metadata.json
382-
const metadataPath = path.join(projectPath, 'metadata.json');
383-
const metadata = JSON.parse(await fs.readFile(metadataPath, 'utf8'));
384-
actualPath = metadata.path || metadata.cwd;
385-
} catch (e) {
386-
// Fallback: try to find the actual path by testing different dash interpretations
387-
let testPath = req.params.projectName;
388-
if (testPath.startsWith('-')) {
389-
testPath = testPath.substring(1);
390-
}
391-
392-
// Try to intelligently decode the path by testing which directories exist
393-
const pathParts = testPath.split('-');
394-
actualPath = '/' + pathParts.join('/');
395-
396-
// If the simple replacement doesn't work, try to find the correct path
397-
// by testing combinations where some dashes might be part of directory names
398-
if (!require('fs').existsSync(actualPath)) {
399-
// Try different combinations of dash vs slash
400-
for (let i = pathParts.length - 1; i >= 0; i--) {
401-
let testParts = [...pathParts];
402-
// Try joining some parts with dashes instead of slashes
403-
for (let j = i; j < testParts.length - 1; j++) {
404-
testParts[j] = testParts[j] + '-' + testParts[j + 1];
405-
testParts.splice(j + 1, 1);
406-
let testActualPath = '/' + testParts.join('/');
407-
if (require('fs').existsSync(testActualPath)) {
408-
actualPath = testActualPath;
409-
break;
410-
}
411-
}
412-
if (require('fs').existsSync(actualPath)) break;
413-
}
414-
}
415-
382+
actualPath = await extractProjectDirectory(req.params.projectName);
383+
} catch (error) {
384+
console.error('Error extracting project directory:', error);
385+
// Fallback to simple dash replacement
386+
actualPath = req.params.projectName.replace(/-/g, '/');
416387
}
417388

418389
// Check if path exists

server/projects.js

Lines changed: 90 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@ const fs = require('fs').promises;
22
const path = require('path');
33
const readline = require('readline');
44

5+
// Cache for extracted project directories
6+
const projectDirectoryCache = new Map();
7+
let cacheTimestamp = Date.now();
8+
9+
// Clear cache when needed (called when project files change)
10+
function clearProjectDirectoryCache() {
11+
projectDirectoryCache.clear();
12+
cacheTimestamp = Date.now();
13+
console.log('🗑️ Project directory cache cleared');
14+
}
15+
516
// Load project configuration file
617
async function loadProjectConfig() {
718
const configPath = path.join(process.env.HOME, '.claude', 'project-config.json');
@@ -54,88 +65,108 @@ async function generateDisplayName(projectName, actualProjectDir = null) {
5465
return projectPath;
5566
}
5667

57-
// Extract the actual project directory from JSONL sessions
68+
// Extract the actual project directory from JSONL sessions (with caching)
5869
async function extractProjectDirectory(projectName) {
70+
// Check cache first
71+
if (projectDirectoryCache.has(projectName)) {
72+
return projectDirectoryCache.get(projectName);
73+
}
74+
75+
console.log(`🔍 Extracting project directory for: ${projectName}`);
76+
5977
const projectDir = path.join(process.env.HOME, '.claude', 'projects', projectName);
6078
const cwdCounts = new Map();
6179
let latestTimestamp = 0;
6280
let latestCwd = null;
81+
let extractedPath;
6382

6483
try {
6584
const files = await fs.readdir(projectDir);
6685
const jsonlFiles = files.filter(file => file.endsWith('.jsonl'));
6786

6887
if (jsonlFiles.length === 0) {
6988
// Fall back to decoded project name if no sessions
70-
return projectName.replace(/-/g, '/');
71-
}
72-
73-
// Process all JSONL files to collect cwd values
74-
for (const file of jsonlFiles) {
75-
const jsonlFile = path.join(projectDir, file);
76-
const fileStream = require('fs').createReadStream(jsonlFile);
77-
const rl = readline.createInterface({
78-
input: fileStream,
79-
crlfDelay: Infinity
80-
});
81-
82-
for await (const line of rl) {
83-
if (line.trim()) {
84-
try {
85-
const entry = JSON.parse(line);
86-
87-
if (entry.cwd) {
88-
// Count occurrences of each cwd
89-
cwdCounts.set(entry.cwd, (cwdCounts.get(entry.cwd) || 0) + 1);
89+
extractedPath = projectName.replace(/-/g, '/');
90+
} else {
91+
// Process all JSONL files to collect cwd values
92+
for (const file of jsonlFiles) {
93+
const jsonlFile = path.join(projectDir, file);
94+
const fileStream = require('fs').createReadStream(jsonlFile);
95+
const rl = readline.createInterface({
96+
input: fileStream,
97+
crlfDelay: Infinity
98+
});
99+
100+
for await (const line of rl) {
101+
if (line.trim()) {
102+
try {
103+
const entry = JSON.parse(line);
90104

91-
// Track the most recent cwd
92-
const timestamp = new Date(entry.timestamp || 0).getTime();
93-
if (timestamp > latestTimestamp) {
94-
latestTimestamp = timestamp;
95-
latestCwd = entry.cwd;
105+
if (entry.cwd) {
106+
// Count occurrences of each cwd
107+
cwdCounts.set(entry.cwd, (cwdCounts.get(entry.cwd) || 0) + 1);
108+
109+
// Track the most recent cwd
110+
const timestamp = new Date(entry.timestamp || 0).getTime();
111+
if (timestamp > latestTimestamp) {
112+
latestTimestamp = timestamp;
113+
latestCwd = entry.cwd;
114+
}
96115
}
116+
} catch (parseError) {
117+
// Skip malformed lines
97118
}
98-
} catch (parseError) {
99-
// Skip malformed lines
100119
}
101120
}
102121
}
103-
}
104-
105-
// Determine the best cwd to use
106-
if (cwdCounts.size === 0) {
107-
// No cwd found, fall back to decoded project name
108-
return projectName.replace(/-/g, '/');
109-
}
110-
111-
if (cwdCounts.size === 1) {
112-
// Only one cwd, use it
113-
return Array.from(cwdCounts.keys())[0];
114-
}
115-
116-
// Multiple cwd values - prefer the most recent one if it has reasonable usage
117-
const mostRecentCount = cwdCounts.get(latestCwd) || 0;
118-
const maxCount = Math.max(...cwdCounts.values());
119-
120-
// Use most recent if it has at least 25% of the max count
121-
if (mostRecentCount >= maxCount * 0.25) {
122-
return latestCwd;
123-
}
124-
125-
// Otherwise use the most frequently used cwd
126-
for (const [cwd, count] of cwdCounts.entries()) {
127-
if (count === maxCount) {
128-
return cwd;
122+
123+
// Determine the best cwd to use
124+
if (cwdCounts.size === 0) {
125+
// No cwd found, fall back to decoded project name
126+
extractedPath = projectName.replace(/-/g, '/');
127+
} else if (cwdCounts.size === 1) {
128+
// Only one cwd, use it
129+
extractedPath = Array.from(cwdCounts.keys())[0];
130+
} else {
131+
// Multiple cwd values - prefer the most recent one if it has reasonable usage
132+
const mostRecentCount = cwdCounts.get(latestCwd) || 0;
133+
const maxCount = Math.max(...cwdCounts.values());
134+
135+
// Use most recent if it has at least 25% of the max count
136+
if (mostRecentCount >= maxCount * 0.25) {
137+
extractedPath = latestCwd;
138+
} else {
139+
// Otherwise use the most frequently used cwd
140+
for (const [cwd, count] of cwdCounts.entries()) {
141+
if (count === maxCount) {
142+
extractedPath = cwd;
143+
break;
144+
}
145+
}
146+
}
147+
148+
// Fallback (shouldn't reach here)
149+
if (!extractedPath) {
150+
extractedPath = latestCwd || projectName.replace(/-/g, '/');
151+
}
129152
}
130153
}
131154

132-
// Fallback (shouldn't reach here)
133-
return latestCwd || projectName.replace(/-/g, '/');
155+
// Cache the result
156+
projectDirectoryCache.set(projectName, extractedPath);
157+
console.log(`💾 Cached project directory: ${projectName} -> ${extractedPath}`);
158+
159+
return extractedPath;
134160

135161
} catch (error) {
136162
console.error(`Error extracting project directory for ${projectName}:`, error);
137163
// Fall back to decoded project name
138-
return projectName.replace(/-/g, '/');
164+
extractedPath = projectName.replace(/-/g, '/');
165+
166+
// Cache the fallback result too
167+
projectDirectoryCache.set(projectName, extractedPath);
168+
169+
return extractedPath;
139170
}
140171
}
141172

@@ -582,5 +613,6 @@ module.exports = {
582613
addProjectManually,
583614
loadProjectConfig,
584615
saveProjectConfig,
585-
extractProjectDirectory
616+
extractProjectDirectory,
617+
clearProjectDirectoryCache
586618
};

0 commit comments

Comments
 (0)