nano-brain 2026.6.5 → 2026.6.7
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 +1 -1
- package/src/server.ts +5 -14
- package/src/store.ts +2 -2
- package/src/watcher.ts +61 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nano-brain",
|
|
3
|
-
"version": "2026.6.
|
|
3
|
+
"version": "2026.6.7",
|
|
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/server.ts
CHANGED
|
@@ -91,7 +91,7 @@ export function resolveWorkspace(deps: ServerDeps, filePath?: string, workspaceP
|
|
|
91
91
|
}
|
|
92
92
|
const wsStore = openWorkspaceStore(deps.dataDir, wsPath);
|
|
93
93
|
if (wsStore) {
|
|
94
|
-
return { store: wsStore, workspaceRoot: wsPath, projectHash: wsHash, needsClose:
|
|
94
|
+
return { store: wsStore, workspaceRoot: wsPath, projectHash: wsHash, needsClose: false };
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
}
|
|
@@ -111,7 +111,7 @@ export function resolveWorkspace(deps: ServerDeps, filePath?: string, workspaceP
|
|
|
111
111
|
}
|
|
112
112
|
const wsStore = openWorkspaceStore(deps.dataDir, bestMatch.wsPath);
|
|
113
113
|
if (wsStore) {
|
|
114
|
-
return { store: wsStore, workspaceRoot: bestMatch.wsPath, projectHash: wsHash, needsClose:
|
|
114
|
+
return { store: wsStore, workspaceRoot: bestMatch.wsPath, projectHash: wsHash, needsClose: false };
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
}
|
|
@@ -1122,7 +1122,7 @@ export function createMcpServer(deps: ServerDeps): McpServer {
|
|
|
1122
1122
|
// Open the correct workspace's symbol graph DB (not deps.db which is the startup workspace)
|
|
1123
1123
|
let symbolGraphDb = deps.db;
|
|
1124
1124
|
let symbolGraphDbNeedsClose = false;
|
|
1125
|
-
if (resolved
|
|
1125
|
+
if (resolved && resolved.projectHash !== deps.currentProjectHash && deps.dataDir && resolved.workspaceRoot) {
|
|
1126
1126
|
const wsDbPath = resolveWorkspaceDbPath(deps.dataDir, resolved.workspaceRoot);
|
|
1127
1127
|
symbolGraphDb = openDatabase(wsDbPath);
|
|
1128
1128
|
symbolGraphDbNeedsClose = true;
|
|
@@ -2609,17 +2609,8 @@ export async function startServer(options: ServerOptions): Promise<void> {
|
|
|
2609
2609
|
let resolvedWorkspaceRoot: string;
|
|
2610
2610
|
if (daemon && config?.workspaces && Object.keys(config.workspaces).length > 0) {
|
|
2611
2611
|
const configuredWorkspaces = Object.keys(config.workspaces);
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
if (cwdMatch) {
|
|
2615
|
-
resolvedWorkspaceRoot = cwdMatch;
|
|
2616
|
-
log('server', 'Daemon mode: cwd matches configured workspace');
|
|
2617
|
-
log('server', `Daemon mode: workspace from cwd = ${resolvedWorkspaceRoot}`);
|
|
2618
|
-
} else {
|
|
2619
|
-
resolvedWorkspaceRoot = configuredWorkspaces[0];
|
|
2620
|
-
log('server', 'Daemon mode: cwd does not match any workspace, using first configured');
|
|
2621
|
-
log('server', `Daemon mode: primary workspace = ${resolvedWorkspaceRoot}`);
|
|
2622
|
-
}
|
|
2612
|
+
resolvedWorkspaceRoot = configuredWorkspaces[0];
|
|
2613
|
+
log('server', `Daemon mode: primary workspace = ${resolvedWorkspaceRoot}`);
|
|
2623
2614
|
} else {
|
|
2624
2615
|
resolvedWorkspaceRoot = root || process.cwd();
|
|
2625
2616
|
}
|
package/src/store.ts
CHANGED
|
@@ -80,7 +80,7 @@ export function createStore(dbPath: string): Store {
|
|
|
80
80
|
|
|
81
81
|
const cached = storeCache.get(resolvedPath);
|
|
82
82
|
if (cached) {
|
|
83
|
-
log('store', 'createStore
|
|
83
|
+
log('store', 'createStore cache hit for ' + resolvedPath, 'debug');
|
|
84
84
|
return cached;
|
|
85
85
|
}
|
|
86
86
|
|
|
@@ -1197,7 +1197,7 @@ export function createStore(dbPath: string): Store {
|
|
|
1197
1197
|
|
|
1198
1198
|
close() {
|
|
1199
1199
|
if (_cached) {
|
|
1200
|
-
log('store', 'close() skipped for cached store');
|
|
1200
|
+
log('store', 'close() skipped for cached store', 'debug');
|
|
1201
1201
|
return;
|
|
1202
1202
|
}
|
|
1203
1203
|
try { db.pragma('wal_checkpoint(PASSIVE)'); } catch { /* ignore checkpoint errors */ }
|
package/src/watcher.ts
CHANGED
|
@@ -66,6 +66,54 @@ export interface WatcherStats {
|
|
|
66
66
|
isReindexing: boolean;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Convert a glob exclude pattern (e.g. from BUILTIN_EXCLUDE_PATTERNS) into a
|
|
71
|
+
* chokidar v5-compatible matcher. Chokidar v5 only does exact-string equality
|
|
72
|
+
* for string matchers, so we must return a RegExp or function instead.
|
|
73
|
+
*
|
|
74
|
+
* Supported pattern shapes:
|
|
75
|
+
* ** /dir/ ** → directory name anywhere → /[/\\]dir([/\\]|$)/
|
|
76
|
+
* ** /a/b/ ** → nested path segment → /[/\\]a[/\\]b([/\\]|$)/
|
|
77
|
+
* ** /*.ext → file extension anywhere → /\.ext$/
|
|
78
|
+
* ** /exact-file → filename anywhere → /[/\\]exact-file$/
|
|
79
|
+
* /absolute/ ** → absolute prefix → starts-with check
|
|
80
|
+
* plain-name → bare directory name → /[/\\]plain-name([/\\]|$)/
|
|
81
|
+
*/
|
|
82
|
+
function globToChokidarMatcher(pattern: string): RegExp {
|
|
83
|
+
const p = pattern.replace(/\\/g, '/')
|
|
84
|
+
|
|
85
|
+
// Absolute prefix: /tmp/** → match paths starting with /tmp/
|
|
86
|
+
if (p.startsWith('/') && !p.startsWith('*')) {
|
|
87
|
+
const prefix = p.replace(/\/\*\*\/?$/, '')
|
|
88
|
+
const escaped = prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
89
|
+
return new RegExp(`^${escaped}([/\\\\]|$)`)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Strip leading **/ and trailing /**
|
|
93
|
+
let core = p
|
|
94
|
+
if (core.startsWith('**/')) core = core.slice(3)
|
|
95
|
+
if (core.endsWith('/**')) core = core.slice(0, -3)
|
|
96
|
+
|
|
97
|
+
if (core.startsWith('*')) {
|
|
98
|
+
const ext = core.slice(1)
|
|
99
|
+
const escaped = ext.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
100
|
+
// *.egg-info etc. can be directories — match as path segment, not just suffix
|
|
101
|
+
return new RegExp(`${escaped}([/\\\\]|$)`)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Contains wildcard in filename: e.g. "assets/index-*.js" or "i18n/locales/*.json"
|
|
105
|
+
if (core.includes('*')) {
|
|
106
|
+
const escaped = core
|
|
107
|
+
.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
|
|
108
|
+
.replace(/\*/g, '[^/\\\\]*')
|
|
109
|
+
return new RegExp(`[/\\\\]${escaped}$`)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Directory or filename: "node_modules", "public/vs", "package-lock.json"
|
|
113
|
+
const escaped = core.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\//g, '[/\\\\]')
|
|
114
|
+
return new RegExp(`[/\\\\]${escaped}([/\\\\]|$)`)
|
|
115
|
+
}
|
|
116
|
+
|
|
69
117
|
export function startWatcher(options: WatcherOptions): Watcher {
|
|
70
118
|
const {
|
|
71
119
|
store,
|
|
@@ -281,7 +329,7 @@ export function startWatcher(options: WatcherOptions): Watcher {
|
|
|
281
329
|
|
|
282
330
|
const setupWatcher = () => {
|
|
283
331
|
const pathsToWatch: string[] = []
|
|
284
|
-
const ignoredPatterns: (string | RegExp)[] = [/(^|[
|
|
332
|
+
const ignoredPatterns: (string | RegExp | ((path: string) => boolean))[] = [/(^|[/\\])\./]
|
|
285
333
|
for (const collection of collections) {
|
|
286
334
|
const expandedPath = collection.path.replace(/^~/, os.homedir())
|
|
287
335
|
if (fs.existsSync(expandedPath)) {
|
|
@@ -294,20 +342,20 @@ export function startWatcher(options: WatcherOptions): Watcher {
|
|
|
294
342
|
watchedPaths.add(workspaceRoot)
|
|
295
343
|
const excludePatterns = mergeExcludePatterns(codebaseConfig, workspaceRoot)
|
|
296
344
|
for (const pattern of excludePatterns) {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
345
|
+
ignoredPatterns.push(globToChokidarMatcher(pattern))
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const deduped: string[] = []
|
|
349
|
+
for (const p of pathsToWatch) {
|
|
350
|
+
const isSubpath = pathsToWatch.some(other =>
|
|
351
|
+
other !== p && p.startsWith(other.endsWith('/') ? other : other + '/')
|
|
352
|
+
)
|
|
353
|
+
if (!isSubpath) {
|
|
354
|
+
deduped.push(p)
|
|
307
355
|
}
|
|
308
356
|
}
|
|
309
|
-
if (
|
|
310
|
-
watcher = watch(
|
|
357
|
+
if (deduped.length === 0) return
|
|
358
|
+
watcher = watch(deduped, {
|
|
311
359
|
ignored: ignoredPatterns,
|
|
312
360
|
persistent: true,
|
|
313
361
|
ignoreInitial: true,
|