@deimoscloud/coreai 0.1.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 (216) hide show
  1. package/.prettierrc +9 -0
  2. package/AGENT_SPEC.md +347 -0
  3. package/ARCHITECTURE.md +547 -0
  4. package/DRAFT_PRD.md +1440 -0
  5. package/IMPLEMENTATION_PLAN.md +256 -0
  6. package/PRODUCT.md +473 -0
  7. package/README.md +303 -0
  8. package/WORKFLOWS.md +295 -0
  9. package/agents/_templates/ic-engineer.md +185 -0
  10. package/agents/_templates/reviewer.md +182 -0
  11. package/agents/backend-engineer.yaml +72 -0
  12. package/agents/devops-engineer.yaml +72 -0
  13. package/agents/engineering-manager.yaml +70 -0
  14. package/agents/examples/android-engineer.md +302 -0
  15. package/agents/examples/backend-engineer.md +320 -0
  16. package/agents/examples/devops-engineer.md +742 -0
  17. package/agents/examples/engineering-manager.md +469 -0
  18. package/agents/examples/frontend-engineer.md +58 -0
  19. package/agents/examples/product-manager.md +315 -0
  20. package/agents/examples/qa-engineer.md +371 -0
  21. package/agents/examples/security-engineer.md +525 -0
  22. package/agents/examples/solutions-architect.md +351 -0
  23. package/agents/examples/wearos-engineer.md +359 -0
  24. package/agents/frontend-engineer.yaml +72 -0
  25. package/commands/core/check-inbox.md +34 -0
  26. package/commands/core/delegate.md +30 -0
  27. package/commands/core/git-commit.md +144 -0
  28. package/commands/core/pr-create.md +193 -0
  29. package/commands/core/review.md +56 -0
  30. package/commands/core/sprint-status.md +65 -0
  31. package/commands/optional/docs-update.md +200 -0
  32. package/commands/optional/jira-create.md +200 -0
  33. package/commands/optional/jira-transition.md +184 -0
  34. package/commands/optional/worktree-cleanup.md +167 -0
  35. package/commands/optional/worktree-setup.md +110 -0
  36. package/dist/cli/index.js +4037 -0
  37. package/dist/cli/index.js.map +1 -0
  38. package/dist/index.d.ts +2978 -0
  39. package/dist/index.js +3867 -0
  40. package/dist/index.js.map +1 -0
  41. package/eslint.config.js +29 -0
  42. package/jest.config.js +22 -0
  43. package/knowledge-library/README.md +118 -0
  44. package/knowledge-library/android-engineer/context/current.txt +42 -0
  45. package/knowledge-library/android-engineer/control/decisions.txt +9 -0
  46. package/knowledge-library/android-engineer/control/dependencies.txt +19 -0
  47. package/knowledge-library/android-engineer/control/objectives.txt +26 -0
  48. package/knowledge-library/android-engineer/history/.gitkeep +0 -0
  49. package/knowledge-library/android-engineer/inbox/processed/.gitkeep +0 -0
  50. package/knowledge-library/android-engineer/outbox/.gitkeep +0 -0
  51. package/knowledge-library/android-engineer/tech/.gitkeep +0 -0
  52. package/knowledge-library/architecture.txt +61 -0
  53. package/knowledge-library/backend-engineer/context/current.txt +42 -0
  54. package/knowledge-library/backend-engineer/control/decisions.txt +9 -0
  55. package/knowledge-library/backend-engineer/control/dependencies.txt +19 -0
  56. package/knowledge-library/backend-engineer/control/objectives.txt +26 -0
  57. package/knowledge-library/backend-engineer/history/.gitkeep +0 -0
  58. package/knowledge-library/backend-engineer/inbox/processed/.gitkeep +0 -0
  59. package/knowledge-library/backend-engineer/outbox/.gitkeep +0 -0
  60. package/knowledge-library/backend-engineer/tech/.gitkeep +0 -0
  61. package/knowledge-library/context.txt +52 -0
  62. package/knowledge-library/devops-engineer/context/current.txt +42 -0
  63. package/knowledge-library/devops-engineer/control/decisions.txt +9 -0
  64. package/knowledge-library/devops-engineer/control/dependencies.txt +19 -0
  65. package/knowledge-library/devops-engineer/control/objectives.txt +26 -0
  66. package/knowledge-library/devops-engineer/history/.gitkeep +0 -0
  67. package/knowledge-library/devops-engineer/inbox/processed/.gitkeep +0 -0
  68. package/knowledge-library/devops-engineer/outbox/.gitkeep +0 -0
  69. package/knowledge-library/devops-engineer/tech/.gitkeep +0 -0
  70. package/knowledge-library/engineering-manager/context/current.txt +40 -0
  71. package/knowledge-library/engineering-manager/control/decisions.txt +9 -0
  72. package/knowledge-library/engineering-manager/control/objectives.txt +27 -0
  73. package/knowledge-library/engineering-manager/history/.gitkeep +0 -0
  74. package/knowledge-library/engineering-manager/inbox/processed/.gitkeep +0 -0
  75. package/knowledge-library/engineering-manager/outbox/.gitkeep +0 -0
  76. package/knowledge-library/engineering-manager/tech/.gitkeep +0 -0
  77. package/knowledge-library/prd.txt +81 -0
  78. package/knowledge-library/product-manager/context/current.txt +42 -0
  79. package/knowledge-library/product-manager/control/decisions.txt +9 -0
  80. package/knowledge-library/product-manager/control/dependencies.txt +19 -0
  81. package/knowledge-library/product-manager/control/objectives.txt +26 -0
  82. package/knowledge-library/product-manager/history/.gitkeep +0 -0
  83. package/knowledge-library/product-manager/inbox/processed/.gitkeep +0 -0
  84. package/knowledge-library/product-manager/outbox/.gitkeep +0 -0
  85. package/knowledge-library/product-manager/tech/.gitkeep +0 -0
  86. package/knowledge-library/qa-engineer/context/current.txt +42 -0
  87. package/knowledge-library/qa-engineer/control/decisions.txt +9 -0
  88. package/knowledge-library/qa-engineer/control/dependencies.txt +19 -0
  89. package/knowledge-library/qa-engineer/control/objectives.txt +26 -0
  90. package/knowledge-library/qa-engineer/history/.gitkeep +0 -0
  91. package/knowledge-library/qa-engineer/inbox/processed/.gitkeep +0 -0
  92. package/knowledge-library/qa-engineer/outbox/.gitkeep +0 -0
  93. package/knowledge-library/qa-engineer/tech/.gitkeep +0 -0
  94. package/knowledge-library/security-engineer/context/current.txt +42 -0
  95. package/knowledge-library/security-engineer/control/decisions.txt +9 -0
  96. package/knowledge-library/security-engineer/control/dependencies.txt +19 -0
  97. package/knowledge-library/security-engineer/control/objectives.txt +26 -0
  98. package/knowledge-library/security-engineer/history/.gitkeep +0 -0
  99. package/knowledge-library/security-engineer/inbox/processed/.gitkeep +0 -0
  100. package/knowledge-library/security-engineer/outbox/.gitkeep +0 -0
  101. package/knowledge-library/security-engineer/tech/.gitkeep +0 -0
  102. package/knowledge-library/solutions-architect/context/current.txt +42 -0
  103. package/knowledge-library/solutions-architect/control/decisions.txt +9 -0
  104. package/knowledge-library/solutions-architect/control/dependencies.txt +19 -0
  105. package/knowledge-library/solutions-architect/control/objectives.txt +26 -0
  106. package/knowledge-library/solutions-architect/history/.gitkeep +0 -0
  107. package/knowledge-library/solutions-architect/inbox/processed/.gitkeep +0 -0
  108. package/knowledge-library/solutions-architect/outbox/.gitkeep +0 -0
  109. package/knowledge-library/solutions-architect/tech/.gitkeep +0 -0
  110. package/knowledge-library/wearos-engineer/context/current.txt +42 -0
  111. package/knowledge-library/wearos-engineer/control/decisions.txt +9 -0
  112. package/knowledge-library/wearos-engineer/control/dependencies.txt +19 -0
  113. package/knowledge-library/wearos-engineer/control/objectives.txt +26 -0
  114. package/knowledge-library/wearos-engineer/history/.gitkeep +0 -0
  115. package/knowledge-library/wearos-engineer/inbox/processed/.gitkeep +0 -0
  116. package/knowledge-library/wearos-engineer/outbox/.gitkeep +0 -0
  117. package/knowledge-library/wearos-engineer/tech/.gitkeep +0 -0
  118. package/package.json +66 -0
  119. package/schemas/agent.schema.json +171 -0
  120. package/schemas/coreai.config.schema.json +257 -0
  121. package/scripts/add-agent.sh +323 -0
  122. package/scripts/install.sh +354 -0
  123. package/src/adapters/factory.test.ts +386 -0
  124. package/src/adapters/factory.ts +305 -0
  125. package/src/adapters/index.ts +113 -0
  126. package/src/adapters/interfaces.ts +268 -0
  127. package/src/adapters/mcp/client.test.ts +130 -0
  128. package/src/adapters/mcp/client.ts +451 -0
  129. package/src/adapters/mcp/discovery.test.ts +315 -0
  130. package/src/adapters/mcp/discovery.ts +340 -0
  131. package/src/adapters/mcp/index.ts +66 -0
  132. package/src/adapters/mcp/mapper.test.ts +218 -0
  133. package/src/adapters/mcp/mapper.ts +536 -0
  134. package/src/adapters/mcp/registry.test.ts +433 -0
  135. package/src/adapters/mcp/registry.ts +550 -0
  136. package/src/adapters/mcp/types.ts +258 -0
  137. package/src/adapters/native/filesystem.test.ts +350 -0
  138. package/src/adapters/native/filesystem.ts +393 -0
  139. package/src/adapters/native/github.test.ts +173 -0
  140. package/src/adapters/native/github.ts +627 -0
  141. package/src/adapters/native/index.ts +22 -0
  142. package/src/adapters/native/selector.test.ts +224 -0
  143. package/src/adapters/native/selector.ts +150 -0
  144. package/src/adapters/types.ts +270 -0
  145. package/src/agents/compiler.test.ts +399 -0
  146. package/src/agents/compiler.ts +359 -0
  147. package/src/agents/index.ts +36 -0
  148. package/src/agents/loader.test.ts +319 -0
  149. package/src/agents/loader.ts +143 -0
  150. package/src/agents/resolver.test.ts +282 -0
  151. package/src/agents/resolver.ts +262 -0
  152. package/src/agents/types.ts +87 -0
  153. package/src/cache/index.ts +38 -0
  154. package/src/cache/interfaces.ts +283 -0
  155. package/src/cache/manager.test.ts +266 -0
  156. package/src/cache/manager.ts +388 -0
  157. package/src/cache/provider.test.ts +485 -0
  158. package/src/cache/provider.ts +745 -0
  159. package/src/cache/types.test.ts +192 -0
  160. package/src/cache/types.ts +313 -0
  161. package/src/cli/commands/build.test.ts +248 -0
  162. package/src/cli/commands/build.ts +244 -0
  163. package/src/cli/commands/cache.test.ts +221 -0
  164. package/src/cli/commands/cache.ts +229 -0
  165. package/src/cli/commands/index.ts +63 -0
  166. package/src/cli/commands/init.test.ts +173 -0
  167. package/src/cli/commands/init.ts +296 -0
  168. package/src/cli/commands/skills.test.ts +272 -0
  169. package/src/cli/commands/skills.ts +348 -0
  170. package/src/cli/commands/status.test.ts +392 -0
  171. package/src/cli/commands/status.ts +332 -0
  172. package/src/cli/commands/sync.test.ts +213 -0
  173. package/src/cli/commands/sync.ts +251 -0
  174. package/src/cli/commands/validate.test.ts +216 -0
  175. package/src/cli/commands/validate.ts +340 -0
  176. package/src/cli/index.test.ts +190 -0
  177. package/src/cli/index.ts +493 -0
  178. package/src/commands/context.test.ts +163 -0
  179. package/src/commands/context.ts +111 -0
  180. package/src/commands/index.ts +56 -0
  181. package/src/commands/loader.test.ts +273 -0
  182. package/src/commands/loader.ts +355 -0
  183. package/src/commands/registry.test.ts +384 -0
  184. package/src/commands/registry.ts +248 -0
  185. package/src/commands/runner.test.ts +297 -0
  186. package/src/commands/runner.ts +222 -0
  187. package/src/commands/types.ts +361 -0
  188. package/src/config/index.ts +19 -0
  189. package/src/config/loader.test.ts +262 -0
  190. package/src/config/loader.ts +188 -0
  191. package/src/config/types.ts +154 -0
  192. package/src/context/index.ts +14 -0
  193. package/src/context/loader.test.ts +334 -0
  194. package/src/context/loader.ts +357 -0
  195. package/src/index.test.ts +13 -0
  196. package/src/index.ts +244 -0
  197. package/src/knowledge-library/index.ts +44 -0
  198. package/src/knowledge-library/manager.test.ts +536 -0
  199. package/src/knowledge-library/manager.ts +804 -0
  200. package/src/knowledge-library/types.ts +432 -0
  201. package/src/skills/generator.test.ts +602 -0
  202. package/src/skills/generator.ts +491 -0
  203. package/src/skills/index.ts +27 -0
  204. package/src/skills/templates.ts +520 -0
  205. package/src/skills/types.ts +251 -0
  206. package/templates/completion-report.md +72 -0
  207. package/templates/feedback.md +56 -0
  208. package/templates/project-files/CLAUDE.md.template +109 -0
  209. package/templates/project-files/coreai.json.example +47 -0
  210. package/templates/project-files/mcp.json.template +20 -0
  211. package/templates/review-complete.md +64 -0
  212. package/templates/review-request.md +67 -0
  213. package/templates/task-assignment.md +51 -0
  214. package/tsconfig.build.json +4 -0
  215. package/tsconfig.json +26 -0
  216. package/tsup.config.ts +23 -0
