wicked-brain 0.4.1 → 0.4.3

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/README.md CHANGED
@@ -126,7 +126,7 @@ Every operation uses **progressive loading** — the agent never pulls more than
126
126
 
127
127
  | Skill | What it does |
128
128
  |---|---|
129
- | `wicked-brain:init` | Set up a new brain — creates directory structure, then onboards your project in parallel |
129
+ | `wicked-brain:init` | Set up a new brain — creates structure, starts the server, and ingests your project in one shot |
130
130
  | `wicked-brain:ingest` | Add source files — text extracted deterministically, binary docs read via LLM vision |
131
131
  | `wicked-brain:search` | Parallel search across your brain and linked brains |
132
132
  | `wicked-brain:read` | Progressive loading: depth 0 (stats), depth 1 (summary), depth 2 (full content) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wicked-brain",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
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": [
@@ -17,6 +17,9 @@ function getArg(name) {
17
17
  const brainPath = resolve(getArg("brain") || ".");
18
18
  const preferredPort = parseInt(getArg("port") || "4242", 10);
19
19
  const configPath = join(brainPath, "brain.json");
20
+ // Source path for LSP workspace root — prefer --source flag, fall back to config, then brainPath
21
+ const sourceArgRaw = getArg("source");
22
+ const sourceArg = sourceArgRaw ? resolve(sourceArgRaw) : null;
20
23
 
21
24
  /** Find a free TCP port starting from `start`. */
22
25
  function findFreePort(start) {
@@ -57,8 +60,18 @@ const db = new SqliteSearch(dbPath, brainId);
57
60
  const pidPath = join(brainPath, "_meta", "server.pid");
58
61
  writeFileSync(pidPath, String(pid));
59
62
 
60
- // LSP client
61
- const lsp = new LspClient(brainPath, db);
63
+ // Read project directories and source path from config (must happen before LspClient)
64
+ const metaConfigPath = join(brainPath, "_meta", "config.json");
65
+ let projects = [];
66
+ let sourcePath = sourceArg;
67
+ try {
68
+ const metaConfig = JSON.parse(readFileSync(metaConfigPath, "utf-8"));
69
+ projects = metaConfig.projects || [];
70
+ if (!sourcePath && metaConfig.source_path) sourcePath = resolve(metaConfig.source_path);
71
+ } catch {}
72
+
73
+ // LSP client — pass source path so language servers are rooted at the project, not the brain dir
74
+ const lsp = new LspClient(brainPath, db, sourcePath);
62
75
 
63
76
  // Graceful shutdown
64
77
  async function shutdown() {
@@ -86,18 +99,22 @@ const actions = {
86
99
  candidates: (p) => ({ candidates: db.candidates(p) }),
87
100
  symbols: async (p) => {
88
101
  // Prefer LSP workspace symbols (structured, language-aware)
89
- const lspResult = await lsp.workspaceSymbols({ query: p.name || p.query || "" });
90
- if (lspResult.symbols && lspResult.symbols.length > 0) {
91
- return {
92
- results: lspResult.symbols.map(s => ({
93
- id: `${s.file}::${s.name}`,
94
- name: s.name,
95
- type: s.kind,
96
- file_path: s.file,
97
- line_start: s.line,
98
- })),
99
- source: "lsp",
100
- };
102
+ try {
103
+ const lspResult = await lsp.workspaceSymbols({ query: p.name || p.query || "" });
104
+ if (lspResult.symbols && lspResult.symbols.length > 0) {
105
+ return {
106
+ results: lspResult.symbols.map(s => ({
107
+ id: `${s.file}::${s.name}`,
108
+ name: s.name,
109
+ type: s.kind,
110
+ file_path: s.file,
111
+ line_start: s.line,
112
+ })),
113
+ source: "lsp",
114
+ };
115
+ }
116
+ } catch {
117
+ // LSP unavailable or errored (e.g. no tsconfig.json) — fall through to FTS
101
118
  }
102
119
  // Fall back to FTS-based symbol search
103
120
  return { ...db.symbols(p), source: "fts" };
@@ -164,14 +181,6 @@ const server = createServer((req, res) => {
164
181
  });
165
182
  });
166
183
 
167
- // Read project directories from config
168
- const metaConfigPath = join(brainPath, "_meta", "config.json");
169
- let projects = [];
170
- try {
171
- const metaConfig = JSON.parse(readFileSync(metaConfigPath, "utf-8"));
172
- projects = metaConfig.projects || [];
173
- } catch {}
174
-
175
184
  const watcher = new FileWatcher(brainPath, db, brainId, projects);
176
185
 
177
186
  // Wire file changes to LSP client for didOpen/didChange/didClose
@@ -21,10 +21,10 @@ export class LspClient {
21
21
  #diagnostics = new Map(); // filePath → Diagnostic[]
22
22
  #diagnosticsSetup = new Set(); // server keys with diagnostics wired
23
23
 
24
- constructor(brainPath, db) {
24
+ constructor(brainPath, db, sourcePath) {
25
25
  this.#brainPath = brainPath;
26
26
  this.#db = db;
27
- this.#manager = new LspManager(brainPath);
27
+ this.#manager = new LspManager(brainPath, sourcePath);
28
28
  this.#userConfig = loadUserConfig(brainPath);
29
29
  }
30
30
 
@@ -13,10 +13,18 @@ const RETRY_RESET_MS = 300000; // 5 minutes
13
13
 
14
14
  export class LspManager {
15
15
  #brainPath;
16
+ #sourcePath;
16
17
  #servers = new Map(); // key → { process, client, state, retries, startedAt, openFiles }
17
18
 
18
- constructor(brainPath) {
19
+ /**
20
+ * @param {string} brainPath - Brain storage directory (used as fallback workspace root)
21
+ * @param {string} [sourcePath] - Actual source project root with tsconfig.json, go.mod, etc.
22
+ * When provided, LSP servers are initialized with this as rootUri so they can find
23
+ * project configuration files. Falls back to brainPath if not provided.
24
+ */
25
+ constructor(brainPath, sourcePath) {
19
26
  this.#brainPath = brainPath;
27
+ this.#sourcePath = sourcePath || brainPath;
20
28
  }
21
29
 
22
30
  /**
@@ -71,7 +79,7 @@ export class LspManager {
71
79
 
72
80
  const proc = spawn(config.command, config.args || [], {
73
81
  stdio: ["pipe", "pipe", "pipe"],
74
- cwd: this.#brainPath,
82
+ cwd: this.#sourcePath,
75
83
  });
76
84
 
77
85
  const client = new RpcClient(proc.stdin, proc.stdout);
@@ -100,13 +108,12 @@ export class LspManager {
100
108
 
101
109
  // Initialize
102
110
  try {
103
- const result = await client.request("initialize", {
111
+ const rootUri = pathToFileURL(resolve(this.#sourcePath)).href;
112
+ await client.request("initialize", {
104
113
  processId: process.pid,
105
114
  capabilities: {},
106
- rootUri: pathToFileURL(resolve(this.#brainPath)).href,
107
- workspaceFolders: [
108
- { uri: pathToFileURL(resolve(this.#brainPath)).href, name: "brain" },
109
- ],
115
+ rootUri,
116
+ workspaceFolders: [{ uri: rootUri, name: "project" }],
110
117
  });
111
118
  client.notify("initialized", {});
112
119
  entry.state = "ready";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wicked-brain-server",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "type": "module",
5
5
  "description": "SQLite FTS5 search server for wicked-brain digital knowledge bases",
6
6
  "keywords": [
@@ -398,7 +398,34 @@ Archived files are invisible to the file watcher, so the server won't clean them
398
398
  - macOS/Linux: `mv "{brain_path}/chunks/extracted/{safe_name}" "{brain_path}/chunks/extracted/{safe_name}.archived-$(date +%s)"`
399
399
  - Windows: `Rename-Item "{brain_path}\chunks\extracted\{safe_name}" "{safe_name}.archived-{timestamp}"`
400
400
 
401
- ### Step 5: Report to user
401
+ ### Step 5: Record source path
402
+
403
+ After ingesting a directory, write the absolute source path to `_meta/config.json`
404
+ so the brain server can use it as the LSP workspace root (enabling symbol lookup,
405
+ go-to-definition, and diagnostics for the ingested project):
406
+
407
+ ```bash
408
+ # Read current config, add source_path, write back
409
+ python3 -c "
410
+ import json, sys
411
+ path = '{brain_path}/_meta/config.json'
412
+ with open(path) as f: cfg = json.load(f)
413
+ cfg['source_path'] = '{absolute_source_path}'
414
+ with open(path, 'w') as f: json.dump(cfg, f, indent=2)
415
+ print('source_path recorded')
416
+ " 2>/dev/null || python -c "
417
+ import json, sys
418
+ path = '{brain_path}/_meta/config.json'
419
+ with open(path) as f: cfg = json.load(f)
420
+ cfg['source_path'] = '{absolute_source_path}'
421
+ with open(path, 'w') as f: json.dump(cfg, f, indent=2)
422
+ print('source_path recorded')
423
+ "
424
+ ```
425
+
426
+ Skip this step if the source is a single file rather than a project directory.
427
+
428
+ ### Step 6: Report to user
402
429
 
403
430
  After the subagent or batch script completes, summarize:
404
431
  - "{N} text files ingested, {M} chunks created"
@@ -47,9 +47,12 @@ For the brain path default:
47
47
  - Windows: `tasklist /FI "PID eq {pid}" 2>nul | findstr {pid}`
48
48
  - Or use Python: `python3 -c "import os; os.kill({pid}, 0)" 2>/dev/null || python -c "import os; os.kill({pid}, 0)"`
49
49
 
50
- c. If the process is dead or no PID file, start the server:
50
+ c. If the process is dead or no PID file, start the server.
51
+ Also pass `--source` if `source_path` is set in `_meta/config.json`
52
+ (this roots LSP language servers at the ingested project so symbol
53
+ lookup and go-to-definition work correctly):
51
54
  ```bash
52
- npx wicked-brain-server --brain {brain_path} --port {port} &
55
+ npx wicked-brain-server --brain {brain_path} --port {port} [--source {source_path}] &
53
56
  ```
54
57
  On Windows (PowerShell):
55
58
  ```powershell