nano-brain 2026.8.6 → 2026.8.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nano-brain",
3
- "version": "2026.8.6",
3
+ "version": "2026.8.8",
4
4
  "description": "Persistent memory and code intelligence for AI coding agents. Local MCP server with self-learning hybrid search (BM25 + vector + knowledge graph + LLM reranking), automatic session ingestion, codebase indexing, and 22 tools. Learns your preferences over time. Works with OpenCode, Claude, Cursor, Windsurf, and any MCP client.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/codebase.ts CHANGED
@@ -322,10 +322,11 @@ export async function indexCodebase(
322
322
  const content = fs.readFileSync(filePath, 'utf-8')
323
323
  const contentSize = Buffer.byteLength(content, 'utf-8')
324
324
  const hash = computeHash(content)
325
- const existingDoc = store.findDocument(filePath)
325
+ const docPath = filePath.startsWith(workspaceRoot + '/') ? filePath.slice(workspaceRoot.length + 1) : filePath
326
+ const existingDoc = store.findDocument(docPath)
326
327
  if (existingDoc && existingDoc.hash === hash) {
327
328
  filesSkippedUnchanged++
328
- activePaths.push(filePath)
329
+ activePaths.push(docPath)
329
330
  scannedFiles.push({ path: filePath, content })
330
331
  continue
331
332
  }
@@ -334,7 +335,7 @@ export async function indexCodebase(
334
335
  if (currentStorageUsed + netIncrease > effectiveMaxSize) {
335
336
  filesSkippedBudget++
336
337
  if (existingDoc) {
337
- activePaths.push(filePath)
338
+ activePaths.push(docPath)
338
339
  }
339
340
  continue
340
341
  }
@@ -355,7 +356,7 @@ export async function indexCodebase(
355
356
  const now = new Date().toISOString()
356
357
  store.insertDocument({
357
358
  collection: 'codebase',
358
- path: filePath,
359
+ path: docPath,
359
360
  title,
360
361
  hash,
361
362
  createdAt: existingDoc?.createdAt ?? now,
@@ -391,7 +392,7 @@ export async function indexCodebase(
391
392
 
392
393
  currentStorageUsed += netIncrease
393
394
  filesIndexed++
394
- activePaths.push(filePath)
395
+ activePaths.push(docPath)
395
396
  scannedFiles.push({ path: filePath, content })
396
397
  } catch {
397
398
  continue
@@ -9,11 +9,14 @@ export interface Reranker {
9
9
  export interface RerankerOptions {
10
10
  apiKey?: string;
11
11
  model?: string;
12
+ provider?: 'voyageai' | 'cohere';
12
13
  onTokenUsage?: (model: string, tokens: number) => void;
13
14
  }
14
15
 
15
16
  const VOYAGE_RERANK_URL = 'https://api.voyageai.com/v1/rerank';
17
+ const COHERE_RERANK_URL = 'https://api.cohere.com/v2/rerank';
16
18
  const DEFAULT_MODEL = 'rerank-2.5-lite';
19
+ const DEFAULT_COHERE_MODEL = 'rerank-v3.5';
17
20
 
18
21
  class VoyageAIReranker implements Reranker {
19
22
  private apiKey: string;
@@ -89,6 +92,79 @@ class VoyageAIReranker implements Reranker {
89
92
  dispose(): void {}
90
93
  }
91
94
 
95
+ class CohereReranker implements Reranker {
96
+ private apiKey: string;
97
+ private model: string;
98
+ private onTokenUsage?: (model: string, tokens: number) => void;
99
+
100
+ constructor(apiKey: string, model: string, onTokenUsage?: (model: string, tokens: number) => void) {
101
+ this.apiKey = apiKey;
102
+ this.model = model;
103
+ this.onTokenUsage = onTokenUsage;
104
+ }
105
+
106
+ async rerank(query: string, documents: RerankDocument[]): Promise<RerankResult> {
107
+ if (documents.length === 0) {
108
+ return { results: [], model: this.model };
109
+ }
110
+
111
+ try {
112
+ const response = await fetch(COHERE_RERANK_URL, {
113
+ method: 'POST',
114
+ headers: {
115
+ 'Authorization': `Bearer ${this.apiKey}`,
116
+ 'Content-Type': 'application/json',
117
+ },
118
+ body: JSON.stringify({
119
+ query,
120
+ documents: documents.map(d => d.text),
121
+ model: this.model,
122
+ top_n: documents.length,
123
+ }),
124
+ signal: AbortSignal.timeout(30000),
125
+ });
126
+
127
+ if (!response.ok) {
128
+ const body = await response.text().catch(() => '');
129
+ log('reranker', `Cohere rerank failed: HTTP ${response.status} ${body}`, 'warn');
130
+ return { results: [], model: this.model };
131
+ }
132
+
133
+ const data = await response.json() as {
134
+ results: Array<{ index: number; relevance_score: number }>;
135
+ meta?: { billed_units?: { search_units?: number } };
136
+ };
137
+
138
+ if (this.onTokenUsage && data.meta?.billed_units?.search_units) {
139
+ this.onTokenUsage(this.model, data.meta.billed_units.search_units);
140
+ }
141
+
142
+ if (!data.results || !Array.isArray(data.results)) {
143
+ log('reranker', `Cohere rerank returned unexpected response: ${JSON.stringify(data).slice(0, 200)}`, 'warn');
144
+ return { results: [], model: this.model };
145
+ }
146
+
147
+ const results = data.results
148
+ .filter(r => r.index >= 0 && r.index < documents.length)
149
+ .map(r => ({
150
+ file: documents[r.index].file,
151
+ score: r.relevance_score,
152
+ index: r.index,
153
+ }));
154
+
155
+ log('reranker', `Cohere rerank model=${this.model} docs=${documents.length} units=${data.meta?.billed_units?.search_units ?? 1}`, 'debug');
156
+
157
+ return { results, model: this.model };
158
+ } catch (err) {
159
+ const msg = err instanceof Error ? err.message : String(err);
160
+ log('reranker', `Cohere rerank error: ${msg}`, 'warn');
161
+ return { results: [], model: this.model };
162
+ }
163
+ }
164
+
165
+ dispose(): void {}
166
+ }
167
+
92
168
  export async function createReranker(
93
169
  options?: RerankerOptions
94
170
  ): Promise<Reranker | null> {
@@ -98,6 +174,14 @@ export async function createReranker(
98
174
  return null;
99
175
  }
100
176
 
177
+ const provider = options?.provider || 'voyageai';
178
+
179
+ if (provider === 'cohere') {
180
+ const model = options?.model || DEFAULT_COHERE_MODEL;
181
+ log('reranker', `Cohere reranker initialized model=${model}`);
182
+ return new CohereReranker(apiKey, model, options?.onTokenUsage);
183
+ }
184
+
101
185
  const model = options?.model || DEFAULT_MODEL;
102
186
  log('reranker', `VoyageAI reranker initialized model=${model}`);
103
187
  return new VoyageAIReranker(apiKey, model, options?.onTokenUsage);
@@ -362,6 +362,7 @@ export async function startServer(options: ServerOptions): Promise<void> {
362
362
  createReranker({
363
363
  apiKey: config?.reranker?.apiKey || config?.embedding?.apiKey,
364
364
  model: config?.reranker?.model,
365
+ provider: config?.reranker?.provider as 'voyageai' | 'cohere' | undefined,
365
366
  onTokenUsage: (model, tokens) => store.recordTokenUsage(model, tokens),
366
367
  })
367
368
  .then((loadedReranker) => {
package/src/types.ts CHANGED
@@ -139,6 +139,7 @@ export interface EmbeddingConfig {
139
139
  export interface RerankerConfig {
140
140
  model?: string
141
141
  apiKey?: string
142
+ provider?: 'voyageai' | 'cohere'
142
143
  }
143
144
 
144
145
  export interface WatcherConfig {