bluera-knowledge 0.9.32 → 0.9.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/.claude/hooks/post-edit-check.sh +5 -3
  2. package/.claude/skills/atomic-commits/SKILL.md +3 -1
  3. package/.husky/pre-commit +3 -2
  4. package/.prettierrc +9 -0
  5. package/.versionrc.json +1 -1
  6. package/CHANGELOG.md +33 -0
  7. package/CLAUDE.md +6 -0
  8. package/README.md +25 -13
  9. package/bun.lock +277 -33
  10. package/dist/{chunk-L2YVNC63.js → chunk-6FHWC36B.js} +9 -1
  11. package/dist/chunk-6FHWC36B.js.map +1 -0
  12. package/dist/{chunk-RST4XGRL.js → chunk-DC7CGSGT.js} +288 -241
  13. package/dist/chunk-DC7CGSGT.js.map +1 -0
  14. package/dist/{chunk-6PBP5DVD.js → chunk-WFNPNAAP.js} +3212 -3054
  15. package/dist/chunk-WFNPNAAP.js.map +1 -0
  16. package/dist/{chunk-WT2DAEO7.js → chunk-Z2KKVH45.js} +548 -482
  17. package/dist/chunk-Z2KKVH45.js.map +1 -0
  18. package/dist/index.js +871 -758
  19. package/dist/index.js.map +1 -1
  20. package/dist/mcp/server.js +3 -3
  21. package/dist/watch.service-BJV3TI3F.js +7 -0
  22. package/dist/workers/background-worker-cli.js +46 -45
  23. package/dist/workers/background-worker-cli.js.map +1 -1
  24. package/eslint.config.js +43 -1
  25. package/package.json +18 -11
  26. package/plugin.json +8 -0
  27. package/python/requirements.txt +1 -1
  28. package/src/analysis/ast-parser.test.ts +12 -11
  29. package/src/analysis/ast-parser.ts +28 -22
  30. package/src/analysis/code-graph.test.ts +52 -62
  31. package/src/analysis/code-graph.ts +9 -13
  32. package/src/analysis/dependency-usage-analyzer.test.ts +91 -271
  33. package/src/analysis/dependency-usage-analyzer.ts +52 -24
  34. package/src/analysis/go-ast-parser.test.ts +22 -22
  35. package/src/analysis/go-ast-parser.ts +18 -25
  36. package/src/analysis/parser-factory.test.ts +9 -9
  37. package/src/analysis/parser-factory.ts +3 -3
  38. package/src/analysis/python-ast-parser.test.ts +27 -27
  39. package/src/analysis/python-ast-parser.ts +2 -2
  40. package/src/analysis/repo-url-resolver.test.ts +82 -82
  41. package/src/analysis/rust-ast-parser.test.ts +19 -19
  42. package/src/analysis/rust-ast-parser.ts +17 -27
  43. package/src/analysis/tree-sitter-parser.test.ts +3 -3
  44. package/src/analysis/tree-sitter-parser.ts +10 -16
  45. package/src/cli/commands/crawl.test.ts +40 -24
  46. package/src/cli/commands/crawl.ts +186 -166
  47. package/src/cli/commands/index-cmd.test.ts +90 -90
  48. package/src/cli/commands/index-cmd.ts +52 -36
  49. package/src/cli/commands/mcp.test.ts +6 -6
  50. package/src/cli/commands/mcp.ts +2 -2
  51. package/src/cli/commands/plugin-api.test.ts +16 -18
  52. package/src/cli/commands/plugin-api.ts +9 -6
  53. package/src/cli/commands/search.test.ts +16 -7
  54. package/src/cli/commands/search.ts +124 -87
  55. package/src/cli/commands/serve.test.ts +67 -25
  56. package/src/cli/commands/serve.ts +18 -3
  57. package/src/cli/commands/setup.test.ts +176 -101
  58. package/src/cli/commands/setup.ts +140 -117
  59. package/src/cli/commands/store.test.ts +82 -53
  60. package/src/cli/commands/store.ts +56 -37
  61. package/src/cli/program.ts +2 -2
  62. package/src/crawl/article-converter.test.ts +4 -1
  63. package/src/crawl/article-converter.ts +46 -31
  64. package/src/crawl/bridge.test.ts +240 -132
  65. package/src/crawl/bridge.ts +87 -30
  66. package/src/crawl/claude-client.test.ts +124 -56
  67. package/src/crawl/claude-client.ts +7 -15
  68. package/src/crawl/intelligent-crawler.test.ts +65 -22
  69. package/src/crawl/intelligent-crawler.ts +86 -53
  70. package/src/crawl/markdown-utils.ts +1 -4
  71. package/src/db/embeddings.ts +4 -6
  72. package/src/db/lance.test.ts +4 -4
  73. package/src/db/lance.ts +16 -12
  74. package/src/index.ts +26 -17
  75. package/src/logging/index.ts +1 -5
  76. package/src/logging/logger.ts +3 -5
  77. package/src/logging/payload.test.ts +1 -1
  78. package/src/logging/payload.ts +3 -5
  79. package/src/mcp/commands/index.ts +2 -2
  80. package/src/mcp/commands/job.commands.ts +12 -18
  81. package/src/mcp/commands/meta.commands.ts +13 -13
  82. package/src/mcp/commands/registry.ts +5 -8
  83. package/src/mcp/commands/store.commands.ts +19 -19
  84. package/src/mcp/handlers/execute.handler.test.ts +10 -10
  85. package/src/mcp/handlers/execute.handler.ts +4 -5
  86. package/src/mcp/handlers/index.ts +10 -14
  87. package/src/mcp/handlers/job.handler.test.ts +10 -10
  88. package/src/mcp/handlers/job.handler.ts +22 -25
  89. package/src/mcp/handlers/search.handler.test.ts +36 -65
  90. package/src/mcp/handlers/search.handler.ts +135 -104
  91. package/src/mcp/handlers/store.handler.test.ts +41 -52
  92. package/src/mcp/handlers/store.handler.ts +108 -88
  93. package/src/mcp/schemas/index.test.ts +73 -68
  94. package/src/mcp/schemas/index.ts +18 -12
  95. package/src/mcp/server.test.ts +1 -1
  96. package/src/mcp/server.ts +59 -46
  97. package/src/plugin/commands.test.ts +230 -95
  98. package/src/plugin/commands.ts +24 -25
  99. package/src/plugin/dependency-analyzer.test.ts +52 -52
  100. package/src/plugin/dependency-analyzer.ts +85 -22
  101. package/src/plugin/git-clone.test.ts +24 -13
  102. package/src/plugin/git-clone.ts +3 -7
  103. package/src/server/app.test.ts +109 -109
  104. package/src/server/app.ts +32 -23
  105. package/src/server/index.test.ts +64 -66
  106. package/src/services/chunking.service.test.ts +32 -32
  107. package/src/services/chunking.service.ts +16 -9
  108. package/src/services/code-graph.service.test.ts +30 -36
  109. package/src/services/code-graph.service.ts +24 -10
  110. package/src/services/code-unit.service.test.ts +55 -11
  111. package/src/services/code-unit.service.ts +85 -11
  112. package/src/services/config.service.test.ts +37 -18
  113. package/src/services/config.service.ts +30 -7
  114. package/src/services/index.service.test.ts +49 -18
  115. package/src/services/index.service.ts +98 -48
  116. package/src/services/index.ts +6 -9
  117. package/src/services/job.service.test.ts +22 -22
  118. package/src/services/job.service.ts +18 -18
  119. package/src/services/project-root.service.test.ts +1 -3
  120. package/src/services/search.service.test.ts +248 -120
  121. package/src/services/search.service.ts +286 -156
  122. package/src/services/services.test.ts +1 -1
  123. package/src/services/snippet.service.test.ts +14 -6
  124. package/src/services/snippet.service.ts +7 -5
  125. package/src/services/store.service.test.ts +68 -29
  126. package/src/services/store.service.ts +41 -12
  127. package/src/services/watch.service.test.ts +34 -14
  128. package/src/services/watch.service.ts +11 -1
  129. package/src/types/brands.test.ts +3 -1
  130. package/src/types/index.ts +2 -13
  131. package/src/types/search.ts +10 -8
  132. package/src/utils/type-guards.test.ts +20 -15
  133. package/src/utils/type-guards.ts +1 -1
  134. package/src/workers/background-worker-cli.ts +2 -2
  135. package/src/workers/background-worker.test.ts +54 -40
  136. package/src/workers/background-worker.ts +76 -60
  137. package/src/workers/spawn-worker.test.ts +22 -10
  138. package/src/workers/spawn-worker.ts +6 -6
  139. package/tests/analysis/ast-parser.test.ts +3 -3
  140. package/tests/analysis/code-graph.test.ts +5 -5
  141. package/tests/fixtures/code-snippets/api/error-handling.ts +4 -15
  142. package/tests/fixtures/code-snippets/api/rest-controller.ts +3 -9
  143. package/tests/fixtures/code-snippets/auth/jwt-auth.ts +5 -21
  144. package/tests/fixtures/code-snippets/auth/oauth-flow.ts +4 -4
  145. package/tests/fixtures/code-snippets/database/repository-pattern.ts +11 -3
  146. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/aws-lambda/handler.ts +2 -2
  147. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/cloudflare-pages/handler.ts +1 -1
  148. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/cloudflare-workers/serve-static.ts +2 -2
  149. package/tests/fixtures/corpus/oss-repos/hono/src/client/client.ts +2 -2
  150. package/tests/fixtures/corpus/oss-repos/hono/src/client/types.ts +22 -20
  151. package/tests/fixtures/corpus/oss-repos/hono/src/context.ts +13 -10
  152. package/tests/fixtures/corpus/oss-repos/hono/src/helper/accepts/accepts.ts +10 -7
  153. package/tests/fixtures/corpus/oss-repos/hono/src/helper/adapter/index.ts +2 -2
  154. package/tests/fixtures/corpus/oss-repos/hono/src/helper/css/index.ts +1 -1
  155. package/tests/fixtures/corpus/oss-repos/hono/src/helper/factory/index.ts +16 -16
  156. package/tests/fixtures/corpus/oss-repos/hono/src/helper/ssg/ssg.ts +2 -2
  157. package/tests/fixtures/corpus/oss-repos/hono/src/hono-base.ts +3 -3
  158. package/tests/fixtures/corpus/oss-repos/hono/src/hono.ts +1 -1
  159. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/css.ts +2 -2
  160. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/intrinsic-element/components.ts +1 -1
  161. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/render.ts +7 -7
  162. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/hooks/index.ts +3 -3
  163. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/intrinsic-element/components.ts +1 -1
  164. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/utils.ts +6 -6
  165. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/jsx-renderer/index.ts +3 -3
  166. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/serve-static/index.ts +1 -1
  167. package/tests/fixtures/corpus/oss-repos/hono/src/preset/quick.ts +1 -1
  168. package/tests/fixtures/corpus/oss-repos/hono/src/preset/tiny.ts +1 -1
  169. package/tests/fixtures/corpus/oss-repos/hono/src/router/pattern-router/router.ts +2 -2
  170. package/tests/fixtures/corpus/oss-repos/hono/src/router/reg-exp-router/node.ts +4 -4
  171. package/tests/fixtures/corpus/oss-repos/hono/src/router/reg-exp-router/router.ts +1 -1
  172. package/tests/fixtures/corpus/oss-repos/hono/src/router/trie-router/node.ts +1 -1
  173. package/tests/fixtures/corpus/oss-repos/hono/src/types.ts +166 -169
  174. package/tests/fixtures/corpus/oss-repos/hono/src/utils/body.ts +8 -8
  175. package/tests/fixtures/corpus/oss-repos/hono/src/utils/color.ts +3 -3
  176. package/tests/fixtures/corpus/oss-repos/hono/src/utils/cookie.ts +2 -2
  177. package/tests/fixtures/corpus/oss-repos/hono/src/utils/encode.ts +2 -2
  178. package/tests/fixtures/corpus/oss-repos/hono/src/utils/types.ts +30 -33
  179. package/tests/fixtures/corpus/oss-repos/hono/src/validator/validator.ts +2 -2
  180. package/tests/fixtures/test-server.ts +3 -2
  181. package/tests/helpers/performance-metrics.ts +8 -25
  182. package/tests/helpers/search-relevance.ts +14 -69
  183. package/tests/integration/cli-consistency.test.ts +5 -4
  184. package/tests/integration/python-bridge.test.ts +13 -3
  185. package/tests/mcp/server.test.ts +1 -1
  186. package/tests/services/code-unit.service.test.ts +48 -0
  187. package/tests/services/job.service.test.ts +124 -0
  188. package/tests/services/search.progressive-context.test.ts +2 -2
  189. package/.claude-plugin/plugin.json +0 -13
  190. package/dist/chunk-6PBP5DVD.js.map +0 -1
  191. package/dist/chunk-L2YVNC63.js.map +0 -1
  192. package/dist/chunk-RST4XGRL.js.map +0 -1
  193. package/dist/chunk-WT2DAEO7.js.map +0 -1
  194. package/dist/watch.service-YAIKKDCF.js +0 -7
  195. package/skills/atomic-commits/SKILL.md +0 -77
  196. /package/dist/{watch.service-YAIKKDCF.js.map → watch.service-BJV3TI3F.js.map} +0 -0
