bluera-knowledge 0.9.26 → 0.9.31

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 (63) hide show
  1. package/.claude/commands/commit.md +4 -7
  2. package/.claude/hooks/post-edit-check.sh +21 -24
  3. package/.claude/skills/atomic-commits/SKILL.md +6 -0
  4. package/.claude-plugin/plugin.json +1 -1
  5. package/.env.example +4 -0
  6. package/.husky/pre-push +12 -2
  7. package/.versionrc.json +0 -4
  8. package/BUGS-FOUND.md +71 -0
  9. package/CHANGELOG.md +76 -0
  10. package/README.md +55 -20
  11. package/bun.lock +35 -1
  12. package/commands/crawl.md +2 -0
  13. package/dist/{chunk-BICFAWMN.js → chunk-2SJHNRXD.js} +73 -8
  14. package/dist/chunk-2SJHNRXD.js.map +1 -0
  15. package/dist/{chunk-J7J6LXOJ.js → chunk-OGEY66FZ.js} +106 -41
  16. package/dist/chunk-OGEY66FZ.js.map +1 -0
  17. package/dist/{chunk-5QMHZUC4.js → chunk-RWSXP3PQ.js} +482 -106
  18. package/dist/chunk-RWSXP3PQ.js.map +1 -0
  19. package/dist/index.js +73 -28
  20. package/dist/index.js.map +1 -1
  21. package/dist/mcp/server.js +2 -2
  22. package/dist/workers/background-worker-cli.js +2 -2
  23. package/eslint.config.js +1 -1
  24. package/package.json +3 -1
  25. package/src/analysis/ast-parser.test.ts +46 -0
  26. package/src/cli/commands/crawl.test.ts +99 -12
  27. package/src/cli/commands/crawl.ts +76 -24
  28. package/src/cli/commands/store.test.ts +68 -1
  29. package/src/cli/commands/store.ts +9 -3
  30. package/src/crawl/article-converter.ts +36 -1
  31. package/src/crawl/bridge.ts +18 -7
  32. package/src/crawl/intelligent-crawler.ts +45 -4
  33. package/src/db/embeddings.test.ts +16 -0
  34. package/src/db/lance.test.ts +31 -0
  35. package/src/db/lance.ts +8 -0
  36. package/src/logging/index.ts +29 -0
  37. package/src/logging/logger.test.ts +75 -0
  38. package/src/logging/logger.ts +147 -0
  39. package/src/logging/payload.test.ts +152 -0
  40. package/src/logging/payload.ts +121 -0
  41. package/src/mcp/handlers/search.handler.test.ts +28 -9
  42. package/src/mcp/handlers/search.handler.ts +69 -29
  43. package/src/mcp/handlers/store.handler.test.ts +1 -0
  44. package/src/mcp/server.ts +44 -16
  45. package/src/services/chunking.service.ts +23 -0
  46. package/src/services/index.service.test.ts +921 -1
  47. package/src/services/index.service.ts +76 -1
  48. package/src/services/index.ts +20 -2
  49. package/src/services/search.service.test.ts +573 -21
  50. package/src/services/search.service.ts +257 -105
  51. package/src/services/services.test.ts +2 -2
  52. package/src/services/snippet.service.ts +28 -3
  53. package/src/services/store.service.test.ts +28 -0
  54. package/src/services/store.service.ts +4 -0
  55. package/src/services/token.service.test.ts +45 -0
  56. package/src/services/token.service.ts +33 -0
  57. package/src/types/result.test.ts +10 -0
  58. package/tests/integration/cli-consistency.test.ts +1 -4
  59. package/vitest.config.ts +4 -0
  60. package/dist/chunk-5QMHZUC4.js.map +0 -1
  61. package/dist/chunk-BICFAWMN.js.map +0 -1
  62. package/dist/chunk-J7J6LXOJ.js.map +0 -1
  63. package/scripts/readme-version-updater.cjs +0 -18
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, beforeAll, afterAll, vi, beforeEach } from 'vitest';
2
- import { IndexService } from './index.service.js';
2
+ import { IndexService, classifyWebContentType } from './index.service.js';
3
3
  import { LanceStore } from '../db/lance.js';
4
4
  import { EmbeddingEngine } from '../db/embeddings.js';
5
5
  import { createStoreId } from '../types/brands.js';
@@ -7,6 +7,7 @@ import { rm, mkdtemp, writeFile, mkdir, symlink, chmod } from 'node:fs/promises'
7
7
  import { tmpdir } from 'node:os';
8
8
  import { join } from 'node:path';
9
9
  import type { FileStore } from '../types/store.js';
10
+ import type { CodeGraphService } from './code-graph.service.js';
10
11
 
11
12
  describe('IndexService', () => {
12
13
  let indexService: IndexService;
@@ -1000,3 +1001,922 @@ describe('IndexService - Custom Chunk Configuration', () => {
1000
1001
  expect(result.success).toBe(true);
1001
1002
  });
1002
1003
  });
