@deimoscloud/coreai 0.1.9 → 0.1.10

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 (196) hide show
  1. package/dist/cli/index.js +1 -1
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/package.json +6 -1
  6. package/.prettierrc +0 -9
  7. package/AGENT_SPEC.md +0 -347
  8. package/ARCHITECTURE.md +0 -547
  9. package/DRAFT_PRD.md +0 -1440
  10. package/IMPLEMENTATION_PLAN.md +0 -256
  11. package/PRODUCT.md +0 -473
  12. package/WORKFLOWS.md +0 -295
  13. package/commands/core/check-inbox.md +0 -34
  14. package/commands/core/delegate.md +0 -30
  15. package/commands/core/git-commit.md +0 -144
  16. package/commands/core/pr-create.md +0 -193
  17. package/commands/core/review.md +0 -56
  18. package/commands/core/sprint-status.md +0 -65
  19. package/commands/optional/docs-update.md +0 -200
  20. package/commands/optional/jira-create.md +0 -200
  21. package/commands/optional/jira-transition.md +0 -184
  22. package/commands/optional/worktree-cleanup.md +0 -167
  23. package/commands/optional/worktree-setup.md +0 -110
  24. package/eslint.config.js +0 -29
  25. package/jest.config.js +0 -22
  26. package/knowledge-library/README.md +0 -118
  27. package/knowledge-library/android-engineer/context/current.txt +0 -42
  28. package/knowledge-library/android-engineer/control/decisions.txt +0 -9
  29. package/knowledge-library/android-engineer/control/dependencies.txt +0 -19
  30. package/knowledge-library/android-engineer/control/objectives.txt +0 -26
  31. package/knowledge-library/android-engineer/history/.gitkeep +0 -0
  32. package/knowledge-library/android-engineer/inbox/processed/.gitkeep +0 -0
  33. package/knowledge-library/android-engineer/outbox/.gitkeep +0 -0
  34. package/knowledge-library/android-engineer/tech/.gitkeep +0 -0
  35. package/knowledge-library/architecture.txt +0 -61
  36. package/knowledge-library/backend-engineer/context/current.txt +0 -42
  37. package/knowledge-library/backend-engineer/control/decisions.txt +0 -9
  38. package/knowledge-library/backend-engineer/control/dependencies.txt +0 -19
  39. package/knowledge-library/backend-engineer/control/objectives.txt +0 -26
  40. package/knowledge-library/backend-engineer/history/.gitkeep +0 -0
  41. package/knowledge-library/backend-engineer/inbox/processed/.gitkeep +0 -0
  42. package/knowledge-library/backend-engineer/outbox/.gitkeep +0 -0
  43. package/knowledge-library/backend-engineer/tech/.gitkeep +0 -0
  44. package/knowledge-library/context.txt +0 -52
  45. package/knowledge-library/devops-engineer/context/current.txt +0 -42
  46. package/knowledge-library/devops-engineer/control/decisions.txt +0 -9
  47. package/knowledge-library/devops-engineer/control/dependencies.txt +0 -19
  48. package/knowledge-library/devops-engineer/control/objectives.txt +0 -26
  49. package/knowledge-library/devops-engineer/history/.gitkeep +0 -0
  50. package/knowledge-library/devops-engineer/inbox/processed/.gitkeep +0 -0
  51. package/knowledge-library/devops-engineer/outbox/.gitkeep +0 -0
  52. package/knowledge-library/devops-engineer/tech/.gitkeep +0 -0
  53. package/knowledge-library/engineering-manager/context/current.txt +0 -40
  54. package/knowledge-library/engineering-manager/control/decisions.txt +0 -9
  55. package/knowledge-library/engineering-manager/control/objectives.txt +0 -27
  56. package/knowledge-library/engineering-manager/history/.gitkeep +0 -0
  57. package/knowledge-library/engineering-manager/inbox/processed/.gitkeep +0 -0
  58. package/knowledge-library/engineering-manager/outbox/.gitkeep +0 -0
  59. package/knowledge-library/engineering-manager/tech/.gitkeep +0 -0
  60. package/knowledge-library/prd.txt +0 -81
  61. package/knowledge-library/product-manager/context/current.txt +0 -42
  62. package/knowledge-library/product-manager/control/decisions.txt +0 -9
  63. package/knowledge-library/product-manager/control/dependencies.txt +0 -19
  64. package/knowledge-library/product-manager/control/objectives.txt +0 -26
  65. package/knowledge-library/product-manager/history/.gitkeep +0 -0
  66. package/knowledge-library/product-manager/inbox/processed/.gitkeep +0 -0
  67. package/knowledge-library/product-manager/outbox/.gitkeep +0 -0
  68. package/knowledge-library/product-manager/tech/.gitkeep +0 -0
  69. package/knowledge-library/qa-engineer/context/current.txt +0 -42
  70. package/knowledge-library/qa-engineer/control/decisions.txt +0 -9
  71. package/knowledge-library/qa-engineer/control/dependencies.txt +0 -19
  72. package/knowledge-library/qa-engineer/control/objectives.txt +0 -26
  73. package/knowledge-library/qa-engineer/history/.gitkeep +0 -0
  74. package/knowledge-library/qa-engineer/inbox/processed/.gitkeep +0 -0
  75. package/knowledge-library/qa-engineer/outbox/.gitkeep +0 -0
  76. package/knowledge-library/qa-engineer/tech/.gitkeep +0 -0
  77. package/knowledge-library/security-engineer/context/current.txt +0 -42
  78. package/knowledge-library/security-engineer/control/decisions.txt +0 -9
  79. package/knowledge-library/security-engineer/control/dependencies.txt +0 -19
  80. package/knowledge-library/security-engineer/control/objectives.txt +0 -26
  81. package/knowledge-library/security-engineer/history/.gitkeep +0 -0
  82. package/knowledge-library/security-engineer/inbox/processed/.gitkeep +0 -0
  83. package/knowledge-library/security-engineer/outbox/.gitkeep +0 -0
  84. package/knowledge-library/security-engineer/tech/.gitkeep +0 -0
  85. package/knowledge-library/solutions-architect/context/current.txt +0 -42
  86. package/knowledge-library/solutions-architect/control/decisions.txt +0 -9
  87. package/knowledge-library/solutions-architect/control/dependencies.txt +0 -19
  88. package/knowledge-library/solutions-architect/control/objectives.txt +0 -26
  89. package/knowledge-library/solutions-architect/history/.gitkeep +0 -0
  90. package/knowledge-library/solutions-architect/inbox/processed/.gitkeep +0 -0
  91. package/knowledge-library/solutions-architect/outbox/.gitkeep +0 -0
  92. package/knowledge-library/solutions-architect/tech/.gitkeep +0 -0
  93. package/knowledge-library/wearos-engineer/context/current.txt +0 -42
  94. package/knowledge-library/wearos-engineer/control/decisions.txt +0 -9
  95. package/knowledge-library/wearos-engineer/control/dependencies.txt +0 -19
  96. package/knowledge-library/wearos-engineer/control/objectives.txt +0 -26
  97. package/knowledge-library/wearos-engineer/history/.gitkeep +0 -0
  98. package/knowledge-library/wearos-engineer/inbox/processed/.gitkeep +0 -0
  99. package/knowledge-library/wearos-engineer/outbox/.gitkeep +0 -0
  100. package/knowledge-library/wearos-engineer/tech/.gitkeep +0 -0
  101. package/scripts/add-agent.sh +0 -323
  102. package/scripts/install.sh +0 -354
  103. package/src/adapters/factory.test.ts +0 -386
  104. package/src/adapters/factory.ts +0 -305
  105. package/src/adapters/index.ts +0 -113
  106. package/src/adapters/interfaces.ts +0 -268
  107. package/src/adapters/mcp/client.test.ts +0 -130
  108. package/src/adapters/mcp/client.ts +0 -451
  109. package/src/adapters/mcp/discovery.test.ts +0 -315
  110. package/src/adapters/mcp/discovery.ts +0 -340
  111. package/src/adapters/mcp/index.ts +0 -66
  112. package/src/adapters/mcp/mapper.test.ts +0 -218
  113. package/src/adapters/mcp/mapper.ts +0 -536
  114. package/src/adapters/mcp/registry.test.ts +0 -433
  115. package/src/adapters/mcp/registry.ts +0 -550
  116. package/src/adapters/mcp/types.ts +0 -258
  117. package/src/adapters/native/filesystem.test.ts +0 -350
  118. package/src/adapters/native/filesystem.ts +0 -393
  119. package/src/adapters/native/github.test.ts +0 -173
  120. package/src/adapters/native/github.ts +0 -627
  121. package/src/adapters/native/index.ts +0 -22
  122. package/src/adapters/native/selector.test.ts +0 -224
  123. package/src/adapters/native/selector.ts +0 -150
  124. package/src/adapters/types.ts +0 -270
  125. package/src/agents/compiler.test.ts +0 -410
  126. package/src/agents/compiler.ts +0 -424
  127. package/src/agents/index.ts +0 -37
  128. package/src/agents/loader.test.ts +0 -319
  129. package/src/agents/loader.ts +0 -143
  130. package/src/agents/resolver.test.ts +0 -282
  131. package/src/agents/resolver.ts +0 -262
  132. package/src/agents/types.ts +0 -97
  133. package/src/cache/index.ts +0 -38
  134. package/src/cache/interfaces.ts +0 -283
  135. package/src/cache/manager.test.ts +0 -266
  136. package/src/cache/manager.ts +0 -388
  137. package/src/cache/provider.test.ts +0 -485
  138. package/src/cache/provider.ts +0 -745
  139. package/src/cache/types.test.ts +0 -192
  140. package/src/cache/types.ts +0 -313
  141. package/src/cli/commands/build.test.ts +0 -248
  142. package/src/cli/commands/build.ts +0 -284
  143. package/src/cli/commands/cache.test.ts +0 -221
  144. package/src/cli/commands/cache.ts +0 -229
  145. package/src/cli/commands/index.ts +0 -63
  146. package/src/cli/commands/init.test.ts +0 -173
  147. package/src/cli/commands/init.ts +0 -296
  148. package/src/cli/commands/skills.test.ts +0 -272
  149. package/src/cli/commands/skills.ts +0 -348
  150. package/src/cli/commands/status.test.ts +0 -392
  151. package/src/cli/commands/status.ts +0 -332
  152. package/src/cli/commands/sync.test.ts +0 -213
  153. package/src/cli/commands/sync.ts +0 -251
  154. package/src/cli/commands/validate.test.ts +0 -216
  155. package/src/cli/commands/validate.ts +0 -340
  156. package/src/cli/index.test.ts +0 -190
  157. package/src/cli/index.ts +0 -493
  158. package/src/commands/context.test.ts +0 -163
  159. package/src/commands/context.ts +0 -111
  160. package/src/commands/index.ts +0 -56
  161. package/src/commands/loader.test.ts +0 -273
  162. package/src/commands/loader.ts +0 -355
  163. package/src/commands/registry.test.ts +0 -384
  164. package/src/commands/registry.ts +0 -248
  165. package/src/commands/runner.test.ts +0 -297
  166. package/src/commands/runner.ts +0 -222
  167. package/src/commands/types.ts +0 -361
  168. package/src/config/index.ts +0 -19
  169. package/src/config/loader.test.ts +0 -262
  170. package/src/config/loader.ts +0 -188
  171. package/src/config/types.ts +0 -154
  172. package/src/context/index.ts +0 -14
  173. package/src/context/loader.test.ts +0 -334
  174. package/src/context/loader.ts +0 -357
  175. package/src/index.test.ts +0 -13
  176. package/src/index.ts +0 -268
  177. package/src/knowledge-library/index.ts +0 -44
  178. package/src/knowledge-library/manager.test.ts +0 -536
  179. package/src/knowledge-library/manager.ts +0 -804
  180. package/src/knowledge-library/types.ts +0 -432
  181. package/src/skills/generator.test.ts +0 -602
  182. package/src/skills/generator.ts +0 -491
  183. package/src/skills/index.ts +0 -27
  184. package/src/skills/templates.ts +0 -520
  185. package/src/skills/types.ts +0 -251
  186. package/templates/completion-report.md +0 -72
  187. package/templates/feedback.md +0 -56
  188. package/templates/project-files/CLAUDE.md.template +0 -109
  189. package/templates/project-files/coreai.json.example +0 -47
  190. package/templates/project-files/mcp.json.template +0 -20
  191. package/templates/review-complete.md +0 -64
  192. package/templates/review-request.md +0 -67
  193. package/templates/task-assignment.md +0 -51
  194. package/tsconfig.build.json +0 -4
  195. package/tsconfig.json +0 -26
  196. package/tsup.config.ts +0 -23
