claude-code-workflow 6.2.9 → 6.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/.claude/CLAUDE.md +17 -2
  2. package/.claude/commands/workflow/lite-plan.md +7 -4
  3. package/.claude/workflows/cli-templates/protocols/analysis-protocol.md +11 -4
  4. package/.claude/workflows/cli-templates/protocols/write-protocol.md +10 -75
  5. package/.claude/workflows/cli-tools-usage.md +14 -24
  6. package/.claude/workflows/context-tools-ace.md +105 -0
  7. package/.codex/AGENTS.md +51 -1
  8. package/.codex/prompts/compact.md +378 -0
  9. package/.gemini/GEMINI.md +57 -20
  10. package/ccw/dist/cli.d.ts.map +1 -1
  11. package/ccw/dist/cli.js +3 -1
  12. package/ccw/dist/cli.js.map +1 -1
  13. package/ccw/dist/commands/cli.d.ts +2 -0
  14. package/ccw/dist/commands/cli.d.ts.map +1 -1
  15. package/ccw/dist/commands/cli.js +129 -8
  16. package/ccw/dist/commands/cli.js.map +1 -1
  17. package/ccw/dist/commands/hook.d.ts.map +1 -1
  18. package/ccw/dist/commands/hook.js +3 -2
  19. package/ccw/dist/commands/hook.js.map +1 -1
  20. package/ccw/dist/config/litellm-api-config-manager.d.ts +180 -0
  21. package/ccw/dist/config/litellm-api-config-manager.d.ts.map +1 -0
  22. package/ccw/dist/config/litellm-api-config-manager.js +770 -0
  23. package/ccw/dist/config/litellm-api-config-manager.js.map +1 -0
  24. package/ccw/dist/config/provider-models.d.ts +73 -0
  25. package/ccw/dist/config/provider-models.d.ts.map +1 -0
  26. package/ccw/dist/config/provider-models.js +172 -0
  27. package/ccw/dist/config/provider-models.js.map +1 -0
  28. package/ccw/dist/core/cache-manager.d.ts.map +1 -1
  29. package/ccw/dist/core/cache-manager.js +3 -5
  30. package/ccw/dist/core/cache-manager.js.map +1 -1
  31. package/ccw/dist/core/dashboard-generator.d.ts.map +1 -1
  32. package/ccw/dist/core/dashboard-generator.js +3 -1
  33. package/ccw/dist/core/dashboard-generator.js.map +1 -1
  34. package/ccw/dist/core/routes/cli-routes.d.ts.map +1 -1
  35. package/ccw/dist/core/routes/cli-routes.js +208 -0
  36. package/ccw/dist/core/routes/cli-routes.js.map +1 -1
  37. package/ccw/dist/core/routes/codexlens-routes.d.ts.map +1 -1
  38. package/ccw/dist/core/routes/codexlens-routes.js +234 -18
  39. package/ccw/dist/core/routes/codexlens-routes.js.map +1 -1
  40. package/ccw/dist/core/routes/hooks-routes.d.ts.map +1 -1
  41. package/ccw/dist/core/routes/hooks-routes.js +30 -32
  42. package/ccw/dist/core/routes/hooks-routes.js.map +1 -1
  43. package/ccw/dist/core/routes/litellm-api-routes.d.ts +21 -0
  44. package/ccw/dist/core/routes/litellm-api-routes.d.ts.map +1 -0
  45. package/ccw/dist/core/routes/litellm-api-routes.js +780 -0
  46. package/ccw/dist/core/routes/litellm-api-routes.js.map +1 -0
  47. package/ccw/dist/core/routes/litellm-routes.d.ts +20 -0
  48. package/ccw/dist/core/routes/litellm-routes.d.ts.map +1 -0
  49. package/ccw/dist/core/routes/litellm-routes.js +85 -0
  50. package/ccw/dist/core/routes/litellm-routes.js.map +1 -0
  51. package/ccw/dist/core/routes/mcp-routes.js +2 -2
  52. package/ccw/dist/core/routes/mcp-routes.js.map +1 -1
  53. package/ccw/dist/core/routes/status-routes.d.ts.map +1 -1
  54. package/ccw/dist/core/routes/status-routes.js +39 -0
  55. package/ccw/dist/core/routes/status-routes.js.map +1 -1
  56. package/ccw/dist/core/server.d.ts.map +1 -1
  57. package/ccw/dist/core/server.js +15 -1
  58. package/ccw/dist/core/server.js.map +1 -1
  59. package/ccw/dist/mcp-server/index.js +1 -1
  60. package/ccw/dist/mcp-server/index.js.map +1 -1
  61. package/ccw/dist/tools/claude-cli-tools.d.ts +97 -0
  62. package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -0
  63. package/ccw/dist/tools/claude-cli-tools.js +276 -0
  64. package/ccw/dist/tools/claude-cli-tools.js.map +1 -0
  65. package/ccw/dist/tools/cli-executor.d.ts.map +1 -1
  66. package/ccw/dist/tools/cli-executor.js +80 -20
  67. package/ccw/dist/tools/cli-executor.js.map +1 -1
  68. package/ccw/dist/tools/codex-lens.d.ts +9 -2
  69. package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
  70. package/ccw/dist/tools/codex-lens.js +114 -9
  71. package/ccw/dist/tools/codex-lens.js.map +1 -1
  72. package/ccw/dist/tools/context-cache-store.d.ts +136 -0
  73. package/ccw/dist/tools/context-cache-store.d.ts.map +1 -0
  74. package/ccw/dist/tools/context-cache-store.js +256 -0
  75. package/ccw/dist/tools/context-cache-store.js.map +1 -0
  76. package/ccw/dist/tools/context-cache.d.ts +56 -0
  77. package/ccw/dist/tools/context-cache.d.ts.map +1 -0
  78. package/ccw/dist/tools/context-cache.js +294 -0
  79. package/ccw/dist/tools/context-cache.js.map +1 -0
  80. package/ccw/dist/tools/core-memory.d.ts.map +1 -1
  81. package/ccw/dist/tools/core-memory.js +33 -19
  82. package/ccw/dist/tools/core-memory.js.map +1 -1
  83. package/ccw/dist/tools/index.d.ts.map +1 -1
  84. package/ccw/dist/tools/index.js +2 -0
  85. package/ccw/dist/tools/index.js.map +1 -1
  86. package/ccw/dist/tools/litellm-client.d.ts +85 -0
  87. package/ccw/dist/tools/litellm-client.d.ts.map +1 -0
  88. package/ccw/dist/tools/litellm-client.js +188 -0
  89. package/ccw/dist/tools/litellm-client.js.map +1 -0
  90. package/ccw/dist/tools/litellm-executor.d.ts +34 -0
  91. package/ccw/dist/tools/litellm-executor.d.ts.map +1 -0
  92. package/ccw/dist/tools/litellm-executor.js +192 -0
  93. package/ccw/dist/tools/litellm-executor.js.map +1 -0
  94. package/ccw/dist/tools/pattern-parser.d.ts +55 -0
  95. package/ccw/dist/tools/pattern-parser.d.ts.map +1 -0
  96. package/ccw/dist/tools/pattern-parser.js +237 -0
  97. package/ccw/dist/tools/pattern-parser.js.map +1 -0
  98. package/ccw/dist/tools/smart-search.d.ts +1 -0
  99. package/ccw/dist/tools/smart-search.d.ts.map +1 -1
  100. package/ccw/dist/tools/smart-search.js +117 -41
  101. package/ccw/dist/tools/smart-search.js.map +1 -1
  102. package/ccw/dist/types/litellm-api-config.d.ts +294 -0
  103. package/ccw/dist/types/litellm-api-config.d.ts.map +1 -0
  104. package/ccw/dist/types/litellm-api-config.js +8 -0
  105. package/ccw/dist/types/litellm-api-config.js.map +1 -0
  106. package/ccw/src/cli.ts +3 -1
  107. package/ccw/src/commands/cli.ts +153 -9
  108. package/ccw/src/commands/hook.ts +3 -2
  109. package/ccw/src/config/.litellm-api-config-manager.ts.2025-12-23T11-57-43-727Z.bak +441 -0
  110. package/ccw/src/config/litellm-api-config-manager.ts +1012 -0
  111. package/ccw/src/config/provider-models.ts +222 -0
  112. package/ccw/src/core/cache-manager.ts +292 -294
  113. package/ccw/src/core/dashboard-generator.ts +3 -1
  114. package/ccw/src/core/routes/cli-routes.ts +234 -0
  115. package/ccw/src/core/routes/codexlens-routes.ts +241 -19
  116. package/ccw/src/core/routes/hooks-routes.ts +399 -405
  117. package/ccw/src/core/routes/litellm-api-routes.ts +930 -0
  118. package/ccw/src/core/routes/litellm-routes.ts +107 -0
  119. package/ccw/src/core/routes/mcp-routes.ts +1271 -1271
  120. package/ccw/src/core/routes/status-routes.ts +51 -0
  121. package/ccw/src/core/server.ts +15 -1
  122. package/ccw/src/mcp-server/index.ts +1 -1
  123. package/ccw/src/templates/dashboard-css/12-cli-legacy.css +44 -0
  124. package/ccw/src/templates/dashboard-css/18-cli-settings.css +34 -0
  125. package/ccw/src/templates/dashboard-css/31-api-settings.css +2265 -0
  126. package/ccw/src/templates/dashboard-js/components/cli-history.js +15 -8
  127. package/ccw/src/templates/dashboard-js/components/cli-status.js +376 -9
  128. package/ccw/src/templates/dashboard-js/components/navigation.js +329 -313
  129. package/ccw/src/templates/dashboard-js/i18n.js +589 -3
  130. package/ccw/src/templates/dashboard-js/views/api-settings.js +3362 -0
  131. package/ccw/src/templates/dashboard-js/views/cli-manager.js +212 -24
  132. package/ccw/src/templates/dashboard-js/views/codexlens-manager.js +1265 -27
  133. package/ccw/src/templates/dashboard.html +840 -831
  134. package/ccw/src/tools/claude-cli-tools.ts +371 -0
  135. package/ccw/src/tools/cli-executor.ts +87 -20
  136. package/ccw/src/tools/codex-lens.ts +146 -9
  137. package/ccw/src/tools/context-cache-store.ts +368 -0
  138. package/ccw/src/tools/context-cache.ts +393 -0
  139. package/ccw/src/tools/core-memory.ts +33 -19
  140. package/ccw/src/tools/index.ts +2 -0
  141. package/ccw/src/tools/litellm-client.ts +246 -0
  142. package/ccw/src/tools/litellm-executor.ts +241 -0
  143. package/ccw/src/tools/pattern-parser.ts +329 -0
  144. package/ccw/src/tools/smart-search.ts +142 -41
  145. package/ccw/src/types/litellm-api-config.ts +402 -0
  146. package/ccw-litellm/README.md +180 -0
  147. package/ccw-litellm/pyproject.toml +35 -0
  148. package/ccw-litellm/src/ccw_litellm/__init__.py +47 -0
  149. package/ccw-litellm/src/ccw_litellm/__pycache__/__init__.cpython-313.pyc +0 -0
  150. package/ccw-litellm/src/ccw_litellm/__pycache__/cli.cpython-313.pyc +0 -0
  151. package/ccw-litellm/src/ccw_litellm/cli.py +108 -0
  152. package/ccw-litellm/src/ccw_litellm/clients/__init__.py +12 -0
  153. package/ccw-litellm/src/ccw_litellm/clients/__pycache__/__init__.cpython-313.pyc +0 -0
  154. package/ccw-litellm/src/ccw_litellm/clients/__pycache__/litellm_embedder.cpython-313.pyc +0 -0
  155. package/ccw-litellm/src/ccw_litellm/clients/__pycache__/litellm_llm.cpython-313.pyc +0 -0
  156. package/ccw-litellm/src/ccw_litellm/clients/litellm_embedder.py +251 -0
  157. package/ccw-litellm/src/ccw_litellm/clients/litellm_llm.py +165 -0
  158. package/ccw-litellm/src/ccw_litellm/config/__init__.py +22 -0
  159. package/ccw-litellm/src/ccw_litellm/config/__pycache__/__init__.cpython-313.pyc +0 -0
  160. package/ccw-litellm/src/ccw_litellm/config/__pycache__/loader.cpython-313.pyc +0 -0
  161. package/ccw-litellm/src/ccw_litellm/config/__pycache__/models.cpython-313.pyc +0 -0
  162. package/ccw-litellm/src/ccw_litellm/config/loader.py +316 -0
  163. package/ccw-litellm/src/ccw_litellm/config/models.py +130 -0
  164. package/ccw-litellm/src/ccw_litellm/interfaces/__init__.py +14 -0
  165. package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/__init__.cpython-313.pyc +0 -0
  166. package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/embedder.cpython-313.pyc +0 -0
  167. package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/llm.cpython-313.pyc +0 -0
  168. package/ccw-litellm/src/ccw_litellm/interfaces/embedder.py +52 -0
  169. package/ccw-litellm/src/ccw_litellm/interfaces/llm.py +45 -0
  170. package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
  171. package/codex-lens/src/codexlens/cli/__pycache__/commands.cpython-313.pyc +0 -0
  172. package/codex-lens/src/codexlens/cli/__pycache__/embedding_manager.cpython-313.pyc +0 -0
  173. package/codex-lens/src/codexlens/cli/__pycache__/model_manager.cpython-313.pyc +0 -0
  174. package/codex-lens/src/codexlens/cli/__pycache__/output.cpython-313.pyc +0 -0
  175. package/codex-lens/src/codexlens/cli/commands.py +360 -22
  176. package/codex-lens/src/codexlens/cli/embedding_manager.py +660 -56
  177. package/codex-lens/src/codexlens/cli/model_manager.py +31 -18
  178. package/codex-lens/src/codexlens/cli/output.py +12 -1
  179. package/codex-lens/src/codexlens/config.py +93 -0
  180. package/codex-lens/src/codexlens/search/__pycache__/chain_search.cpython-313.pyc +0 -0
  181. package/codex-lens/src/codexlens/search/__pycache__/hybrid_search.cpython-313.pyc +0 -0
  182. package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
  183. package/codex-lens/src/codexlens/search/chain_search.py +6 -2
  184. package/codex-lens/src/codexlens/search/hybrid_search.py +44 -21
  185. package/codex-lens/src/codexlens/search/ranking.py +1 -1
  186. package/codex-lens/src/codexlens/semantic/__init__.py +42 -0
  187. package/codex-lens/src/codexlens/semantic/__pycache__/__init__.cpython-313.pyc +0 -0
  188. package/codex-lens/src/codexlens/semantic/__pycache__/base.cpython-313.pyc +0 -0
  189. package/codex-lens/src/codexlens/semantic/__pycache__/chunker.cpython-313.pyc +0 -0
  190. package/codex-lens/src/codexlens/semantic/__pycache__/embedder.cpython-313.pyc +0 -0
  191. package/codex-lens/src/codexlens/semantic/__pycache__/factory.cpython-313.pyc +0 -0
  192. package/codex-lens/src/codexlens/semantic/__pycache__/gpu_support.cpython-313.pyc +0 -0
  193. package/codex-lens/src/codexlens/semantic/__pycache__/litellm_embedder.cpython-313.pyc +0 -0
  194. package/codex-lens/src/codexlens/semantic/__pycache__/vector_store.cpython-313.pyc +0 -0
  195. package/codex-lens/src/codexlens/semantic/base.py +61 -0
  196. package/codex-lens/src/codexlens/semantic/chunker.py +43 -20
  197. package/codex-lens/src/codexlens/semantic/embedder.py +60 -13
  198. package/codex-lens/src/codexlens/semantic/factory.py +98 -0
  199. package/codex-lens/src/codexlens/semantic/gpu_support.py +225 -3
  200. package/codex-lens/src/codexlens/semantic/litellm_embedder.py +144 -0
  201. package/codex-lens/src/codexlens/semantic/rotational_embedder.py +434 -0
  202. package/codex-lens/src/codexlens/semantic/vector_store.py +33 -8
  203. package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_004_dual_fts.cpython-313.pyc +0 -0
  204. package/package.json +3 -1
  205. package/.codex/prompts.zip +0 -0
