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 +1 -1
- package/package.json +1 -1
- package/server/bin/wicked-brain-server.mjs +31 -22
- package/server/lib/lsp-client.mjs +2 -2
- package/server/lib/lsp-manager.mjs +14 -7
- package/server/package.json +1 -1
- package/skills/wicked-brain-ingest/SKILL.md +28 -1
- package/skills/wicked-brain-server/SKILL.md +5 -2
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
|
|
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
|
@@ -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
|
-
//
|
|
61
|
-
const
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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.#
|
|
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
|
|
111
|
+
const rootUri = pathToFileURL(resolve(this.#sourcePath)).href;
|
|
112
|
+
await client.request("initialize", {
|
|
104
113
|
processId: process.pid,
|
|
105
114
|
capabilities: {},
|
|
106
|
-
rootUri
|
|
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";
|
package/server/package.json
CHANGED
|
@@ -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:
|
|
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
|