@nahisaho/shikigami-mcp-server 1.7.0 → 1.10.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 (114) hide show
  1. package/README.md +29 -0
  2. package/dist/cache/__tests__/global.test.d.ts +6 -0
  3. package/dist/cache/__tests__/global.test.d.ts.map +1 -0
  4. package/dist/cache/__tests__/global.test.js +269 -0
  5. package/dist/cache/__tests__/global.test.js.map +1 -0
  6. package/dist/cache/__tests__/manager.test.d.ts +6 -0
  7. package/dist/cache/__tests__/manager.test.d.ts.map +1 -0
  8. package/dist/cache/__tests__/manager.test.js +286 -0
  9. package/dist/cache/__tests__/manager.test.js.map +1 -0
  10. package/dist/cache/__tests__/semantic.test.d.ts +6 -0
  11. package/dist/cache/__tests__/semantic.test.d.ts.map +1 -0
  12. package/dist/cache/__tests__/semantic.test.js +271 -0
  13. package/dist/cache/__tests__/semantic.test.js.map +1 -0
  14. package/dist/cache/__tests__/store.test.d.ts +6 -0
  15. package/dist/cache/__tests__/store.test.d.ts.map +1 -0
  16. package/dist/cache/__tests__/store.test.js +289 -0
  17. package/dist/cache/__tests__/store.test.js.map +1 -0
  18. package/dist/cache/global.d.ts +140 -0
  19. package/dist/cache/global.d.ts.map +1 -0
  20. package/dist/cache/global.js +260 -0
  21. package/dist/cache/global.js.map +1 -0
  22. package/dist/cache/index.d.ts +10 -0
  23. package/dist/cache/index.d.ts.map +1 -0
  24. package/dist/cache/index.js +10 -0
  25. package/dist/cache/index.js.map +1 -0
  26. package/dist/cache/manager.d.ts +146 -0
  27. package/dist/cache/manager.d.ts.map +1 -0
  28. package/dist/cache/manager.js +229 -0
  29. package/dist/cache/manager.js.map +1 -0
  30. package/dist/cache/semantic.d.ts +164 -0
  31. package/dist/cache/semantic.d.ts.map +1 -0
  32. package/dist/cache/semantic.js +241 -0
  33. package/dist/cache/semantic.js.map +1 -0
  34. package/dist/cache/store.d.ts +98 -0
  35. package/dist/cache/store.d.ts.map +1 -0
  36. package/dist/cache/store.js +469 -0
  37. package/dist/cache/store.js.map +1 -0
  38. package/dist/cache/types.d.ts +171 -0
  39. package/dist/cache/types.d.ts.map +1 -0
  40. package/dist/cache/types.js +8 -0
  41. package/dist/cache/types.js.map +1 -0
  42. package/dist/config/types.d.ts +67 -0
  43. package/dist/config/types.d.ts.map +1 -1
  44. package/dist/config/types.js +30 -0
  45. package/dist/config/types.js.map +1 -1
  46. package/dist/tools/__tests__/multilingual-search.test.d.ts +7 -0
  47. package/dist/tools/__tests__/multilingual-search.test.d.ts.map +1 -0
  48. package/dist/tools/__tests__/multilingual-search.test.js +71 -0
  49. package/dist/tools/__tests__/multilingual-search.test.js.map +1 -0
  50. package/dist/tools/search/recovery/__tests__/logger.test.d.ts +8 -0
  51. package/dist/tools/search/recovery/__tests__/logger.test.d.ts.map +1 -0
  52. package/dist/tools/search/recovery/__tests__/logger.test.js +249 -0
  53. package/dist/tools/search/recovery/__tests__/logger.test.js.map +1 -0
  54. package/dist/tools/search/recovery/__tests__/manager-logger.test.d.ts +8 -0
  55. package/dist/tools/search/recovery/__tests__/manager-logger.test.d.ts.map +1 -0
  56. package/dist/tools/search/recovery/__tests__/manager-logger.test.js +158 -0
  57. package/dist/tools/search/recovery/__tests__/manager-logger.test.js.map +1 -0
  58. package/dist/tools/search/recovery/index.d.ts +31 -2
  59. package/dist/tools/search/recovery/index.d.ts.map +1 -1
  60. package/dist/tools/search/recovery/index.js +51 -7
  61. package/dist/tools/search/recovery/index.js.map +1 -1
  62. package/dist/tools/search/recovery/logger.d.ts +149 -0
  63. package/dist/tools/search/recovery/logger.d.ts.map +1 -0
  64. package/dist/tools/search/recovery/logger.js +218 -0
  65. package/dist/tools/search/recovery/logger.js.map +1 -0
  66. package/dist/tools/search.d.ts +48 -0
  67. package/dist/tools/search.d.ts.map +1 -1
  68. package/dist/tools/search.js +152 -0
  69. package/dist/tools/search.js.map +1 -1
  70. package/dist/tools/visit/recovery/__tests__/index.test.d.ts +10 -0
  71. package/dist/tools/visit/recovery/__tests__/index.test.d.ts.map +1 -0
  72. package/dist/tools/visit/recovery/__tests__/index.test.js +239 -0
  73. package/dist/tools/visit/recovery/__tests__/index.test.js.map +1 -0
  74. package/dist/tools/visit/recovery/__tests__/wayback.test.d.ts +8 -0
  75. package/dist/tools/visit/recovery/__tests__/wayback.test.d.ts.map +1 -0
  76. package/dist/tools/visit/recovery/__tests__/wayback.test.js +271 -0
  77. package/dist/tools/visit/recovery/__tests__/wayback.test.js.map +1 -0
  78. package/dist/tools/visit/recovery/index.d.ts +126 -0
  79. package/dist/tools/visit/recovery/index.d.ts.map +1 -0
  80. package/dist/tools/visit/recovery/index.js +203 -0
  81. package/dist/tools/visit/recovery/index.js.map +1 -0
  82. package/dist/tools/visit/recovery/wayback.d.ts +101 -0
  83. package/dist/tools/visit/recovery/wayback.d.ts.map +1 -0
  84. package/dist/tools/visit/recovery/wayback.js +140 -0
  85. package/dist/tools/visit/recovery/wayback.js.map +1 -0
  86. package/dist/tools/visit.d.ts +33 -0
  87. package/dist/tools/visit.d.ts.map +1 -1
  88. package/dist/tools/visit.js +127 -1
  89. package/dist/tools/visit.js.map +1 -1
  90. package/package.json +7 -3
  91. package/shikigami.config.example.yaml +9 -0
  92. package/src/cache/__tests__/global.test.ts +340 -0
  93. package/src/cache/__tests__/manager.test.ts +353 -0
  94. package/src/cache/__tests__/semantic.test.ts +331 -0
  95. package/src/cache/__tests__/store.test.ts +369 -0
  96. package/src/cache/global.ts +351 -0
  97. package/src/cache/index.ts +10 -0
  98. package/src/cache/manager.ts +325 -0
  99. package/src/cache/semantic.ts +368 -0
  100. package/src/cache/store.ts +555 -0
  101. package/src/cache/types.ts +189 -0
  102. package/src/config/types.ts +108 -0
  103. package/src/tools/__tests__/multilingual-search.test.ts +88 -0
  104. package/src/tools/search/recovery/__tests__/logger.test.ts +334 -0
  105. package/src/tools/search/recovery/__tests__/manager-logger.test.ts +199 -0
  106. package/src/tools/search/recovery/index.ts +67 -9
  107. package/src/tools/search/recovery/logger.ts +351 -0
  108. package/src/tools/search.ts +212 -0
  109. package/src/tools/visit/recovery/__tests__/index.test.ts +297 -0
  110. package/src/tools/visit/recovery/__tests__/wayback.test.ts +344 -0
  111. package/src/tools/visit/recovery/index.ts +312 -0
  112. package/src/tools/visit/recovery/wayback.ts +210 -0
  113. package/src/tools/visit.ts +159 -2
  114. package/vitest.config.ts +22 -0