@@ -1,12 +1,17 @@
1
- import type { LanceStore } from '../db/lance.js';
2
- import type { EmbeddingEngine } from '../db/embeddings.js';
3
- import type { SearchQuery, SearchResponse, SearchResult, DetailLevel } from '../types/search.js';
4
- import type { StoreId } from '../types/brands.js';
5
1
  import { CodeUnitService } from './code-unit.service.js';
6
- import type { CodeUnit } from '../types/search.js';
2
+ import { createLogger } from '../logging/index.js';
7
3
  import type { CodeGraphService } from './code-graph.service.js';
8
4
  import type { CodeGraph } from '../analysis/code-graph.js';
9
- import { createLogger } from '../logging/index.js';
5
+ import type { EmbeddingEngine } from '../db/embeddings.js';
6
+ import type { LanceStore } from '../db/lance.js';
7
+ import type { StoreId } from '../types/brands.js';
8
+ import type {
9
+ SearchQuery,
10
+ SearchResponse,
11
+ SearchResult,
12
+ DetailLevel,
13
+ CodeUnit,
14
+ } from '../types/search.js';
10
15
 
11
16
  const logger = createLogger('search-service');
12
17
 
@@ -32,54 +37,54 @@ export interface ClassifiedIntent {
32
37
  */
33
38
  const INTENT_FILE_BOOSTS: Record<QueryIntent, Record<string, number>> = {
34
39
  'how-to': {
35
- 'documentation-primary': 1.3, // Strong boost for docs
36
- 'documentation': 1.2,
37
- 'example': 1.5, // Examples are ideal for "how to"
38
- 'source': 0.85, // Moderate penalty - source might still have good content
39
- 'source-internal': 0.7, // Stronger penalty - internal code less useful
40
- 'test': 0.8,
41
- 'config': 0.7,
42
- 'other': 0.9,
40
+ 'documentation-primary': 1.3, // Strong boost for docs
41
+ documentation: 1.2,
42
+ example: 1.5, // Examples are ideal for "how to"
43
+ source: 0.85, // Moderate penalty - source might still have good content
44
+ 'source-internal': 0.7, // Stronger penalty - internal code less useful
45
+ test: 0.8,
46
+ config: 0.7,
47
+ other: 0.9,
43
48
  },
44
- 'implementation': {
49
+ implementation: {
45
50
  'documentation-primary': 0.95,
46
- 'documentation': 1.0,
47
- 'example': 1.0,
48
- 'source': 1.1, // Slight boost for source code
49
- 'source-internal': 1.05, // Internal code can be relevant
50
- 'test': 1.0,
51
- 'config': 0.95,
52
- 'other': 1.0,
51
+ documentation: 1.0,
52
+ example: 1.0,
53
+ source: 1.1, // Slight boost for source code
54
+ 'source-internal': 1.05, // Internal code can be relevant
55
+ test: 1.0,
56
+ config: 0.95,
57
+ other: 1.0,
53
58
  },
54
- 'conceptual': {
59
+ conceptual: {
55
60
  'documentation-primary': 1.1,
56
- 'documentation': 1.05,
57
- 'example': 1.0,
58
- 'source': 0.95,
61
+ documentation: 1.05,
62
+ example: 1.0,
63
+ source: 0.95,
59
64
  'source-internal': 0.9,
60
- 'test': 0.9,
61
- 'config': 0.85,
62
- 'other': 0.95,
65
+ test: 0.9,
66
+ config: 0.85,
67
+ other: 0.95,
63
68
  },
64
- 'comparison': {
69
+ comparison: {
65
70
  'documentation-primary': 1.15,
66
- 'documentation': 1.1,
67
- 'example': 1.05,
68
- 'source': 0.9,
71
+ documentation: 1.1,
72
+ example: 1.05,
73
+ source: 0.9,
69
74
  'source-internal': 0.85,
70
- 'test': 0.9,
71
- 'config': 0.85,
72
- 'other': 0.95,
75
+ test: 0.9,
76
+ config: 0.85,
77
+ other: 0.95,
73
78
  },
74
- 'debugging': {
79
+ debugging: {
75
80
  'documentation-primary': 1.0,
76
- 'documentation': 1.0,
77
- 'example': 1.05,
78
- 'source': 1.0, // Source code helps with debugging
81
+ documentation: 1.0,
82
+ example: 1.05,
83
+ source: 1.0, // Source code helps with debugging
79
84
  'source-internal': 0.95,
80
- 'test': 1.05, // Tests can show expected behavior
81
- 'config': 0.9,
82
- 'other': 1.0,
85
+ test: 1.05, // Tests can show expected behavior
86
+ config: 0.9,
87
+ other: 1.0,
83
88
  },
84
89
  };
85
90
 
@@ -153,23 +158,23 @@ function classifyQueryIntents(query: string): ClassifiedIntent[] {
153
158
  const intents: ClassifiedIntent[] = [];
154
159
 
155
160
  // Check all pattern groups and add matching intents with confidence
156
- if (IMPLEMENTATION_PATTERNS.some(p => p.test(q))) {
161
+ if (IMPLEMENTATION_PATTERNS.some((p) => p.test(q))) {
157
162
  intents.push({ intent: 'implementation', confidence: 0.9 });
158
163
  }
159
164
 
160
- if (DEBUGGING_PATTERNS.some(p => p.test(q))) {
165
+ if (DEBUGGING_PATTERNS.some((p) => p.test(q))) {
161
166
  intents.push({ intent: 'debugging', confidence: 0.85 });
162
167
  }
163
168
 
164
- if (COMPARISON_PATTERNS.some(p => p.test(q))) {
169
+ if (COMPARISON_PATTERNS.some((p) => p.test(q))) {
165
170
  intents.push({ intent: 'comparison', confidence: 0.8 });
166
171
  }
167
172
 
168
- if (HOW_TO_PATTERNS.some(p => p.test(q))) {
173
+ if (HOW_TO_PATTERNS.some((p) => p.test(q))) {
169
174
  intents.push({ intent: 'how-to', confidence: 0.75 });
170
175
  }
171
176
 
172
- if (CONCEPTUAL_PATTERNS.some(p => p.test(q))) {
177
+ if (CONCEPTUAL_PATTERNS.some((p) => p.test(q))) {
173
178
  intents.push({ intent: 'conceptual', confidence: 0.7 });
174
179
  }
175
180
 
@@ -202,7 +207,7 @@ const RRF_PRESETS = {
202
207
  * Detect if results are primarily web content (have urls vs file paths).
203
208
  */
204
209
  function detectContentType(results: SearchResult[]): 'web' | 'code' {
205
- const webCount = results.filter(r => 'url' in r.metadata).length;
210
+ const webCount = results.filter((r) => 'url' in r.metadata).length;
206
211
  return webCount > results.length / 2 ? 'web' : 'code';
207
212
  }
208
213
 
@@ -250,15 +255,18 @@ export class SearchService {
250
255
  const intents = classifyQueryIntents(query.query);
251
256
  const primaryIntent = getPrimaryIntent(intents);
252
257
 
253
- logger.debug({
254
- query: query.query,
255
- mode,
256
- limit,
257
- stores,
258
- detail,
259
- intent: primaryIntent,
260
- intents,
261
- }, 'Search query received');
258
+ logger.debug(
259
+ {
260
+ query: query.query,
261
+ mode,
262
+ limit,
263
+ stores,
264
+ detail,
265
+ intent: primaryIntent,
266
+ intents,
267
+ },
268
+ 'Search query received'
269
+ );
262
270
 
263
271
  let allResults: SearchResult[] = [];
264
272
 
@@ -281,28 +289,31 @@ export class SearchService {
281
289
  // Load code graphs for stores in results (for contextual/full detail levels)
282
290
  const graphs = new Map<string, CodeGraph | null>();
283
291
  if (detail === 'contextual' || detail === 'full') {
284
- const storeIds = new Set(resultsToEnhance.map(r => r.metadata.storeId));
292
+ const storeIds = new Set(resultsToEnhance.map((r) => r.metadata.storeId));
285
293
  for (const storeId of storeIds) {
286
294
  graphs.set(storeId, await this.loadGraphForStore(storeId));
287
295
  }
288
296
  }
289
297
 
290
298
  // Enhance results with progressive context
291
- const enhancedResults = resultsToEnhance.map(r => {
299
+ const enhancedResults = resultsToEnhance.map((r) => {
292
300
  const graph = graphs.get(r.metadata.storeId) ?? null;
293
301
  return this.addProgressiveContext(r, query.query, detail, graph);
294
302
  });
295
303
 
296
304
  const timeMs = Date.now() - startTime;
297
305
 
298
- logger.info({
299
- query: query.query,
300
- mode,
301
- resultCount: enhancedResults.length,
302
- dedupedFrom: allResults.length,
303
- intents: intents.map(i => `${i.intent}(${i.confidence.toFixed(2)})`),
304
- timeMs,
305
- }, 'Search complete');
306
+ logger.info(
307
+ {
308
+ query: query.query,
309
+ mode,
310
+ resultCount: enhancedResults.length,
311
+ dedupedFrom: allResults.length,
312
+ intents: intents.map((i) => `${i.intent}(${i.confidence.toFixed(2)})`),
313
+ timeMs,
314
+ },
315
+ 'Search complete'
316
+ );
306
317
 
307
318
  return {
308
319
  query: query.query,
@@ -320,7 +331,10 @@ export class SearchService {
320
331
  */
321
332
  private deduplicateBySource(results: SearchResult[], query: string): SearchResult[] {
322
333
  const bySource = new Map<string, SearchResult>();
323
- const queryTerms = query.toLowerCase().split(/\s+/).filter(t => t.length > 2);
334
+ const queryTerms = query
335
+ .toLowerCase()
336
+ .split(/\s+/)
337
+ .filter((t) => t.length > 2);
324
338
 
325
339
  for (const result of results) {
326
340
  // Use file path as the source key (or url for web content, or id as last resort)
@@ -353,7 +367,7 @@ export class SearchService {
353
367
  */
354
368
  private countQueryTerms(content: string, queryTerms: string[]): number {
355
369
  const lowerContent = content.toLowerCase();
356
- return queryTerms.filter(term => lowerContent.includes(term)).length;
370
+ return queryTerms.filter((term) => lowerContent.includes(term)).length;
357
371
  }
358
372
 
359
373
  private async vectorSearch(
@@ -367,12 +381,14 @@ export class SearchService {
367
381
 
368
382
  for (const storeId of stores) {
369
383
  const hits = await this.lanceStore.search(storeId, queryVector, limit, threshold);
370
- results.push(...hits.map(r => ({
371
- id: r.id,
372
- score: r.score,
373
- content: r.content,
374
- metadata: r.metadata,
375
- })));
384
+ results.push(
385
+ ...hits.map((r) => ({
386
+ id: r.id,
387
+ score: r.score,
388
+ content: r.content,
389
+ metadata: r.metadata,
390
+ }))
391
+ );
376
392
  }
377
393
 
378
394
  return results.sort((a, b) => b.score - a.score).slice(0, limit);
@@ -387,12 +403,14 @@ export class SearchService {
387
403
 
388
404
  for (const storeId of stores) {
389
405
  const hits = await this.lanceStore.fullTextSearch(storeId, query, limit);
390
- results.push(...hits.map(r => ({
391
- id: r.id,
392
- score: r.score,
393
- content: r.content,
394
- metadata: r.metadata,
395
- })));
406
+ results.push(
407
+ ...hits.map((r) => ({
408
+ id: r.id,
409
+ score: r.score,
410
+ content: r.content,
411
+ metadata: r.metadata,
412
+ }))
413
+ );
396
414
  }
397
415
 
398
416
  return results.sort((a, b) => b.score - a.score).slice(0, limit);
@@ -501,7 +519,12 @@ export class SearchService {
501
519
 
502
520
  rrfScores.push({
503
521
  id,
504
- score: (vectorRRF + ftsRRF) * fileTypeBoost * frameworkBoost * urlKeywordBoost * pathKeywordBoost,
522
+ score:
523
+ (vectorRRF + ftsRRF) *
524
+ fileTypeBoost *
525
+ frameworkBoost *
526
+ urlKeywordBoost *
527
+ pathKeywordBoost,
505
528
  result,
506
529
  metadata,
507
530
  });
@@ -515,7 +538,7 @@ export class SearchService {
515
538
  const first = sorted[0];
516
539
  const last = sorted[sorted.length - 1];
517
540
  if (first === undefined || last === undefined) {
518
- return sorted.map(r => ({
541
+ return sorted.map((r) => ({
519
542
  ...r.result,
520
543
  score: r.score,
521
544
  rankingMetadata: r.metadata,
@@ -526,7 +549,7 @@ export class SearchService {
526
549
  const range = maxScore - minScore;
527
550
 
528
551
  if (range > 0) {
529
- return sorted.map(r => ({
552
+ return sorted.map((r) => ({
530
553
  ...r.result,
531
554
  score: (r.score - minScore) / range,
532
555
  rankingMetadata: r.metadata,
@@ -534,17 +557,14 @@ export class SearchService {
534
557
  }
535
558
  }
536
559
 
537
- return sorted.map(r => ({
560
+ return sorted.map((r) => ({
538
561
  ...r.result,
539
562
  score: r.score,
540
563
  rankingMetadata: r.metadata,
541
564
  }));
542
565
  }
543
566
 
544
- async searchAllStores(
545
- query: SearchQuery,
546
- storeIds: StoreId[]
547
- ): Promise<SearchResponse> {
567
+ async searchAllStores(query: SearchQuery, storeIds: StoreId[]): Promise<SearchResponse> {
548
568
  return this.search({
549
569
  ...query,
550
570
  stores: storeIds,
@@ -562,25 +582,25 @@ export class SearchService {
562
582
  let baseBoost: number;
563
583
  switch (fileType) {
564
584
  case 'documentation-primary':
565
- baseBoost = 1.8; // README, guides get very strong boost
585
+ baseBoost = 1.8; // README, guides get very strong boost
566
586
  break;
567
587
  case 'documentation':
568
- baseBoost = 1.5; // docs/, tutorials/ get strong boost
588
+ baseBoost = 1.5; // docs/, tutorials/ get strong boost
569
589
  break;
570
590
  case 'example':
571
- baseBoost = 1.4; // examples/, demos/ are highly valuable
591
+ baseBoost = 1.4; // examples/, demos/ are highly valuable
572
592
  break;
573
593
  case 'source':
574
- baseBoost = 1.0; // Source code baseline
594
+ baseBoost = 1.0; // Source code baseline
575
595
  break;
576
596
  case 'source-internal':
577
- baseBoost = 0.75; // Internal implementation files (not too harsh)
597
+ baseBoost = 0.75; // Internal implementation files (not too harsh)
578
598
  break;
579
599
  case 'test':
580
- baseBoost = 0.7; // Tests significantly lower
600
+ baseBoost = 0.7; // Tests significantly lower
581
601
  break;
582
602
  case 'config':
583
- baseBoost = 0.5; // Config files rarely answer questions
603
+ baseBoost = 0.5; // Config files rarely answer questions
584
604
  break;
585
605
  default:
586
606
  baseBoost = 1.0;
@@ -597,9 +617,7 @@ export class SearchService {
597
617
  totalConfidence += confidence;
598
618
  }
599
619
 
600
- const blendedMultiplier = totalConfidence > 0
601
- ? weightedMultiplier / totalConfidence
602
- : 1.0;
620
+ const blendedMultiplier = totalConfidence > 0 ? weightedMultiplier / totalConfidence : 1.0;
603
621
 
604
622
  return baseBoost * blendedMultiplier;
605
623
  }
@@ -618,27 +636,52 @@ export class SearchService {
618
636
 
619
637
  // Common stop words to filter from queries
620
638
  const stopWords = new Set([
621
- 'how', 'to', 'the', 'a', 'an', 'is', 'are', 'what', 'why', 'when',
622
- 'where', 'can', 'do', 'does', 'i', 'my', 'your', 'it', 'in', 'on',
623
- 'for', 'with', 'this', 'that', 'get', 'use', 'using'
639
+ 'how',
640
+ 'to',
641
+ 'the',
642
+ 'a',
643
+ 'an',
644
+ 'is',
645
+ 'are',
646
+ 'what',
647
+ 'why',
648
+ 'when',
649
+ 'where',
650
+ 'can',
651
+ 'do',
652
+ 'does',
653
+ 'i',
654
+ 'my',
655
+ 'your',
656
+ 'it',
657
+ 'in',
658
+ 'on',
659
+ 'for',
660
+ 'with',
661
+ 'this',
662
+ 'that',
663
+ 'get',
664
+ 'use',
665
+ 'using',
624
666
  ]);
625
667
 
626
668
  // Extract meaningful query terms
627
- const queryTerms = query.toLowerCase()
669
+ const queryTerms = query
670
+ .toLowerCase()
628
671
  .split(/\s+/)
629
- .filter(t => t.length > 2 && !stopWords.has(t));
672
+ .filter((t) => t.length > 2 && !stopWords.has(t));
630
673
 
631
674
  if (queryTerms.length === 0) return 1.0;
632
675
 
633
676
  // Count matching terms in URL path
634
- const matchingTerms = queryTerms.filter(term => urlPath.includes(term));
677
+ const matchingTerms = queryTerms.filter((term) => urlPath.includes(term));
635
678
 
636
679
  if (matchingTerms.length === 0) return 1.0;
637
680
 
638
681
  // Boost based on proportion of matching terms
639
682
  // Single match: ~1.5, all terms match: ~2.0
640
683
  const matchRatio = matchingTerms.length / queryTerms.length;
641
- return 1.0 + (1.0 * matchRatio);
684
+ return 1.0 + 1.0 * matchRatio;
642
685
  }
643
686
 
644
687
  /**
@@ -655,27 +698,52 @@ export class SearchService {
655
698
 
656
699
  // Common stop words to filter from queries
657
700
  const stopWords = new Set([
658
- 'how', 'to', 'the', 'a', 'an', 'is', 'are', 'what', 'why', 'when',
659
- 'where', 'can', 'do', 'does', 'i', 'my', 'your', 'it', 'in', 'on',
660
- 'for', 'with', 'this', 'that', 'get', 'use', 'using'
701
+ 'how',
702
+ 'to',
703
+ 'the',
704
+ 'a',
705
+ 'an',
706
+ 'is',
707
+ 'are',
708
+ 'what',
709
+ 'why',
710
+ 'when',
711
+ 'where',
712
+ 'can',
713
+ 'do',
714
+ 'does',
715
+ 'i',
716
+ 'my',
717
+ 'your',
718
+ 'it',
719
+ 'in',
720
+ 'on',
721
+ 'for',
722
+ 'with',
723
+ 'this',
724
+ 'that',
725
+ 'get',
726
+ 'use',
727
+ 'using',
661
728
  ]);
662
729
 
663
730
  // Extract meaningful query terms
664
- const queryTerms = query.toLowerCase()
731
+ const queryTerms = query
732
+ .toLowerCase()
665
733
  .split(/\s+/)
666
- .filter(t => t.length > 2 && !stopWords.has(t));
734
+ .filter((t) => t.length > 2 && !stopWords.has(t));
667
735
 
668
736
  if (queryTerms.length === 0) return 1.0;
669
737
 
670
738
  // Count matching terms in file path
671
- const matchingTerms = queryTerms.filter(term => pathSegments.includes(term));
739
+ const matchingTerms = queryTerms.filter((term) => pathSegments.includes(term));
672
740
 
673
741
  if (matchingTerms.length === 0) return 1.0;
674
742
 
675
743
  // Boost based on proportion of matching terms
676
744
  // Single match: ~1.5, all terms match: ~2.0
677
745
  const matchRatio = matchingTerms.length / queryTerms.length;
678
- return 1.0 + (1.0 * matchRatio);
746
+ return 1.0 + 1.0 * matchRatio;
679
747
  }
680
748
 
681
749
  /**
@@ -691,8 +759,8 @@ export class SearchService {
691
759
  for (const { pattern, terms } of FRAMEWORK_PATTERNS) {
692
760
  if (pattern.test(query)) {
693
761
  // Query mentions this framework - check if result is from that framework
694
- const resultMatchesFramework = terms.some(term =>
695
- pathLower.includes(term) || content.includes(term)
762
+ const resultMatchesFramework = terms.some(
763
+ (term) => pathLower.includes(term) || content.includes(term)
696
764
  );
697
765
 
698
766
  if (resultMatchesFramework) {
@@ -728,8 +796,8 @@ export class SearchService {
728
796
  name: symbolName,
729
797
  signature: codeUnit?.signature ?? '',
730
798
  purpose: this.generatePurpose(result.content, query),
731
- location: `${path}${codeUnit ? ':' + String(codeUnit.startLine) : ''}`,
732
- relevanceReason: this.generateRelevanceReason(result, query)
799
+ location: `${path}${codeUnit ? `:${String(codeUnit.startLine)}` : ''}`,
800
+ relevanceReason: this.generateRelevanceReason(result, query),
733
801
  };
734
802
 
735
803
  // Layer 2: Add context if requested
@@ -741,7 +809,7 @@ export class SearchService {
741
809
  interfaces: this.extractInterfaces(result.content),
742
810
  keyImports: this.extractImports(result.content),
743
811
  relatedConcepts: this.extractConcepts(result.content, query),
744
- usage
812
+ usage,
745
813
  };
746
814
  }
747
815
 
@@ -754,7 +822,7 @@ export class SearchService {
754
822
  completeCode: codeUnit?.fullContent ?? result.content,
755
823
  relatedCode,
756
824
  documentation: this.extractDocumentation(result.content),
757
- tests: undefined
825
+ tests: undefined,
758
826
  };
759
827
  }
760
828
 
@@ -766,8 +834,12 @@ export class SearchService {
766
834
  if (path === undefined || path === '') return undefined;
767
835
 
768
836
  const ext = path.split('.').pop() ?? '';
769
- const language = ext === 'ts' || ext === 'tsx' ? 'typescript' :
770
- ext === 'js' || ext === 'jsx' ? 'javascript' : ext;
837
+ const language =
838
+ ext === 'ts' || ext === 'tsx'
839
+ ? 'typescript'
840
+ : ext === 'js' || ext === 'jsx'
841
+ ? 'javascript'
842
+ : ext;
771
843
 
772
844
  // Try to find a symbol name in the content
773
845
  const symbolName = this.extractSymbolName(result.content);
@@ -779,45 +851,54 @@ export class SearchService {
779
851
  private extractSymbolName(content: string): string {
780
852
  // Extract function or class name
781
853
  const funcMatch = content.match(/(?:export\s+)?(?:async\s+)?function\s+(\w+)/);
782
- if (funcMatch !== null && funcMatch[1] !== undefined && funcMatch[1] !== '') return funcMatch[1];
854
+ if (funcMatch?.[1] !== undefined && funcMatch[1] !== '') return funcMatch[1];
783
855
 
784
856
  const classMatch = content.match(/(?:export\s+)?class\s+(\w+)/);
785
- if (classMatch !== null && classMatch[1] !== undefined && classMatch[1] !== '') return classMatch[1];
857
+ if (classMatch?.[1] !== undefined && classMatch[1] !== '') return classMatch[1];
786
858
 
787
859
  const constMatch = content.match(/(?:export\s+)?const\s+(\w+)/);
788
- if (constMatch !== null && constMatch[1] !== undefined && constMatch[1] !== '') return constMatch[1];
860
+ if (constMatch?.[1] !== undefined && constMatch[1] !== '') return constMatch[1];
789
861
 
790
862
  // Fallback: return "(anonymous)" for unnamed symbols
791
863
  return '(anonymous)';
792
864
  }
793
865
 
794
- private inferType(fileType: string | undefined, codeUnit: CodeUnit | undefined): import('../types/search.js').ResultSummary['type'] {
866
+ private inferType(
867
+ fileType: string | undefined,
868
+ codeUnit: CodeUnit | undefined
869
+ ): import('../types/search.js').ResultSummary['type'] {
795
870
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
796
871
  if (codeUnit) return codeUnit.type as import('../types/search.js').ResultSummary['type'];
797
- if (fileType === 'documentation' || fileType === 'documentation-primary') return 'documentation';
872
+ if (fileType === 'documentation' || fileType === 'documentation-primary')
873
+ return 'documentation';
798
874
  return 'function';
799
875
  }
800
876
 
801
877
  private generatePurpose(content: string, query: string): string {
802
878
  // Extract first line of JSDoc comment if present
803
879
  const docMatch = content.match(/\/\*\*\s*\n\s*\*\s*([^\n]+)/);
804
- if (docMatch !== null && docMatch[1] !== undefined && docMatch[1] !== '') return docMatch[1].trim();
880
+ if (docMatch?.[1] !== undefined && docMatch[1] !== '') return docMatch[1].trim();
805
881
 
806
882
  const lines = content.split('\n');
807
- const queryTerms = query.toLowerCase().split(/\s+/).filter(t => t.length > 2);
883
+ const queryTerms = query
884
+ .toLowerCase()
885
+ .split(/\s+/)
886
+ .filter((t) => t.length > 2);
808
887
 
809
888
  // Helper to check if line is skippable (imports, declarations)
810
889
  const shouldSkip = (cleaned: string): boolean => {
811
- return cleaned.startsWith('import ') ||
812
- cleaned.startsWith('export ') ||
813
- cleaned.startsWith('interface ') ||
814
- cleaned.startsWith('type ');
890
+ return (
891
+ cleaned.startsWith('import ') ||
892
+ cleaned.startsWith('export ') ||
893
+ cleaned.startsWith('interface ') ||
894
+ cleaned.startsWith('type ')
895
+ );
815
896
  };
816
897
 
817
898
  // Helper to score a line based on query term matches
818
899
  const scoreLine = (cleaned: string): number => {
819
900
  const lowerLine = cleaned.toLowerCase();
820
- return queryTerms.filter(term => lowerLine.includes(term)).length;
901
+ return queryTerms.filter((term) => lowerLine.includes(term)).length;
821
902
  };
822
903
 
823
904
  // Helper to check if line is meaningful (length, not a comment)
@@ -839,18 +920,18 @@ export class SearchService {
839
920
  if (shouldSkip(cleaned) || !isMeaningful(cleaned)) continue;
840
921
 
841
922
  let score = scoreLine(cleaned);
842
-
923
+
843
924
  // Boost score for complete sentences (end with period, !, ?)
844
925
  if (/[.!?]$/.test(cleaned)) {
845
926
  score += 0.5;
846
927
  }
847
-
928
+
848
929
  // Boost score for code examples (contains function calls or assignments)
849
930
  // Favor complete patterns: function calls WITH arguments, assignments with values
850
931
  if (/\w+\([^)]*\)|=\s*\w+\(|=>/.test(cleaned)) {
851
- score += 0.6; // Enhanced boost to preserve code examples in snippets
932
+ score += 0.6; // Enhanced boost to preserve code examples in snippets
852
933
  }
853
-
934
+
854
935
  if (score > bestScore) {
855
936
  bestScore = score;
856
937
  bestLine = cleaned;
@@ -864,7 +945,7 @@ export class SearchService {
864
945
  if (firstSentence && firstSentence[0].length >= 20 && firstSentence[0].length <= 150) {
865
946
  return firstSentence[0].trim();
866
947
  }
867
- return bestLine.substring(0, 147) + '...';
948
+ return `${bestLine.substring(0, 147)}...`;
868
949
  }
869
950
  return bestLine;
870
951
  }
@@ -879,7 +960,7 @@ export class SearchService {
879
960
  if (firstSentence && firstSentence[0].length >= 20 && firstSentence[0].length <= 150) {
880
961
  return firstSentence[0].trim();
881
962
  }
882
- return cleaned.substring(0, 147) + '...';
963
+ return `${cleaned.substring(0, 147)}...`;
883
964
  }
884
965
 
885
966
  return cleaned;
@@ -889,10 +970,13 @@ export class SearchService {
889
970
  }
890
971
 
891
972
  private generateRelevanceReason(result: SearchResult, query: string): string {
892
- const queryTerms = query.toLowerCase().split(/\s+/).filter(t => t.length > 2);
973
+ const queryTerms = query
974
+ .toLowerCase()
975
+ .split(/\s+/)
976
+ .filter((t) => t.length > 2);
893
977
  const contentLower = result.content.toLowerCase();
894
978
 
895
- const matchedTerms = queryTerms.filter(term => contentLower.includes(term));
979
+ const matchedTerms = queryTerms.filter((term) => contentLower.includes(term));
896
980
 
897
981
  if (matchedTerms.length > 0) {
898
982
  return `Matches: ${matchedTerms.join(', ')}`;
@@ -924,14 +1008,60 @@ export class SearchService {
924
1008
 
925
1009
  // Common stopwords to filter out
926
1010
  const stopwords = new Set([
927
- 'this', 'that', 'these', 'those', 'from', 'with', 'have', 'will',
928
- 'would', 'should', 'could', 'about', 'been', 'were', 'being',
929
- 'function', 'return', 'const', 'import', 'export', 'default',
930
- 'type', 'interface', 'class', 'extends', 'implements', 'async',
931
- 'await', 'then', 'catch', 'throw', 'error', 'undefined', 'null',
932
- 'true', 'false', 'void', 'number', 'string', 'boolean', 'object',
933
- 'array', 'promise', 'callback', 'resolve', 'reject', 'value',
934
- 'param', 'params', 'args', 'props', 'options', 'config', 'data'
1011
+ 'this',
1012
+ 'that',
1013
+ 'these',
1014
+ 'those',
1015
+ 'from',
1016
+ 'with',
1017
+ 'have',
1018
+ 'will',
1019
+ 'would',
1020
+ 'should',
1021
+ 'could',
1022
+ 'about',
1023
+ 'been',
1024
+ 'were',
1025
+ 'being',
1026
+ 'function',
1027
+ 'return',
1028
+ 'const',
1029
+ 'import',
1030
+ 'export',
1031
+ 'default',
1032
+ 'type',
1033
+ 'interface',
1034
+ 'class',
1035
+ 'extends',
1036
+ 'implements',
1037
+ 'async',
1038
+ 'await',
1039
+ 'then',
1040
+ 'catch',
1041
+ 'throw',
1042
+ 'error',
1043
+ 'undefined',
1044
+ 'null',
1045
+ 'true',
1046
+ 'false',
1047
+ 'void',
1048
+ 'number',
1049
+ 'string',
1050
+ 'boolean',
1051
+ 'object',
1052
+ 'array',
1053
+ 'promise',
1054
+ 'callback',
1055
+ 'resolve',
1056
+ 'reject',
1057
+ 'value',
1058
+ 'param',
1059
+ 'params',
1060
+ 'args',
1061
+ 'props',
1062
+ 'options',
1063
+ 'config',
1064
+ 'data',
935
1065
  ]);
936
1066
 
937
1067
  // Simple keyword extraction
@@ -953,11 +1083,11 @@ export class SearchService {
953
1083
 
954
1084
  private extractDocumentation(content: string): string {
955
1085
  const docMatch = content.match(/\/\*\*([\s\S]*?)\*\//);
956
- if (docMatch !== null && docMatch[1] !== undefined && docMatch[1] !== '') {
1086
+ if (docMatch?.[1] !== undefined && docMatch[1] !== '') {
957
1087
  return docMatch[1]
958
1088
  .split('\n')
959
- .map(line => line.replace(/^\s*\*\s?/, '').trim())
960
- .filter(line => line.length > 0)
1089
+ .map((line) => line.replace(/^\s*\*\s?/, '').trim())
1090
+ .filter((line) => line.length > 0)
961
1091
  .join('\n');
962
1092
  }
963
1093
  return '';
@@ -979,7 +1109,7 @@ export class SearchService {
979
1109
  const nodeId = `${filePath}:${symbolName}`;
980
1110
  return {
981
1111
  calledBy: graph.getCalledByCount(nodeId),
982
- calls: graph.getCallsCount(nodeId)
1112
+ calls: graph.getCallsCount(nodeId),
983
1113
  };
984
1114
  }
985
1115
 
@@ -1008,7 +1138,7 @@ export class SearchService {
1008
1138
  related.push({
1009
1139
  file,
1010
1140
  summary: symbol ? `${symbol}()` : 'unknown',
1011
- relationship: 'calls this'
1141
+ relationship: 'calls this',
1012
1142
  });
1013
1143
  }
1014
1144
  }
@@ -1022,7 +1152,7 @@ export class SearchService {
1022
1152
  related.push({
1023
1153
  file,
1024
1154
  summary: symbol ? `${symbol}()` : 'unknown',
1025
- relationship: 'called by this'
1155
+ relationship: 'called by this',
1026
1156
  });
1027
1157
  }
1028
1158
  }