@staticn0va/wigolo 0.4.0 → 0.5.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.
Files changed (135) hide show
  1. package/SKILL.md +97 -50
  2. package/dist/agent/executor.d.ts +13 -0
  3. package/dist/agent/executor.d.ts.map +1 -0
  4. package/dist/agent/executor.js +128 -0
  5. package/dist/agent/executor.js.map +1 -0
  6. package/dist/agent/pipeline.d.ts +5 -0
  7. package/dist/agent/pipeline.d.ts.map +1 -0
  8. package/dist/agent/pipeline.js +198 -0
  9. package/dist/agent/pipeline.js.map +1 -0
  10. package/dist/agent/planner.d.ts +9 -0
  11. package/dist/agent/planner.d.ts.map +1 -0
  12. package/dist/agent/planner.js +190 -0
  13. package/dist/agent/planner.js.map +1 -0
  14. package/dist/cache/db.d.ts.map +1 -1
  15. package/dist/cache/db.js +32 -0
  16. package/dist/cache/db.js.map +1 -1
  17. package/dist/cache/store.d.ts +14 -0
  18. package/dist/cache/store.d.ts.map +1 -1
  19. package/dist/cache/store.js +69 -0
  20. package/dist/cache/store.js.map +1 -1
  21. package/dist/cli/warmup.d.ts +4 -0
  22. package/dist/cli/warmup.d.ts.map +1 -1
  23. package/dist/cli/warmup.js +58 -0
  24. package/dist/cli/warmup.js.map +1 -1
  25. package/dist/config.d.ts +8 -0
  26. package/dist/config.d.ts.map +1 -1
  27. package/dist/config.js +8 -0
  28. package/dist/config.js.map +1 -1
  29. package/dist/embedding/embed.d.ts +19 -0
  30. package/dist/embedding/embed.d.ts.map +1 -0
  31. package/dist/embedding/embed.js +131 -0
  32. package/dist/embedding/embed.js.map +1 -0
  33. package/dist/embedding/key-terms.d.ts +12 -0
  34. package/dist/embedding/key-terms.d.ts.map +1 -0
  35. package/dist/embedding/key-terms.js +138 -0
  36. package/dist/embedding/key-terms.js.map +1 -0
  37. package/dist/embedding/subprocess.d.ts +31 -0
  38. package/dist/embedding/subprocess.d.ts.map +1 -0
  39. package/dist/embedding/subprocess.js +213 -0
  40. package/dist/embedding/subprocess.js.map +1 -0
  41. package/dist/embedding/vector-index.d.ts +26 -0
  42. package/dist/embedding/vector-index.d.ts.map +1 -0
  43. package/dist/embedding/vector-index.js +78 -0
  44. package/dist/embedding/vector-index.js.map +1 -0
  45. package/dist/fetch/browser-pool.d.ts.map +1 -1
  46. package/dist/fetch/browser-pool.js +61 -0
  47. package/dist/fetch/browser-pool.js.map +1 -1
  48. package/dist/fetch/browser-types.js +1 -1
  49. package/dist/fetch/browser-types.js.map +1 -1
  50. package/dist/fetch/lightpanda.d.ts +28 -0
  51. package/dist/fetch/lightpanda.d.ts.map +1 -0
  52. package/dist/fetch/lightpanda.js +177 -0
  53. package/dist/fetch/lightpanda.js.map +1 -0
  54. package/dist/instructions.d.ts +9 -6
  55. package/dist/instructions.d.ts.map +1 -1
  56. package/dist/instructions.js +95 -20
  57. package/dist/instructions.js.map +1 -1
  58. package/dist/logger.d.ts +1 -1
  59. package/dist/logger.d.ts.map +1 -1
  60. package/dist/repl/commands/agent.d.ts +5 -0
  61. package/dist/repl/commands/agent.d.ts.map +1 -0
  62. package/dist/repl/commands/agent.js +48 -0
  63. package/dist/repl/commands/agent.js.map +1 -0
  64. package/dist/repl/commands/find-similar.d.ts +5 -0
  65. package/dist/repl/commands/find-similar.d.ts.map +1 -0
  66. package/dist/repl/commands/find-similar.js +61 -0
  67. package/dist/repl/commands/find-similar.js.map +1 -0
  68. package/dist/repl/commands/research.d.ts +5 -0
  69. package/dist/repl/commands/research.d.ts.map +1 -0
  70. package/dist/repl/commands/research.js +50 -0
  71. package/dist/repl/commands/research.js.map +1 -0
  72. package/dist/repl/formatters.d.ts +4 -1
  73. package/dist/repl/formatters.d.ts.map +1 -1
  74. package/dist/repl/formatters.js +73 -0
  75. package/dist/repl/formatters.js.map +1 -1
  76. package/dist/repl/shell.d.ts.map +1 -1
  77. package/dist/repl/shell.js +22 -1
  78. package/dist/repl/shell.js.map +1 -1
  79. package/dist/research/decompose.d.ts +7 -0
  80. package/dist/research/decompose.d.ts.map +1 -0
  81. package/dist/research/decompose.js +195 -0
  82. package/dist/research/decompose.js.map +1 -0
  83. package/dist/research/pipeline.d.ts +5 -0
  84. package/dist/research/pipeline.d.ts.map +1 -0
  85. package/dist/research/pipeline.js +135 -0
  86. package/dist/research/pipeline.js.map +1 -0
  87. package/dist/research/synthesize.d.ts +10 -0
  88. package/dist/research/synthesize.d.ts.map +1 -0
  89. package/dist/research/synthesize.js +119 -0
  90. package/dist/research/synthesize.js.map +1 -0
  91. package/dist/search/answer-synthesis.d.ts +13 -0
  92. package/dist/search/answer-synthesis.d.ts.map +1 -0
  93. package/dist/search/answer-synthesis.js +120 -0
  94. package/dist/search/answer-synthesis.js.map +1 -0
  95. package/dist/search/find-similar.d.ts +5 -0
  96. package/dist/search/find-similar.d.ts.map +1 -0
  97. package/dist/search/find-similar.js +329 -0
  98. package/dist/search/find-similar.js.map +1 -0
  99. package/dist/search/multi-query.d.ts +22 -0
  100. package/dist/search/multi-query.d.ts.map +1 -0
  101. package/dist/search/multi-query.js +157 -0
  102. package/dist/search/multi-query.js.map +1 -0
  103. package/dist/search/rrf.d.ts +17 -0
  104. package/dist/search/rrf.d.ts.map +1 -0
  105. package/dist/search/rrf.js +48 -0
  106. package/dist/search/rrf.js.map +1 -0
  107. package/dist/search/sampling.d.ts +25 -0
  108. package/dist/search/sampling.d.ts.map +1 -0
  109. package/dist/search/sampling.js +52 -0
  110. package/dist/search/sampling.js.map +1 -0
  111. package/dist/server.d.ts.map +1 -1
  112. package/dist/server.js +165 -4
  113. package/dist/server.js.map +1 -1
  114. package/dist/tools/agent.d.ts +5 -0
  115. package/dist/tools/agent.d.ts.map +1 -0
  116. package/dist/tools/agent.js +67 -0
  117. package/dist/tools/agent.js.map +1 -0
  118. package/dist/tools/fetch.d.ts.map +1 -1
  119. package/dist/tools/fetch.js +10 -0
  120. package/dist/tools/fetch.js.map +1 -1
  121. package/dist/tools/find-similar.d.ts +5 -0
  122. package/dist/tools/find-similar.d.ts.map +1 -0
  123. package/dist/tools/find-similar.js +48 -0
  124. package/dist/tools/find-similar.js.map +1 -0
  125. package/dist/tools/research.d.ts +5 -0
  126. package/dist/tools/research.d.ts.map +1 -0
  127. package/dist/tools/research.js +50 -0
  128. package/dist/tools/research.js.map +1 -0
  129. package/dist/tools/search.d.ts +2 -1
  130. package/dist/tools/search.d.ts.map +1 -1
  131. package/dist/tools/search.js +169 -13
  132. package/dist/tools/search.js.map +1 -1
  133. package/dist/types.d.ts +100 -3
  134. package/dist/types.d.ts.map +1 -1
  135. package/package.json +9 -3