@@ -33,6 +33,14 @@ const VENV_PYTHON =
33
33
  let bootstrapChecked = false;
34
34
  let bootstrapReady = false;
35
35
 
36
+ // Venv status cache with TTL
37
+ interface VenvStatusCache {
38
+ status: ReadyStatus;
39
+ timestamp: number;
40
+ }
41
+ let venvStatusCache: VenvStatusCache | null = null;
42
+ const VENV_STATUS_TTL = 5 * 60 * 1000; // 5 minutes TTL
43
+
36
44
  // Track running indexing process for cancellation
37
45
  let currentIndexingProcess: ReturnType<typeof spawn> | null = null;
38
46
  let currentIndexingAborted = false;
@@ -77,6 +85,7 @@ interface SemanticStatus {
77
85
  backend?: string;
78
86
  accelerator?: string;
79
87
  providers?: string[];
88
+ litellmAvailable?: boolean;
80
89
  error?: string;
81
90
  }
82
91
 
@@ -115,6 +124,13 @@ interface ProgressInfo {
115
124
  totalFiles?: number;
116
125
  }
117
126
 
127
+ /**
128
+ * Clear venv status cache (call after install/uninstall operations)
129
+ */
130
+ function clearVenvStatusCache(): void {
131
+ venvStatusCache = null;
132
+ }
133
+
118
134
  /**
119
135
  * Detect available Python 3 executable
120
136
  * @returns Python executable command
@@ -137,17 +153,27 @@ function getSystemPython(): string {
137
153
 
138
154
  /**
139
155
  * Check if CodexLens venv exists and has required packages
156
+ * @param force - Force refresh cache (default: false)
140
157
  * @returns Ready status
141
158
  */
