@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,388 @@
1
+ /**
2
+ * Cache Manager
3
+ *
4
+ * Higher-level cache management that combines caching with remote fetching.
5
+ * Implements cache-first resolution with remote fallback strategy.
6
+ */
7
+
8
+ import type { CacheEntry, CacheMetadata, CacheOptions, CacheSource, SyncResult } from './types.js';
9
+ import { CacheError } from './types.js';
10
+ import type {
11
+ CacheProvider,
12
+ CacheManager as ICacheManager,
13
+ RemoteFetcher,
14
+ FetchOptions,
15
+ SyncOptions,
16
+ } from './interfaces.js';
17
+
18
+ /**
19
+ * Options for creating a cache manager
20
+ */
21
+ export interface CacheManagerOptions {
22
+ /**
23
+ * The underlying cache provider
24
+ */
25
+ cache: CacheProvider;
26
+
27
+ /**
28
+ * Default TTL in seconds
29
+ */
30
+ defaultTtl?: number;
31
+
32
+ /**
33
+ * Default source for new entries
34
+ */
35
+ defaultSource?: CacheSource;
36
+ }
37
+
38
+ /**
39
+ * Cache manager implementation
40
+ *
41
+ * Provides cache-first resolution strategy:
42
+ * 1. Check cache for valid entry
43
+ * 2. If cached and valid, return cached content
44
+ * 3. If stale or missing, fetch from remote
45
+ * 4. Cache the fetched content
46
+ * 5. Return the content
47
+ */
48
+ export class CacheManager implements ICacheManager {
49
+ private cache: CacheProvider;
50
+ private fetchers = new Map<string, RemoteFetcher>();
51
+ private defaultTtl: number;
52
+ private defaultSource: CacheSource;
53
+
54
+ constructor(options: CacheManagerOptions) {
55
+ this.cache = options.cache;
56
+ this.defaultTtl = options.defaultTtl ?? 3600;
57
+ this.defaultSource = options.defaultSource ?? 'custom';
58
+ }
59
+
60
+ /**
61
+ * Get content with cache-first strategy
62
+ */
63
+ async getWithFallback<T = string>(
64
+ key: string,
65
+ url: string,
66
+ options?: CacheOptions & FetchOptions
67
+ ): Promise<CacheEntry<T>> {
68
+ // Try cache first (unless skipCache is set)
69
+ if (!options?.skipCache && !options?.forceRefresh) {
70
+ const cached = await this.cache.get<T>(key);
71
+ if (cached) {
72
+ const status = await this.cache.getStatus(key);
73
+ // Return if valid (not stale or expired)
74
+ if (status === 'valid') {
75
+ return cached;
76
+ }
77
+ // If stale, try to refresh but fall back to stale if fetch fails
78
+ if (status === 'stale') {
79
+ try {
80
+ return await this.fetchAndCache<T>(key, url, options);
81
+ } catch {
82
+ // Return stale content on fetch failure
83
+ return cached;
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ // Cache miss or forceRefresh - fetch from remote
90
+ return this.fetchAndCache<T>(key, url, options);
91
+ }
92
+
93
+ /**
94
+ * Sync all entries from a source
95
+ */
96
+ async syncSource(source: string, options?: SyncOptions): Promise<SyncResult> {
97
+ const startTime = Date.now();
98
+ const result: SyncResult = {
99
+ added: 0,
100
+ updated: 0,
101
+ removed: 0,
102
+ failed: 0,
103
+ errors: [],
104
+ duration: 0,
105
+ };
106
+
107
+ // Get all entries from this source
108
+ const entries = await this.cache.list({ source: source as CacheSource });
109
+
110
+ // Sync each entry
111
+ const concurrency = options?.concurrency ?? 5;
112
+ const chunks = this.chunkArray(entries, concurrency);
113
+
114
+ for (const chunk of chunks) {
115
+ const promises = chunk.map(async (entry) => {
116
+ try {
117
+ const fetcher = this.fetchers.get(source);
118
+ if (!fetcher) {
119
+ throw new Error(`No fetcher registered for source: ${source}`);
120
+ }
121
+
122
+ // Check if content has changed
123
+ const hasChanged = await fetcher.hasChanged(entry.sourceUrl, entry.etag);
124
+
125
+ if (hasChanged || options?.force) {
126
+ // Fetch and update
127
+ const fetchOptions: FetchOptions = {};
128
+ if (entry.etag) {
129
+ fetchOptions.etag = entry.etag;
130
+ }
131
+ const { content, metadata } = await fetcher.fetch(entry.sourceUrl, fetchOptions);
132
+
133
+ await this.cache.set(entry.key, content, {
134
+ ...entry,
135
+ ...metadata,
136
+ });
137
+
138
+ result.updated++;
139
+ }
140
+ } catch (error) {
141
+ result.failed++;
142
+ result.errors.push({
143
+ key: entry.key,
144
+ error: error instanceof Error ? error.message : String(error),
145
+ });
146
+
147
+ if (!options?.continueOnError) {
148
+ throw error;
149
+ }
150
+ }
151
+ });
152
+
153
+ await Promise.all(promises);
154
+ }
155
+
156
+ // Report final progress
157
+ if (options?.onProgress) {
158
+ options.onProgress(entries.length, entries.length);
159
+ }
160
+
161
+ result.duration = Date.now() - startTime;
162
+ return result;
163
+ }
164
+
165
+ /**
166
+ * Sync specific entries
167
+ */
168
+ async syncEntries(keys: string[], options?: SyncOptions): Promise<SyncResult> {
169
+ const startTime = Date.now();
170
+ const result: SyncResult = {
171
+ added: 0,
172
+ updated: 0,
173
+ removed: 0,
174
+ failed: 0,
175
+ errors: [],
176
+ duration: 0,
177
+ };
178
+
179
+ const concurrency = options?.concurrency ?? 5;
180
+ const chunks = this.chunkArray(keys, concurrency);
181
+
182
+ for (const chunk of chunks) {
183
+ const promises = chunk.map(async (key) => {
184
+ try {
185
+ const metadata = await this.cache.getMetadata(key);
186
+ if (!metadata) {
187
+ result.failed++;
188
+ result.errors.push({ key, error: 'Entry not found in cache' });
189
+ return;
190
+ }
191
+
192
+ const fetcher = this.fetchers.get(metadata.source);
193
+ if (!fetcher) {
194
+ result.failed++;
195
+ result.errors.push({ key, error: `No fetcher for source: ${metadata.source}` });
196
+ return;
197
+ }
198
+
199
+ // Check if content has changed
200
+ const hasChanged = await fetcher.hasChanged(metadata.sourceUrl, metadata.etag);
201
+
202
+ if (hasChanged || options?.force) {
203
+ const fetchOpts: FetchOptions = {};
204
+ if (metadata.etag) {
205
+ fetchOpts.etag = metadata.etag;
206
+ }
207
+ const { content, metadata: newMeta } = await fetcher.fetch(
208
+ metadata.sourceUrl,
209
+ fetchOpts
210
+ );
211
+
212
+ await this.cache.set(key, content, {
213
+ ...metadata,
214
+ ...newMeta,
215
+ });
216
+
217
+ result.updated++;
218
+ }
219
+ } catch (error) {
220
+ result.failed++;
221
+ result.errors.push({
222
+ key,
223
+ error: error instanceof Error ? error.message : String(error),
224
+ });
225
+
226
+ if (!options?.continueOnError) {
227
+ throw error;
228
+ }
229
+ }
230
+ });
231
+
232
+ await Promise.all(promises);
233
+ }
234
+
235
+ // Report final progress
236
+ if (options?.onProgress) {
237
+ options.onProgress(keys.length, keys.length);
238
+ }
239
+
240
+ result.duration = Date.now() - startTime;
241
+ return result;
242
+ }
243
+
244
+ /**
245
+ * Register a fetcher for a source
246
+ */
247
+ registerFetcher(source: string, fetcher: RemoteFetcher): void {
248
+ this.fetchers.set(source, fetcher);
249
+ }
250
+
251
+ /**
252
+ * Get the underlying cache provider
253
+ */
254
+ getCache(): CacheProvider {
255
+ return this.cache;
256
+ }
257
+
258
+ /**
259
+ * Check if a fetcher is registered for a source
260
+ */
261
+ hasFetcher(source: string): boolean {
262
+ return this.fetchers.has(source);
263
+ }
264
+
265
+ /**
266
+ * Get all registered sources
267
+ */
268
+ getRegisteredSources(): string[] {
269
+ return Array.from(this.fetchers.keys());
270
+ }
271
+
272
+ // Private helpers
273
+
274
+ private async fetchAndCache<T>(
275
+ key: string,
276
+ url: string,
277
+ options?: CacheOptions & FetchOptions
278
+ ): Promise<CacheEntry<T>> {
279
+ // Determine source from URL
280
+ const source = this.getSourceFromUrl(url);
281
+ const fetcher = this.fetchers.get(source);
282
+
283
+ if (!fetcher) {
284
+ throw new CacheError(
285
+ `No fetcher registered for source: ${source}. Register one with registerFetcher().`,
286
+ 'fetch_failed',
287
+ key
288
+ );
289
+ }
290
+
291
+ try {
292
+ // Fetch from remote
293
+ const fetchOpts: FetchOptions = {};
294
+ if (options?.timeout) {
295
+ fetchOpts.timeout = options.timeout;
296
+ }
297
+ if (options?.headers) {
298
+ fetchOpts.headers = options.headers;
299
+ }
300
+ const { content, metadata } = await fetcher.fetch(url, fetchOpts);
301
+
302
+ // Build full metadata
303
+ const fullMetadata: Partial<CacheMetadata> = {
304
+ source: source as CacheSource,
305
+ sourceUrl: url,
306
+ contentType: metadata.contentType ?? 'text/plain',
307
+ };
308
+ if (metadata.etag) {
309
+ fullMetadata.etag = metadata.etag;
310
+ }
311
+ if (metadata.title) {
312
+ fullMetadata.title = metadata.title;
313
+ }
314
+ if (metadata.lastModified) {
315
+ fullMetadata.lastModified = metadata.lastModified;
316
+ }
317
+ if (options?.tags) {
318
+ fullMetadata.tags = options.tags;
319
+ }
320
+
321
+ // Cache the content
322
+ const cacheOpts: CacheOptions = {
323
+ ttl: options?.ttl ?? this.defaultTtl,
324
+ };
325
+ if (options?.tags) {
326
+ cacheOpts.tags = options.tags;
327
+ }
328
+ await this.cache.set(key, content, fullMetadata, cacheOpts);
329
+
330
+ // Return the entry
331
+ const entry = await this.cache.get<T>(key);
332
+ if (!entry) {
333
+ throw new CacheError('Failed to retrieve cached entry after write', 'read_failed', key);
334
+ }
335
+
336
+ return entry;
337
+ } catch (error) {
338
+ if (error instanceof CacheError) {
339
+ throw error;
340
+ }
341
+ throw new CacheError(
342
+ `Failed to fetch content: ${error instanceof Error ? error.message : String(error)}`,
343
+ 'fetch_failed',
344
+ key,
345
+ error instanceof Error ? error : undefined
346
+ );
347
+ }
348
+ }
349
+
350
+ private getSourceFromUrl(url: string): string {
351
+ try {
352
+ const parsed = new URL(url);
353
+ const hostname = parsed.hostname.toLowerCase();
354
+
355
+ // Map common hostnames to sources
356
+ if (hostname.includes('confluence') || hostname.includes('atlassian.net')) {
357
+ return 'confluence';
358
+ }
359
+ if (hostname.includes('github.com') || hostname.includes('github')) {
360
+ return 'github';
361
+ }
362
+ if (hostname.includes('notion.so') || hostname.includes('notion')) {
363
+ return 'notion';
364
+ }
365
+
366
+ // Default to custom
367
+ return this.defaultSource;
368
+ } catch {
369
+ // Invalid URL, might be a file path
370
+ return 'local';
371
+ }
372
+ }
373
+
374
+ private chunkArray<T>(array: T[], size: number): T[][] {
375
+ const chunks: T[][] = [];
376
+ for (let i = 0; i < array.length; i += size) {
377
+ chunks.push(array.slice(i, i + size));
378
+ }
379
+ return chunks;
380
+ }
381
+ }
382
+
383
+ /**
384
+ * Create a cache manager
385
+ */
386
+ export function createCacheManager(options: CacheManagerOptions): CacheManager {
387
+ return new CacheManager(options);
388
+ }