@@ -1,745 +0,0 @@
1
- /**
2
- * File-based Cache Provider
3
- *
4
- * Implements CacheProvider using the local filesystem.
5
- * Stores content in files with separate metadata JSON files.
6
- */
7
-
8
- import { promises as fs } from 'fs';
9
- import { join, dirname } from 'path';
10
- import { createHash } from 'crypto';
11
- import type {
12
- CacheEntry,
13
- CacheMetadata,
14
- CacheOptions,
15
- CacheListOptions,
16
- CacheStats,
17
- CacheStatus,
18
- CacheSource,
19
- } from './types.js';
20
- import { CacheError, CACHE_PATHS, DEFAULT_CACHE_CONFIG } from './types.js';
21
- import type { CacheProvider } from './interfaces.js';
22
-
23
- /**
24
- * No-op function for ignoring errors
25
- */
26
- function noop(): void {
27
- // Intentionally empty
28
- }
29
-
30
- /**
31
- * Options for creating a file cache provider
32
- */
33
- export interface FileCacheProviderOptions {
34
- /**
35
- * Base path for cache storage (project root)
36
- */
37
- basePath: string;
38
-
39
- /**
40
- * Default TTL in seconds
41
- */
42
- ttl?: number;
43
-
44
- /**
45
- * Maximum cache size in bytes
46
- */
47
- maxSize?: number;
48
-
49
- /**
50
- * Maximum number of entries
51
- */
52
- maxEntries?: number;
53
- }
54
-
55
- /**
56
- * Cache index structure for fast lookups
57
- */
58
- interface CacheIndex {
59
- version: number;
60
- entries: Record<string, CacheIndexEntry>;
61
- stats: {
62
- totalSize: number;
63
- entryCount: number;
64
- lastCleanup: string | null;
65
- };
66
- }
67
-
68
- /**
69
- * Index entry for quick access
70
- */
71
- interface CacheIndexEntry {
72
- key: string;
73
- source: CacheSource;
74
- cachedAt: string;
75
- expiresAt: string;
76
- size: number;
77
- contentFile: string;
78
- metadataFile: string;
79
- }
80
-
81
- const INDEX_VERSION = 1;
82
-
83
- /**
84
- * File-based cache provider implementation
85
- */
86
- export class FileCacheProvider implements CacheProvider {
87
- private basePath: string;
88
- private cachePath: string;
89
- private contentPath: string;
90
- private metadataPath: string;
91
- private indexPath: string;
92
- private ttl: number;
93
- private maxSize: number;
94
- private maxEntries: number;
95
- private initialized = false;
96
- private index: CacheIndex | null = null;
97
-
98
- constructor(options: FileCacheProviderOptions) {
99
- this.basePath = options.basePath;
100
- this.cachePath = join(this.basePath, CACHE_PATHS.ROOT);
101
- this.contentPath = join(this.basePath, CACHE_PATHS.CONTENT);
102
- this.metadataPath = join(this.basePath, CACHE_PATHS.METADATA);
103
- this.indexPath = join(this.basePath, CACHE_PATHS.INDEX);
104
- this.ttl = options.ttl ?? DEFAULT_CACHE_CONFIG.ttl;
105
- this.maxSize = options.maxSize ?? DEFAULT_CACHE_CONFIG.maxSize;
106
- this.maxEntries = options.maxEntries ?? DEFAULT_CACHE_CONFIG.maxEntries;
107
- }
108
-
109
- /**
110
- * Initialize the cache (create directories, load index)
111
- */
112
- async initialize(): Promise<void> {
113
- if (this.initialized) return;
114
-
115
- try {
116
- // Create cache directories
117
- await fs.mkdir(this.contentPath, { recursive: true });
118
- await fs.mkdir(this.metadataPath, { recursive: true });
119
-
120
- // Load or create index
121
- this.index = await this.loadIndex();
122
-
123
- // Save index to ensure file exists
124
- await this.saveIndex();
125
-
126
- this.initialized = true;
127
- } catch (error) {
128
- throw new CacheError(
129
- `Failed to initialize cache: ${error instanceof Error ? error.message : String(error)}`,
130
- 'write_failed',
131
- undefined,
132
- error instanceof Error ? error : undefined
133
- );
134
- }
135
- }
136
-
137
- /**
138
- * Check if the cache is initialized
139
- */
140
- isInitialized(): boolean {
141
- return this.initialized;
142
- }
143
-
144
- /**
145
- * Get a cached entry by key
146
- */
147
- async get<T = string>(key: string, options?: CacheOptions): Promise<CacheEntry<T> | null> {
148
- this.ensureInitialized();
149
-
150
- if (options?.skipCache) {
151
- return null;
152
- }
153
-
154
- const indexEntry = this.index?.entries[key];
155
- if (!indexEntry) {
156
- return null;
157
- }
158
-
159
- // Check expiration (unless force refresh)
160
- if (!options?.forceRefresh) {
161
- const status = this.getEntryStatus(indexEntry);
162
- if (status === 'expired') {
163
- return null;
164
- }
165
- }
166
-
167
- try {
168
- // Read metadata
169
- const metadataContent = await fs.readFile(indexEntry.metadataFile, 'utf-8');
170
- const metadata: CacheMetadata = JSON.parse(metadataContent);
171
-
172
- // Read content
173
- const contentRaw = await fs.readFile(indexEntry.contentFile, 'utf-8');
174
- let content: T;
175
-
176
- // Try to parse as JSON if content type indicates it
177
- if (metadata.contentType.includes('json')) {
178
- content = JSON.parse(contentRaw) as T;
179
- } else {
180
- content = contentRaw as T;
181
- }
182
-
183
- return { metadata, content };
184
- } catch {
185
- // Entry exists in index but files are missing - clean up
186
- await this.delete(key);
187
- return null;
188
- }
189
- }
190
-
191
- /**
192
- * Get the content only (convenience method)
193
- */
194
- async getContent<T = string>(key: string, options?: CacheOptions): Promise<T | null> {
195
- const entry = await this.get<T>(key, options);
196
- return entry?.content ?? null;
197
- }
198
-
199
- /**
200
- * Set a cache entry
201
- */
202
- async set<T = string>(
203
- key: string,
204
- content: T,
205
- metadata: Partial<CacheMetadata>,
206
- options?: CacheOptions
207
- ): Promise<void> {
208
- this.ensureInitialized();
209
-
210
- const now = new Date();
211
- const ttl = options?.ttl ?? this.ttl;
212
- const expiresAt = new Date(now.getTime() + ttl * 1000);
213
-
214
- // Serialize content
215
- const contentStr = typeof content === 'string' ? content : JSON.stringify(content, null, 2);
216
- const contentHash = this.hashContent(contentStr);
217
- const size = Buffer.byteLength(contentStr, 'utf-8');
218
-
219
- // Generate file paths
220
- const safeKey = this.sanitizeKey(key);
221
- const contentFile = join(this.contentPath, `${safeKey}.cache`);
222
- const metadataFile = join(this.metadataPath, `${safeKey}.json`);
223
-
224
- // Build full metadata
225
- const fullMetadata: CacheMetadata = {
226
- key,
227
- source: metadata.source ?? 'custom',
228
- sourceUrl: metadata.sourceUrl ?? '',
229
- cachedAt: now.toISOString(),
230
- expiresAt: expiresAt.toISOString(),
231
- contentHash,
232
- size,
233
- contentType:
234
- metadata.contentType ?? (typeof content === 'string' ? 'text/plain' : 'application/json'),
235
- };
236
- // Conditionally add optional properties
237
- if (metadata.etag) {
238
- fullMetadata.etag = metadata.etag;
239
- }
240
- if (metadata.title) {
241
- fullMetadata.title = metadata.title;
242
- }
243
- if (metadata.lastModified) {
244
- fullMetadata.lastModified = metadata.lastModified;
245
- }
246
- const tags = options?.tags ?? metadata.tags;
247
- if (tags) {
248
- fullMetadata.tags = tags;
249
- }
250
-
251
- try {
252
- // Ensure directories exist
253
- await fs.mkdir(dirname(contentFile), { recursive: true });
254
- await fs.mkdir(dirname(metadataFile), { recursive: true });
255
-
256
- // Write files
257
- await fs.writeFile(contentFile, contentStr, 'utf-8');
258
- await fs.writeFile(metadataFile, JSON.stringify(fullMetadata, null, 2), 'utf-8');
259
-
260
- // Update index
261
- if (this.index) {
262
- // Remove old size from stats if updating
263
- const oldEntry = this.index.entries[key];
264
- if (oldEntry) {
265
- this.index.stats.totalSize -= oldEntry.size;
266
- } else {
267
- this.index.stats.entryCount++;
268
- }
269
-
270
- this.index.entries[key] = {
271
- key,
272
- source: fullMetadata.source,
273
- cachedAt: fullMetadata.cachedAt,
274
- expiresAt: fullMetadata.expiresAt,
275
- size,
276
- contentFile,
277
- metadataFile,
278
- };
279
- this.index.stats.totalSize += size;
280
-
281
- await this.saveIndex();
282
- }
283
- } catch (error) {
284
- throw new CacheError(
285
- `Failed to write cache entry: ${error instanceof Error ? error.message : String(error)}`,
286
- 'write_failed',
287
- key,
288
- error instanceof Error ? error : undefined
289
- );
290
- }
291
- }
292
-
293
- /**
294
- * Check if a key exists in the cache
295
- */
296
- async has(key: string): Promise<boolean> {
297
- this.ensureInitialized();
298
- return key in (this.index?.entries ?? {});
299
- }
300
-
301
- /**
302
- * Delete a cache entry
303
- */
304
- async delete(key: string): Promise<boolean> {
305
- this.ensureInitialized();
306
-
307
- const indexEntry = this.index?.entries[key];
308
- if (!indexEntry) {
309
- return false;
310
- }
311
-
312
- try {
313
- // Delete files (ignore if already missing)
314
- await fs.unlink(indexEntry.contentFile).catch(noop);
315
- await fs.unlink(indexEntry.metadataFile).catch(noop);
316
-
317
- // Update index
318
- if (this.index) {
319
- this.index.stats.totalSize -= indexEntry.size;
320
- this.index.stats.entryCount--;
321
- // Remove entry from index using destructuring
322
- const { [key]: _removed, ...remaining } = this.index.entries;
323
- this.index.entries = remaining;
324
- await this.saveIndex();
325
- }
326
-
327
- return true;
328
- } catch (error) {
329
- throw new CacheError(
330
- `Failed to delete cache entry: ${error instanceof Error ? error.message : String(error)}`,
331
- 'write_failed',
332
- key,
333
- error instanceof Error ? error : undefined
334
- );
335
- }
336
- }
337
-
338
- /**
339
- * Get the status of a cache entry
340
- */
341
- async getStatus(key: string): Promise<CacheStatus | null> {
342
- this.ensureInitialized();
343
-
344
- const indexEntry = this.index?.entries[key];
345
- if (!indexEntry) {
346
- return null;
347
- }
348
-
349
- return this.getEntryStatus(indexEntry);
350
- }
351
-
352
- /**
353
- * Get metadata for a cache entry
354
- */
355
- async getMetadata(key: string): Promise<CacheMetadata | null> {
356
- this.ensureInitialized();
357
-
358
- const indexEntry = this.index?.entries[key];
359
- if (!indexEntry) {
360
- return null;
361
- }
362
-
363
- try {
364
- const content = await fs.readFile(indexEntry.metadataFile, 'utf-8');
365
- return JSON.parse(content);
366
- } catch {
367
- return null;
368
- }
369
- }
370
-
371
- /**
372
- * Update metadata for a cache entry
373
- */
374
- async updateMetadata(key: string, metadata: Partial<CacheMetadata>): Promise<void> {
375
- this.ensureInitialized();
376
-
377
- const existing = await this.getMetadata(key);
378
- if (!existing) {
379
- throw new CacheError(`Cache entry not found: ${key}`, 'not_found', key);
380
- }
381
-
382
- const indexEntry = this.index?.entries[key];
383
- if (!indexEntry) {
384
- throw new CacheError(`Cache entry not found: ${key}`, 'not_found', key);
385
- }
386
-
387
- const updated: CacheMetadata = {
388
- ...existing,
389
- ...metadata,
390
- key: existing.key, // Can't change key
391
- };
392
-
393
- try {
394
- await fs.writeFile(indexEntry.metadataFile, JSON.stringify(updated, null, 2), 'utf-8');
395
-
396
- // Update index if relevant fields changed
397
- if (this.index && indexEntry) {
398
- if (metadata.source) indexEntry.source = metadata.source;
399
- if (metadata.expiresAt) indexEntry.expiresAt = metadata.expiresAt;
400
- await this.saveIndex();
401
- }
402
- } catch (error) {
403
- throw new CacheError(
404
- `Failed to update metadata: ${error instanceof Error ? error.message : String(error)}`,
405
- 'write_failed',
406
- key,
407
- error instanceof Error ? error : undefined
408
- );
409
- }
410
- }
411
-
412
- /**
413
- * List cache entries
414
- */
415
- async list(options?: CacheListOptions): Promise<CacheMetadata[]> {
416
- this.ensureInitialized();
417
-
418
- if (!this.index) return [];
419
-
420
- let entries = Object.values(this.index.entries);
421
-
422
- // Apply filters
423
- if (options?.source) {
424
- entries = entries.filter((e) => e.source === options.source);
425
- }
426
-
427
- if (options?.status) {
428
- entries = entries.filter((e) => this.getEntryStatus(e) === options.status);
429
- }
430
-
431
- // Load full metadata for tag filtering and return
432
- const metadataPromises = entries.map(async (e) => {
433
- const metadata = await this.getMetadata(e.key);
434
- return metadata;
435
- });
436
-
437
- let results = (await Promise.all(metadataPromises)).filter(
438
- (m): m is CacheMetadata => m !== null
439
- );
440
-
441
- // Filter by tag if specified
442
- if (options?.tag) {
443
- const tag = options.tag;
444
- results = results.filter((m) => m.tags?.includes(tag));
445
- }
446
-
447
- // Apply limit
448
- if (options?.limit && results.length > options.limit) {
449
- results = results.slice(0, options.limit);
450
- }
451
-
452
- return results;
453
- }
454
-
455
- /**
456
- * Get cache statistics
457
- */
458
- async getStats(): Promise<CacheStats> {
459
- this.ensureInitialized();
460
-
461
- if (!this.index) {
462
- return {
463
- totalEntries: 0,
464
- totalSize: 0,
465
- validEntries: 0,
466
- staleEntries: 0,
467
- expiredEntries: 0,
468
- bySource: {
469
- confluence: 0,
470
- github: 0,
471
- notion: 0,
472
- local: 0,
473
- custom: 0,
474
- },
475
- };
476
- }
477
-
478
- const entries = Object.values(this.index.entries);
479
- const bySource: Record<CacheSource, number> = {
480
- confluence: 0,
481
- github: 0,
482
- notion: 0,
483
- local: 0,
484
- custom: 0,
485
- };
486
-
487
- let validEntries = 0;
488
- let staleEntries = 0;
489
- let expiredEntries = 0;
490
- let oldestEntry: string | undefined;
491
- let newestEntry: string | undefined;
492
-
493
- for (const entry of entries) {
494
- bySource[entry.source]++;
495
-
496
- const status = this.getEntryStatus(entry);
497
- if (status === 'valid') validEntries++;
498
- else if (status === 'stale') staleEntries++;
499
- else if (status === 'expired') expiredEntries++;
500
-
501
- if (!oldestEntry || entry.cachedAt < oldestEntry) {
502
- oldestEntry = entry.cachedAt;
503
- }
504
- if (!newestEntry || entry.cachedAt > newestEntry) {
505
- newestEntry = entry.cachedAt;
506
- }
507
- }
508
-
509
- const stats: CacheStats = {
510
- totalEntries: this.index.stats.entryCount,
511
- totalSize: this.index.stats.totalSize,
512
- validEntries,
513
- staleEntries,
514
- expiredEntries,
515
- bySource,
516
- };
517
-
518
- if (oldestEntry) stats.oldestEntry = oldestEntry;
519
- if (newestEntry) stats.newestEntry = newestEntry;
520
-
521
- return stats;
522
- }
523
-
524
- /**
525
- * Clear all cache entries
526
- */
527
- async clear(): Promise<number> {
528
- this.ensureInitialized();
529
-
530
- if (!this.index) return 0;
531
-
532
- const count = this.index.stats.entryCount;
533
-
534
- try {
535
- // Delete all content and metadata files
536
- await fs.rm(this.contentPath, { recursive: true, force: true });
537
- await fs.rm(this.metadataPath, { recursive: true, force: true });
538
-
539
- // Recreate directories
540
- await fs.mkdir(this.contentPath, { recursive: true });
541
- await fs.mkdir(this.metadataPath, { recursive: true });
542
-
543
- // Reset index
544
- this.index = this.createEmptyIndex();
545
- await this.saveIndex();
546
-
547
- return count;
548
- } catch (error) {
549
- throw new CacheError(
550
- `Failed to clear cache: ${error instanceof Error ? error.message : String(error)}`,
551
- 'write_failed',
552
- undefined,
553
- error instanceof Error ? error : undefined
554
- );
555
- }
556
- }
557
-
558
- /**
559
- * Clear expired entries only
560
- */
561
- async clearExpired(): Promise<number> {
562
- this.ensureInitialized();
563
-
564
- if (!this.index) return 0;
565
-
566
- const expiredKeys = Object.entries(this.index.entries)
567
- .filter(([, entry]) => this.getEntryStatus(entry) === 'expired')
568
- .map(([key]) => key);
569
-
570
- for (const key of expiredKeys) {
571
- await this.delete(key);
572
- }
573
-
574
- if (this.index) {
575
- this.index.stats.lastCleanup = new Date().toISOString();
576
- await this.saveIndex();
577
- }
578
-
579
- return expiredKeys.length;
580
- }
581
-
582
- /**
583
- * Clear entries by source
584
- */
585
- async clearBySource(source: string): Promise<number> {
586
- this.ensureInitialized();
587
-
588
- if (!this.index) return 0;
589
-
590
- const keys = Object.entries(this.index.entries)
591
- .filter(([, entry]) => entry.source === source)
592
- .map(([key]) => key);
593
-
594
- for (const key of keys) {
595
- await this.delete(key);
596
- }
597
-
598
- return keys.length;
599
- }
600
-
601
- /**
602
- * Clear entries by tag
603
- */
604
- async clearByTag(tag: string): Promise<number> {
605
- this.ensureInitialized();
606
-
607
- const entries = await this.list({ tag });
608
- for (const entry of entries) {
609
- await this.delete(entry.key);
610
- }
611
-
612
- return entries.length;
613
- }
614
-
615
- // Private helpers
616
-
617
- private ensureInitialized(): void {
618
- if (!this.initialized) {
619
- throw new CacheError('Cache not initialized. Call initialize() first.', 'invalid_config');
620
- }
621
- }
622
-
623
- private async loadIndex(): Promise<CacheIndex> {
624
- try {
625
- const content = await fs.readFile(this.indexPath, 'utf-8');
626
- const index = JSON.parse(content) as CacheIndex;
627
-
628
- // Validate version
629
- if (index.version !== INDEX_VERSION) {
630
- // Index version mismatch - rebuild
631
- return this.rebuildIndex();
632
- }
633
-
634
- return index;
635
- } catch {
636
- // Index doesn't exist or is invalid - create new one
637
- return this.createEmptyIndex();
638
- }
639
- }
640
-
641
- private async saveIndex(): Promise<void> {
642
- if (!this.index) return;
643
-
644
- try {
645
- await fs.writeFile(this.indexPath, JSON.stringify(this.index, null, 2), 'utf-8');
646
- } catch (error) {
647
- throw new CacheError(
648
- `Failed to save cache index: ${error instanceof Error ? error.message : String(error)}`,
649
- 'write_failed',
650
- undefined,
651
- error instanceof Error ? error : undefined
652
- );
653
- }
654
- }
655
-
656
- private createEmptyIndex(): CacheIndex {
657
- return {
658
- version: INDEX_VERSION,
659
- entries: {},
660
- stats: {
661
- totalSize: 0,
662
- entryCount: 0,
663
- lastCleanup: null,
664
- },
665
- };
666
- }
667
-
668
- private async rebuildIndex(): Promise<CacheIndex> {
669
- const index = this.createEmptyIndex();
670
-
671
- try {
672
- const metadataFiles = await fs.readdir(this.metadataPath);
673
-
674
- for (const file of metadataFiles) {
675
- if (!file.endsWith('.json')) continue;
676
-
677
- try {
678
- const metadataPath = join(this.metadataPath, file);
679
- const content = await fs.readFile(metadataPath, 'utf-8');
680
- const metadata: CacheMetadata = JSON.parse(content);
681
-
682
- const safeKey = this.sanitizeKey(metadata.key);
683
- const contentFile = join(this.contentPath, `${safeKey}.cache`);
684
-
685
- // Verify content file exists
686
- await fs.access(contentFile);
687
-
688
- index.entries[metadata.key] = {
689
- key: metadata.key,
690
- source: metadata.source,
691
- cachedAt: metadata.cachedAt,
692
- expiresAt: metadata.expiresAt,
693
- size: metadata.size,
694
- contentFile,
695
- metadataFile: metadataPath,
696
- };
697
-
698
- index.stats.totalSize += metadata.size;
699
- index.stats.entryCount++;
700
- } catch {
701
- // Skip invalid entries
702
- }
703
- }
704
- } catch {
705
- // Metadata directory doesn't exist or is empty
706
- }
707
-
708
- return index;
709
- }
710
-
711
- private getEntryStatus(entry: CacheIndexEntry): CacheStatus {
712
- const now = new Date();
713
- const expiresAt = new Date(entry.expiresAt);
714
- const cachedAt = new Date(entry.cachedAt);
715
-
716
- if (now > expiresAt) {
717
- return 'expired';
718
- }
719
-
720
- // Consider stale if more than 80% of TTL has passed
721
- const totalTtl = expiresAt.getTime() - cachedAt.getTime();
722
- const elapsed = now.getTime() - cachedAt.getTime();
723
- if (elapsed > totalTtl * 0.8) {
724
- return 'stale';
725
- }
726
-
727
- return 'valid';
728
- }
729
-
730
- private sanitizeKey(key: string): string {
731
- // Create a safe filename from the key
732
- return createHash('sha256').update(key).digest('hex').substring(0, 32);
733
- }
734
-
735
- private hashContent(content: string): string {
736
- return createHash('sha256').update(content).digest('hex');
737
- }
738
- }
739
-
740
- /**
741
- * Create a file cache provider
742
- */
743
- export function createFileCacheProvider(options: FileCacheProviderOptions): FileCacheProvider {
744
- return new FileCacheProvider(options);
745
- }