142
- async function checkVenvStatus(): Promise<ReadyStatus> {
159
+ async function checkVenvStatus(force = false): Promise<ReadyStatus> {
160
+ // Use cached result if available and not expired
161
+ if (!force && venvStatusCache && (Date.now() - venvStatusCache.timestamp < VENV_STATUS_TTL)) {
162
+ return venvStatusCache.status;
163
+ }
164
+
143
165
  // Check venv exists
144
166
  if (!existsSync(CODEXLENS_VENV)) {
145
- return { ready: false, error: 'Venv not found' };
167
+ const result = { ready: false, error: 'Venv not found' };
168
+ venvStatusCache = { status: result, timestamp: Date.now() };
169
+ return result;
146
170
  }
147
171
 
148
172
  // Check python executable exists
149
173
  if (!existsSync(VENV_PYTHON)) {
150
- return { ready: false, error: 'Python executable not found in venv' };
174
+ const result = { ready: false, error: 'Python executable not found in venv' };
175
+ venvStatusCache = { status: result, timestamp: Date.now() };
176
+ return result;
151
177
  }
152
178
 
153
179
  // Check codexlens is importable
@@ -168,15 +194,21 @@ async function checkVenvStatus(): Promise<ReadyStatus> {
168
194
  });
169
195
 
170
196
  child.on('close', (code) => {
197
+ let result: ReadyStatus;
171
198
  if (code === 0) {
172
- resolve({ ready: true, version: stdout.trim() });
199
+ result = { ready: true, version: stdout.trim() };
173
200
  } else {
174
- resolve({ ready: false, error: `CodexLens not installed: ${stderr}` });
201
+ result = { ready: false, error: `CodexLens not installed: ${stderr}` };
175
202
  }
203
+ // Cache the result
204
+ venvStatusCache = { status: result, timestamp: Date.now() };
205
+ resolve(result);
176
206
  });
177
207
 
178
208
  child.on('error', (err) => {
179
- resolve({ ready: false, error: `Failed to check venv: ${err.message}` });
209
+ const result = { ready: false, error: `Failed to check venv: ${err.message}` };
210
+ venvStatusCache = { status: result, timestamp: Date.now() };
211
+ resolve(result);
180
212
  });
181
213
  });
