claude-code-workflow 6.2.7 → 6.3.0

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 (208) hide show
  1. package/.claude/CLAUDE.md +16 -1
  2. package/.claude/workflows/cli-templates/protocols/analysis-protocol.md +11 -4
  3. package/.claude/workflows/cli-templates/protocols/write-protocol.md +10 -75
  4. package/.claude/workflows/cli-tools-usage.md +14 -24
  5. package/.codex/AGENTS.md +51 -1
  6. package/.codex/prompts/compact.md +378 -0
  7. package/.gemini/GEMINI.md +57 -20
  8. package/ccw/dist/cli.d.ts.map +1 -1
  9. package/ccw/dist/cli.js +21 -8
  10. package/ccw/dist/cli.js.map +1 -1
  11. package/ccw/dist/commands/cli.d.ts +2 -0
  12. package/ccw/dist/commands/cli.d.ts.map +1 -1
  13. package/ccw/dist/commands/cli.js +129 -8
  14. package/ccw/dist/commands/cli.js.map +1 -1
  15. package/ccw/dist/commands/hook.d.ts.map +1 -1
  16. package/ccw/dist/commands/hook.js +3 -2
  17. package/ccw/dist/commands/hook.js.map +1 -1
  18. package/ccw/dist/config/litellm-api-config-manager.d.ts +180 -0
  19. package/ccw/dist/config/litellm-api-config-manager.d.ts.map +1 -0
  20. package/ccw/dist/config/litellm-api-config-manager.js +770 -0
  21. package/ccw/dist/config/litellm-api-config-manager.js.map +1 -0
  22. package/ccw/dist/config/provider-models.d.ts +73 -0
  23. package/ccw/dist/config/provider-models.d.ts.map +1 -0
  24. package/ccw/dist/config/provider-models.js +172 -0
  25. package/ccw/dist/config/provider-models.js.map +1 -0
  26. package/ccw/dist/core/cache-manager.d.ts.map +1 -1
  27. package/ccw/dist/core/cache-manager.js +3 -5
  28. package/ccw/dist/core/cache-manager.js.map +1 -1
  29. package/ccw/dist/core/dashboard-generator.d.ts.map +1 -1
  30. package/ccw/dist/core/dashboard-generator.js +3 -1
  31. package/ccw/dist/core/dashboard-generator.js.map +1 -1
  32. package/ccw/dist/core/routes/cli-routes.d.ts.map +1 -1
  33. package/ccw/dist/core/routes/cli-routes.js +169 -0
  34. package/ccw/dist/core/routes/cli-routes.js.map +1 -1
  35. package/ccw/dist/core/routes/codexlens-routes.d.ts.map +1 -1
  36. package/ccw/dist/core/routes/codexlens-routes.js +234 -18
  37. package/ccw/dist/core/routes/codexlens-routes.js.map +1 -1
  38. package/ccw/dist/core/routes/hooks-routes.d.ts.map +1 -1
  39. package/ccw/dist/core/routes/hooks-routes.js +30 -32
  40. package/ccw/dist/core/routes/hooks-routes.js.map +1 -1
  41. package/ccw/dist/core/routes/litellm-api-routes.d.ts +21 -0
  42. package/ccw/dist/core/routes/litellm-api-routes.d.ts.map +1 -0
  43. package/ccw/dist/core/routes/litellm-api-routes.js +780 -0
  44. package/ccw/dist/core/routes/litellm-api-routes.js.map +1 -0
  45. package/ccw/dist/core/routes/litellm-routes.d.ts +20 -0
  46. package/ccw/dist/core/routes/litellm-routes.d.ts.map +1 -0
  47. package/ccw/dist/core/routes/litellm-routes.js +85 -0
  48. package/ccw/dist/core/routes/litellm-routes.js.map +1 -0
  49. package/ccw/dist/core/routes/mcp-routes.js +2 -2
  50. package/ccw/dist/core/routes/mcp-routes.js.map +1 -1
  51. package/ccw/dist/core/routes/status-routes.d.ts.map +1 -1
  52. package/ccw/dist/core/routes/status-routes.js +39 -0
  53. package/ccw/dist/core/routes/status-routes.js.map +1 -1
  54. package/ccw/dist/core/routes/system-routes.js +1 -1
  55. package/ccw/dist/core/routes/system-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 +82 -0
  62. package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -0
  63. package/ccw/dist/tools/claude-cli-tools.js +216 -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 +76 -14
  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 +258 -244
  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 +192 -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/routes/system-routes.ts +1 -1
  122. package/ccw/src/core/server.ts +15 -1
  123. package/ccw/src/mcp-server/index.ts +1 -1
  124. package/ccw/src/templates/dashboard-css/12-cli-legacy.css +44 -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 +323 -9
  128. package/ccw/src/templates/dashboard-js/components/navigation.js +329 -313
  129. package/ccw/src/templates/dashboard-js/i18n.js +583 -1
  130. package/ccw/src/templates/dashboard-js/views/api-settings.js +3362 -0
  131. package/ccw/src/templates/dashboard-js/views/cli-manager.js +199 -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 +300 -0
  135. package/ccw/src/tools/cli-executor.ts +83 -14
  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 +378 -23
  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/__pycache__/path_mapper.cpython-313.pyc +0 -0
  204. package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_004_dual_fts.cpython-313.pyc +0 -0
  205. package/codex-lens/src/codexlens/storage/path_mapper.py +27 -1
  206. package/package.json +15 -5
  207. package/.codex/prompts.zip +0 -0
  208. package/ccw/package.json +0 -65
@@ -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 };