@@ -0,0 +1,485 @@
1
+ /**
2
+ * File Cache Provider Tests
3
+ */
4
+
5
+ import { promises as fs } from 'fs';
6
+ import { join } from 'path';
7
+ import { tmpdir } from 'os';
8
+ import { FileCacheProvider, createFileCacheProvider } from './provider.js';
9
+ import { CacheError, CACHE_PATHS } from './types.js';
10
+ import type { CacheMetadata } from './types.js';
11
+
12
+ describe('FileCacheProvider', () => {
13
+ let testDir: string;
14
+ let provider: FileCacheProvider;
15
+
16
+ beforeEach(async () => {
17
+ // Create a unique temp directory for each test
18
+ testDir = join(tmpdir(), `cache-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
19
+ await fs.mkdir(testDir, { recursive: true });
20
+
21
+ provider = new FileCacheProvider({
22
+ basePath: testDir,
23
+ ttl: 3600,
24
+ });
25
+ });
26
+
27
+ afterEach(async () => {
28
+ // Clean up test directory
29
+ try {
30
+ await fs.rm(testDir, { recursive: true, force: true });
31
+ } catch {
32
+ // Ignore cleanup errors
33
+ }
34
+ });
35
+
36
+ describe('initialization', () => {
37
+ it('should not be initialized by default', () => {
38
+ expect(provider.isInitialized()).toBe(false);
39
+ });
40
+
41
+ it('should initialize successfully', async () => {
42
+ await provider.initialize();
43
+ expect(provider.isInitialized()).toBe(true);
44
+ });
45
+
46
+ it('should create cache directories on initialize', async () => {
47
+ await provider.initialize();
48
+
49
+ const contentPath = join(testDir, CACHE_PATHS.CONTENT);
50
+ const metadataPath = join(testDir, CACHE_PATHS.METADATA);
51
+
52
+ const contentStat = await fs.stat(contentPath);
53
+ const metadataStat = await fs.stat(metadataPath);
54
+
55
+ expect(contentStat.isDirectory()).toBe(true);
56
+ expect(metadataStat.isDirectory()).toBe(true);
57
+ });
58
+
59
+ it('should create index file on initialize', async () => {
60
+ await provider.initialize();
61
+
62
+ const indexPath = join(testDir, CACHE_PATHS.INDEX);
63
+ const indexContent = await fs.readFile(indexPath, 'utf-8');
64
+ const index = JSON.parse(indexContent);
65
+
66
+ expect(index.version).toBe(1);
67
+ expect(index.entries).toEqual({});
68
+ });
69
+
70
+ it('should be idempotent', async () => {
71
+ await provider.initialize();
72
+ await provider.initialize();
73
+ expect(provider.isInitialized()).toBe(true);
74
+ });
75
+ });
76
+
77
+ describe('operations without initialization', () => {
78
+ it('should throw on get without initialization', async () => {
79
+ await expect(provider.get('key')).rejects.toThrow(CacheError);
80
+ });
81
+
82
+ it('should throw on set without initialization', async () => {
83
+ await expect(provider.set('key', 'content', {})).rejects.toThrow(CacheError);
84
+ });
85
+
86
+ it('should throw on has without initialization', async () => {
87
+ await expect(provider.has('key')).rejects.toThrow(CacheError);
88
+ });
89
+
90
+ it('should throw on delete without initialization', async () => {
91
+ await expect(provider.delete('key')).rejects.toThrow(CacheError);
92
+ });
93
+ });
94
+
95
+ describe('set and get', () => {
96
+ beforeEach(async () => {
97
+ await provider.initialize();
98
+ });
99
+
100
+ it('should set and get string content', async () => {
101
+ await provider.set('test-key', 'Hello, World!', {
102
+ source: 'local',
103
+ sourceUrl: '/test/path',
104
+ });
105
+
106
+ const entry = await provider.get('test-key');
107
+
108
+ expect(entry).not.toBeNull();
109
+ expect(entry?.content).toBe('Hello, World!');
110
+ expect(entry?.metadata.key).toBe('test-key');
111
+ expect(entry?.metadata.source).toBe('local');
112
+ });
113
+
114
+ it('should set and get object content', async () => {
115
+ const data = { name: 'Test', value: 42 };
116
+
117
+ await provider.set('object-key', data, {
118
+ source: 'github',
119
+ sourceUrl: 'https://github.com/owner/repo',
120
+ contentType: 'application/json',
121
+ });
122
+
123
+ const entry = await provider.get<typeof data>('object-key');
124
+
125
+ expect(entry).not.toBeNull();
126
+ expect(entry?.content).toEqual(data);
127
+ });
128
+
129
+ it('should return null for non-existent key', async () => {
130
+ const entry = await provider.get('non-existent');
131
+ expect(entry).toBeNull();
132
+ });
133
+
134
+ it('should update existing entry', async () => {
135
+ await provider.set('update-key', 'original', { source: 'local', sourceUrl: '' });
136
+ await provider.set('update-key', 'updated', { source: 'local', sourceUrl: '' });
137
+
138
+ const entry = await provider.get('update-key');
139
+ expect(entry?.content).toBe('updated');
140
+ });
141
+
142
+ it('should respect skipCache option', async () => {
143
+ await provider.set('skip-key', 'content', { source: 'local', sourceUrl: '' });
144
+
145
+ const entry = await provider.get('skip-key', { skipCache: true });
146
+ expect(entry).toBeNull();
147
+ });
148
+
149
+ it('should apply custom tags', async () => {
150
+ await provider.set(
151
+ 'tagged-key',
152
+ 'content',
153
+ { source: 'local', sourceUrl: '' },
154
+ { tags: ['docs', 'api'] }
155
+ );
156
+
157
+ const metadata = await provider.getMetadata('tagged-key');
158
+ expect(metadata?.tags).toEqual(['docs', 'api']);
159
+ });
160
+ });
161
+
162
+ describe('getContent', () => {
163
+ beforeEach(async () => {
164
+ await provider.initialize();
165
+ });
166
+
167
+ it('should return only content', async () => {
168
+ await provider.set('content-key', 'Just content', { source: 'local', sourceUrl: '' });
169
+
170
+ const content = await provider.getContent('content-key');
171
+ expect(content).toBe('Just content');
172
+ });
173
+
174
+ it('should return null for non-existent key', async () => {
175
+ const content = await provider.getContent('missing');
176
+ expect(content).toBeNull();
177
+ });
178
+ });
179
+
180
+ describe('has', () => {
181
+ beforeEach(async () => {
182
+ await provider.initialize();
183
+ });
184
+
185
+ it('should return true for existing key', async () => {
186
+ await provider.set('exists', 'content', { source: 'local', sourceUrl: '' });
187
+ expect(await provider.has('exists')).toBe(true);
188
+ });
189
+
190
+ it('should return false for non-existent key', async () => {
191
+ expect(await provider.has('not-exists')).toBe(false);
192
+ });
193
+ });
194
+
195
+ describe('delete', () => {
196
+ beforeEach(async () => {
197
+ await provider.initialize();
198
+ });
199
+
200
+ it('should delete existing entry', async () => {
201
+ await provider.set('delete-me', 'content', { source: 'local', sourceUrl: '' });
202
+ expect(await provider.has('delete-me')).toBe(true);
203
+
204
+ const result = await provider.delete('delete-me');
205
+ expect(result).toBe(true);
206
+ expect(await provider.has('delete-me')).toBe(false);
207
+ });
208
+
209
+ it('should return false for non-existent key', async () => {
210
+ const result = await provider.delete('not-exists');
211
+ expect(result).toBe(false);
212
+ });
213
+ });
214
+
215
+ describe('getStatus', () => {
216
+ beforeEach(async () => {
217
+ await provider.initialize();
218
+ });
219
+
220
+ it('should return valid for fresh entry', async () => {
221
+ await provider.set('fresh', 'content', { source: 'local', sourceUrl: '' }, { ttl: 3600 });
222
+
223
+ const status = await provider.getStatus('fresh');
224
+ expect(status).toBe('valid');
225
+ });
226
+
227
+ it('should return null for non-existent key', async () => {
228
+ const status = await provider.getStatus('missing');
229
+ expect(status).toBeNull();
230
+ });
231
+
232
+ it('should return expired for old entry', async () => {
233
+ // Create provider with very short TTL
234
+ const shortTtlProvider = new FileCacheProvider({
235
+ basePath: testDir,
236
+ ttl: -1, // Already expired
237
+ });
238
+ await shortTtlProvider.initialize();
239
+
240
+ await shortTtlProvider.set('expired', 'content', { source: 'local', sourceUrl: '' });
241
+
242
+ const status = await shortTtlProvider.getStatus('expired');
243
+ expect(status).toBe('expired');
244
+ });
245
+ });
246
+
247
+ describe('getMetadata', () => {
248
+ beforeEach(async () => {
249
+ await provider.initialize();
250
+ });
251
+
252
+ it('should return metadata for existing entry', async () => {
253
+ await provider.set('meta-key', 'content', {
254
+ source: 'confluence',
255
+ sourceUrl: 'https://confluence.example.com/page',
256
+ title: 'Test Page',
257
+ });
258
+
259
+ const metadata = await provider.getMetadata('meta-key');
260
+
261
+ expect(metadata).not.toBeNull();
262
+ expect(metadata?.key).toBe('meta-key');
263
+ expect(metadata?.source).toBe('confluence');
264
+ expect(metadata?.title).toBe('Test Page');
265
+ expect(metadata?.contentHash).toBeDefined();
266
+ });
267
+
268
+ it('should return null for non-existent key', async () => {
269
+ const metadata = await provider.getMetadata('missing');
270
+ expect(metadata).toBeNull();
271
+ });
272
+ });
273
+
274
+ describe('updateMetadata', () => {
275
+ beforeEach(async () => {
276
+ await provider.initialize();
277
+ });
278
+
279
+ it('should update metadata fields', async () => {
280
+ await provider.set('update-meta', 'content', { source: 'local', sourceUrl: '' });
281
+
282
+ await provider.updateMetadata('update-meta', { title: 'New Title', tags: ['updated'] });
283
+
284
+ const metadata = await provider.getMetadata('update-meta');
285
+ expect(metadata?.title).toBe('New Title');
286
+ expect(metadata?.tags).toEqual(['updated']);
287
+ });
288
+
289
+ it('should throw for non-existent key', async () => {
290
+ await expect(provider.updateMetadata('missing', { title: 'New' })).rejects.toThrow(
291
+ CacheError
292
+ );
293
+ });
294
+
295
+ it('should not change the key', async () => {
296
+ await provider.set('original-key', 'content', { source: 'local', sourceUrl: '' });
297
+
298
+ // Try to change key (should be ignored)
299
+ await provider.updateMetadata('original-key', { key: 'new-key' } as Partial<CacheMetadata>);
300
+
301
+ const metadata = await provider.getMetadata('original-key');
302
+ expect(metadata?.key).toBe('original-key');
303
+ });
304
+ });
305
+
306
+ describe('list', () => {
307
+ beforeEach(async () => {
308
+ await provider.initialize();
309
+
310
+ // Add test entries
311
+ await provider.set(
312
+ 'github-1',
313
+ 'content',
314
+ { source: 'github', sourceUrl: '' },
315
+ { tags: ['docs'] }
316
+ );
317
+ await provider.set(
318
+ 'github-2',
319
+ 'content',
320
+ { source: 'github', sourceUrl: '' },
321
+ { tags: ['api'] }
322
+ );
323
+ await provider.set(
324
+ 'confluence-1',
325
+ 'content',
326
+ { source: 'confluence', sourceUrl: '' },
327
+ { tags: ['docs'] }
328
+ );
329
+ });
330
+
331
+ it('should list all entries', async () => {
332
+ const entries = await provider.list();
333
+ expect(entries).toHaveLength(3);
334
+ });
335
+
336
+ it('should filter by source', async () => {
337
+ const entries = await provider.list({ source: 'github' });
338
+ expect(entries).toHaveLength(2);
339
+ expect(entries.every((e) => e.source === 'github')).toBe(true);
340
+ });
341
+
342
+ it('should filter by tag', async () => {
343
+ const entries = await provider.list({ tag: 'docs' });
344
+ expect(entries).toHaveLength(2);
345
+ });
346
+
347
+ it('should apply limit', async () => {
348
+ const entries = await provider.list({ limit: 2 });
349
+ expect(entries).toHaveLength(2);
350
+ });
351
+ });
352
+
353
+ describe('getStats', () => {
354
+ beforeEach(async () => {
355
+ await provider.initialize();
356
+ });
357
+
358
+ it('should return empty stats for empty cache', async () => {
359
+ const stats = await provider.getStats();
360
+
361
+ expect(stats.totalEntries).toBe(0);
362
+ expect(stats.totalSize).toBe(0);
363
+ expect(stats.validEntries).toBe(0);
364
+ });
365
+
366
+ it('should track entries and size', async () => {
367
+ await provider.set('key-1', 'content-1', { source: 'github', sourceUrl: '' });
368
+ await provider.set('key-2', 'content-2', { source: 'confluence', sourceUrl: '' });
369
+
370
+ const stats = await provider.getStats();
371
+
372
+ expect(stats.totalEntries).toBe(2);
373
+ expect(stats.totalSize).toBeGreaterThan(0);
374
+ expect(stats.bySource.github).toBe(1);
375
+ expect(stats.bySource.confluence).toBe(1);
376
+ });
377
+ });
378
+
379
+ describe('clear', () => {
380
+ beforeEach(async () => {
381
+ await provider.initialize();
382
+ await provider.set('key-1', 'content', { source: 'local', sourceUrl: '' });
383
+ await provider.set('key-2', 'content', { source: 'local', sourceUrl: '' });
384
+ });
385
+
386
+ it('should clear all entries', async () => {
387
+ const count = await provider.clear();
388
+
389
+ expect(count).toBe(2);
390
+ expect(await provider.has('key-1')).toBe(false);
391
+ expect(await provider.has('key-2')).toBe(false);
392
+ });
393
+
394
+ it('should reset stats', async () => {
395
+ await provider.clear();
396
+
397
+ const stats = await provider.getStats();
398
+ expect(stats.totalEntries).toBe(0);
399
+ expect(stats.totalSize).toBe(0);
400
+ });
401
+ });
402
+
403
+ describe('clearExpired', () => {
404
+ it('should clear only expired entries', async () => {
405
+ // Create provider with short TTL
406
+ const shortProvider = new FileCacheProvider({
407
+ basePath: testDir,
408
+ ttl: -1,
409
+ });
410
+ await shortProvider.initialize();
411
+
412
+ await shortProvider.set('expired', 'content', { source: 'local', sourceUrl: '' });
413
+
414
+ // Create fresh entry with long TTL
415
+ const longProvider = new FileCacheProvider({
416
+ basePath: testDir,
417
+ ttl: 86400,
418
+ });
419
+ await longProvider.initialize();
420
+ await longProvider.set('fresh', 'content', { source: 'local', sourceUrl: '' });
421
+
422
+ const count = await longProvider.clearExpired();
423
+ expect(count).toBe(1);
424
+ expect(await longProvider.has('fresh')).toBe(true);
425
+ });
426
+ });
427
+
428
+ describe('clearBySource', () => {
429
+ beforeEach(async () => {
430
+ await provider.initialize();
431
+ await provider.set('github-1', 'content', { source: 'github', sourceUrl: '' });
432
+ await provider.set('github-2', 'content', { source: 'github', sourceUrl: '' });
433
+ await provider.set('local-1', 'content', { source: 'local', sourceUrl: '' });
434
+ });
435
+
436
+ it('should clear entries by source', async () => {
437
+ const count = await provider.clearBySource('github');
438
+
439
+ expect(count).toBe(2);
440
+ expect(await provider.has('github-1')).toBe(false);
441
+ expect(await provider.has('github-2')).toBe(false);
442
+ expect(await provider.has('local-1')).toBe(true);
443
+ });
444
+ });
445
+
446
+ describe('clearByTag', () => {
447
+ beforeEach(async () => {
448
+ await provider.initialize();
449
+ await provider.set(
450
+ 'docs-1',
451
+ 'content',
452
+ { source: 'local', sourceUrl: '' },
453
+ { tags: ['docs'] }
454
+ );
455
+ await provider.set(
456
+ 'docs-2',
457
+ 'content',
458
+ { source: 'local', sourceUrl: '' },
459
+ { tags: ['docs', 'api'] }
460
+ );
461
+ await provider.set(
462
+ 'other',
463
+ 'content',
464
+ { source: 'local', sourceUrl: '' },
465
+ { tags: ['other'] }
466
+ );
467
+ });
468
+
469
+ it('should clear entries by tag', async () => {
470
+ const count = await provider.clearByTag('docs');
471
+
472
+ expect(count).toBe(2);
473
+ expect(await provider.has('docs-1')).toBe(false);
474
+ expect(await provider.has('docs-2')).toBe(false);
475
+ expect(await provider.has('other')).toBe(true);
476
+ });
477
+ });
478
+ });
479
+
480
+ describe('createFileCacheProvider', () => {
481
+ it('should create a provider instance', () => {
482
+ const provider = createFileCacheProvider({ basePath: '/tmp' });
483
+ expect(provider).toBeInstanceOf(FileCacheProvider);
484
+ });
485
+ });