182
214
  }
@@ -198,8 +230,15 @@ async function checkSemanticStatus(): Promise<SemanticStatus> {
198
230
  import sys
199
231
  import json
200
232
  try:
201
- from codexlens.semantic import SEMANTIC_AVAILABLE, SEMANTIC_BACKEND
202
- result = {"available": SEMANTIC_AVAILABLE, "backend": SEMANTIC_BACKEND if SEMANTIC_AVAILABLE else None}
233
+ import codexlens.semantic as semantic
234
+ SEMANTIC_AVAILABLE = bool(getattr(semantic, "SEMANTIC_AVAILABLE", False))
235
+ SEMANTIC_BACKEND = getattr(semantic, "SEMANTIC_BACKEND", None)
236
+ LITELLM_AVAILABLE = bool(getattr(semantic, "LITELLM_AVAILABLE", False))
237
+ result = {
238
+ "available": SEMANTIC_AVAILABLE,
239
+ "backend": SEMANTIC_BACKEND if SEMANTIC_AVAILABLE else None,
240
+ "litellm_available": LITELLM_AVAILABLE,
241
+ }
203
242
 
204
243
  # Get ONNX providers for accelerator info
205
244
  try:
@@ -250,6 +289,7 @@ except Exception as e:
250
289
  backend: result.backend,
251
290
  accelerator: result.accelerator || 'CPU',
252
291
  providers: result.providers || [],
292
+ litellmAvailable: result.litellm_available || false,
253
293
  error: result.error
254
294
  });
