wicked-brain 0.14.0 → 0.14.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wicked-brain",
3
- "version": "0.14.0",
3
+ "version": "0.14.1",
4
4
  "type": "module",
5
5
  "description": "Digital brain as skills for AI coding CLIs — no vector DB, no embeddings, no infrastructure",
6
6
  "keywords": [
@@ -1,4 +1,4 @@
1
- import { watch, readFileSync, existsSync, readdirSync, statSync } from "node:fs";
1
+ import { watch, readFileSync, existsSync, readdirSync, statSync, realpathSync } from "node:fs";
2
2
  import { join, relative } from "node:path";
3
3
  import { createHash } from "node:crypto";
4
4
 
@@ -6,6 +6,31 @@ function normalizePath(p) {
6
6
  return p.replace(/\\/g, "/");
7
7
  }
8
8
 
9
+ // Canonicalize to the real (long) path before watching. On Windows, calling
10
+ // fs.watch on an 8.3 short-name path (e.g. C:\Users\RUNNER~1\AppData\Local\Temp)
11
+ // makes libuv abort the process with "Assertion failed: !_wcsnicmp(...)" in
12
+ // fs-event.c — change events report the long filename, which no longer shares
13
+ // the watched (short) directory prefix. That abort is a native crash, so it
14
+ // bypasses the try/catch → polling fallback below. filename in the callbacks
15
+ // stays relative to the watched dir, so the logical relPath construction is
16
+ // unaffected.
17
+ //
18
+ // realpathSync.native (OS GetFinalPathNameByHandle) is required to expand 8.3
19
+ // short names to their long form; the JS realpathSync only resolves symlinks
20
+ // and leaves short-name components like RUNNER~1 intact. Fall back to the JS
21
+ // implementation, then to the original path.
22
+ function canonicalDir(p) {
23
+ try {
24
+ return realpathSync.native(p);
25
+ } catch {
26
+ try {
27
+ return realpathSync(p);
28
+ } catch {
29
+ return p;
30
+ }
31
+ }
32
+ }
33
+
9
34
  const IGNORE_DIRS = new Set([
10
35
  "node_modules", ".git", "__pycache__", ".venv", "venv",
11
36
  "target", "dist", "build", ".next", ".nuxt", "coverage",
@@ -70,7 +95,7 @@ export class FileWatcher {
70
95
  if (!existsSync(project.path)) continue;
71
96
  this.#scanAndHashProject(project);
72
97
  try {
73
- const watcher = watch(project.path, { recursive: true }, (eventType, filename) => {
98
+ const watcher = watch(canonicalDir(project.path), { recursive: true }, (eventType, filename) => {
74
99
  if (!filename) return;
75
100
  const parts = filename.split(/[/\\]/);
76
101
  if (parts.some(p => IGNORE_DIRS.has(p))) return;
@@ -96,7 +121,7 @@ export class FileWatcher {
96
121
  const absDir = join(this.#brainPath, dir);
97
122
  if (!existsSync(absDir)) return false;
98
123
  try {
99
- const watcher = watch(absDir, { recursive: true }, (eventType, filename) => {
124
+ const watcher = watch(canonicalDir(absDir), { recursive: true }, (eventType, filename) => {
100
125
  if (!filename || !filename.endsWith(".md")) return;
101
126
  const relPath = normalizePath(`${dir}/${filename}`);
102
127
  this.#debounce(relPath, () => this.#handleChange(relPath));
@@ -795,10 +795,10 @@ export class SqliteSearch {
795
795
  d.id,
796
796
  d.path,
797
797
  d.brain_id,
798
- snippet(${attached}.documents_fts, 2, '<b>', '</b>', '…', 32) AS snippet
798
+ snippet(documents_fts, 2, '<b>', '</b>', '…', 32) AS snippet
799
799
  FROM ${attached}.documents_fts f
800
800
  JOIN ${attached}.documents d ON d.id = f.id
801
- WHERE ${attached}.documents_fts MATCH ?
801
+ WHERE documents_fts MATCH ?
802
802
  ORDER BY rank
803
803
  LIMIT ?
804
804
  `)
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wicked-brain-server",
3
- "version": "0.14.0",
3
+ "version": "0.14.1",
4
4
  "type": "module",
5
5
  "description": "SQLite FTS5 search server for wicked-brain digital knowledge bases",
6
6
  "keywords": [