bluera-knowledge 0.10.0 → 0.10.1
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.
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +12 -0
- package/README.md +73 -2
- package/commands/sync.md +96 -0
- package/dist/{chunk-ITH6FWQY.js → chunk-6U45VP5Z.js} +24 -3
- package/dist/{chunk-ITH6FWQY.js.map → chunk-6U45VP5Z.js.map} +1 -1
- package/dist/{chunk-CUHYSPRV.js → chunk-DP5XBPQV.js} +372 -2
- package/dist/chunk-DP5XBPQV.js.map +1 -0
- package/dist/{chunk-DWAIT2OD.js → chunk-UE4ZIJYA.js} +74 -5
- package/dist/{chunk-DWAIT2OD.js.map → chunk-UE4ZIJYA.js.map} +1 -1
- package/dist/index.js +213 -5
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +2 -2
- package/dist/workers/background-worker-cli.js +2 -2
- package/package.json +1 -1
- package/src/cli/commands/sync.test.ts +54 -0
- package/src/cli/commands/sync.ts +264 -0
- package/src/cli/index.ts +1 -0
- package/src/crawl/claude-client.test.ts +56 -0
- package/src/crawl/claude-client.ts +27 -1
- package/src/index.ts +2 -0
- package/src/mcp/commands/index.ts +2 -0
- package/src/mcp/commands/sync.commands.test.ts +283 -0
- package/src/mcp/commands/sync.commands.ts +233 -0
- package/src/services/gitignore.service.test.ts +157 -0
- package/src/services/gitignore.service.ts +132 -0
- package/src/services/store-definition.service.test.ts +440 -0
- package/src/services/store-definition.service.ts +198 -0
- package/src/services/store.service.test.ts +279 -1
- package/src/services/store.service.ts +101 -4
- package/src/types/index.ts +18 -0
- package/src/types/store-definition.test.ts +492 -0
- package/src/types/store-definition.ts +129 -0
- package/dist/chunk-CUHYSPRV.js.map +0 -1
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
FileStoreDefinitionSchema,
|
|
4
|
+
RepoStoreDefinitionSchema,
|
|
5
|
+
WebStoreDefinitionSchema,
|
|
6
|
+
StoreDefinitionSchema,
|
|
7
|
+
StoreDefinitionsConfigSchema,
|
|
8
|
+
isFileStoreDefinition,
|
|
9
|
+
isRepoStoreDefinition,
|
|
10
|
+
isWebStoreDefinition,
|
|
11
|
+
} from './store-definition.js';
|
|
12
|
+
|
|
13
|
+
describe('Store Definition Types', () => {
|
|
14
|
+
describe('FileStoreDefinitionSchema', () => {
|
|
15
|
+
it('validates a valid file store definition', () => {
|
|
16
|
+
const valid = {
|
|
17
|
+
type: 'file',
|
|
18
|
+
name: 'my-docs',
|
|
19
|
+
path: './docs',
|
|
20
|
+
};
|
|
21
|
+
const result = FileStoreDefinitionSchema.safeParse(valid);
|
|
22
|
+
expect(result.success).toBe(true);
|
|
23
|
+
if (result.success) {
|
|
24
|
+
expect(result.data.type).toBe('file');
|
|
25
|
+
expect(result.data.name).toBe('my-docs');
|
|
26
|
+
expect(result.data.path).toBe('./docs');
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('validates file store with optional fields', () => {
|
|
31
|
+
const valid = {
|
|
32
|
+
type: 'file',
|
|
33
|
+
name: 'my-docs',
|
|
34
|
+
path: './docs',
|
|
35
|
+
description: 'Local documentation',
|
|
36
|
+
tags: ['docs', 'reference'],
|
|
37
|
+
};
|
|
38
|
+
const result = FileStoreDefinitionSchema.safeParse(valid);
|
|
39
|
+
expect(result.success).toBe(true);
|
|
40
|
+
if (result.success) {
|
|
41
|
+
expect(result.data.description).toBe('Local documentation');
|
|
42
|
+
expect(result.data.tags).toEqual(['docs', 'reference']);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('accepts absolute paths', () => {
|
|
47
|
+
const valid = {
|
|
48
|
+
type: 'file',
|
|
49
|
+
name: 'absolute-docs',
|
|
50
|
+
path: '/home/user/docs',
|
|
51
|
+
};
|
|
52
|
+
const result = FileStoreDefinitionSchema.safeParse(valid);
|
|
53
|
+
expect(result.success).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('rejects missing name', () => {
|
|
57
|
+
const invalid = {
|
|
58
|
+
type: 'file',
|
|
59
|
+
path: './docs',
|
|
60
|
+
};
|
|
61
|
+
const result = FileStoreDefinitionSchema.safeParse(invalid);
|
|
62
|
+
expect(result.success).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('rejects missing path', () => {
|
|
66
|
+
const invalid = {
|
|
67
|
+
type: 'file',
|
|
68
|
+
name: 'my-docs',
|
|
69
|
+
};
|
|
70
|
+
const result = FileStoreDefinitionSchema.safeParse(invalid);
|
|
71
|
+
expect(result.success).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('rejects empty name', () => {
|
|
75
|
+
const invalid = {
|
|
76
|
+
type: 'file',
|
|
77
|
+
name: '',
|
|
78
|
+
path: './docs',
|
|
79
|
+
};
|
|
80
|
+
const result = FileStoreDefinitionSchema.safeParse(invalid);
|
|
81
|
+
expect(result.success).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('rejects empty path', () => {
|
|
85
|
+
const invalid = {
|
|
86
|
+
type: 'file',
|
|
87
|
+
name: 'my-docs',
|
|
88
|
+
path: '',
|
|
89
|
+
};
|
|
90
|
+
const result = FileStoreDefinitionSchema.safeParse(invalid);
|
|
91
|
+
expect(result.success).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('RepoStoreDefinitionSchema', () => {
|
|
96
|
+
it('validates a valid repo store definition', () => {
|
|
97
|
+
const valid = {
|
|
98
|
+
type: 'repo',
|
|
99
|
+
name: 'react',
|
|
100
|
+
url: 'https://github.com/facebook/react',
|
|
101
|
+
};
|
|
102
|
+
const result = RepoStoreDefinitionSchema.safeParse(valid);
|
|
103
|
+
expect(result.success).toBe(true);
|
|
104
|
+
if (result.success) {
|
|
105
|
+
expect(result.data.type).toBe('repo');
|
|
106
|
+
expect(result.data.name).toBe('react');
|
|
107
|
+
expect(result.data.url).toBe('https://github.com/facebook/react');
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('validates repo store with optional branch', () => {
|
|
112
|
+
const valid = {
|
|
113
|
+
type: 'repo',
|
|
114
|
+
name: 'react',
|
|
115
|
+
url: 'https://github.com/facebook/react',
|
|
116
|
+
branch: 'main',
|
|
117
|
+
};
|
|
118
|
+
const result = RepoStoreDefinitionSchema.safeParse(valid);
|
|
119
|
+
expect(result.success).toBe(true);
|
|
120
|
+
if (result.success) {
|
|
121
|
+
expect(result.data.branch).toBe('main');
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('validates repo store with optional depth', () => {
|
|
126
|
+
const valid = {
|
|
127
|
+
type: 'repo',
|
|
128
|
+
name: 'react',
|
|
129
|
+
url: 'https://github.com/facebook/react',
|
|
130
|
+
depth: 1,
|
|
131
|
+
};
|
|
132
|
+
const result = RepoStoreDefinitionSchema.safeParse(valid);
|
|
133
|
+
expect(result.success).toBe(true);
|
|
134
|
+
if (result.success) {
|
|
135
|
+
expect(result.data.depth).toBe(1);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('validates repo store with all optional fields', () => {
|
|
140
|
+
const valid = {
|
|
141
|
+
type: 'repo',
|
|
142
|
+
name: 'react',
|
|
143
|
+
url: 'https://github.com/facebook/react',
|
|
144
|
+
branch: 'develop',
|
|
145
|
+
depth: 2,
|
|
146
|
+
description: 'React library source',
|
|
147
|
+
tags: ['frontend', 'ui'],
|
|
148
|
+
};
|
|
149
|
+
const result = RepoStoreDefinitionSchema.safeParse(valid);
|
|
150
|
+
expect(result.success).toBe(true);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('rejects invalid URL', () => {
|
|
154
|
+
const invalid = {
|
|
155
|
+
type: 'repo',
|
|
156
|
+
name: 'react',
|
|
157
|
+
url: 'not-a-valid-url',
|
|
158
|
+
};
|
|
159
|
+
const result = RepoStoreDefinitionSchema.safeParse(invalid);
|
|
160
|
+
expect(result.success).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('rejects missing URL', () => {
|
|
164
|
+
const invalid = {
|
|
165
|
+
type: 'repo',
|
|
166
|
+
name: 'react',
|
|
167
|
+
};
|
|
168
|
+
const result = RepoStoreDefinitionSchema.safeParse(invalid);
|
|
169
|
+
expect(result.success).toBe(false);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('rejects negative depth', () => {
|
|
173
|
+
const invalid = {
|
|
174
|
+
type: 'repo',
|
|
175
|
+
name: 'react',
|
|
176
|
+
url: 'https://github.com/facebook/react',
|
|
177
|
+
depth: -1,
|
|
178
|
+
};
|
|
179
|
+
const result = RepoStoreDefinitionSchema.safeParse(invalid);
|
|
180
|
+
expect(result.success).toBe(false);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('rejects zero depth', () => {
|
|
184
|
+
const invalid = {
|
|
185
|
+
type: 'repo',
|
|
186
|
+
name: 'react',
|
|
187
|
+
url: 'https://github.com/facebook/react',
|
|
188
|
+
depth: 0,
|
|
189
|
+
};
|
|
190
|
+
const result = RepoStoreDefinitionSchema.safeParse(invalid);
|
|
191
|
+
expect(result.success).toBe(false);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('WebStoreDefinitionSchema', () => {
|
|
196
|
+
it('validates a valid web store definition with default depth', () => {
|
|
197
|
+
const valid = {
|
|
198
|
+
type: 'web',
|
|
199
|
+
name: 'docs',
|
|
200
|
+
url: 'https://example.com/docs',
|
|
201
|
+
};
|
|
202
|
+
const result = WebStoreDefinitionSchema.safeParse(valid);
|
|
203
|
+
expect(result.success).toBe(true);
|
|
204
|
+
if (result.success) {
|
|
205
|
+
expect(result.data.type).toBe('web');
|
|
206
|
+
expect(result.data.name).toBe('docs');
|
|
207
|
+
expect(result.data.url).toBe('https://example.com/docs');
|
|
208
|
+
expect(result.data.depth).toBe(1); // default
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('validates web store with custom depth', () => {
|
|
213
|
+
const valid = {
|
|
214
|
+
type: 'web',
|
|
215
|
+
name: 'docs',
|
|
216
|
+
url: 'https://example.com/docs',
|
|
217
|
+
depth: 3,
|
|
218
|
+
};
|
|
219
|
+
const result = WebStoreDefinitionSchema.safeParse(valid);
|
|
220
|
+
expect(result.success).toBe(true);
|
|
221
|
+
if (result.success) {
|
|
222
|
+
expect(result.data.depth).toBe(3);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('validates web store with maxPages', () => {
|
|
227
|
+
const valid = {
|
|
228
|
+
type: 'web',
|
|
229
|
+
name: 'docs',
|
|
230
|
+
url: 'https://example.com/docs',
|
|
231
|
+
maxPages: 100,
|
|
232
|
+
};
|
|
233
|
+
const result = WebStoreDefinitionSchema.safeParse(valid);
|
|
234
|
+
expect(result.success).toBe(true);
|
|
235
|
+
if (result.success) {
|
|
236
|
+
expect(result.data.maxPages).toBe(100);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('validates web store with crawl instructions', () => {
|
|
241
|
+
const valid = {
|
|
242
|
+
type: 'web',
|
|
243
|
+
name: 'docs',
|
|
244
|
+
url: 'https://example.com/docs',
|
|
245
|
+
crawlInstructions: 'Focus on API reference pages',
|
|
246
|
+
};
|
|
247
|
+
const result = WebStoreDefinitionSchema.safeParse(valid);
|
|
248
|
+
expect(result.success).toBe(true);
|
|
249
|
+
if (result.success) {
|
|
250
|
+
expect(result.data.crawlInstructions).toBe('Focus on API reference pages');
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('validates web store with extract instructions', () => {
|
|
255
|
+
const valid = {
|
|
256
|
+
type: 'web',
|
|
257
|
+
name: 'docs',
|
|
258
|
+
url: 'https://example.com/docs',
|
|
259
|
+
extractInstructions: 'Extract code examples and signatures',
|
|
260
|
+
};
|
|
261
|
+
const result = WebStoreDefinitionSchema.safeParse(valid);
|
|
262
|
+
expect(result.success).toBe(true);
|
|
263
|
+
if (result.success) {
|
|
264
|
+
expect(result.data.extractInstructions).toBe('Extract code examples and signatures');
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('validates web store with all optional fields', () => {
|
|
269
|
+
const valid = {
|
|
270
|
+
type: 'web',
|
|
271
|
+
name: 'typescript-docs',
|
|
272
|
+
url: 'https://www.typescriptlang.org/docs/',
|
|
273
|
+
depth: 2,
|
|
274
|
+
maxPages: 100,
|
|
275
|
+
crawlInstructions: 'Focus on handbook pages',
|
|
276
|
+
extractInstructions: 'Extract code examples',
|
|
277
|
+
description: 'TypeScript documentation',
|
|
278
|
+
tags: ['docs', 'typescript'],
|
|
279
|
+
};
|
|
280
|
+
const result = WebStoreDefinitionSchema.safeParse(valid);
|
|
281
|
+
expect(result.success).toBe(true);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('allows depth of 0 for single page', () => {
|
|
285
|
+
const valid = {
|
|
286
|
+
type: 'web',
|
|
287
|
+
name: 'single-page',
|
|
288
|
+
url: 'https://example.com/page',
|
|
289
|
+
depth: 0,
|
|
290
|
+
};
|
|
291
|
+
const result = WebStoreDefinitionSchema.safeParse(valid);
|
|
292
|
+
expect(result.success).toBe(true);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('rejects invalid URL', () => {
|
|
296
|
+
const invalid = {
|
|
297
|
+
type: 'web',
|
|
298
|
+
name: 'docs',
|
|
299
|
+
url: 'not-a-valid-url',
|
|
300
|
+
};
|
|
301
|
+
const result = WebStoreDefinitionSchema.safeParse(invalid);
|
|
302
|
+
expect(result.success).toBe(false);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('rejects negative depth', () => {
|
|
306
|
+
const invalid = {
|
|
307
|
+
type: 'web',
|
|
308
|
+
name: 'docs',
|
|
309
|
+
url: 'https://example.com/docs',
|
|
310
|
+
depth: -1,
|
|
311
|
+
};
|
|
312
|
+
const result = WebStoreDefinitionSchema.safeParse(invalid);
|
|
313
|
+
expect(result.success).toBe(false);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('rejects negative maxPages', () => {
|
|
317
|
+
const invalid = {
|
|
318
|
+
type: 'web',
|
|
319
|
+
name: 'docs',
|
|
320
|
+
url: 'https://example.com/docs',
|
|
321
|
+
maxPages: -1,
|
|
322
|
+
};
|
|
323
|
+
const result = WebStoreDefinitionSchema.safeParse(invalid);
|
|
324
|
+
expect(result.success).toBe(false);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('rejects zero maxPages', () => {
|
|
328
|
+
const invalid = {
|
|
329
|
+
type: 'web',
|
|
330
|
+
name: 'docs',
|
|
331
|
+
url: 'https://example.com/docs',
|
|
332
|
+
maxPages: 0,
|
|
333
|
+
};
|
|
334
|
+
const result = WebStoreDefinitionSchema.safeParse(invalid);
|
|
335
|
+
expect(result.success).toBe(false);
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
describe('StoreDefinitionSchema (discriminated union)', () => {
|
|
340
|
+
it('parses file store definition', () => {
|
|
341
|
+
const def = { type: 'file', name: 'docs', path: './docs' };
|
|
342
|
+
const result = StoreDefinitionSchema.safeParse(def);
|
|
343
|
+
expect(result.success).toBe(true);
|
|
344
|
+
if (result.success) {
|
|
345
|
+
expect(result.data.type).toBe('file');
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('parses repo store definition', () => {
|
|
350
|
+
const def = { type: 'repo', name: 'lib', url: 'https://github.com/org/lib' };
|
|
351
|
+
const result = StoreDefinitionSchema.safeParse(def);
|
|
352
|
+
expect(result.success).toBe(true);
|
|
353
|
+
if (result.success) {
|
|
354
|
+
expect(result.data.type).toBe('repo');
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('parses web store definition', () => {
|
|
359
|
+
const def = { type: 'web', name: 'docs', url: 'https://docs.example.com' };
|
|
360
|
+
const result = StoreDefinitionSchema.safeParse(def);
|
|
361
|
+
expect(result.success).toBe(true);
|
|
362
|
+
if (result.success) {
|
|
363
|
+
expect(result.data.type).toBe('web');
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('rejects unknown type', () => {
|
|
368
|
+
const def = { type: 'unknown', name: 'test' };
|
|
369
|
+
const result = StoreDefinitionSchema.safeParse(def);
|
|
370
|
+
expect(result.success).toBe(false);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it('rejects missing type', () => {
|
|
374
|
+
const def = { name: 'test', path: './test' };
|
|
375
|
+
const result = StoreDefinitionSchema.safeParse(def);
|
|
376
|
+
expect(result.success).toBe(false);
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
describe('StoreDefinitionsConfigSchema', () => {
|
|
381
|
+
it('validates empty config', () => {
|
|
382
|
+
const config = { version: 1, stores: [] };
|
|
383
|
+
const result = StoreDefinitionsConfigSchema.safeParse(config);
|
|
384
|
+
expect(result.success).toBe(true);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it('validates config with multiple stores', () => {
|
|
388
|
+
const config = {
|
|
389
|
+
version: 1,
|
|
390
|
+
stores: [
|
|
391
|
+
{ type: 'file', name: 'docs', path: './docs' },
|
|
392
|
+
{ type: 'repo', name: 'react', url: 'https://github.com/facebook/react' },
|
|
393
|
+
{ type: 'web', name: 'api-docs', url: 'https://api.example.com/docs' },
|
|
394
|
+
],
|
|
395
|
+
};
|
|
396
|
+
const result = StoreDefinitionsConfigSchema.safeParse(config);
|
|
397
|
+
expect(result.success).toBe(true);
|
|
398
|
+
if (result.success) {
|
|
399
|
+
expect(result.data.stores).toHaveLength(3);
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it('rejects missing version', () => {
|
|
404
|
+
const config = { stores: [] };
|
|
405
|
+
const result = StoreDefinitionsConfigSchema.safeParse(config);
|
|
406
|
+
expect(result.success).toBe(false);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it('rejects wrong version', () => {
|
|
410
|
+
const config = { version: 2, stores: [] };
|
|
411
|
+
const result = StoreDefinitionsConfigSchema.safeParse(config);
|
|
412
|
+
expect(result.success).toBe(false);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it('rejects missing stores array', () => {
|
|
416
|
+
const config = { version: 1 };
|
|
417
|
+
const result = StoreDefinitionsConfigSchema.safeParse(config);
|
|
418
|
+
expect(result.success).toBe(false);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it('rejects invalid store in array', () => {
|
|
422
|
+
const config = {
|
|
423
|
+
version: 1,
|
|
424
|
+
stores: [
|
|
425
|
+
{ type: 'file', name: 'docs', path: './docs' },
|
|
426
|
+
{ type: 'invalid', name: 'bad' },
|
|
427
|
+
],
|
|
428
|
+
};
|
|
429
|
+
const result = StoreDefinitionsConfigSchema.safeParse(config);
|
|
430
|
+
expect(result.success).toBe(false);
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
describe('Type guard functions', () => {
|
|
435
|
+
describe('isFileStoreDefinition', () => {
|
|
436
|
+
it('returns true for file store definition', () => {
|
|
437
|
+
const def = StoreDefinitionSchema.parse({ type: 'file', name: 'docs', path: './docs' });
|
|
438
|
+
expect(isFileStoreDefinition(def)).toBe(true);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it('returns false for repo store definition', () => {
|
|
442
|
+
const def = StoreDefinitionSchema.parse({
|
|
443
|
+
type: 'repo',
|
|
444
|
+
name: 'lib',
|
|
445
|
+
url: 'https://github.com/org/lib',
|
|
446
|
+
});
|
|
447
|
+
expect(isFileStoreDefinition(def)).toBe(false);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it('returns false for web store definition', () => {
|
|
451
|
+
const def = StoreDefinitionSchema.parse({
|
|
452
|
+
type: 'web',
|
|
453
|
+
name: 'docs',
|
|
454
|
+
url: 'https://docs.example.com',
|
|
455
|
+
});
|
|
456
|
+
expect(isFileStoreDefinition(def)).toBe(false);
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
describe('isRepoStoreDefinition', () => {
|
|
461
|
+
it('returns true for repo store definition', () => {
|
|
462
|
+
const def = StoreDefinitionSchema.parse({
|
|
463
|
+
type: 'repo',
|
|
464
|
+
name: 'lib',
|
|
465
|
+
url: 'https://github.com/org/lib',
|
|
466
|
+
});
|
|
467
|
+
expect(isRepoStoreDefinition(def)).toBe(true);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it('returns false for file store definition', () => {
|
|
471
|
+
const def = StoreDefinitionSchema.parse({ type: 'file', name: 'docs', path: './docs' });
|
|
472
|
+
expect(isRepoStoreDefinition(def)).toBe(false);
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
describe('isWebStoreDefinition', () => {
|
|
477
|
+
it('returns true for web store definition', () => {
|
|
478
|
+
const def = StoreDefinitionSchema.parse({
|
|
479
|
+
type: 'web',
|
|
480
|
+
name: 'docs',
|
|
481
|
+
url: 'https://docs.example.com',
|
|
482
|
+
});
|
|
483
|
+
expect(isWebStoreDefinition(def)).toBe(true);
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it('returns false for file store definition', () => {
|
|
487
|
+
const def = StoreDefinitionSchema.parse({ type: 'file', name: 'docs', path: './docs' });
|
|
488
|
+
expect(isWebStoreDefinition(def)).toBe(false);
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Store definition schemas for git-committable configuration.
|
|
5
|
+
*
|
|
6
|
+
* Store definitions capture the essential information needed to recreate
|
|
7
|
+
* a store, without the runtime data (vector embeddings, cloned repos).
|
|
8
|
+
* This allows teams to share store configurations via version control.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Base Schema
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Base fields common to all store definitions
|
|
17
|
+
*/
|
|
18
|
+
const BaseStoreDefinitionSchema = z.object({
|
|
19
|
+
name: z.string().min(1, 'Store name is required'),
|
|
20
|
+
description: z.string().optional(),
|
|
21
|
+
tags: z.array(z.string()).optional(),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// File Store Definition
|
|
26
|
+
// ============================================================================
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* File store definition - references a local directory.
|
|
30
|
+
* Path can be relative (resolved against project root) or absolute.
|
|
31
|
+
*/
|
|
32
|
+
export const FileStoreDefinitionSchema = BaseStoreDefinitionSchema.extend({
|
|
33
|
+
type: z.literal('file'),
|
|
34
|
+
path: z.string().min(1, 'Path is required for file stores'),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export type FileStoreDefinition = z.infer<typeof FileStoreDefinitionSchema>;
|
|
38
|
+
|
|
39
|
+
// ============================================================================
|
|
40
|
+
// Repo Store Definition
|
|
41
|
+
// ============================================================================
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Repo store definition - references a git repository.
|
|
45
|
+
* The repo will be cloned on sync.
|
|
46
|
+
*/
|
|
47
|
+
export const RepoStoreDefinitionSchema = BaseStoreDefinitionSchema.extend({
|
|
48
|
+
type: z.literal('repo'),
|
|
49
|
+
url: z.url('Valid URL is required for repo stores'),
|
|
50
|
+
branch: z.string().optional(),
|
|
51
|
+
depth: z.number().int().positive('Depth must be a positive integer').optional(),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export type RepoStoreDefinition = z.infer<typeof RepoStoreDefinitionSchema>;
|
|
55
|
+
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// Web Store Definition
|
|
58
|
+
// ============================================================================
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Web store definition - references a website to crawl.
|
|
62
|
+
* Supports intelligent crawling with natural language instructions.
|
|
63
|
+
*/
|
|
64
|
+
export const WebStoreDefinitionSchema = BaseStoreDefinitionSchema.extend({
|
|
65
|
+
type: z.literal('web'),
|
|
66
|
+
url: z.url('Valid URL is required for web stores'),
|
|
67
|
+
depth: z.number().int().min(0, 'Depth must be non-negative').default(1),
|
|
68
|
+
maxPages: z.number().int().positive('maxPages must be a positive integer').optional(),
|
|
69
|
+
crawlInstructions: z.string().optional(),
|
|
70
|
+
extractInstructions: z.string().optional(),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export type WebStoreDefinition = z.infer<typeof WebStoreDefinitionSchema>;
|
|
74
|
+
|
|
75
|
+
// ============================================================================
|
|
76
|
+
// Union Type
|
|
77
|
+
// ============================================================================
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Discriminated union of all store definition types.
|
|
81
|
+
* Use the `type` field to narrow the type.
|
|
82
|
+
*/
|
|
83
|
+
export const StoreDefinitionSchema = z.discriminatedUnion('type', [
|
|
84
|
+
FileStoreDefinitionSchema,
|
|
85
|
+
RepoStoreDefinitionSchema,
|
|
86
|
+
WebStoreDefinitionSchema,
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
export type StoreDefinition = z.infer<typeof StoreDefinitionSchema>;
|
|
90
|
+
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// Config Schema
|
|
93
|
+
// ============================================================================
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Root configuration schema for store definitions.
|
|
97
|
+
* Version field enables future schema migrations.
|
|
98
|
+
*/
|
|
99
|
+
export const StoreDefinitionsConfigSchema = z.object({
|
|
100
|
+
version: z.literal(1),
|
|
101
|
+
stores: z.array(StoreDefinitionSchema),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
export type StoreDefinitionsConfig = z.infer<typeof StoreDefinitionsConfigSchema>;
|
|
105
|
+
|
|
106
|
+
// ============================================================================
|
|
107
|
+
// Type Guards
|
|
108
|
+
// ============================================================================
|
|
109
|
+
|
|
110
|
+
export function isFileStoreDefinition(def: StoreDefinition): def is FileStoreDefinition {
|
|
111
|
+
return def.type === 'file';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function isRepoStoreDefinition(def: StoreDefinition): def is RepoStoreDefinition {
|
|
115
|
+
return def.type === 'repo';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function isWebStoreDefinition(def: StoreDefinition): def is WebStoreDefinition {
|
|
119
|
+
return def.type === 'web';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ============================================================================
|
|
123
|
+
// Default Config
|
|
124
|
+
// ============================================================================
|
|
125
|
+
|
|
126
|
+
export const DEFAULT_STORE_DEFINITIONS_CONFIG: StoreDefinitionsConfig = {
|
|
127
|
+
version: 1,
|
|
128
|
+
stores: [],
|
|
129
|
+
};
|