255
295
  } catch {
@@ -263,6 +303,77 @@ except Exception as e:
263
303
  });
264
304
  }
265
305
 
306
+ /**
307
+ * Ensure LiteLLM embedder dependencies are available in the CodexLens venv.
308
+ * Installs ccw-litellm into the venv if needed.
309
+ */
310
+ async function ensureLiteLLMEmbedderReady(): Promise<BootstrapResult> {
311
+ // Ensure CodexLens venv exists and CodexLens is installed.
312
+ const readyStatus = await ensureReady();
313
+ if (!readyStatus.ready) {
314
+ return { success: false, error: readyStatus.error || 'CodexLens not ready' };
315
+ }
316
+
317
+ // Check if ccw_litellm can be imported
318
+ const importStatus = await new Promise<{ ok: boolean; error?: string }>((resolve) => {
319
+ const child = spawn(VENV_PYTHON, ['-c', 'import ccw_litellm; print("OK")'], {
320
+ stdio: ['ignore', 'pipe', 'pipe'],
321
+ timeout: 15000,
322
+ });
323
+
324
+ let stderr = '';
325
+ child.stderr.on('data', (data) => {
326
+ stderr += data.toString();
327
+ });
328
+
329
+ child.on('close', (code) => {
330
+ resolve({ ok: code === 0, error: stderr.trim() || undefined });
331
+ });
332
+
333
+ child.on('error', (err) => {
334
+ resolve({ ok: false, error: err.message });
335
+ });
336
+ });
337
+
338
+ if (importStatus.ok) {
339
+ return { success: true };
340
+ }
341
+
342
+ const pipPath =
343
+ process.platform === 'win32'
344
+ ? join(CODEXLENS_VENV, 'Scripts', 'pip.exe')
345
+ : join(CODEXLENS_VENV, 'bin', 'pip');
346
+
347
+ try {
348
+ console.log('[CodexLens] Installing ccw-litellm for LiteLLM embedding backend...');
349
+
350
+ const possiblePaths = [
351
+ join(process.cwd(), 'ccw-litellm'),
352
+ join(__dirname, '..', '..', '..', 'ccw-litellm'), // ccw/src/tools -> project root
353
+ join(homedir(), 'ccw-litellm'),
354
+ ];
355
+
356
+ let installed = false;
357
+ for (const localPath of possiblePaths) {
358
+ if (existsSync(join(localPath, 'pyproject.toml'))) {
359
+ console.log(`[CodexLens] Installing ccw-litellm from local path: ${localPath}`);
360
+ execSync(`"${pipPath}" install -e "${localPath}"`, { stdio: 'inherit' });
361
+ installed = true;
362
+ break;
363
+ }
364
+ }
365
+
366
+ if (!installed) {
367
+ console.log('[CodexLens] Installing ccw-litellm from PyPI...');
368
+ execSync(`"${pipPath}" install ccw-litellm`, { stdio: 'inherit' });
369
+ }
370
+
371
+ return { success: true };
372
+ } catch (err) {
373
+ return { success: false, error: `Failed to install ccw-litellm: ${(err as Error).message}` };
374
+ }
375
+ }
376
+
266
377
  /**
267
378
  * GPU acceleration mode for semantic search
268
379
  */
