@@ -2,6 +2,17 @@ const fs = require('fs').promises;
2
2
const path = require ( 'path' ) ;
3
3
const readline = require ( 'readline' ) ;
4
4
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
+
5
16
// Load project configuration file
6
17
async function loadProjectConfig ( ) {
7
18
const configPath = path . join ( process . env . HOME , '.claude' , 'project-config.json' ) ;
@@ -54,88 +65,108 @@ async function generateDisplayName(projectName, actualProjectDir = null) {
54
65
return projectPath ;
55
66
}
56
67
57
- // Extract the actual project directory from JSONL sessions
68
+ // Extract the actual project directory from JSONL sessions (with caching)
58
69
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
+
59
77
const projectDir = path . join ( process . env . HOME , '.claude' , 'projects' , projectName ) ;
60
78
const cwdCounts = new Map ( ) ;
61
79
let latestTimestamp = 0 ;
62
80
let latestCwd = null ;
81
+ let extractedPath ;
63
82
64
83
try {
65
84
const files = await fs . readdir ( projectDir ) ;
66
85
const jsonlFiles = files . filter ( file => file . endsWith ( '.jsonl' ) ) ;
67
86
68
87
if ( jsonlFiles . length === 0 ) {
69
88
// 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 ) ;
90
104
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
+ }
96
115
}
116
+ } catch ( parseError ) {
117
+ // Skip malformed lines
97
118
}
98
- } catch ( parseError ) {
99
- // Skip malformed lines
100
119
}
101
120
}
102
121
}
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
+ }
129
152
}
130
153
}
131
154
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 ;
134
160
135
161
} catch ( error ) {
136
162
console . error ( `Error extracting project directory for ${ projectName } :` , error ) ;
137
163
// 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 ;
139
170
}
140
171
}
141
172
@@ -582,5 +613,6 @@ module.exports = {
582
613
addProjectManually,
583
614
loadProjectConfig,
584
615
saveProjectConfig,
585
- extractProjectDirectory
616
+ extractProjectDirectory,
617
+ clearProjectDirectoryCache
586
618
} ;
0 commit comments