@@ -0,0 +1,351 @@
1
+ /**
2
+ * Global Cache Store
3
+ * v1.0.0 - REQ-CACHE-001-03
4
+ *
5
+ * ユーザー横断のグローバルキャッシュを管理
6
+ * ~/.shikigami/global-cache/ に保存
7
+ */
8
+
9
+ import * as os from 'os';
10
+ import * as path from 'path';
11
+ import { FileCacheStore } from './store.js';
12
+ import { CacheConfig, CacheStats, CacheSource, CacheSetOptions, CacheResult } from './types.js';
13
+
14
+ /**
15
+ * グローバルキャッシュ設定
16
+ */
17
+ export interface GlobalCacheConfig extends Partial<CacheConfig> {
18
+ /** グローバルキャッシュの有効化 */
19
+ enabled: boolean;
20
+ /** プロジェクト固有キャッシュを優先するか */
21
+ preferProjectCache: boolean;
22
+ /** グローバルキャッシュのベースディレクトリ(デフォルト: ~/.shikigami) */
23
+ baseDir?: string;
24
+ }
25
+
26
+ /**
27
+ * デフォルトグローバルキャッシュ設定
28
+ */
29
+ export const DEFAULT_GLOBAL_CACHE_CONFIG: GlobalCacheConfig = {
30
+ enabled: true,
31
+ preferProjectCache: true,
32
+ maxEntries: 5000,
33
+ defaultTtlSeconds: 3600 * 24 * 7, // 1週間
34
+ maxSizeBytes: 500 * 1024 * 1024, // 500MB
35
+ };
36
+
37
+ /**
38
+ * キャッシュスコープ
39
+ */
40
+ export type CacheScope = 'global' | 'project';
41
+
42
+ /**
43
+ * スコープ付きキャッシュ結果
44
+ */
45
+ export interface ScopedCacheResult<T = unknown> extends CacheResult<T> {
46
+ /** キャッシュが見つかったスコープ */
47
+ scope?: CacheScope;
48
+ }
49
+
50
+ /**
51
+ * GlobalCacheStore
52
+ * ユーザー横断のグローバルキャッシュを管理
53
+ */
54
+ export class GlobalCacheStore {
55
+ private globalStore: FileCacheStore;
56
+ private projectStore: FileCacheStore | null = null;
57
+ private config: GlobalCacheConfig;
58
+ private globalCacheDir: string;
59
+
60
+ constructor(config: Partial<GlobalCacheConfig> = {}) {
61
+ this.config = { ...DEFAULT_GLOBAL_CACHE_CONFIG, ...config };
62
+
63
+ // グローバルキャッシュディレクトリを設定
64
+ const baseDir = this.config.baseDir || path.join(os.homedir(), '.shikigami');
65
+ this.globalCacheDir = path.join(baseDir, 'global-cache');
66
+
67
+ // グローバルストアを初期化
68
+ this.globalStore = new FileCacheStore({
69
+ cacheDir: this.globalCacheDir,
70
+ maxEntries: this.config.maxEntries,
71
+ defaultTtlSeconds: this.config.defaultTtlSeconds,
72
+ maxSizeBytes: this.config.maxSizeBytes,
73
+ });
74
+ }
75
+
76
+ /**
77
+ * プロジェクト固有のキャッシュストアを設定
78
+ */
79
+ setProjectStore(projectCacheDir: string): void {
80
+ this.projectStore = new FileCacheStore({
81
+ cacheDir: projectCacheDir,
82
+ maxEntries: this.config.maxEntries,
83
+ defaultTtlSeconds: this.config.defaultTtlSeconds,
84
+ maxSizeBytes: this.config.maxSizeBytes,
85
+ });
86
+ }
87
+
88
+ /**
89
+ * プロジェクトストアをクリア
90
+ */
91
+ clearProjectStore(): void {
92
+ this.projectStore = null;
93
+ }
94
+
95
+ /**
96
+ * キャッシュエントリを取得(スコープ優先順位に従う)
97
+ */
98
+ async get<T = unknown>(key: string): Promise<ScopedCacheResult<T>> {
99
+ // プロジェクトキャッシュを優先する場合
100
+ if (this.config.preferProjectCache && this.projectStore) {
101
+ const projectResult = await this.projectStore.get<T>(key);
102
+ if (projectResult.hit) {
103
+ return { ...projectResult, scope: 'project' };
104
+ }
105
+ }
106
+
107
+ // グローバルキャッシュから取得
108
+ if (this.config.enabled) {
109
+ const globalResult = await this.globalStore.get<T>(key);
110
+ if (globalResult.hit) {
111
+ return { ...globalResult, scope: 'global' };
112
+ }
113
+ }
114
+
115
+ // プロジェクトキャッシュを後で確認(優先しない場合)
116
+ if (!this.config.preferProjectCache && this.projectStore) {
117
+ const projectResult = await this.projectStore.get<T>(key);
118
+ if (projectResult.hit) {
119
+ return { ...projectResult, scope: 'project' };
120
+ }
121
+ }
122
+
123
+ return { hit: false, key };
124
+ }
125
+
126
+ /**
127
+ * キャッシュエントリを保存
128
+ */
129
+ async set<T = unknown>(
130
+ key: string,
131
+ value: T,
132
+ options: CacheSetOptions & { scope?: CacheScope } = {}
133
+ ): Promise<void> {
134
+ const { scope = 'global', ...cacheOptions } = options;
135
+
136
+ if (scope === 'project' && this.projectStore) {
137
+ await this.projectStore.set(key, value, cacheOptions);
138
+ } else if (scope === 'global' && this.config.enabled) {
139
+ await this.globalStore.set(key, value, cacheOptions);
140
+ }
141
+ }
142
+
143
+ /**
144
+ * キャッシュエントリを削除
145
+ */
146
+ async delete(key: string, scope?: CacheScope): Promise<boolean> {
147
+ let deleted = false;
148
+
149
+ if (scope === 'project' || scope === undefined) {
150
+ if (this.projectStore) {
151
+ deleted = (await this.projectStore.delete(key)) || deleted;
152
+ }
153
+ }
154
+
155
+ if (scope === 'global' || scope === undefined) {
156
+ if (this.config.enabled) {
157
+ deleted = (await this.globalStore.delete(key)) || deleted;
158
+ }
159
+ }
160
+
161
+ return deleted;
162
+ }
163
+
164
+ /**
165
+ * キャッシュの存在確認
166
+ */
167
+ async has(key: string): Promise<{ exists: boolean; scope?: CacheScope }> {
168
+ if (this.config.preferProjectCache && this.projectStore) {
169
+ if (await this.projectStore.has(key)) {
170
+ return { exists: true, scope: 'project' };
171
+ }
172
+ }
173
+
174
+ if (this.config.enabled && (await this.globalStore.has(key))) {
175
+ return { exists: true, scope: 'global' };
176
+ }
177
+
178
+ if (!this.config.preferProjectCache && this.projectStore) {
179
+ if (await this.projectStore.has(key)) {
180
+ return { exists: true, scope: 'project' };
181
+ }
182
+ }
183
+
184
+ return { exists: false };
185
+ }
186
+
187
+ /**
188
+ * 全エントリを削除
189
+ */
190
+ async clear(scope?: CacheScope): Promise<void> {
191
+ if (scope === 'project' || scope === undefined) {
192
+ if (this.projectStore) {
193
+ await this.projectStore.clear();
194
+ }
195
+ }
196
+
197
+ if (scope === 'global' || scope === undefined) {
198
+ if (this.config.enabled) {
199
+ await this.globalStore.clear();
200
+ }
201
+ }
202
+ }
203
+
204
+ /**
205
+ * 統計情報を取得
206
+ */
207
+ async getStats(scope?: CacheScope): Promise<{
208
+ global?: CacheStats;
209
+ project?: CacheStats;
210
+ combined?: {
211
+ totalEntries: number;
212
+ totalSizeBytes: number;
213
+ globalHitRate: number;
214
+ projectHitRate: number;
215
+ };
216
+ }> {
217
+ const result: {
218
+ global?: CacheStats;
219
+ project?: CacheStats;
220
+ combined?: {
221
+ totalEntries: number;
222
+ totalSizeBytes: number;
223
+ globalHitRate: number;
224
+ projectHitRate: number;
225
+ };
226
+ } = {};
227
+
228
+ if (scope === 'global' || scope === undefined) {
229
+ if (this.config.enabled) {
230
+ result.global = await this.globalStore.getStats();
231
+ }
232
+ }
233
+
234
+ if (scope === 'project' || scope === undefined) {
235
+ if (this.projectStore) {
236
+ result.project = await this.projectStore.getStats();
237
+ }
238
+ }
239
+
240
+ // 結合統計
241
+ if (scope === undefined && result.global) {
242
+ result.combined = {
243
+ totalEntries: (result.global?.totalEntries || 0) + (result.project?.totalEntries || 0),
244
+ totalSizeBytes:
245
+ (result.global?.totalSizeBytes || 0) + (result.project?.totalSizeBytes || 0),
246
+ globalHitRate: result.global?.hitRate || 0,
247
+ projectHitRate: result.project?.hitRate || 0,
248
+ };
249
+ }
250
+
251
+ return result;
252
+ }
253
+
254
+ /**
255
+ * 期限切れエントリを削除
256
+ */
257
+ async evictExpired(scope?: CacheScope): Promise<{ global: number; project: number }> {
258
+ let globalEvicted = 0;
259
+ let projectEvicted = 0;
260
+
261
+ if (scope === 'global' || scope === undefined) {
262
+ if (this.config.enabled) {
263
+ globalEvicted = await this.globalStore.evictExpired();
264
+ }
265
+ }
266
+
267
+ if (scope === 'project' || scope === undefined) {
268
+ if (this.projectStore) {
269
+ projectEvicted = await this.projectStore.evictExpired();
270
+ }
271
+ }
272
+
273
+ return { global: globalEvicted, project: projectEvicted };
274
+ }
275
+
276
+ /**
277
+ * グローバルキャッシュディレクトリを取得
278
+ */
279
+ getGlobalCacheDir(): string {
280
+ return this.globalCacheDir;
281
+ }
282
+
283
+ /**
284
+ * 設定を取得
285
+ */
286
+ getConfig(): GlobalCacheConfig {
287
+ return { ...this.config };
288
+ }
289
+
290
+ /**
291
+ * グローバルキャッシュの有効/無効を切り替え
292
+ */
293
+ setEnabled(enabled: boolean): void {
294
+ this.config.enabled = enabled;
295
+ }
296
+
297
+ /**
298
+ * プロジェクトキャッシュ優先設定を変更
299
+ */
300
+ setPreferProjectCache(prefer: boolean): void {
301
+ this.config.preferProjectCache = prefer;
302
+ }
303
+
304
+ /**
305
+ * プロジェクトストアが設定されているか確認
306
+ */
307
+ hasProjectStore(): boolean {
308
+ return this.projectStore !== null;
309
+ }
310
+
311
+ /**
312
+ * 基盤となるストアを取得
313
+ */
314
+ getGlobalStore(): FileCacheStore {
315
+ return this.globalStore;
316
+ }
317
+
318
+ /**
319
+ * プロジェクトストアを取得
320
+ */
321
+ getProjectStore(): FileCacheStore | null {
322
+ return this.projectStore;
323
+ }
324
+ }
325
+
326
+ // シングルトンインスタンス
327
+ let globalCacheInstance: GlobalCacheStore | null = null;
328
+
329
+ /**
330
+ * デフォルトのグローバルキャッシュインスタンスを取得
331
+ */
332
+ export function getGlobalCacheStore(config?: Partial<GlobalCacheConfig>): GlobalCacheStore {
333
+ if (!globalCacheInstance) {
334
+ globalCacheInstance = new GlobalCacheStore(config);
335
+ }
336
+ return globalCacheInstance;
337
+ }
338
+
339
+ /**
340
+ * グローバルキャッシュインスタンスをリセット(テスト用)
341
+ */
342
+ export function resetGlobalCacheStore(): void {
343
+ globalCacheInstance = null;
344
+ }
345
+
346
+ /**
347
+ * グローバルキャッシュストアを作成
348
+ */
349
+ export function createGlobalCacheStore(config?: Partial<GlobalCacheConfig>): GlobalCacheStore {
350
+ return new GlobalCacheStore(config);
351
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Cache Module Index
3
+ * v1.0.0 - REQ-CACHE-001
4
+ */
5
+
6
+ export * from './types.js';
7
+ export * from './store.js';
8
+ export * from './manager.js';
9
+ export * from './semantic.js';
10
+ export * from './global.js';
@@ -0,0 +1,325 @@
1
+ /**
2
+ * Query Cache Manager
3
+ * v1.0.0 - REQ-CACHE-001-01
4
+ *
5
+ * 検索・訪問クエリのキャッシュ管理
6
+ */
7
+
8
+ import { FileCacheStore, createCacheStore } from './store.js';
9
+ import {
10
+ CacheConfig,
11
+ CacheResult,
12
+ CacheStats,
13
+ CacheSource,
14
+ CacheSetOptions,
15
+ } from './types.js';
16
+
17
+ /**
18
+ * 検索クエリのキャッシュキー生成パラメータ
19
+ */
20
+ export interface SearchCacheKeyParams {
21
+ query: string;
22
+ engine?: string;
23
+ options?: Record<string, unknown>;
24
+ }
25
+
26
+ /**
27
+ * 訪問クエリのキャッシュキー生成パラメータ
28
+ */
29
+ export interface VisitCacheKeyParams {
30
+ url: string;
31
+ options?: {
32
+ extractImages?: boolean;
33
+ extractLinks?: boolean;
34
+ };
35
+ }
36
+
37
+ /**
38
+ * 埋め込みキャッシュキー生成パラメータ
39
+ */
40
+ export interface EmbeddingCacheKeyParams {
41
+ text: string;
42
+ model?: string;
43
+ }
44
+
45
+ /**
46
+ * キャッシュマネージャー設定
47
+ */
48
+ export interface QueryCacheManagerConfig extends Partial<CacheConfig> {
49
+ /** 自動期限切れ削除の間隔 (秒) */
50
+ evictionIntervalSeconds?: number;
51
+ /** 統計保存の間隔 (秒) */
52
+ statsSaveIntervalSeconds?: number;
53
+ }
54
+
55
+ /**
56
+ * クエリキャッシュマネージャー
57
+ * 検索、訪問、埋め込みなどのクエリ結果をキャッシュ
58
+ */
59
+ export class QueryCacheManager {
60
+ private store: FileCacheStore;
61
+ private config: QueryCacheManagerConfig;
62
+ private evictionTimer?: NodeJS.Timeout;
63
+ private statsSaveTimer?: NodeJS.Timeout;
64
+
65
+ constructor(config: QueryCacheManagerConfig = {}) {
66
+ this.config = {
67
+ evictionIntervalSeconds: 300, // 5分
68
+ statsSaveIntervalSeconds: 60, // 1分
69
+ ...config,
70
+ };
71
+ this.store = createCacheStore(config);
72
+ }
73
+
74
+ /**
75
+ * 定期的なメンテナンスタスクを開始
76
+ */
77
+ startMaintenanceTasks(): void {
78
+ // 期限切れ削除タイマー
79
+ if (this.config.evictionIntervalSeconds && this.config.evictionIntervalSeconds > 0) {
80
+ this.evictionTimer = setInterval(
81
+ () => this.store.evictExpired(),
82
+ this.config.evictionIntervalSeconds * 1000
83
+ );
84
+ }
85
+
86
+ // 統計保存タイマー
87
+ if (this.config.statsSaveIntervalSeconds && this.config.statsSaveIntervalSeconds > 0) {
88
+ this.statsSaveTimer = setInterval(
89
+ () => this.store.saveStats(),
90
+ this.config.statsSaveIntervalSeconds * 1000
91
+ );
92
+ }
93
+ }
94
+
95
+ /**
96
+ * メンテナンスタスクを停止
97
+ */
98
+ stopMaintenanceTasks(): void {
99
+ if (this.evictionTimer) {
100
+ clearInterval(this.evictionTimer);
101
+ this.evictionTimer = undefined;
102
+ }
103
+ if (this.statsSaveTimer) {
104
+ clearInterval(this.statsSaveTimer);
105
+ this.statsSaveTimer = undefined;
106
+ }
107
+ }
108
+
109
+ /**
110
+ * 検索クエリのキャッシュキーを生成
111
+ */
112
+ static generateSearchKey(params: SearchCacheKeyParams): string {
113
+ const { query, engine = 'default', options = {} } = params;
114
+ const optionsStr = Object.keys(options).length > 0 ? JSON.stringify(options) : '';
115
+ return `search:${engine}:${query}${optionsStr ? ':' + optionsStr : ''}`;
116
+ }
117
+
118
+ /**
119
+ * 訪問クエリのキャッシュキーを生成
120
+ */
121
+ static generateVisitKey(params: VisitCacheKeyParams): string {
122
+ const { url, options = {} } = params;
123
+ const optionsStr = Object.keys(options).length > 0 ? JSON.stringify(options) : '';
124
+ return `visit:${url}${optionsStr ? ':' + optionsStr : ''}`;
125
+ }
126
+
127
+ /**
128
+ * 埋め込みキャッシュキーを生成
129
+ */
130
+ static generateEmbeddingKey(params: EmbeddingCacheKeyParams): string {
131
+ const { text, model = 'default' } = params;
132
+ // テキストが長い場合はハッシュ化
133
+ const textKey = text.length > 100 ? FileCacheStore.generateQueryHash(text) : text;
134
+ return `embedding:${model}:${textKey}`;
135
+ }
136
+
137
+ /**
138
+ * 検索結果をキャッシュから取得
139
+ */
140
+ async getSearchResult<T = unknown>(params: SearchCacheKeyParams): Promise<CacheResult<T>> {
141
+ const key = QueryCacheManager.generateSearchKey(params);
142
+ return this.store.get<T>(key);
143
+ }
144
+
145
+ /**
146
+ * 検索結果をキャッシュに保存
147
+ */
148
+ async setSearchResult<T = unknown>(
149
+ params: SearchCacheKeyParams,
150
+ value: T,
151
+ options: Omit<CacheSetOptions, 'source'> = {}
152
+ ): Promise<void> {
153
+ const key = QueryCacheManager.generateSearchKey(params);
154
+ await this.store.set(key, value, { ...options, source: 'search' });
155
+ }
156
+
157
+ /**
158
+ * 訪問結果をキャッシュから取得
159
+ */
160
+ async getVisitResult<T = unknown>(params: VisitCacheKeyParams): Promise<CacheResult<T>> {
161
+ const key = QueryCacheManager.generateVisitKey(params);
162
+ return this.store.get<T>(key);
163
+ }
164
+
165
+ /**
166
+ * 訪問結果をキャッシュに保存
167
+ */
168
+ async setVisitResult<T = unknown>(
169
+ params: VisitCacheKeyParams,
170
+ value: T,
171
+ options: Omit<CacheSetOptions, 'source'> = {}
172
+ ): Promise<void> {
173
+ const key = QueryCacheManager.generateVisitKey(params);
174
+ await this.store.set(key, value, { ...options, source: 'visit' });
175
+ }
176
+
177
+ /**
178
+ * 埋め込みベクトルをキャッシュから取得
179
+ */
180
+ async getEmbedding<T = unknown>(params: EmbeddingCacheKeyParams): Promise<CacheResult<T>> {
181
+ const key = QueryCacheManager.generateEmbeddingKey(params);
182
+ return this.store.get<T>(key);
183
+ }
184
+
185
+ /**
186
+ * 埋め込みベクトルをキャッシュに保存
187
+ */
188
+ async setEmbedding<T = unknown>(
189
+ params: EmbeddingCacheKeyParams,
190
+ value: T,
191
+ options: Omit<CacheSetOptions, 'source'> = {}
192
+ ): Promise<void> {
193
+ const key = QueryCacheManager.generateEmbeddingKey(params);
194
+ await this.store.set(key, value, { ...options, source: 'embedding' });
195
+ }
196
+
197
+ /**
198
+ * 汎用的なキャッシュ取得
199
+ */
200
+ async get<T = unknown>(key: string): Promise<CacheResult<T>> {
201
+ return this.store.get<T>(key);
202
+ }
203
+
204
+ /**
205
+ * 汎用的なキャッシュ保存
206
+ */
207
+ async set<T = unknown>(key: string, value: T, options: CacheSetOptions = {}): Promise<void> {
208
+ await this.store.set(key, value, options);
209
+ }
210
+
211
+ /**
212
+ * キャッシュエントリを削除
213
+ */
214
+ async delete(key: string): Promise<boolean> {
215
+ return this.store.delete(key);
216
+ }
217
+
218
+ /**
219
+ * URLに関連するキャッシュを削除
220
+ */
221
+ async invalidateUrl(url: string): Promise<number> {
222
+ let deletedCount = 0;
223
+
224
+ // 訪問キャッシュを削除
225
+ const visitKey = QueryCacheManager.generateVisitKey({ url });
226
+ if (await this.store.delete(visitKey)) {
227
+ deletedCount++;
228
+ }
229
+
230
+ // URL関連の検索キャッシュを検索・削除
231
+ const entries = await this.store.query({ source: 'visit' });
232
+ for (const entry of entries) {
233
+ if (entry.key.includes(url)) {
234
+ await this.store.delete(entry.key);
235
+ deletedCount++;
236
+ }
237
+ }
238
+
239
+ return deletedCount;
240
+ }
241
+
242
+ /**
243
+ * 特定のソースのキャッシュをすべて削除
244
+ */
245
+ async invalidateBySource(source: CacheSource): Promise<number> {
246
+ const entries = await this.store.query({ source });
247
+ let deletedCount = 0;
248
+
249
+ for (const entry of entries) {
250
+ if (await this.store.delete(entry.key)) {
251
+ deletedCount++;
252
+ }
253
+ }
254
+
255
+ return deletedCount;
256
+ }
257
+
258
+ /**
259
+ * すべてのキャッシュをクリア
260
+ */
261
+ async clear(): Promise<void> {
262
+ await this.store.clear();
263
+ }
264
+
265
+ /**
266
+ * 統計情報を取得
267
+ */
268
+ async getStats(): Promise<CacheStats> {
269
+ return this.store.getStats();
270
+ }
271
+
272
+ /**
273
+ * 期限切れエントリを削除
274
+ */
275
+ async evictExpired(): Promise<number> {
276
+ return this.store.evictExpired();
277
+ }
278
+
279
+ /**
280
+ * キャッシュサマリーを取得(ログ用)
281
+ */
282
+ async getSummary(): Promise<string> {
283
+ const stats = await this.getStats();
284
+ const hitRatePercent = (stats.hitRate * 100).toFixed(1);
285
+ const sizeMB = (stats.totalSizeBytes / (1024 * 1024)).toFixed(2);
286
+
287
+ return [
288
+ `Cache Summary:`,
289
+ ` Entries: ${stats.totalEntries}`,
290
+ ` Size: ${sizeMB} MB`,
291
+ ` Hit Rate: ${hitRatePercent}%`,
292
+ ` Hits: ${stats.hits}, Misses: ${stats.misses}`,
293
+ ` Evictions: ${stats.expiredEvictions} (expired), ${stats.lruEvictions} (LRU)`,
294
+ ].join('\n');
295
+ }
296
+
297
+ /**
298
+ * ストアインスタンスを取得(テスト用)
299
+ */
300
+ getStore(): FileCacheStore {
301
+ return this.store;
302
+ }
303
+ }
304
+
305
+ /**
306
+ * シングルトンインスタンス
307
+ */
308
+ let defaultManager: QueryCacheManager | null = null;
309
+
310
+ /**
311
+ * デフォルトのキャッシュマネージャーを取得
312
+ */
313
+ export function getDefaultCacheManager(config?: QueryCacheManagerConfig): QueryCacheManager {
314
+ if (!defaultManager) {
315
+ defaultManager = new QueryCacheManager(config);
316
+ }
317
+ return defaultManager;
318
+ }
319
+
320
+ /**
321
+ * キャッシュマネージャーを作成
322
+ */
323
+ export function createCacheManager(config?: QueryCacheManagerConfig): QueryCacheManager {
324
+ return new QueryCacheManager(config);
325
+ }