@@ -421,6 +532,17 @@ async function installSemantic(gpuMode: GpuMode = 'cpu'): Promise<BootstrapResul
421
532
 
422
533
  child.on('close', (code) => {
423
534
  if (code === 0) {
535
+ // IMPORTANT: fastembed installs onnxruntime (CPU) as dependency, which conflicts
536
+ // with onnxruntime-directml/gpu. Reinstall the GPU version to ensure it takes precedence.
537
+ if (gpuMode !== 'cpu') {
538
+ try {
539
+ console.log(`[CodexLens] Reinstalling ${onnxPackage} to ensure GPU provider works...`);
540
+ execSync(`"${pipPath}" install --force-reinstall ${onnxPackage}`, { stdio: 'pipe', timeout: 300000 });
541
+ console.log(`[CodexLens] ${onnxPackage} reinstalled successfully`);
542
+ } catch (e) {
543
+ console.warn(`[CodexLens] Warning: Failed to reinstall ${onnxPackage}: ${(e as Error).message}`);
544
+ }
545
+ }
424
546
  console.log(`[CodexLens] Semantic dependencies installed successfully (${gpuMode} mode)`);
425
547
  resolve({ success: true, message: `Installed with ${modeDescription}` });
426
548
  } else {
@@ -490,6 +612,8 @@ async function bootstrapVenv(): Promise<BootstrapResult> {
490
612
  execSync(`"${pipPath}" install codexlens`, { stdio: 'inherit' });
491
613
  }
492
614
 
615
+ // Clear cache after successful installation
616
+ clearVenvStatusCache();
493
617
  return { success: true };
494
618
  } catch (err) {
495
619
  return { success: false, error: `Failed to install codexlens: ${(err as Error).message}` };
@@ -1209,6 +1333,7 @@ async function uninstallCodexLens(): Promise<BootstrapResult> {
1209
1333
  // Reset bootstrap cache
1210
1334
  bootstrapChecked = false;
1211
1335
  bootstrapReady = false;
1336
+ clearVenvStatusCache();
1212
1337
 
1213
1338
  console.log('[CodexLens] CodexLens uninstalled successfully');
1214
1339
  return { success: true, message: 'CodexLens uninstalled successfully' };
@@ -1273,7 +1398,19 @@ function isIndexingInProgress(): boolean {
1273
1398
  export type { ProgressInfo, ExecuteOptions };
1274
1399
 
1275
1400
  // Export for direct usage
1276
- export { ensureReady, executeCodexLens, checkVenvStatus, bootstrapVenv, checkSemanticStatus, installSemantic, detectGpuSupport, uninstallCodexLens, cancelIndexing, isIndexingInProgress };
1401
+ export {
1402
+ ensureReady,
1403
+ executeCodexLens,
1404
+ checkVenvStatus,
1405
+ bootstrapVenv,
1406
+ checkSemanticStatus,
1407
+ ensureLiteLLMEmbedderReady,
1408
+ installSemantic,
1409
+ detectGpuSupport,
1410
+ uninstallCodexLens,
1411
+ cancelIndexing,
1412
+ isIndexingInProgress,
1413
+ };
1277
1414
  export type { GpuMode };
1278
1415
 
1279
1416
  // Backward-compatible export for tests
@@ -0,0 +1,368 @@
1
+ /**
2
+ * Context Cache Store - In-memory cache with TTL and LRU eviction
3
+ * Stores packed file contents with session-based lifecycle management
4
+ */
5
+
6
+ /** Cache entry metadata */
7
+ export interface CacheMetadata {
8
+ files: string[]; // Source file paths
9
+ patterns: string[]; // Original @patterns
10
+ total_bytes: number; // Total content bytes
11
+ file_count: number; // Number of files packed
12
+ }
13
+
14
+ /** Cache entry structure */
15
+ export interface CacheEntry {
16
+ session_id: string;
17
+ created_at: number; // Timestamp ms
18
+ accessed_at: number; // Last access timestamp
19
+ ttl: number; // TTL in ms
20
+ content: string; // Packed file content
21
+ metadata: CacheMetadata;
22
+ }
23
+
24
+ /** Paginated read result */
25
+ export interface PagedReadResult {
26
+ content: string; // Current page content
27
+ offset: number; // Current byte offset
28
+ limit: number; // Requested bytes
29
+ total_bytes: number; // Total content bytes
30
+ has_more: boolean; // Has more content
31
+ next_offset: number | null; // Next page offset (null if no more)
32
+ }
33
+
34
+ /** Cache status info */
35
+ export interface CacheStatus {
36
+ entries: number; // Total cache entries
37
+ total_bytes: number; // Total bytes cached
38
+ oldest_session: string | null;
39
+ newest_session: string | null;
40
+ }
41
+
42
+ /** Session status info */
43
+ export interface SessionStatus {
44
+ session_id: string;
45
+ exists: boolean;
46
+ files?: string[];
47
+ file_count?: number;
48
+ total_bytes?: number;
49
+ created_at?: string;
50
+ expires_at?: string;
51
+ accessed_at?: string;
52
+ ttl_remaining_ms?: number;
53
+ }
54
+
55
+ /** Default configuration */
56
+ const DEFAULT_MAX_ENTRIES = 100;
57
+ const DEFAULT_TTL_MS = 30 * 60 * 1000; // 30 minutes
58
+ const DEFAULT_PAGE_SIZE = 65536; // 64KB
59
+
60
+ /**
61
+ * Context Cache Store singleton
62
+ * Manages in-memory cache with TTL expiration and LRU eviction
63
+ */
64
+ class ContextCacheStore {
65
+ private cache: Map<string, CacheEntry> = new Map();
66
+ private maxEntries: number;
67
+ private defaultTTL: number;
68
+ private cleanupInterval: NodeJS.Timeout | null = null;
69
+
70
+ constructor(options: {
71
+ maxEntries?: number;
72
+ defaultTTL?: number;
73
+ cleanupIntervalMs?: number;
74
+ } = {}) {
75
+ this.maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
76
+ this.defaultTTL = options.defaultTTL ?? DEFAULT_TTL_MS;
77
+
78
+ // Start periodic cleanup
79
+ const cleanupMs = options.cleanupIntervalMs ?? 60000; // 1 minute
80
+ this.cleanupInterval = setInterval(() => {
81
+ this.cleanupExpired();
82
+ }, cleanupMs);
83
+
84
+ // Allow cleanup to not keep process alive
85
+ if (this.cleanupInterval.unref) {
86
+ this.cleanupInterval.unref();
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Store packed content in cache
92
+ */
93
+ set(
94
+ sessionId: string,
95
+ content: string,
96
+ metadata: CacheMetadata,
97
+ ttl?: number
98
+ ): CacheEntry {
99
+ const now = Date.now();
100
+ const entryTTL = ttl ?? this.defaultTTL;
101
+
102
+ // Evict if at capacity
103
+ if (this.cache.size >= this.maxEntries && !this.cache.has(sessionId)) {
104
+ this.evictOldest();
105
+ }
106
+
107
+ const entry: CacheEntry = {
108
+ session_id: sessionId,
109
+ created_at: now,
110
+ accessed_at: now,
111
+ ttl: entryTTL,
112
+ content,
113
+ metadata,
114
+ };
115
+
116
+ this.cache.set(sessionId, entry);
117
+ return entry;
118
+ }
119
+
120
+ /**
121
+ * Get cache entry by session ID
122
+ */
123
+ get(sessionId: string): CacheEntry | null {
124
+ const entry = this.cache.get(sessionId);
125
+
126
+ if (!entry) {
127
+ return null;
128
+ }
129
+
130
+ // Check TTL expiration
131
+ if (this.isExpired(entry)) {
132
+ this.cache.delete(sessionId);
133
+ return null;
134
+ }
135
+
136
+ // Update access time (LRU)
137
+ entry.accessed_at = Date.now();
138
+ return entry;
139
+ }
140
+
141
+ /**
142
+ * Read content with pagination
143
+ */
144
+ read(
145
+ sessionId: string,
146
+ offset: number = 0,
147
+ limit: number = DEFAULT_PAGE_SIZE
148
+ ): PagedReadResult | null {
149
+ const entry = this.get(sessionId);
150
+
151
+ if (!entry) {
152
+ return null;
153
+ }
154
+
155
+ const content = entry.content;
156
+ const totalBytes = Buffer.byteLength(content, 'utf-8');
157
+
158
+ // Handle byte-based offset for UTF-8
159
+ // For simplicity, we use character-based slicing
160
+ // This is approximate but works for most use cases
161
+ const charOffset = Math.min(offset, content.length);
162
+ const charLimit = Math.min(limit, content.length - charOffset);
163
+
164
+ const pageContent = content.slice(charOffset, charOffset + charLimit);
165
+ const endOffset = charOffset + pageContent.length;
166
+ const hasMore = endOffset < content.length;
167
+
168
+ return {
169
+ content: pageContent,
170
+ offset: charOffset,
171
+ limit: charLimit,
172
+ total_bytes: totalBytes,
173
+ has_more: hasMore,
174
+ next_offset: hasMore ? endOffset : null,
175
+ };
176
+ }
177
+
178
+ /**
179
+ * Release (delete) cache entry
180
+ */
181
+ release(sessionId: string): { released: boolean; freed_bytes: number } {
182
+ const entry = this.cache.get(sessionId);
183
+
184
+ if (!entry) {
185
+ return { released: false, freed_bytes: 0 };
186
+ }
187
+
188
+ const freedBytes = entry.metadata.total_bytes;
189
+ this.cache.delete(sessionId);
190
+
191
+ return { released: true, freed_bytes: freedBytes };
192
+ }
193
+
194
+ /**
195
+ * Get session status
196
+ */
197
+ getSessionStatus(sessionId: string): SessionStatus {
198
+ const entry = this.cache.get(sessionId);
199
+
200
+ if (!entry) {
201
+ return { session_id: sessionId, exists: false };
202
+ }
203
+
204
+ // Check if expired
205
+ if (this.isExpired(entry)) {
206
+ this.cache.delete(sessionId);
207
+ return { session_id: sessionId, exists: false };
208
+ }
209
+
210
+ const now = Date.now();
211
+ const expiresAt = entry.created_at + entry.ttl;
212
+ const ttlRemaining = Math.max(0, expiresAt - now);
213
+
214
+ return {
215
+ session_id: sessionId,
216
+ exists: true,
217
+ files: entry.metadata.files,
218
+ file_count: entry.metadata.file_count,
219
+ total_bytes: entry.metadata.total_bytes,
220
+ created_at: new Date(entry.created_at).toISOString(),
221
+ expires_at: new Date(expiresAt).toISOString(),
222
+ accessed_at: new Date(entry.accessed_at).toISOString(),
223
+ ttl_remaining_ms: ttlRemaining,
224
+ };
225
+ }
226
+
227
+ /**
228
+ * Get overall cache status
229
+ */
230
+ getStatus(): CacheStatus {
231
+ let totalBytes = 0;
232
+ let oldest: CacheEntry | null = null;
233
+ let newest: CacheEntry | null = null;
234
+
235
+ for (const entry of this.cache.values()) {
236
+ // Skip expired entries
237
+ if (this.isExpired(entry)) {
238
+ continue;
239
+ }
240
+
241
+ totalBytes += entry.metadata.total_bytes;
242
+
243
+ if (!oldest || entry.created_at < oldest.created_at) {
244
+ oldest = entry;
245
+ }
246
+ if (!newest || entry.created_at > newest.created_at) {
247
+ newest = entry;
248
+ }
249
+ }
250
+
251
+ return {
252
+ entries: this.cache.size,
253
+ total_bytes: totalBytes,
254
+ oldest_session: oldest?.session_id ?? null,
255
+ newest_session: newest?.session_id ?? null,
256
+ };
257
+ }
258
+
259
+ /**
260
+ * Cleanup expired entries
261
+ */
262
+ cleanupExpired(): { removed: number } {
263
+ let removed = 0;
264
+ const now = Date.now();
265
+
266
+ for (const [sessionId, entry] of this.cache.entries()) {
267
+ if (this.isExpired(entry, now)) {
268
+ this.cache.delete(sessionId);
269
+ removed++;
270
+ }
271
+ }
272
+
273
+ return { removed };
274
+ }
275
+
276
+ /**
277
+ * Clear all cache entries
278
+ */
279
+ clear(): { removed: number } {
280
+ const count = this.cache.size;
281
+ this.cache.clear();
282
+ return { removed: count };
283
+ }
284
+
285
+ /**
286
+ * Check if entry is expired
287
+ */
288
+ private isExpired(entry: CacheEntry, now?: number): boolean {
289
+ const currentTime = now ?? Date.now();
290
+ return currentTime > entry.created_at + entry.ttl;
291
+ }
292
+
293
+ /**
294
+ * Evict oldest entry (LRU)
295
+ */
296
+ private evictOldest(): void {
297
+ let oldest: [string, CacheEntry] | null = null;
298
+
299
+ for (const [sessionId, entry] of this.cache.entries()) {
300
+ if (!oldest || entry.accessed_at < oldest[1].accessed_at) {
301
+ oldest = [sessionId, entry];
302
+ }
303
+ }
304
+
305
+ if (oldest) {
306
+ this.cache.delete(oldest[0]);
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Stop cleanup timer (for graceful shutdown)
312
+ */
313
+ destroy(): void {
314
+ if (this.cleanupInterval) {
315
+ clearInterval(this.cleanupInterval);
316
+ this.cleanupInterval = null;
317
+ }
318
+ }
319
+
320
+ /**
321
+ * List all session IDs
322
+ */
323
+ listSessions(): string[] {
324
+ return Array.from(this.cache.keys());
325
+ }
326
+
327
+ /**
328
+ * Check if session exists and is valid
329
+ */
330
+ has(sessionId: string): boolean {
331
+ const entry = this.cache.get(sessionId);
332
+ if (!entry) return false;
333
+ if (this.isExpired(entry)) {
334
+ this.cache.delete(sessionId);
335
+ return false;
336
+ }
337
+ return true;
338
+ }
339
+ }
340
+
341
+ // Singleton instance
342
+ let cacheInstance: ContextCacheStore | null = null;
343
+
344
+ /**
345
+ * Get the singleton cache instance
346
+ */
347
+ export function getContextCacheStore(options?: {
348
+ maxEntries?: number;
349
+ defaultTTL?: number;
350
+ cleanupIntervalMs?: number;
351
+ }): ContextCacheStore {
352
+ if (!cacheInstance) {
353
+ cacheInstance = new ContextCacheStore(options);
354
+ }
355
+ return cacheInstance;
356
+ }
357
+
358
+ /**
359
+ * Reset the cache instance (for testing)
360
+ */
361
+ export function resetContextCacheStore(): void {
362
+ if (cacheInstance) {
363
+ cacheInstance.destroy();
364
+ cacheInstance = null;
365
+ }
366
+ }
367
+
368
+ export { ContextCacheStore };