@@ -0,0 +1,120 @@
1
+ import { checkSamplingSupport, requestSampling, extractTextFromSamplingResponse, } from './sampling.js';
2
+ import { createLogger } from '../logger.js';
3
+ const log = createLogger('search');
4
+ const MAX_CHARS_PER_SOURCE = 3000;
5
+ const MAX_RESPONSE_TOKENS = 1500;
6
+ export async function synthesizeAnswer(results, query, server) {
7
+ try {
8
+ const sourcesText = buildSourcesText(results);
9
+ if (!sourcesText) {
10
+ log.info('no content available for synthesis');
11
+ return {
12
+ fallback: true,
13
+ warning: 'No results with content available for answer synthesis',
14
+ };
15
+ }
16
+ if (!checkSamplingSupport(server)) {
17
+ log.info('sampling not supported by client, falling back to context format');
18
+ return {
19
+ fallback: true,
20
+ warning: 'Client does not support MCP sampling; falling back to context format',
21
+ };
22
+ }
23
+ const prompt = buildSynthesisPrompt(query, sourcesText);
24
+ const response = await requestSampling(server, [{ role: 'user', content: { type: 'text', text: prompt } }], MAX_RESPONSE_TOKENS);
25
+ const answerText = extractTextFromSamplingResponse(response);
26
+ if (!answerText) {
27
+ log.warn('sampling returned empty response');
28
+ return {
29
+ fallback: true,
30
+ warning: 'Sampling returned empty response; falling back to context format',
31
+ };
32
+ }
33
+ const citations = extractCitations(answerText, results);
34
+ log.info('answer synthesis complete', {
35
+ answerLength: answerText.length,
36
+ citationCount: citations.length,
37
+ });
38
+ return {
39
+ answer: answerText,
40
+ citations,
41
+ fallback: false,
42
+ };
43
+ }
44
+ catch (err) {
45
+ log.error('answer synthesis failed', { error: String(err) });
46
+ return {
47
+ fallback: true,
48
+ warning: `Answer synthesis failed: ${err instanceof Error ? err.message : String(err)}; falling back to context format`,
49
+ };
50
+ }
51
+ }
52
+ export function buildSourcesText(results) {
53
+ try {
54
+ const blocks = [];
55
+ let sourceIndex = 1;
56
+ for (const result of results) {
57
+ const content = result.markdown_content || result.snippet || '';
58
+ if (!content.trim())
59
+ continue;
60
+ const truncated = content.length > MAX_CHARS_PER_SOURCE
61
+ ? content.slice(0, MAX_CHARS_PER_SOURCE)
62
+ : content;
63
+ blocks.push(`[${sourceIndex}] ${result.title} (${result.url})\n${truncated}`);
64
+ sourceIndex++;
65
+ }
66
+ if (blocks.length === 0)
67
+ return '';
68
+ return blocks.join('\n\n---\n\n');
69
+ }
70
+ catch (err) {
71
+ log.error('buildSourcesText failed', { error: String(err) });
72
+ return '';
73
+ }
74
+ }
75
+ export function buildSynthesisPrompt(query, sourcesText) {
76
+ return `Based on the following sources, provide a concise and direct answer to the question. Use numbered citations like [1], [2] to reference specific sources.
77
+
78
+ Question: ${query}
79
+
80
+ Sources:
81
+ ${sourcesText}
82
+
83
+ Instructions:
84
+ - Be concise and direct. Answer in 2-4 paragraphs maximum.
85
+ - Cite sources using [1], [2], etc. matching the source numbers above.
86
+ - If sources contain conflicting information, note the discrepancy.
87
+ - If the sources don't adequately answer the question, say so.
88
+ - Do not include information not found in the provided sources.`;
89
+ }
90
+ export function extractCitations(answer, results) {
91
+ try {
92
+ if (!answer || results.length === 0)
93
+ return [];
94
+ const citationRegex = /\[(\d+)\]/g;
95
+ const seen = new Set();
96
+ const citations = [];
97
+ let match;
98
+ while ((match = citationRegex.exec(answer)) !== null) {
99
+ const index = parseInt(match[1], 10);
100
+ if (isNaN(index) || index < 1 || index > results.length)
101
+ continue;
102
+ if (seen.has(index))
103
+ continue;
104
+ seen.add(index);
105
+ const result = results[index - 1];
106
+ citations.push({
107
+ index,
108
+ url: result.url,
109
+ title: result.title,
110
+ snippet: result.snippet,
111
+ });
112
+ }
113
+ return citations;
114
+ }
115
+ catch (err) {
116
+ log.error('citation extraction failed', { error: String(err) });
117
+ return [];
118
+ }
119
+ }
120
+ //# sourceMappingURL=answer-synthesis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"answer-synthesis.js","sourceRoot":"","sources":["../../src/search/answer-synthesis.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { FindSimilarInput, FindSimilarOutput, SearchEngine } from '../types.js';
2
+ import type { SmartRouter } from '../fetch/router.js';
3
+ import type { BackendStatus } from '../server/backend-status.js';
4
+ export declare function findSimilar(input: FindSimilarInput, engines: SearchEngine[], router: SmartRouter, backendStatus?: BackendStatus): Promise<FindSimilarOutput>;
5
+ //# sourceMappingURL=find-similar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find-similar.d.ts","sourceRoot":"","sources":["../../src/search/find-similar.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EAEjB,YAAY,EAEb,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAuBjE,wBAAsB,WAAW,CAC/B,KAAK,EAAE,gBAAgB,EACvB,OAAO,EAAE,YAAY,EAAE,EACvB,MAAM,EAAE,WAAW,EACnB,aAAa,CAAC,EAAE,aAAa,GAC5B,OAAO,CAAC,iBAAiB,CAAC,CAoH5B"}
@@ -0,0 +1,329 @@
1
+ import { extractKeyTerms, buildFTS5Query } from '../embedding/key-terms.js';
2
+ import { reciprocalRankFusion, sortByRRFScore } from './rrf.js';
3
+ import { searchCache, getCachedContent, normalizeUrl } from '../cache/store.js';
4
+ import { filterByDomains } from './filters.js';
5
+ import { handleSearch } from '../tools/search.js';
6
+ import { extractContent } from '../extraction/pipeline.js';
7
+ import { getEmbeddingService } from '../embedding/embed.js';
8
+ import { createLogger } from '../logger.js';
9
+ const log = createLogger('search');
10
+ const DEFAULT_MAX_RESULTS = 10;
11
+ const MAX_FTS5_CANDIDATES = 20;
12
+ const WEB_SEARCH_QUERY_COUNT = 3;
13
+ export async function findSimilar(input, engines, router, backendStatus) {
14
+ const start = Date.now();
15
+ try {
16
+ const url = input.url?.trim();
17
+ const concept = input.concept?.trim();
18
+ if (!url && !concept) {
19
+ return {
20
+ results: [],
21
+ method: 'fts5',
22
+ cache_hits: 0,
23
+ search_hits: 0,
24
+ embedding_available: false,
25
+ error: 'Either url or concept must be provided',
26
+ total_time_ms: Date.now() - start,
27
+ };
28
+ }
29
+ const maxResults = input.max_results ?? DEFAULT_MAX_RESULTS;
30
+ const includeCache = input.include_cache ?? true;
31
+ const includeWeb = input.include_web ?? true;
32
+ const signal = await prepareSignal(url, concept, router);
33
+ if (signal.terms.length === 0) {
34
+ log.warn('no key terms extracted, falling back to web search');
35
+ if (!includeWeb) {
36
+ return {
37
+ results: [],
38
+ method: 'fts5',
39
+ cache_hits: 0,
40
+ search_hits: 0,
41
+ embedding_available: false,
42
+ error: 'Could not extract key terms from input and web search is disabled',
43
+ total_time_ms: Date.now() - start,
44
+ };
45
+ }
46
+ }
47
+ // FTS5 search on cache
48
+ let cacheResults = [];
49
+ const fts5RankMap = new Map();
50
+ if (includeCache && signal.terms.length > 0) {
51
+ cacheResults = runFTS5Search(signal.terms, signal.inputNormalizedUrl, input.include_domains, input.exclude_domains, MAX_FTS5_CANDIDATES, fts5RankMap);
52
+ log.debug('FTS5 search complete', { hits: cacheResults.length });
53
+ }
54
+ // Web search fallback
55
+ let searchResults = [];
56
+ const searchRankMap = new Map();
57
+ if (cacheResults.length < maxResults && includeWeb) {
58
+ searchResults = await runWebSearchFallback(signal, engines, router, backendStatus, maxResults, signal.inputNormalizedUrl, input.include_domains, input.exclude_domains, searchRankMap);
59
+ log.debug('web search fallback complete', { hits: searchResults.length });
60
+ }
61
+ // Fuse results via RRF
62
+ const rankedLists = [];
63
+ if (fts5RankMap.size > 0)
64
+ rankedLists.push(fts5RankMap);
65
+ if (searchRankMap.size > 0)
66
+ rankedLists.push(searchRankMap);
67
+ let finalResults;
68
+ if (rankedLists.length >= 1) {
69
+ finalResults = fuseResults(rankedLists, cacheResults, searchResults, maxResults);
70
+ }
71
+ else {
72
+ finalResults = [...cacheResults, ...searchResults]
73
+ .sort((a, b) => b.relevance_score - a.relevance_score)
74
+ .slice(0, maxResults);
75
+ }
76
+ const method = determineMethod(cacheResults.length > 0, searchResults.length > 0);
77
+ const cacheHits = finalResults.filter(r => r.source === 'cache').length;
78
+ const searchHits = finalResults.filter(r => r.source === 'search').length;
79
+ return {
80
+ results: finalResults,
81
+ method,
82
+ cache_hits: cacheHits,
83
+ search_hits: searchHits,
84
+ embedding_available: false,
85
+ total_time_ms: Date.now() - start,
86
+ };
87
+ }
88
+ catch (err) {
89
+ log.error('findSimilar failed', { error: String(err) });
90
+ return {
91
+ results: [],
92
+ method: 'fts5',
93
+ cache_hits: 0,
94
+ search_hits: 0,
95
+ embedding_available: false,
96
+ error: `find_similar failed: ${err instanceof Error ? err.message : String(err)}`,
97
+ total_time_ms: Date.now() - start,
98
+ };
99
+ }
100
+ }
101
+ async function prepareSignal(url, concept, router) {
102
+ if (url) {
103
+ return await prepareSignalFromUrl(url, router);
104
+ }
105
+ if (concept) {
106
+ const terms = extractKeyTerms(concept, '');
107
+ return { terms, title: concept };
108
+ }
109
+ return { terms: [], title: '' };
110
+ }
111
+ async function prepareSignalFromUrl(url, router) {
112
+ let normalizedInputUrl;
113
+ try {
114
+ normalizedInputUrl = normalizeUrl(url);
115
+ }
116
+ catch {
117
+ normalizedInputUrl = url;
118
+ }
119
+ const cached = getCachedContent(url);
120
+ if (cached) {
121
+ const terms = extractKeyTerms(cached.markdown, cached.title);
122
+ return {
123
+ terms,
124
+ title: cached.title,
125
+ inputUrl: url,
126
+ inputNormalizedUrl: normalizedInputUrl,
127
+ };
128
+ }
129
+ try {
130
+ log.info('fetching URL for signal extraction', { url });
131
+ const raw = await router.fetch(url, { renderJs: 'auto' });
132
+ const extraction = await extractContent(raw.html, raw.finalUrl, {
133
+ contentType: raw.contentType,
134
+ });
135
+ const terms = extractKeyTerms(extraction.markdown, extraction.title);
136
+ return {
137
+ terms,
138
+ title: extraction.title,
139
+ inputUrl: url,
140
+ inputNormalizedUrl: normalizedInputUrl,
141
+ };
142
+ }
143
+ catch (err) {
144
+ log.warn('failed to fetch URL for signal extraction', { url, error: String(err) });
145
+ const urlTerms = extractKeyTerms('', url);
146
+ return {
147
+ terms: urlTerms,
148
+ title: url,
149
+ inputUrl: url,
150
+ inputNormalizedUrl: normalizedInputUrl,
151
+ };
152
+ }
153
+ }
154
+ function runFTS5Search(terms, excludeNormalizedUrl, includeDomains, excludeDomains, maxCandidates, rankMap) {
155
+ try {
156
+ const fts5Query = buildFTS5Query(terms);
157
+ if (!fts5Query)
158
+ return [];
159
+ let cached = searchCache(fts5Query);
160
+ if (excludeNormalizedUrl) {
161
+ cached = cached.filter(c => {
162
+ try {
163
+ return normalizeUrl(c.url) !== excludeNormalizedUrl;
164
+ }
165
+ catch {
166
+ return c.url !== excludeNormalizedUrl;
167
+ }
168
+ });
169
+ }
170
+ cached = filterByDomains(cached, includeDomains, excludeDomains);
171
+ cached = cached.slice(0, maxCandidates);
172
+ const results = [];
173
+ for (let i = 0; i < cached.length; i++) {
174
+ const entry = cached[i];
175
+ let nUrl;
176
+ try {
177
+ nUrl = normalizeUrl(entry.url);
178
+ }
179
+ catch {
180
+ nUrl = entry.url;
181
+ }
182
+ rankMap.set(nUrl, i + 1);
183
+ results.push({
184
+ url: entry.url,
185
+ title: entry.title,
186
+ markdown: entry.markdown.slice(0, 5000),
187
+ relevance_score: 0,
188
+ source: 'cache',
189
+ match_signals: {
190
+ fts5_rank: i + 1,
191
+ fused_score: 0,
192
+ },
193
+ });
194
+ }
195
+ return results;
196
+ }
197
+ catch (err) {
198
+ log.error('FTS5 search failed', { error: String(err) });
199
+ return [];
200
+ }
201
+ }
202
+ async function runWebSearchFallback(signal, engines, router, backendStatus, maxResults, excludeNormalizedUrl, includeDomains, excludeDomains, rankMap) {
203
+ try {
204
+ const queries = generateSearchQueries(signal.terms, signal.title);
205
+ if (queries.length === 0)
206
+ return [];
207
+ const allResults = [];
208
+ const seenUrls = new Set();
209
+ if (excludeNormalizedUrl) {
210
+ seenUrls.add(excludeNormalizedUrl);
211
+ }
212
+ for (const query of queries) {
213
+ try {
214
+ const searchOutput = await handleSearch({
215
+ query,
216
+ max_results: maxResults,
217
+ include_content: true,
218
+ include_domains: includeDomains,
219
+ exclude_domains: excludeDomains,
220
+ }, engines, router, backendStatus);
221
+ for (const item of searchOutput.results) {
222
+ let nUrl;
223
+ try {
224
+ nUrl = normalizeUrl(item.url);
225
+ }
226
+ catch {
227
+ nUrl = item.url;
228
+ }
229
+ if (seenUrls.has(nUrl))
230
+ continue;
231
+ seenUrls.add(nUrl);
232
+ const rank = allResults.length + 1;
233
+ rankMap.set(nUrl, rank);
234
+ allResults.push({
235
+ url: item.url,
236
+ title: item.title,
237
+ markdown: (item.markdown_content ?? item.snippet).slice(0, 5000),
238
+ relevance_score: item.relevance_score,
239
+ source: 'search',
240
+ match_signals: {
241
+ fused_score: 0,
242
+ },
243
+ });
244
+ }
245
+ }
246
+ catch (err) {
247
+ log.warn('web search query failed', { query, error: String(err) });
248
+ }
249
+ }
250
+ try {
251
+ const embeddingService = getEmbeddingService();
252
+ if (embeddingService.isAvailable()) {
253
+ for (const result of allResults) {
254
+ if (result.markdown) {
255
+ embeddingService.embedAsync(result.url, result.markdown);
256
+ }
257
+ }
258
+ }
259
+ }
260
+ catch (err) {
261
+ log.debug('embedding hook skipped for find_similar results', { error: String(err) });
262
+ }
263
+ return allResults;
264
+ }
265
+ catch (err) {
266
+ log.error('web search fallback failed', { error: String(err) });
267
+ return [];
268
+ }
269
+ }
270
+ function generateSearchQueries(terms, title) {
271
+ if (terms.length === 0 && !title)
272
+ return [];
273
+ const queries = [];
274
+ if (title && title.length > 3) {
275
+ queries.push(title.slice(0, 150));
276
+ }
277
+ if (terms.length >= 3) {
278
+ queries.push(terms.slice(0, 5).join(' '));
279
+ }
280
+ if (terms.length >= 2) {
281
+ queries.push(`${terms.slice(0, 3).join(' ')} tutorial guide`);
282
+ }
283
+ const unique = [...new Set(queries)];
284
+ return unique.slice(0, WEB_SEARCH_QUERY_COUNT);
285
+ }
286
+ function fuseResults(rankedLists, cacheResults, searchResults, maxResults) {
287
+ const scores = reciprocalRankFusion(rankedLists);
288
+ const sorted = sortByRRFScore(scores);
289
+ const resultsByNormalizedUrl = new Map();
290
+ for (const r of [...cacheResults, ...searchResults]) {
291
+ let key;
292
+ try {
293
+ key = normalizeUrl(r.url);
294
+ }
295
+ catch {
296
+ key = r.url;
297
+ }
298
+ if (!resultsByNormalizedUrl.has(key)) {
299
+ resultsByNormalizedUrl.set(key, r);
300
+ }
301
+ }
302
+ const fused = [];
303
+ for (const [nUrl, score] of sorted) {
304
+ if (fused.length >= maxResults)
305
+ break;
306
+ const result = resultsByNormalizedUrl.get(nUrl);
307
+ if (!result)
308
+ continue;
309
+ fused.push({
310
+ ...result,
311
+ relevance_score: score,
312
+ match_signals: {
313
+ ...result.match_signals,
314
+ fused_score: score,
315
+ },
316
+ });
317
+ }
318
+ return fused;
319
+ }
320
+ function determineMethod(hasCache, hasSearch) {
321
+ if (hasCache && hasSearch)
322
+ return 'hybrid';
323
+ if (hasCache)
324
+ return 'fts5';
325
+ if (hasSearch)
326
+ return 'search';
327
+ return 'fts5';
328
+ }
329
+ //# sourceMappingURL=find-similar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find-similar.js","sourceRoot":"","sources":["../../src/search/find-similar.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { RawSearchResult, SearchEngine } from '../types.js';
2
+ import type { MergedSearchResult } from './dedup.js';
3
+ export declare function normalizeQueries(queries: string[]): string[];
4
+ export interface FanOutOptions {
5
+ maxResults: number;
6
+ timeRange?: string;
7
+ language?: string;
8
+ includeDomains?: string[];
9
+ excludeDomains?: string[];
10
+ fromDate?: string;
11
+ toDate?: string;
12
+ category?: 'general' | 'news' | 'code' | 'docs' | 'papers' | 'images';
13
+ }
14
+ export interface FanOutResult {
15
+ results: RawSearchResult[];
16
+ enginesUsed: string[];
17
+ errors: string[];
18
+ }
19
+ export declare function fanOutSearch(queries: string[], engines: SearchEngine[], options: FanOutOptions): Promise<FanOutResult>;
20
+ export declare function synthesizeIntent(queries: string[]): string;
21
+ export declare function mergeWithRRF(rankedLists: MergedSearchResult[][]): MergedSearchResult[];
22
+ //# sourceMappingURL=multi-query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-query.d.ts","sourceRoot":"","sources":["../../src/search/multi-query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAuB,MAAM,aAAa,CAAC;AACtF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AASrD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CA2B5D;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;CACvE;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE,YAAY,EAAE,EACvB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,YAAY,CAAC,CAsEvB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAO1D;AAED,wBAAgB,YAAY,CAAC,WAAW,EAAE,kBAAkB,EAAE,EAAE,GAAG,kBAAkB,EAAE,CAmDtF"}
@@ -0,0 +1,157 @@
1
+ import { normalizeUrl } from '../cache/store.js';
2
+ import { getConfig } from '../config.js';
3
+ import { createLogger } from '../logger.js';
4
+ const log = createLogger('search');
5
+ const RRF_K = 60;
6
+ export function normalizeQueries(queries) {
7
+ try {
8
+ const config = getConfig();
9
+ const seen = new Set();
10
+ const normalized = [];
11
+ for (const raw of queries) {
12
+ const q = raw.toLowerCase().trim().replace(/\s+/g, ' ');
13
+ if (q.length === 0)
14
+ continue;
15
+ if (seen.has(q))
16
+ continue;
17
+ seen.add(q);
18
+ normalized.push(q);
19
+ }
20
+ if (normalized.length > config.multiQueryMax) {
21
+ log.warn('multi-query array exceeds max, truncating', {
22
+ provided: normalized.length,
23
+ max: config.multiQueryMax,
24
+ });
25
+ return normalized.slice(0, config.multiQueryMax);
26
+ }
27
+ return normalized;
28
+ }
29
+ catch (err) {
30
+ log.error('normalizeQueries failed', { error: String(err) });
31
+ return [];
32
+ }
33
+ }
34
+ export async function fanOutSearch(queries, engines, options) {
35
+ const allResults = [];
36
+ const enginesUsed = new Set();
37
+ const errors = [];
38
+ if (queries.length === 0 || engines.length === 0) {
39
+ return { results: [], enginesUsed: [], errors: [] };
40
+ }
41
+ try {
42
+ const config = getConfig();
43
+ const concurrency = config.multiQueryConcurrency;
44
+ const hasFilterAttrition = !!(options.includeDomains?.length || options.excludeDomains?.length);
45
+ const overfetchFactor = hasFilterAttrition ? 3 : 2;
46
+ const engineOptions = {
47
+ maxResults: options.maxResults * overfetchFactor,
48
+ timeRange: options.timeRange,
49
+ language: options.language,
50
+ includeDomains: options.includeDomains,
51
+ excludeDomains: options.excludeDomains,
52
+ fromDate: options.fromDate,
53
+ toDate: options.toDate,
54
+ category: options.category,
55
+ };
56
+ const tasks = [];
57
+ for (const engine of engines) {
58
+ for (const query of queries) {
59
+ tasks.push({ engine, query });
60
+ }
61
+ }
62
+ for (let i = 0; i < tasks.length; i += concurrency) {
63
+ const batch = tasks.slice(i, i + concurrency);
64
+ const promises = batch.map(async ({ engine, query }) => {
65
+ try {
66
+ const results = await engine.search(query, engineOptions);
67
+ for (const r of results) {
68
+ allResults.push(r);
69
+ enginesUsed.add(engine.name);
70
+ }
71
+ }
72
+ catch (err) {
73
+ const msg = err instanceof Error ? err.message : String(err);
74
+ log.warn('multi-query engine search failed', {
75
+ engine: engine.name,
76
+ query,
77
+ error: msg,
78
+ });
79
+ errors.push(`${engine.name}(${query}): ${msg}`);
80
+ }
81
+ });
82
+ await Promise.allSettled(promises);
83
+ }
84
+ return {
85
+ results: allResults,
86
+ enginesUsed: [...enginesUsed],
87
+ errors,
88
+ };
89
+ }
90
+ catch (err) {
91
+ log.error('fanOutSearch failed', { error: String(err) });
92
+ return {
93
+ results: allResults,
94
+ enginesUsed: [...enginesUsed],
95
+ errors: [...errors, `fanOutSearch: ${String(err)}`],
96
+ };
97
+ }
98
+ }
99
+ export function synthesizeIntent(queries) {
100
+ try {
101
+ return queries.map(q => q.trim()).filter(Boolean).join('; ');
102
+ }
103
+ catch (err) {
104
+ log.error('synthesizeIntent failed', { error: String(err) });
105
+ return '';
106
+ }
107
+ }
108
+ export function mergeWithRRF(rankedLists) {
109
+ try {
110
+ if (rankedLists.length === 0)
111
+ return [];
112
+ const nonEmpty = rankedLists.filter(l => l.length > 0);
113
+ if (nonEmpty.length === 0)
114
+ return [];
115
+ const rrfScores = new Map();
116
+ const bestAppearance = new Map();
117
+ for (const list of nonEmpty) {
118
+ for (let rank = 0; rank < list.length; rank++) {
119
+ const item = list[rank];
120
+ let normalizedUrlStr;
121
+ try {
122
+ normalizedUrlStr = normalizeUrl(item.url);
123
+ }
124
+ catch {
125
+ normalizedUrlStr = item.url;
126
+ }
127
+ const rrfContribution = 1 / (RRF_K + rank + 1);
128
+ const current = rrfScores.get(normalizedUrlStr) ?? 0;
129
+ rrfScores.set(normalizedUrlStr, current + rrfContribution);
130
+ const existing = bestAppearance.get(normalizedUrlStr);
131
+ if (!existing || rank < existing.bestRank) {
132
+ bestAppearance.set(normalizedUrlStr, { result: item, bestRank: rank });
133
+ }
134
+ }
135
+ }
136
+ let maxScore = 0;
137
+ for (const score of rrfScores.values()) {
138
+ if (score > maxScore)
139
+ maxScore = score;
140
+ }
141
+ const merged = [];
142
+ for (const [normalizedUrlStr, score] of rrfScores.entries()) {
143
+ const appearance = bestAppearance.get(normalizedUrlStr);
144
+ merged.push({
145
+ ...appearance.result,
146
+ relevance_score: maxScore > 0 ? score / maxScore : 0,
147
+ });
148
+ }
149
+ merged.sort((a, b) => b.relevance_score - a.relevance_score);
150
+ return merged;
151
+ }
152
+ catch (err) {
153
+ log.error('mergeWithRRF failed', { error: String(err) });
154
+ return [];
155
+ }
156
+ }
157
+ //# sourceMappingURL=multi-query.js.map