1004
+
1005
+ describe('IndexService - Unsupported Store Types', () => {
1006
+ let indexService: IndexService;
1007
+ let lanceStore: LanceStore;
1008
+ let embeddingEngine: EmbeddingEngine;
1009
+ let tempDir: string;
1010
+ const storeId = createStoreId('test-store');
1011
+
1012
+ beforeAll(async () => {
1013
+ tempDir = await mkdtemp(join(tmpdir(), 'index-unsupported-test-'));
1014
+
1015
+ lanceStore = new LanceStore(tempDir);
1016
+ embeddingEngine = new EmbeddingEngine();
1017
+
1018
+ await embeddingEngine.initialize();
1019
+ await lanceStore.initialize(storeId);
1020
+
1021
+ indexService = new IndexService(lanceStore, embeddingEngine);
1022
+ }, 120000);
1023
+
1024
+ afterAll(async () => {
1025
+ await rm(tempDir, { recursive: true, force: true });
1026
+ });
1027
+
1028
+ it('returns error for unsupported web store type', async () => {
1029
+ const store = {
1030
+ type: 'web' as const,
1031
+ id: storeId,
1032
+ name: 'Web Store',
1033
+ url: 'https://example.com',
1034
+ depth: 1,
1035
+ createdAt: new Date(),
1036
+ updatedAt: new Date(),
1037
+ };
1038
+
1039
+ const result = await indexService.indexStore(store);
1040
+
1041
+ expect(result.success).toBe(false);
1042
+ if (!result.success) {
1043
+ expect(result.error.message).toContain('Indexing not supported for store type');
1044
+ }
1045
+ });
1046
+
1047
+ it('handles repo store type same as file store', async () => {
1048
+ const testFilesDir = join(tempDir, 'repo-files');
1049
+ await mkdir(testFilesDir, { recursive: true });
1050
+ await writeFile(join(testFilesDir, 'repo-file.ts'), 'export const repo = true;');
1051
+
1052
+ const store = {
1053
+ type: 'repo' as const,
1054
+ id: storeId,
1055
+ name: 'Repo Store',
1056
+ path: testFilesDir,
1057
+ url: 'https://github.com/example/repo',
1058
+ branch: 'main',
1059
+ createdAt: new Date(),
1060
+ updatedAt: new Date(),
1061
+ };
1062
+
1063
+ const result = await indexService.indexStore(store);
1064
+
1065
+ expect(result.success).toBe(true);
1066
+ if (result.success) {
1067
+ expect(result.data.documentsIndexed).toBe(1);
1068
+ }
1069
+ });
1070
+ });
1071
+
1072
+ describe('IndexService - Error Handling Edge Cases', () => {
1073
+ let lanceStore: LanceStore;
1074
+ let embeddingEngine: EmbeddingEngine;
1075
+ let tempDir: string;
1076
+ const storeId = createStoreId('test-store');
1077
+
1078
+ beforeAll(async () => {
1079
+ tempDir = await mkdtemp(join(tmpdir(), 'index-error-edge-test-'));
1080
+
1081
+ lanceStore = new LanceStore(tempDir);
1082
+ embeddingEngine = new EmbeddingEngine();
1083
+
1084
+ await embeddingEngine.initialize();
1085
+ await lanceStore.initialize(storeId);
1086
+ }, 120000);
1087
+
1088
+ afterAll(async () => {
1089
+ await rm(tempDir, { recursive: true, force: true });
1090
+ });
1091
+
1092
+ it('converts non-Error thrown values to Error', async () => {
1093
+ // Create a mock that throws a string instead of Error
1094
+ const mockLanceStore = {
1095
+ addDocuments: vi.fn().mockRejectedValue('string error'),
1096
+ } as unknown as LanceStore;
1097
+
1098
+ const indexService = new IndexService(mockLanceStore, embeddingEngine);
1099
+
1100
+ const testFilesDir = join(tempDir, 'error-test');
1101
+ await mkdir(testFilesDir, { recursive: true });
1102
+ await writeFile(join(testFilesDir, 'test.ts'), 'const x = 1;');
1103
+
1104
+ const store = {
1105
+ type: 'file' as const,
1106
+ id: storeId,
1107
+ name: 'Test Store',
1108
+ path: testFilesDir,
1109
+ createdAt: new Date(),
1110
+ updatedAt: new Date(),
1111
+ };
1112
+
1113
+ const result = await indexService.indexStore(store);
1114
+
1115
+ expect(result.success).toBe(false);
1116
+ if (!result.success) {
1117
+ expect(result.error).toBeInstanceOf(Error);
1118
+ expect(result.error.message).toBe('string error');
1119
+ }
1120
+ });
1121
+ });
1122
+
1123
+ describe('IndexService - Code Graph Integration', () => {
1124
+ let lanceStore: LanceStore;
1125
+ let embeddingEngine: EmbeddingEngine;
1126
+ let tempDir: string;
1127
+ let testFilesDir: string;
1128
+ const storeId = createStoreId('test-store');
1129
+
1130
+ beforeAll(async () => {
1131
+ tempDir = await mkdtemp(join(tmpdir(), 'index-codegraph-test-'));
1132
+ testFilesDir = join(tempDir, 'files');
1133
+ await mkdir(testFilesDir, { recursive: true });
1134
+
1135
+ lanceStore = new LanceStore(tempDir);
1136
+ embeddingEngine = new EmbeddingEngine();
1137
+
1138
+ await embeddingEngine.initialize();
1139
+ await lanceStore.initialize(storeId);
1140
+ }, 120000);
1141
+
1142
+ afterAll(async () => {
1143
+ await rm(tempDir, { recursive: true, force: true });
1144
+ });
1145
+
1146
+ it('builds and saves code graph when codeGraphService is provided', async () => {
1147
+ const buildGraphMock = vi.fn().mockResolvedValue({ nodes: [], edges: [] });
1148
+ const saveGraphMock = vi.fn().mockResolvedValue(undefined);
1149
+
1150
+ const mockCodeGraphService = {
1151
+ buildGraph: buildGraphMock,
1152
+ saveGraph: saveGraphMock,
1153
+ } as unknown as CodeGraphService;
1154
+
1155
+ const indexService = new IndexService(lanceStore, embeddingEngine, {
1156
+ codeGraphService: mockCodeGraphService,
1157
+ });
1158
+
1159
+ // Create TypeScript files (source files for code graph)
1160
+ await writeFile(join(testFilesDir, 'module.ts'), 'export function foo() { return 1; }');
1161
+ await writeFile(join(testFilesDir, 'utils.tsx'), 'export const Bar = () => <div />;');
1162
+
1163
+ const store: FileStore = {
1164
+ type: 'file',
1165
+ id: storeId,
1166
+ name: 'Test Store',
1167
+ path: testFilesDir,
1168
+ createdAt: new Date(),
1169
+ updatedAt: new Date(),
1170
+ };
1171
+
1172
+ const result = await indexService.indexStore(store);
1173
+
1174
+ expect(result.success).toBe(true);
1175
+ expect(buildGraphMock).toHaveBeenCalled();
1176
+ expect(saveGraphMock).toHaveBeenCalledWith(storeId, expect.anything());
1177
+ });
1178
+
1179
+ it('does not build code graph when no source files are present', async () => {
1180
+ const buildGraphMock = vi.fn();
1181
+ const saveGraphMock = vi.fn();
1182
+
1183
+ const mockCodeGraphService = {
1184
+ buildGraph: buildGraphMock,
1185
+ saveGraph: saveGraphMock,
1186
+ } as unknown as CodeGraphService;
1187
+
1188
+ const indexService = new IndexService(lanceStore, embeddingEngine, {
1189
+ codeGraphService: mockCodeGraphService,
1190
+ });
1191
+
1192
+ const isolatedDir = join(tempDir, 'no-source-files');
1193
+ await mkdir(isolatedDir, { recursive: true });
1194
+ // Only non-source files
1195
+ await writeFile(join(isolatedDir, 'readme.md'), '# Readme');
1196
+ await writeFile(join(isolatedDir, 'data.json'), '{}');
1197
+
1198
+ const store: FileStore = {
1199
+ type: 'file',
1200
+ id: storeId,
1201
+ name: 'Test Store',
1202
+ path: isolatedDir,
1203
+ createdAt: new Date(),
1204
+ updatedAt: new Date(),
1205
+ };
1206
+
1207
+ const result = await indexService.indexStore(store);
1208
+
1209
+ expect(result.success).toBe(true);
1210
+ expect(buildGraphMock).not.toHaveBeenCalled();
1211
+ expect(saveGraphMock).not.toHaveBeenCalled();
1212
+ });
1213
+
1214
+ it('collects .js and .jsx files for code graph', async () => {
1215
+ const buildGraphMock = vi.fn().mockResolvedValue({ nodes: [], edges: [] });
1216
+ const saveGraphMock = vi.fn().mockResolvedValue(undefined);
1217
+
1218
+ const mockCodeGraphService = {
1219
+ buildGraph: buildGraphMock,
1220
+ saveGraph: saveGraphMock,
1221
+ } as unknown as CodeGraphService;
1222
+
1223
+ const indexService = new IndexService(lanceStore, embeddingEngine, {
1224
+ codeGraphService: mockCodeGraphService,
1225
+ });
1226
+
1227
+ const jsDir = join(tempDir, 'js-files');
1228
+ await mkdir(jsDir, { recursive: true });
1229
+ await writeFile(join(jsDir, 'app.js'), 'function main() {}');
1230
+ await writeFile(join(jsDir, 'component.jsx'), 'export const App = () => null;');
1231
+
1232
+ const store: FileStore = {
1233
+ type: 'file',
1234
+ id: storeId,
1235
+ name: 'Test Store',
1236
+ path: jsDir,
1237
+ createdAt: new Date(),
1238
+ updatedAt: new Date(),
1239
+ };
1240
+
1241
+ const result = await indexService.indexStore(store);
1242
+
1243
+ expect(result.success).toBe(true);
1244
+ expect(buildGraphMock).toHaveBeenCalled();
1245
+ // Verify the source files passed to buildGraph
1246
+ const sourceFiles = buildGraphMock.mock.calls[0][0] as Array<{ path: string; content: string }>;
1247
+ expect(sourceFiles.length).toBe(2);
1248
+ expect(sourceFiles.some(f => f.path.endsWith('.js'))).toBe(true);
1249
+ expect(sourceFiles.some(f => f.path.endsWith('.jsx'))).toBe(true);
1250
+ });
1251
+ });
1252
+
1253
+ describe('classifyWebContentType', () => {
1254
+ describe('documentation-primary classification', () => {
1255
+ it('classifies API reference URLs', () => {
1256
+ expect(classifyWebContentType('https://example.com/api-reference/endpoints')).toBe('documentation-primary');
1257
+ expect(classifyWebContentType('https://example.com/api-docs/v2')).toBe('documentation-primary');
1258
+ expect(classifyWebContentType('https://example.com/apiref/methods')).toBe('documentation-primary');
1259
+ });
1260
+
1261
+ it('classifies API reference by title', () => {
1262
+ expect(classifyWebContentType('https://example.com/page', 'API Reference Guide')).toBe('documentation-primary');
1263
+ expect(classifyWebContentType('https://example.com/page', 'Complete API Documentation')).toBe('documentation-primary');
1264
+ });
1265
+
1266
+ it('classifies getting started URLs', () => {
1267
+ expect(classifyWebContentType('https://example.com/getting-started')).toBe('documentation-primary');
1268
+ expect(classifyWebContentType('https://example.com/getting_started')).toBe('documentation-primary');
1269
+ expect(classifyWebContentType('https://example.com/gettingstarted')).toBe('documentation-primary');
1270
+ });
1271
+
1272
+ it('classifies quickstart URLs', () => {
1273
+ expect(classifyWebContentType('https://example.com/quickstart')).toBe('documentation-primary');
1274
+ });
1275
+
1276
+ it('classifies tutorial URLs', () => {
1277
+ expect(classifyWebContentType('https://example.com/tutorial/basics')).toBe('documentation-primary');
1278
+ });
1279
+
1280
+ it('classifies setup URLs', () => {
1281
+ expect(classifyWebContentType('https://example.com/setup')).toBe('documentation-primary');
1282
+ });
1283
+
1284
+ it('classifies getting started by title', () => {
1285
+ expect(classifyWebContentType('https://example.com/page', 'Getting Started with React')).toBe('documentation-primary');
1286
+ expect(classifyWebContentType('https://example.com/page', 'Quickstart Guide')).toBe('documentation-primary');
1287
+ expect(classifyWebContentType('https://example.com/page', 'Tutorial: Build Your First App')).toBe('documentation-primary');
1288
+ });
1289
+ });
1290
+
1291
+ describe('documentation classification', () => {
1292
+ it('classifies docs paths', () => {
1293
+ expect(classifyWebContentType('https://example.com/docs/intro')).toBe('documentation');
1294
+ expect(classifyWebContentType('https://example.com/documentation/advanced')).toBe('documentation');
1295
+ });
1296
+
1297
+ it('classifies reference paths', () => {
1298
+ expect(classifyWebContentType('https://example.com/reference/types')).toBe('documentation');
1299
+ });
1300
+
1301
+ it('classifies learn paths', () => {
1302
+ expect(classifyWebContentType('https://example.com/learn/basics')).toBe('documentation');
1303
+ });
1304
+
1305
+ it('classifies manual paths', () => {
1306
+ expect(classifyWebContentType('https://example.com/manual/chapter1')).toBe('documentation');
1307
+ });
1308
+
1309
+ it('classifies guide paths', () => {
1310
+ expect(classifyWebContentType('https://example.com/guide/installation')).toBe('documentation');
1311
+ });
1312
+ });
1313
+
1314
+ describe('example classification', () => {
1315
+ it('classifies examples paths', () => {
1316
+ expect(classifyWebContentType('https://example.com/examples/basic')).toBe('example');
1317
+ expect(classifyWebContentType('https://example.com/example/advanced')).toBe('example');
1318
+ });
1319
+
1320
+ it('classifies demos paths', () => {
1321
+ expect(classifyWebContentType('https://example.com/demos/interactive')).toBe('example');
1322
+ expect(classifyWebContentType('https://example.com/demo/live')).toBe('example');
1323
+ });
1324
+
1325
+ it('classifies samples paths', () => {
1326
+ expect(classifyWebContentType('https://example.com/samples/code')).toBe('example');
1327
+ });
1328
+
1329
+ it('classifies cookbook paths', () => {
1330
+ expect(classifyWebContentType('https://example.com/cookbook/recipes')).toBe('example');
1331
+ });
1332
+ });
1333
+
1334
+ describe('changelog classification', () => {
1335
+ it('classifies changelog paths', () => {
1336
+ // Note: URL must not contain /docs/, /doc/, etc. earlier in the path since those match first
1337
+ expect(classifyWebContentType('https://mysite.org/changelog')).toBe('changelog');
1338
+ });
1339
+
1340
+ it('classifies release notes paths', () => {
1341
+ expect(classifyWebContentType('https://mysite.org/release-notes')).toBe('changelog');
1342
+ expect(classifyWebContentType('https://mysite.org/release_notes')).toBe('changelog');
1343
+ expect(classifyWebContentType('https://mysite.org/releasenotes')).toBe('changelog');
1344
+ });
1345
+ });
1346
+
1347
+ describe('other classification', () => {
1348
+ it('classifies blog paths', () => {
1349
+ // Note: URL must not contain /docs/, /doc/, etc. earlier in the path since those match first
1350
+ expect(classifyWebContentType('https://mysite.org/blog/article')).toBe('other');
1351
+ });
1352
+ });
1353
+
1354
+ describe('default classification', () => {
1355
+ it('returns documentation for unrecognized paths', () => {
1356
+ // Note: URLs without recognizable patterns default to 'documentation'
1357
+ expect(classifyWebContentType('https://mysite.org/about')).toBe('documentation');
1358
+ expect(classifyWebContentType('https://mysite.org/')).toBe('documentation');
1359
+ });
1360
+
1361
+ it('handles undefined title', () => {
1362
+ expect(classifyWebContentType('https://mysite.org/page')).toBe('documentation');
1363
+ });
1364
+
1365
+ it('handles empty title', () => {
1366
+ expect(classifyWebContentType('https://mysite.org/page', '')).toBe('documentation');
1367
+ });
1368
+ });
1369
+ });
1370
+
1371
+ describe('IndexService - Additional File Type Classification', () => {
1372
+ let indexService: IndexService;
1373
+ let lanceStore: LanceStore;
1374
+ let embeddingEngine: EmbeddingEngine;
1375
+ let tempDir: string;
1376
+ let testFilesDir: string;
1377
+ const storeId = createStoreId('test-store');
1378
+
1379
+ beforeAll(async () => {
1380
+ tempDir = await mkdtemp(join(tmpdir(), 'index-addl-filetype-test-'));
1381
+ testFilesDir = join(tempDir, 'files');
1382
+ await mkdir(testFilesDir, { recursive: true });
1383
+
1384
+ lanceStore = new LanceStore(tempDir);
1385
+ embeddingEngine = new EmbeddingEngine();
1386
+
1387
+ await embeddingEngine.initialize();
1388
+ await lanceStore.initialize(storeId);
1389
+
1390
+ indexService = new IndexService(lanceStore, embeddingEngine);
1391
+ }, 120000);
1392
+
1393
+ afterAll(async () => {
1394
+ await rm(tempDir, { recursive: true, force: true });
1395
+ });
1396
+
1397
+ it('classifies changes.md as changelog', async () => {
1398
+ await writeFile(join(testFilesDir, 'changes.md'), '# Changes\n\n## v1.0.0');
1399
+
1400
+ const store: FileStore = {
1401
+ type: 'file',
1402
+ id: storeId,
1403
+ name: 'Test Store',
1404
+ path: testFilesDir,
1405
+ createdAt: new Date(),
1406
+ updatedAt: new Date(),
1407
+ };
1408
+
1409
+ const result = await indexService.indexStore(store);
1410
+ expect(result.success).toBe(true);
1411
+ });
1412
+
1413
+ it('classifies files with changelog in name', async () => {
1414
+ await writeFile(join(testFilesDir, 'project-changelog.md'), '# Project Changelog');
1415
+
1416
+ const store: FileStore = {
1417
+ type: 'file',
1418
+ id: storeId,
1419
+ name: 'Test Store',
1420
+ path: testFilesDir,
1421
+ createdAt: new Date(),
1422
+ updatedAt: new Date(),
1423
+ };
1424
+
1425
+ const result = await indexService.indexStore(store);
1426
+ expect(result.success).toBe(true);
1427
+ });
1428
+
1429
+ it('classifies CONTRIBUTING.md as documentation-primary', async () => {
1430
+ await writeFile(join(testFilesDir, 'CONTRIBUTING.md'), '# Contributing\n\nHow to contribute.');
1431
+
1432
+ const store: FileStore = {
1433
+ type: 'file',
1434
+ id: storeId,
1435
+ name: 'Test Store',
1436
+ path: testFilesDir,
1437
+ createdAt: new Date(),
1438
+ updatedAt: new Date(),
1439
+ };
1440
+
1441
+ const result = await indexService.indexStore(store);
1442
+ expect(result.success).toBe(true);
1443
+ });
1444
+
1445
+ it('classifies files in documentation/ directory', async () => {
1446
+ const docDir = join(testFilesDir, 'documentation');
1447
+ await mkdir(docDir, { recursive: true });
1448
+ await writeFile(join(docDir, 'overview.md'), '# Overview');
1449
+
1450
+ const store: FileStore = {
1451
+ type: 'file',
1452
+ id: storeId,
1453
+ name: 'Test Store',
1454
+ path: testFilesDir,
1455
+ createdAt: new Date(),
1456
+ updatedAt: new Date(),
1457
+ };
1458
+
1459
+ const result = await indexService.indexStore(store);
1460
+ expect(result.success).toBe(true);
1461
+ });
1462
+
1463
+ it('classifies files in guides/ directory', async () => {
1464
+ const guidesDir = join(testFilesDir, 'guides');
1465
+ await mkdir(guidesDir, { recursive: true });
1466
+ await writeFile(join(guidesDir, 'setup.md'), '# Setup Guide');
1467
+
1468
+ const store: FileStore = {
1469
+ type: 'file',
1470
+ id: storeId,
1471
+ name: 'Test Store',
1472
+ path: testFilesDir,
1473
+ createdAt: new Date(),
1474
+ updatedAt: new Date(),
1475
+ };
1476
+
1477
+ const result = await indexService.indexStore(store);
1478
+ expect(result.success).toBe(true);
1479
+ });
1480
+
1481
+ it('classifies files in tutorials/ directory', async () => {
1482
+ const tutorialsDir = join(testFilesDir, 'tutorials');
1483
+ await mkdir(tutorialsDir, { recursive: true });
1484
+ await writeFile(join(tutorialsDir, 'basics.md'), '# Basics Tutorial');
1485
+
1486
+ const store: FileStore = {
1487
+ type: 'file',
1488
+ id: storeId,
1489
+ name: 'Test Store',
1490
+ path: testFilesDir,
1491
+ createdAt: new Date(),
1492
+ updatedAt: new Date(),
1493
+ };
1494
+
1495
+ const result = await indexService.indexStore(store);
1496
+ expect(result.success).toBe(true);
1497
+ });
1498
+
1499
+ it('classifies files in articles/ directory', async () => {
1500
+ const articlesDir = join(testFilesDir, 'articles');
1501
+ await mkdir(articlesDir, { recursive: true });
1502
+ await writeFile(join(articlesDir, 'intro.md'), '# Introduction Article');
1503
+
1504
+ const store: FileStore = {
1505
+ type: 'file',
1506
+ id: storeId,
1507
+ name: 'Test Store',
1508
+ path: testFilesDir,
1509
+ createdAt: new Date(),
1510
+ updatedAt: new Date(),
1511
+ };
1512
+
1513
+ const result = await indexService.indexStore(store);
1514
+ expect(result.success).toBe(true);
1515
+ });
1516
+
1517
+ it('classifies files in __tests__/ directory as test', async () => {
1518
+ const testsDir = join(testFilesDir, '__tests__');
1519
+ await mkdir(testsDir, { recursive: true });
1520
+ await writeFile(join(testsDir, 'util.ts'), 'export function testHelper() {}');
1521
+
1522
+ const store: FileStore = {
1523
+ type: 'file',
1524
+ id: storeId,
1525
+ name: 'Test Store',
1526
+ path: testFilesDir,
1527
+ createdAt: new Date(),
1528
+ updatedAt: new Date(),
1529
+ };
1530
+
1531
+ const result = await indexService.indexStore(store);
1532
+ expect(result.success).toBe(true);
1533
+ });
1534
+
1535
+ it('classifies files with example in filename', async () => {
1536
+ await writeFile(join(testFilesDir, 'example-config.ts'), 'export const config = {};');
1537
+
1538
+ const store: FileStore = {
1539
+ type: 'file',
1540
+ id: storeId,
1541
+ name: 'Test Store',
1542
+ path: testFilesDir,
1543
+ createdAt: new Date(),
1544
+ updatedAt: new Date(),
1545
+ };
1546
+
1547
+ const result = await indexService.indexStore(store);
1548
+ expect(result.success).toBe(true);
1549
+ });
1550
+
1551
+ it('classifies .eslintrc as config', async () => {
1552
+ await writeFile(join(testFilesDir, '.eslintrc.json'), '{}');
1553
+
1554
+ const store: FileStore = {
1555
+ type: 'file',
1556
+ id: storeId,
1557
+ name: 'Test Store',
1558
+ path: testFilesDir,
1559
+ createdAt: new Date(),
1560
+ updatedAt: new Date(),
1561
+ };
1562
+
1563
+ const result = await indexService.indexStore(store);
1564
+ expect(result.success).toBe(true);
1565
+ });
1566
+
1567
+ it('classifies .prettierrc as config', async () => {
1568
+ await writeFile(join(testFilesDir, '.prettierrc.json'), '{}');
1569
+
1570
+ const store: FileStore = {
1571
+ type: 'file',
1572
+ id: storeId,
1573
+ name: 'Test Store',
1574
+ path: testFilesDir,
1575
+ createdAt: new Date(),
1576
+ updatedAt: new Date(),
1577
+ };
1578
+
1579
+ const result = await indexService.indexStore(store);
1580
+ expect(result.success).toBe(true);
1581
+ });
1582
+
1583
+ it('classifies vite.config as config', async () => {
1584
+ await writeFile(join(testFilesDir, 'vite.config.ts'), 'export default {};');
1585
+
1586
+ const store: FileStore = {
1587
+ type: 'file',
1588
+ id: storeId,
1589
+ name: 'Test Store',
1590
+ path: testFilesDir,
1591
+ createdAt: new Date(),
1592
+ updatedAt: new Date(),
1593
+ };
1594
+
1595
+ const result = await indexService.indexStore(store);
1596
+ expect(result.success).toBe(true);
1597
+ });
1598
+
1599
+ it('classifies next.config as config', async () => {
1600
+ await writeFile(join(testFilesDir, 'next.config.js'), 'module.exports = {};');
1601
+
1602
+ const store: FileStore = {
1603
+ type: 'file',
1604
+ id: storeId,
1605
+ name: 'Test Store',
1606
+ path: testFilesDir,
1607
+ createdAt: new Date(),
1608
+ updatedAt: new Date(),
1609
+ };
1610
+
1611
+ const result = await indexService.indexStore(store);
1612
+ expect(result.success).toBe(true);
1613
+ });
1614
+
1615
+ it('classifies .tsx files as source', async () => {
1616
+ await writeFile(join(testFilesDir, 'Component.tsx'), 'export const App = () => <div />;');
1617
+
1618
+ const store: FileStore = {
1619
+ type: 'file',
1620
+ id: storeId,
1621
+ name: 'Test Store',
1622
+ path: testFilesDir,
1623
+ createdAt: new Date(),
1624
+ updatedAt: new Date(),
1625
+ };
1626
+
1627
+ const result = await indexService.indexStore(store);
1628
+ expect(result.success).toBe(true);
1629
+ });
1630
+
1631
+ it('classifies .jsx files as source', async () => {
1632
+ await writeFile(join(testFilesDir, 'Component.jsx'), 'export const App = () => null;');
1633
+
1634
+ const store: FileStore = {
1635
+ type: 'file',
1636
+ id: storeId,
1637
+ name: 'Test Store',
1638
+ path: testFilesDir,
1639
+ createdAt: new Date(),
1640
+ updatedAt: new Date(),
1641
+ };
1642
+
1643
+ const result = await indexService.indexStore(store);
1644
+ expect(result.success).toBe(true);
1645
+ });
1646
+
1647
+ it('classifies .java files as source', async () => {
1648
+ await writeFile(join(testFilesDir, 'Main.java'), 'public class Main {}');
1649
+
1650
+ const store: FileStore = {
1651
+ type: 'file',
1652
+ id: storeId,
1653
+ name: 'Test Store',
1654
+ path: testFilesDir,
1655
+ createdAt: new Date(),
1656
+ updatedAt: new Date(),
1657
+ };
1658
+
1659
+ const result = await indexService.indexStore(store);
1660
+ expect(result.success).toBe(true);
1661
+ });
1662
+
1663
+ it('classifies unknown file types as other', async () => {
1664
+ await writeFile(join(testFilesDir, 'data.csv'), 'a,b,c\n1,2,3');
1665
+
1666
+ const store: FileStore = {
1667
+ type: 'file',
1668
+ id: storeId,
1669
+ name: 'Test Store',
1670
+ path: testFilesDir,
1671
+ createdAt: new Date(),
1672
+ updatedAt: new Date(),
1673
+ };
1674
+
1675
+ const result = await indexService.indexStore(store);
1676
+ expect(result.success).toBe(true);
1677
+ });
1678
+ });
1679
+
1680
+ describe('IndexService - Additional Internal Implementation Detection', () => {
1681
+ let indexService: IndexService;
1682
+ let lanceStore: LanceStore;
1683
+ let embeddingEngine: EmbeddingEngine;
1684
+ let tempDir: string;
1685
+ let testFilesDir: string;
1686
+ const storeId = createStoreId('test-store');
1687
+
1688
+ beforeAll(async () => {
1689
+ tempDir = await mkdtemp(join(tmpdir(), 'index-addl-internal-test-'));
1690
+ testFilesDir = join(tempDir, 'files');
1691
+ await mkdir(testFilesDir, { recursive: true });
1692
+
1693
+ lanceStore = new LanceStore(tempDir);
1694
+ embeddingEngine = new EmbeddingEngine();
1695
+
1696
+ await embeddingEngine.initialize();
1697
+ await lanceStore.initialize(storeId);
1698
+
1699
+ indexService = new IndexService(lanceStore, embeddingEngine);
1700
+ }, 120000);
1701
+
1702
+ afterAll(async () => {
1703
+ await rm(tempDir, { recursive: true, force: true });
1704
+ });
1705
+
1706
+ it('treats index.js in packages/*/src/ as public API', async () => {
1707
+ const pkgDir = join(testFilesDir, 'packages', 'ui', 'src');
1708
+ await mkdir(pkgDir, { recursive: true });
1709
+ await writeFile(join(pkgDir, 'index.js'), 'export * from "./components";');
1710
+
1711
+ const store: FileStore = {
1712
+ type: 'file',
1713
+ id: storeId,
1714
+ name: 'Test Store',
1715
+ path: testFilesDir,
1716
+ createdAt: new Date(),
1717
+ updatedAt: new Date(),
1718
+ };
1719
+
1720
+ const result = await indexService.indexStore(store);
1721
+ expect(result.success).toBe(true);
1722
+ });
1723
+
1724
+ it('detects lib/core/ directory files as internal', async () => {
1725
+ const libCoreDir = join(testFilesDir, 'lib', 'core');
1726
+ await mkdir(libCoreDir, { recursive: true });
1727
+ await writeFile(join(libCoreDir, 'internals.ts'), 'export function internal() {}');
1728
+
1729
+ const store: FileStore = {
1730
+ type: 'file',
1731
+ id: storeId,
1732
+ name: 'Test Store',
1733
+ path: testFilesDir,
1734
+ createdAt: new Date(),
1735
+ updatedAt: new Date(),
1736
+ };
1737
+
1738
+ const result = await indexService.indexStore(store);
1739
+ expect(result.success).toBe(true);
1740
+ });
1741
+
1742
+ it('detects core/src/ directory files as internal', async () => {
1743
+ const coreSrcDir = join(testFilesDir, 'core', 'src');
1744
+ await mkdir(coreSrcDir, { recursive: true });
1745
+ await writeFile(join(coreSrcDir, 'engine.ts'), 'export class Engine {}');
1746
+
1747
+ const store: FileStore = {
1748
+ type: 'file',
1749
+ id: storeId,
1750
+ name: 'Test Store',
1751
+ path: testFilesDir,
1752
+ createdAt: new Date(),
1753
+ updatedAt: new Date(),
1754
+ };
1755
+
1756
+ const result = await indexService.indexStore(store);
1757
+ expect(result.success).toBe(true);
1758
+ });
1759
+
1760
+ it('detects _internal/ directory files as internal', async () => {
1761
+ const internalDir = join(testFilesDir, '_internal');
1762
+ await mkdir(internalDir, { recursive: true });
1763
+ await writeFile(join(internalDir, 'utils.ts'), 'export function hidden() {}');
1764
+
1765
+ const store: FileStore = {
1766
+ type: 'file',
1767
+ id: storeId,
1768
+ name: 'Test Store',
1769
+ path: testFilesDir,
1770
+ createdAt: new Date(),
1771
+ updatedAt: new Date(),
1772
+ };
1773
+
1774
+ const result = await indexService.indexStore(store);
1775
+ expect(result.success).toBe(true);
1776
+ });
1777
+
1778
+ it('detects transforms/ directory files as internal', async () => {
1779
+ const transformsDir = join(testFilesDir, 'transforms');
1780
+ await mkdir(transformsDir, { recursive: true });
1781
+ await writeFile(join(transformsDir, 'optimize.ts'), 'export function transform() {}');
1782
+
1783
+ const store: FileStore = {
1784
+ type: 'file',
1785
+ id: storeId,
1786
+ name: 'Test Store',
1787
+ path: testFilesDir,
1788
+ createdAt: new Date(),
1789
+ updatedAt: new Date(),
1790
+ };
1791
+
1792
+ const result = await indexService.indexStore(store);
1793
+ expect(result.success).toBe(true);
1794
+ });
1795
+
1796
+ it('detects parse/ directory files as internal', async () => {
1797
+ const parseDir = join(testFilesDir, 'parse');
1798
+ await mkdir(parseDir, { recursive: true });
1799
+ await writeFile(join(parseDir, 'lexer.ts'), 'export class Lexer {}');
1800
+
1801
+ const store: FileStore = {
1802
+ type: 'file',
1803
+ id: storeId,
1804
+ name: 'Test Store',
1805
+ path: testFilesDir,
1806
+ createdAt: new Date(),
1807
+ updatedAt: new Date(),
1808
+ };
1809
+
1810
+ const result = await indexService.indexStore(store);
1811
+ expect(result.success).toBe(true);
1812
+ });
1813
+
1814
+ it('detects codegen/ directory files as internal', async () => {
1815
+ const codegenDir = join(testFilesDir, 'codegen');
1816
+ await mkdir(codegenDir, { recursive: true });
1817
+ await writeFile(join(codegenDir, 'generator.ts'), 'export class Generator {}');
1818
+
1819
+ const store: FileStore = {
1820
+ type: 'file',
1821
+ id: storeId,
1822
+ name: 'Test Store',
1823
+ path: testFilesDir,
1824
+ createdAt: new Date(),
1825
+ updatedAt: new Date(),
1826
+ };
1827
+
1828
+ const result = await indexService.indexStore(store);
1829
+ expect(result.success).toBe(true);
1830
+ });
1831
+
1832
+ it('does not mark readme in compiler dir as internal', async () => {
1833
+ const compilerDir = join(testFilesDir, 'src', 'compiler');
1834
+ await mkdir(compilerDir, { recursive: true });
1835
+ await writeFile(join(compilerDir, 'README.md'), '# Compiler Documentation');
1836
+
1837
+ const store: FileStore = {
1838
+ type: 'file',
1839
+ id: storeId,
1840
+ name: 'Test Store',
1841
+ path: testFilesDir,
1842
+ createdAt: new Date(),
1843
+ updatedAt: new Date(),
1844
+ };
1845
+
1846
+ const result = await indexService.indexStore(store);
1847
+ expect(result.success).toBe(true);
1848
+ });
1849
+
1850
+ it('does not mark index.ts in compiler dir as internal', async () => {
1851
+ const compilerDir = join(testFilesDir, 'src', 'compiler2');
1852
+ await mkdir(compilerDir, { recursive: true });
1853
+ await writeFile(join(compilerDir, 'index.ts'), 'export * from "./api";');
1854
+
1855
+ const store: FileStore = {
1856
+ type: 'file',
1857
+ id: storeId,
1858
+ name: 'Test Store',
1859
+ path: testFilesDir,
1860
+ createdAt: new Date(),
1861
+ updatedAt: new Date(),
1862
+ };
1863
+
1864
+ const result = await indexService.indexStore(store);
1865
+ expect(result.success).toBe(true);
1866
+ });
1867
+ });
1868
+
1869
+ describe('IndexService - Symlink Handling', () => {
1870
+ let indexService: IndexService;
1871
+ let lanceStore: LanceStore;
1872
+ let embeddingEngine: EmbeddingEngine;
1873
+ let tempDir: string;
1874
+ let testFilesDir: string;
1875
+ const storeId = createStoreId('test-store');
1876
+
1877
+ beforeAll(async () => {
1878
+ tempDir = await mkdtemp(join(tmpdir(), 'index-symlink-test-'));
1879
+ testFilesDir = join(tempDir, 'files');
1880
+ await mkdir(testFilesDir, { recursive: true });
1881
+
1882
+ lanceStore = new LanceStore(tempDir);
1883
+ embeddingEngine = new EmbeddingEngine();
1884
+
1885
+ await embeddingEngine.initialize();
1886
+ await lanceStore.initialize(storeId);
1887
+
1888
+ indexService = new IndexService(lanceStore, embeddingEngine);
1889
+ }, 120000);
1890
+
1891
+ afterAll(async () => {
1892
+ await rm(tempDir, { recursive: true, force: true });
1893
+ });
1894
+
1895
+ it('skips symlinks (non-file, non-directory entries)', async () => {
1896
+ // Create a real file
1897
+ await writeFile(join(testFilesDir, 'real.ts'), 'export const real = true;');
1898
+
1899
+ // Create a symlink to the file (symlinks are neither isDirectory() nor isFile() when using withFileTypes)
1900
+ try {
1901
+ await symlink(join(testFilesDir, 'real.ts'), join(testFilesDir, 'link.ts'));
1902
+ } catch {
1903
+ // Symlink creation may fail on some systems/permissions - skip test in that case
1904
+ return;
1905
+ }
1906
+
1907
+ const store: FileStore = {
1908
+ type: 'file',
1909
+ id: storeId,
1910
+ name: 'Test Store',
1911
+ path: testFilesDir,
1912
+ createdAt: new Date(),
1913
+ updatedAt: new Date(),
1914
+ };
1915
+
1916
+ const result = await indexService.indexStore(store);
1917
+
1918
+ expect(result.success).toBe(true);
1919
+ // The symlink should be processed if it points to a valid file
1920
+ // (on most systems, readdir with withFileTypes shows symlinks as isFile() if target is file)
1921
+ });
1922
+ });