recker 1.0.68 → 1.0.70-next.9b4eebc

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 (47) hide show
  1. package/dist/ai/index.d.ts +0 -1
  2. package/dist/ai/index.js +0 -1
  3. package/dist/browser/ai/index.d.ts +0 -1
  4. package/dist/browser/ai/index.js +0 -1
  5. package/dist/browser/index.iife.min.js +51 -51
  6. package/dist/browser/index.min.js +51 -51
  7. package/dist/browser/index.umd.min.js +51 -51
  8. package/dist/cli/index.js +0 -2
  9. package/dist/cli/tui/components/command-palette.d.ts +0 -3
  10. package/dist/cli/tui/components/command-palette.js +0 -16
  11. package/dist/cli/tui/shell-search.d.ts +10 -4
  12. package/dist/cli/tui/shell-search.js +55 -92
  13. package/dist/mcp/index.d.ts +0 -1
  14. package/dist/mcp/index.js +0 -1
  15. package/dist/mcp/server.d.ts +0 -6
  16. package/dist/mcp/server.js +27 -138
  17. package/dist/mcp/tools/ai.js +0 -82
  18. package/dist/mini.d.ts +0 -6
  19. package/dist/mini.js +0 -3
  20. package/dist/version.js +1 -1
  21. package/package.json +12 -16
  22. package/dist/ai/vector/index.d.ts +0 -2
  23. package/dist/ai/vector/index.js +0 -2
  24. package/dist/ai/vector/similarity.d.ts +0 -2
  25. package/dist/ai/vector/similarity.js +0 -27
  26. package/dist/ai/vector/store.d.ts +0 -27
  27. package/dist/ai/vector/store.js +0 -82
  28. package/dist/browser/ai/vector/index.d.ts +0 -2
  29. package/dist/browser/ai/vector/index.js +0 -2
  30. package/dist/browser/ai/vector/similarity.d.ts +0 -2
  31. package/dist/browser/ai/vector/similarity.js +0 -27
  32. package/dist/browser/ai/vector/store.d.ts +0 -27
  33. package/dist/browser/ai/vector/store.js +0 -82
  34. package/dist/cli/commands/vector.d.ts +0 -8
  35. package/dist/cli/commands/vector.js +0 -214
  36. package/dist/mcp/embeddings-loader.d.ts +0 -17
  37. package/dist/mcp/embeddings-loader.js +0 -162
  38. package/dist/mcp/search/embedder.d.ts +0 -9
  39. package/dist/mcp/search/embedder.js +0 -83
  40. package/dist/mcp/search/hybrid-search.d.ts +0 -30
  41. package/dist/mcp/search/hybrid-search.js +0 -402
  42. package/dist/mcp/search/index.d.ts +0 -4
  43. package/dist/mcp/search/index.js +0 -3
  44. package/dist/mcp/search/math.d.ts +0 -5
  45. package/dist/mcp/search/math.js +0 -63
  46. package/dist/mcp/search/types.d.ts +0 -51
  47. package/dist/mcp/search/types.js +0 -1
package/dist/cli/index.js CHANGED
@@ -15,7 +15,6 @@ import { registerNetworkCommands } from './commands/network.js';
15
15
  import { registerProtocolCommands } from './commands/protocols.js';
16
16
  import { registerUtilsCommands } from './commands/utils.js';
17
17
  import { registerHarCommand } from './commands/har.js';
18
- import { registerVectorCommand } from './commands/vector.js';
19
18
  import { registerSeoCommand } from './commands/seo.js';
20
19
  import { registerVideoCommand } from './commands/video.js';
21
20
  import { registerLiveCommand } from './commands/live.js';
@@ -320,7 +319,6 @@ ${formatColumns(PRESET_NAMES, { prefix: '@', indent: 2, minWidth: 16, transform:
320
319
  registerDnsCommands(program);
321
320
  registerProtocolCommands(program);
322
321
  registerHarCommand(program);
323
- registerVectorCommand(program);
324
322
  registerHlsCommand(program);
325
323
  registerVideoCommand(program);
326
324
  registerLiveCommand(program);
@@ -40,7 +40,4 @@ export interface QuickActionBarProps {
40
40
  width?: number;
41
41
  }
42
42
  export declare function QuickActionBar(props: QuickActionBarProps): VNode;
43
- export declare function navigatePalette(direction: 'up' | 'down', _maxItems: number): void;
44
- export declare function getPaletteSelection(): number;
45
- export declare function getPaletteQuery(): string;
46
43
  export {};
@@ -228,19 +228,3 @@ export function QuickActionBar(props) {
228
228
  paddingX: 1,
229
229
  }, ...actionTexts);
230
230
  }
231
- export function navigatePalette(direction, _maxItems) {
232
- if (!paletteState)
233
- return;
234
- if (direction === 'up') {
235
- paletteState.selectPrev();
236
- }
237
- else {
238
- paletteState.selectNext();
239
- }
240
- }
241
- export function getPaletteSelection() {
242
- return paletteState?.selectedIndex() ?? 0;
243
- }
244
- export function getPaletteQuery() {
245
- return paletteState?.query() ?? '';
246
- }
@@ -1,7 +1,14 @@
1
- import type { SearchResult } from '../../mcp/search/types.js';
2
1
  export type ProgressCallback = (stage: string, percent?: number) => void;
2
+ export interface SearchResult {
3
+ id: string;
4
+ path: string;
5
+ title: string;
6
+ content: string;
7
+ snippet: string;
8
+ score: number;
9
+ source: 'fuzzy';
10
+ }
3
11
  export declare class ShellSearch {
4
- private hybridSearch;
5
12
  private docsIndex;
6
13
  private codeExamples;
7
14
  private typeDefinitions;
@@ -12,7 +19,6 @@ export declare class ShellSearch {
12
19
  private examplesPath;
13
20
  private srcPath;
14
21
  private spinner;
15
- private hasSemanticSearch;
16
22
  constructor();
17
23
  private updateSpinner;
18
24
  private ensureInitialized;
@@ -23,6 +29,7 @@ export declare class ShellSearch {
23
29
  category?: string;
24
30
  silent?: boolean;
25
31
  }): Promise<SearchResult[]>;
32
+ private extractSnippet;
26
33
  suggest(useCase: string): Promise<string>;
27
34
  getExamples(feature: string, options?: {
28
35
  limit?: number;
@@ -39,7 +46,6 @@ export declare class ShellSearch {
39
46
  private findExamplesPath;
40
47
  private findSrcPath;
41
48
  private buildIndex;
42
- private indexDocsFromEmbeddings;
43
49
  private indexDocsFromFilesystem;
44
50
  private indexExamples;
45
51
  private indexTypes;
@@ -1,12 +1,9 @@
1
- import { createHybridSearch, createEmbedder, isFastembedAvailable } from '../../mcp/search/index.js';
2
- import { loadEmbeddings } from '../../mcp/embeddings-loader.js';
3
1
  import { readFileSync, readdirSync, statSync, existsSync } from 'fs';
4
2
  import { join, relative, extname, basename, dirname } from 'path';
5
3
  import { fileURLToPath } from 'url';
6
4
  import { createSpinner } from './spinner.js';
7
5
  const IDLE_TIMEOUT_MS = 5 * 60 * 1000;
8
6
  export class ShellSearch {
9
- hybridSearch = null;
10
7
  docsIndex = [];
11
8
  codeExamples = [];
12
9
  typeDefinitions = [];
@@ -17,7 +14,6 @@ export class ShellSearch {
17
14
  examplesPath;
18
15
  srcPath;
19
16
  spinner = null;
20
- hasSemanticSearch = false;
21
17
  constructor() {
22
18
  this.docsPath = this.findDocsPath();
23
19
  this.examplesPath = this.findExamplesPath();
@@ -30,7 +26,7 @@ export class ShellSearch {
30
26
  }
31
27
  async ensureInitialized() {
32
28
  this.resetIdleTimer();
33
- if (this.initialized && this.hybridSearch) {
29
+ if (this.initialized) {
34
30
  return;
35
31
  }
36
32
  if (this.initializing) {
@@ -42,21 +38,10 @@ export class ShellSearch {
42
38
  this.initializing = true;
43
39
  this.spinner = createSpinner({ text: 'Initializing search...' }).start();
44
40
  try {
45
- this.updateSpinner('Creating search index...');
46
- this.hybridSearch = createHybridSearch({ debug: false });
47
- this.updateSpinner('Checking semantic search availability...');
48
- const fastembedAvailable = await isFastembedAvailable();
49
- if (fastembedAvailable) {
50
- this.updateSpinner('Loading AI embedding model (first time may take a while)...');
51
- this.hybridSearch.setEmbedder(createEmbedder());
52
- this.hasSemanticSearch = true;
53
- }
54
41
  this.updateSpinner('Indexing documentation...');
55
42
  await this.buildIndex();
56
- this.updateSpinner('Finalizing search index...');
57
- await this.hybridSearch.initialize(this.docsIndex);
58
43
  this.initialized = true;
59
- this.spinner.succeed(`Search ready (${this.docsIndex.length} docs${this.hasSemanticSearch ? ', semantic enabled' : ''})`);
44
+ this.spinner.succeed(`Search ready (${this.docsIndex.length} docs)`);
60
45
  }
61
46
  catch (error) {
62
47
  this.spinner.fail(`Search initialization failed: ${error}`);
@@ -75,7 +60,6 @@ export class ShellSearch {
75
60
  }
76
61
  unload() {
77
62
  if (this.initialized) {
78
- this.hybridSearch = null;
79
63
  this.docsIndex = [];
80
64
  this.codeExamples = [];
81
65
  this.typeDefinitions = [];
@@ -84,30 +68,59 @@ export class ShellSearch {
84
68
  }
85
69
  async search(query, options = {}) {
86
70
  await this.ensureInitialized();
87
- const { limit = 5, category, silent = false } = options;
88
- if (!this.hybridSearch) {
89
- return [];
90
- }
91
- let searchSpinner = null;
92
- if (!silent && this.hasSemanticSearch) {
93
- searchSpinner = createSpinner({ text: 'Generating query embedding...' }).start();
94
- }
95
- try {
96
- if (searchSpinner) {
97
- searchSpinner.text = 'Searching documentation...';
98
- }
99
- const results = await this.hybridSearch.search(query, { limit, category, mode: 'hybrid' });
100
- if (searchSpinner) {
101
- searchSpinner.succeed(`Found ${results.length} result${results.length !== 1 ? 's' : ''}`);
71
+ const { limit = 5, category } = options;
72
+ const queryLower = query.toLowerCase();
73
+ const queryTerms = queryLower.split(/\s+/);
74
+ const scored = this.docsIndex
75
+ .filter(doc => !category || doc.category.toLowerCase().includes(category.toLowerCase()))
76
+ .map(doc => {
77
+ let score = 0;
78
+ for (const term of queryTerms) {
79
+ if (doc.title.toLowerCase().includes(term))
80
+ score += 10;
81
+ if (doc.path.toLowerCase().includes(term))
82
+ score += 5;
83
+ if (doc.keywords.some(k => k.includes(term)))
84
+ score += 3;
85
+ if (doc.content.toLowerCase().includes(term))
86
+ score += 1;
102
87
  }
103
- return results;
104
- }
105
- catch (error) {
106
- if (searchSpinner) {
107
- searchSpinner.fail(`Search failed: ${error}`);
88
+ return { doc, score };
89
+ })
90
+ .filter(r => r.score > 0)
91
+ .sort((a, b) => b.score - a.score)
92
+ .slice(0, limit);
93
+ return scored.map(r => ({
94
+ id: r.doc.id,
95
+ path: r.doc.path,
96
+ title: r.doc.title,
97
+ content: r.doc.content,
98
+ snippet: this.extractSnippet(r.doc.content, query),
99
+ score: Math.min(1, r.score / 20),
100
+ source: 'fuzzy',
101
+ }));
102
+ }
103
+ extractSnippet(content, query) {
104
+ const lowerContent = content.toLowerCase();
105
+ const queryTerms = query.toLowerCase().split(/\s+/);
106
+ let bestIndex = -1;
107
+ for (const term of queryTerms) {
108
+ const idx = lowerContent.indexOf(term);
109
+ if (idx !== -1 && (bestIndex === -1 || idx < bestIndex)) {
110
+ bestIndex = idx;
108
111
  }
109
- throw error;
110
112
  }
113
+ if (bestIndex === -1) {
114
+ return content.slice(0, 150) + '...';
115
+ }
116
+ const start = Math.max(0, bestIndex - 50);
117
+ const end = Math.min(content.length, bestIndex + 150);
118
+ let snippet = content.slice(start, end);
119
+ if (start > 0)
120
+ snippet = '...' + snippet;
121
+ if (end < content.length)
122
+ snippet = snippet + '...';
123
+ return snippet.replace(/\n/g, ' ');
111
124
  }
112
125
  async suggest(useCase) {
113
126
  const spinner = createSpinner({ text: 'Generating suggestions...' }).start();
@@ -146,8 +159,8 @@ import { createClient } from 'recker';
146
159
  const client = createClient({
147
160
  baseUrl: 'https://api.example.com',
148
161
  cache: {
149
- storage: 'memory', // or 'file'
150
- ttl: 300000, // 5 minutes
162
+ storage: 'memory',
163
+ ttl: 300000,
151
164
  strategy: 'stale-while-revalidate'
152
165
  }
153
166
  });
@@ -160,27 +173,9 @@ import { createClient } from 'recker';
160
173
 
161
174
  const client = createClient({ baseUrl: 'https://api.openai.com' });
162
175
 
163
- // SSE streaming
164
176
  for await (const event of client.post('/v1/chat/completions', { body, stream: true }).sse()) {
165
177
  console.log(event.data);
166
178
  }
167
- \`\`\``);
168
- }
169
- if (useCaseLower.includes('parallel') || useCaseLower.includes('batch') || useCaseLower.includes('concurrent')) {
170
- suggestions.push(`\n**Batch/Parallel Requests:**
171
- \`\`\`typescript
172
- import { createClient } from 'recker';
173
-
174
- const client = createClient({
175
- baseUrl: 'https://api.example.com',
176
- concurrency: { max: 10 }
177
- });
178
-
179
- const { results, stats } = await client.batch([
180
- { path: '/users/1' },
181
- { path: '/users/2' },
182
- { path: '/users/3' }
183
- ], { mapResponse: r => r.json() });
184
179
  \`\`\``);
185
180
  }
186
181
  let output = `**Suggestion for: "${useCase}"**\n`;
@@ -299,42 +294,10 @@ const { results, stats } = await client.batch([
299
294
  return candidates[0];
300
295
  }
301
296
  async buildIndex() {
302
- await this.indexDocsFromEmbeddings();
297
+ this.indexDocsFromFilesystem();
303
298
  this.indexExamples();
304
299
  this.indexTypes();
305
300
  }
306
- async indexDocsFromEmbeddings() {
307
- try {
308
- this.updateSpinner('Loading documentation index...');
309
- const data = await loadEmbeddings({ debug: false });
310
- if (data && data.documents && data.documents.length > 0) {
311
- for (const doc of data.documents) {
312
- let content = doc.content || '';
313
- if (!content && !doc.section) {
314
- const fullPath = join(this.docsPath, doc.path);
315
- if (existsSync(fullPath)) {
316
- content = readFileSync(fullPath, 'utf-8');
317
- }
318
- }
319
- this.docsIndex.push({
320
- id: doc.id,
321
- path: doc.path,
322
- title: doc.title,
323
- content,
324
- category: doc.category,
325
- keywords: doc.keywords || [],
326
- section: doc.section,
327
- parentPath: doc.parentPath,
328
- });
329
- }
330
- return;
331
- }
332
- }
333
- catch (error) {
334
- this.updateSpinner('Downloading documentation index failed, trying filesystem...');
335
- }
336
- this.indexDocsFromFilesystem();
337
- }
338
301
  indexDocsFromFilesystem() {
339
302
  if (!existsSync(this.docsPath))
340
303
  return;
@@ -1,7 +1,6 @@
1
1
  export * from './types.js';
2
2
  export * from './client.js';
3
3
  export * from './server.js';
4
- export * from './embeddings-loader.js';
5
4
  export * from './profiles.js';
6
5
  export * from './prompts/index.js';
7
6
  export * from './resources/index.js';
package/dist/mcp/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  export * from './types.js';
2
2
  export * from './client.js';
3
3
  export * from './server.js';
4
- export * from './embeddings-loader.js';
5
4
  export * from './profiles.js';
6
5
  export * from './prompts/index.js';
7
6
  export * from './resources/index.js';
@@ -19,7 +19,6 @@ export interface MCPServerOptions {
19
19
  export declare class MCPServer {
20
20
  private options;
21
21
  private server?;
22
- private hybridSearch;
23
22
  private docsIndex;
24
23
  private codeExamples;
25
24
  private typeDefinitions;
@@ -28,21 +27,16 @@ export declare class MCPServer {
28
27
  private toolRegistry;
29
28
  private promptRegistry;
30
29
  private resourceRegistry;
31
- private aiClient?;
32
30
  constructor(options?: MCPServerOptions);
33
31
  private applyCategoryFiltering;
34
32
  private indexReady;
35
33
  private ensureIndexReady;
36
- private initAI;
37
- private fastEmbedModel;
38
- private generateEmbedding;
39
34
  private log;
40
35
  private findDocsPath;
41
36
  private findExamplesPath;
42
37
  private findSrcPath;
43
38
  private buildIndex;
44
39
  private indexDocs;
45
- private loadDocsFromEmbeddings;
46
40
  private indexCodeExamples;
47
41
  private parseCodeExample;
48
42
  private humanizeFilename;
@@ -3,8 +3,6 @@ import { readFileSync, readdirSync, statSync, existsSync } from 'fs';
3
3
  import { join, relative, extname, basename, dirname } from 'path';
4
4
  import { createInterface } from 'readline';
5
5
  import { fileURLToPath } from 'url';
6
- import { createAI } from '../ai/index.js';
7
- import { createHybridSearch } from './search/index.js';
8
6
  import { UnsupportedError } from '../core/errors.js';
9
7
  import { getIpInfo, isValidIP, isGeoIPAvailable, isBogon, isIPv6 } from './ip-intel.js';
10
8
  import { networkTools, networkToolHandlers } from './tools/network.js';
@@ -26,7 +24,6 @@ import { resolveCategories, DEFAULT_CATEGORY, } from './profiles.js';
26
24
  export class MCPServer {
27
25
  options;
28
26
  server;
29
- hybridSearch;
30
27
  docsIndex = [];
31
28
  codeExamples = [];
32
29
  typeDefinitions = [];
@@ -35,7 +32,6 @@ export class MCPServer {
35
32
  toolRegistry;
36
33
  promptRegistry;
37
34
  resourceRegistry;
38
- aiClient;
39
35
  constructor(options = {}) {
40
36
  this.options = {
41
37
  name: options.name || 'recker',
@@ -51,12 +47,6 @@ export class MCPServer {
51
47
  toolPaths: options.toolPaths || [],
52
48
  category: options.category,
53
49
  };
54
- this.aiClient = this.initAI();
55
- this.hybridSearch = createHybridSearch({
56
- debug: this.options.debug,
57
- offline: this.options.offline,
58
- embedder: (text, model) => this.generateEmbedding(text, model || 'BGESmallENV15'),
59
- });
60
50
  this.toolRegistry = new ToolRegistry();
61
51
  this.promptRegistry = new PromptRegistry();
62
52
  this.resourceRegistry = new ResourceRegistry();
@@ -137,56 +127,6 @@ export class MCPServer {
137
127
  }
138
128
  await this.indexReady;
139
129
  }
140
- initAI() {
141
- const hasKeys = process.env.OPENAI_API_KEY || process.env.GOOGLE_API_KEY;
142
- const hasLocal = process.env.OLLAMA_HOST || true;
143
- if (!hasKeys) {
144
- return undefined;
145
- }
146
- try {
147
- return createAI({ debug: this.options.debug });
148
- }
149
- catch (e) {
150
- this.log('Failed to initialize AI client:', e);
151
- return undefined;
152
- }
153
- }
154
- fastEmbedModel = null;
155
- async generateEmbedding(text, model) {
156
- if (model.toLowerCase().includes('bge')) {
157
- if (!this.fastEmbedModel) {
158
- try {
159
- const fastembed = await import('fastembed');
160
- const { FlagEmbedding } = fastembed;
161
- this.fastEmbedModel = await FlagEmbedding.init({
162
- model: model,
163
- showDownloadProgress: false
164
- });
165
- }
166
- catch (e) {
167
- throw new Error(`FastEmbed required for model ${model}. Install with: pnpm add fastembed`);
168
- }
169
- }
170
- const vectors = this.fastEmbedModel.embed([text]);
171
- for await (const v of vectors)
172
- return Array.from(v);
173
- throw new Error('No vector generated');
174
- }
175
- if (!this.aiClient) {
176
- throw new Error('No AI client available for external embeddings');
177
- }
178
- let provider = 'openai';
179
- if (model.includes('google') || model.includes('gecko'))
180
- provider = 'google';
181
- else if (model.includes('nomic') || model.includes('llama'))
182
- provider = 'ollama';
183
- const res = await this.aiClient.embed({
184
- input: text,
185
- provider,
186
- model
187
- });
188
- return res.embeddings[0];
189
- }
190
130
  log(message, data) {
191
131
  if (this.options.debug) {
192
132
  if (this.options.transport === 'stdio') {
@@ -250,78 +190,36 @@ export class MCPServer {
250
190
  await this.indexDocs();
251
191
  this.indexCodeExamples();
252
192
  this.indexTypeDefinitions();
253
- await this.hybridSearch.initialize(this.docsIndex);
254
- const stats = this.hybridSearch.getStats();
255
- this.log(`Indexed ${stats.documents} docs, ${this.codeExamples.length} examples, ${this.typeDefinitions.length} types`);
256
- if (stats.embeddings > 0) {
257
- this.log(`Loaded ${stats.embeddings} embeddings (model: ${stats.model})`);
258
- }
193
+ this.log(`Indexed ${this.docsIndex.length} docs, ${this.codeExamples.length} examples, ${this.typeDefinitions.length} types`);
259
194
  }
260
195
  async indexDocs() {
261
- if (existsSync(this.options.docsPath)) {
262
- const files = this.walkDir(this.options.docsPath);
263
- for (let i = 0; i < files.length; i++) {
264
- const file = files[i];
265
- if (!file.endsWith('.md'))
266
- continue;
267
- try {
268
- const content = readFileSync(file, 'utf-8');
269
- const relativePath = relative(this.options.docsPath, file);
270
- const category = relativePath.split('/')[0] || 'root';
271
- const title = this.extractTitle(content) || relativePath;
272
- const keywords = this.extractKeywords(content);
273
- this.docsIndex.push({
274
- id: `doc-${i}`,
275
- path: relativePath,
276
- title,
277
- category,
278
- content,
279
- keywords,
280
- });
281
- }
282
- catch (err) {
283
- this.log(`Failed to index ${file}:`, err);
284
- }
285
- }
196
+ if (!existsSync(this.options.docsPath)) {
197
+ this.log(`Docs path not found: ${this.options.docsPath}`);
286
198
  return;
287
199
  }
288
- this.log(`Docs path not found: ${this.options.docsPath}, loading from embeddings...`);
289
- await this.loadDocsFromEmbeddings();
290
- }
291
- async loadDocsFromEmbeddings() {
292
- try {
293
- const bundledPath = join(dirname(fileURLToPath(import.meta.url)), 'data', 'embeddings.json');
294
- let embeddingsData = null;
295
- if (existsSync(bundledPath)) {
296
- const raw = readFileSync(bundledPath, 'utf-8');
297
- embeddingsData = JSON.parse(raw);
298
- this.log(`Loaded ${embeddingsData.documents?.length || 0} docs from bundled embeddings`);
299
- }
300
- else {
301
- const { loadEmbeddings } = await import('./embeddings-loader.js');
302
- embeddingsData = await loadEmbeddings({ offline: this.options.offline });
303
- if (embeddingsData) {
304
- this.log(`Loaded ${embeddingsData.documents?.length || 0} docs from cached embeddings`);
305
- }
306
- }
307
- if (!embeddingsData?.documents) {
308
- this.log('No embeddings data available');
309
- return;
310
- }
311
- for (const doc of embeddingsData.documents) {
200
+ const files = this.walkDir(this.options.docsPath);
201
+ for (let i = 0; i < files.length; i++) {
202
+ const file = files[i];
203
+ if (!file.endsWith('.md'))
204
+ continue;
205
+ try {
206
+ const content = readFileSync(file, 'utf-8');
207
+ const relativePath = relative(this.options.docsPath, file);
208
+ const category = relativePath.split('/')[0] || 'root';
209
+ const title = this.extractTitle(content) || relativePath;
210
+ const keywords = this.extractKeywords(content);
312
211
  this.docsIndex.push({
313
- id: doc.id,
314
- path: doc.path,
315
- title: doc.title,
316
- category: doc.category || 'root',
317
- content: doc.content || '',
318
- keywords: doc.keywords || [],
319
- section: doc.section,
212
+ id: `doc-${i}`,
213
+ path: relativePath,
214
+ title,
215
+ category,
216
+ content,
217
+ keywords,
320
218
  });
321
219
  }
322
- }
323
- catch (err) {
324
- this.log(`Failed to load docs from embeddings: ${err}`);
220
+ catch (err) {
221
+ this.log(`Failed to index ${file}:`, err);
222
+ }
325
223
  }
326
224
  }
327
225
  indexCodeExamples() {
@@ -732,15 +630,12 @@ export class MCPServer {
732
630
  const query = String(args.query || '');
733
631
  const category = args.category ? String(args.category) : undefined;
734
632
  const limit = Math.min(Number(args.limit) || 5, 10);
735
- const mode = args.mode || 'hybrid';
736
633
  if (!query) {
737
634
  return {
738
635
  content: [{ type: 'text', text: 'Error: query is required' }],
739
636
  isError: true,
740
637
  };
741
638
  }
742
- const searchPromise = this.hybridSearch.search(query, { limit, category, mode });
743
- let results = [];
744
639
  const queryLower = query.toLowerCase();
745
640
  const queryTerms = queryLower.split(/\s+/);
746
641
  const scored = this.docsIndex
@@ -762,7 +657,7 @@ export class MCPServer {
762
657
  .filter(r => r.score > 0)
763
658
  .sort((a, b) => b.score - a.score)
764
659
  .slice(0, limit);
765
- results = scored.map(r => ({
660
+ const results = scored.map(r => ({
766
661
  id: r.doc.id,
767
662
  path: r.doc.path,
768
663
  title: r.doc.title,
@@ -779,13 +674,11 @@ export class MCPServer {
779
674
  }],
780
675
  };
781
676
  }
782
- const stats = this.hybridSearch.getStats();
783
- const searchMode = stats.embeddings > 0 ? mode : 'fuzzy';
784
677
  const output = results.map((r, i) => `${i + 1}. **${r.title}** (${(r.score * 100).toFixed(0)}% match)\n Path: \`${r.path}\`\n ${r.snippet}`).join('\\n\\n');
785
678
  return {
786
679
  content: [{
787
680
  type: 'text',
788
- text: `Found ${results.length} result(s) for "${query}" (${searchMode} search):\n\n${output}\n\nUse get_doc with the path to read full content.`,
681
+ text: `Found ${results.length} result(s) for "${query}":\n\n${output}\n\nUse get_doc with the path to read full content.`,
789
682
  }],
790
683
  };
791
684
  }
@@ -1323,16 +1216,14 @@ const client = createClient({
1323
1216
  return;
1324
1217
  }
1325
1218
  if (req.method === 'GET' && req.url === '/health') {
1326
- const stats = this.hybridSearch.getStats();
1327
1219
  res.writeHead(200, { 'Content-Type': 'application/json' });
1328
1220
  res.end(JSON.stringify({
1329
1221
  status: 'ok',
1330
1222
  name: this.options.name,
1331
1223
  version: this.options.version,
1332
- docsCount: stats.documents,
1224
+ docsCount: this.docsIndex.length,
1333
1225
  examplesCount: this.codeExamples.length,
1334
1226
  typesCount: this.typeDefinitions.length,
1335
- embeddingsLoaded: stats.embeddings > 0,
1336
1227
  sseClients: this.sseClients.size,
1337
1228
  }));
1338
1229
  return;
@@ -1416,16 +1307,14 @@ const client = createClient({
1416
1307
  return;
1417
1308
  }
1418
1309
  if (req.method === 'GET' && url === '/health') {
1419
- const stats = this.hybridSearch.getStats();
1420
1310
  res.writeHead(200, { 'Content-Type': 'application/json' });
1421
1311
  res.end(JSON.stringify({
1422
1312
  status: 'ok',
1423
1313
  name: this.options.name,
1424
1314
  version: this.options.version,
1425
- docsCount: stats.documents,
1315
+ docsCount: this.docsIndex.length,
1426
1316
  examplesCount: this.codeExamples.length,
1427
1317
  typesCount: this.typeDefinitions.length,
1428
- embeddingsLoaded: stats.embeddings > 0,
1429
1318
  sseClients: this.sseClients.size,
1430
1319
  }));
1431
1320
  return;