wicked-brain 0.4.7 → 0.4.9

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
@@ -127,6 +127,7 @@ Every operation uses **progressive loading** — the agent never pulls more than
127
127
  | Skill | What it does |
128
128
  |---|---|
129
129
  | `wicked-brain:init` | Set up a new brain — creates structure, starts the server, and ingests your project in one shot |
130
+ | `wicked-brain:migrate` | Migrate a legacy flat brain at `~/.wicked-brain/` into the per-project layout |
130
131
  | `wicked-brain:ingest` | Add source files — text extracted deterministically, binary docs read via LLM vision |
131
132
  | `wicked-brain:search` | Parallel search across your brain and linked brains |
132
133
  | `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.7",
3
+ "version": "0.4.9",
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": [
@@ -96,6 +96,7 @@ const actions = {
96
96
  backlinks: (p) => ({ links: db.backlinks(p.id) }),
97
97
  forward_links: (p) => ({ links: db.forwardLinks(p.id) }),
98
98
  stats: () => db.stats(),
99
+ memory_stats: () => db.memoryStats(),
99
100
  candidates: (p) => ({ candidates: db.candidates(p) }),
100
101
  symbols: async (p) => {
101
102
  // Prefer LSP workspace symbols (structured, language-aware)
@@ -421,7 +421,51 @@ export class SqliteSearch {
421
421
  // in-memory or inaccessible
422
422
  }
423
423
 
424
- return { total, chunks, wiki, memory, last_indexed, db_size };
424
+ return {
425
+ total,
426
+ chunks,
427
+ wiki,
428
+ memory,
429
+ memory_breakdown: this.memoryStats(),
430
+ last_indexed,
431
+ db_size,
432
+ };
433
+ }
434
+
435
+ /**
436
+ * Breakdown of memory documents by type, tier, and age.
437
+ * Parses frontmatter from `memory/%` rows. Missing fields count as "unknown".
438
+ * Age buckets: <1d, 1-7d, 7-30d, 30-90d, >90d.
439
+ */
440
+ memoryStats() {
441
+ const rows = this.#db.prepare(`
442
+ SELECT frontmatter, indexed_at FROM documents WHERE path LIKE 'memory/%'
443
+ `).all();
444
+
445
+ const by_type = {};
446
+ const by_tier = {};
447
+ const by_age = { "<1d": 0, "1-7d": 0, "7-30d": 0, "30-90d": 0, ">90d": 0 };
448
+ const now = Date.now();
449
+ const DAY = 86400000;
450
+
451
+ for (const row of rows) {
452
+ const fm = row.frontmatter || "";
453
+ const typeMatch = fm.match(/^type:\s*(\S+)/m);
454
+ const tierMatch = fm.match(/^tier:\s*(\S+)/m);
455
+ const type = typeMatch ? typeMatch[1].replace(/["']/g, "") : "unknown";
456
+ const tier = tierMatch ? tierMatch[1].replace(/["']/g, "") : "unknown";
457
+ by_type[type] = (by_type[type] ?? 0) + 1;
458
+ by_tier[tier] = (by_tier[tier] ?? 0) + 1;
459
+
460
+ const age = now - (row.indexed_at ?? now);
461
+ if (age < DAY) by_age["<1d"]++;
462
+ else if (age < 7 * DAY) by_age["1-7d"]++;
463
+ else if (age < 30 * DAY) by_age["7-30d"]++;
464
+ else if (age < 90 * DAY) by_age["30-90d"]++;
465
+ else by_age[">90d"]++;
466
+ }
467
+
468
+ return { total: rows.length, by_type, by_tier, by_age };
425
469
  }
426
470
 
427
471
  health() {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wicked-brain-server",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "type": "module",
5
5
  "description": "SQLite FTS5 search server for wicked-brain digital knowledge bases",
6
6
  "keywords": [
@@ -0,0 +1,93 @@
1
+ ---
2
+ name: wicked-brain:forget
3
+ description: |
4
+ Archive or delete a memory (or any indexed document) by id or path.
5
+ Removes the document from the FTS index and renames the file with an
6
+ `.archived-{timestamp}` suffix so the data is recoverable.
7
+
8
+ Use when: "forget this memory", "archive this", "drop this decision",
9
+ "remove from brain", "brain forget".
10
+ ---
11
+
12
+ # wicked-brain:forget
13
+
14
+ Archive or hard-delete a memory by id or path. Wraps the server `remove` action
15
+ and the archive-rename convention used by wicked-brain:agent dispatch consolidate.
16
+
17
+ ## Cross-Platform Notes
18
+
19
+ - Uses `curl` for server API calls (Windows 10+, macOS, Linux)
20
+ - Uses agent-native Read/Bash tools for file ops — no Unix-only shell features
21
+ - Paths always use forward slashes
22
+
23
+ ## Config
24
+
25
+ Read `_meta/config.json` for brain path and server port.
26
+ If it doesn't exist, trigger wicked-brain:init.
27
+
28
+ ## Parameters
29
+
30
+ - **id** (required if `path` not given): document id as returned by search
31
+ - **path** (required if `id` not given): path relative to the brain root (e.g. `memory/jwt-decision.md`)
32
+ - **mode** (optional, default `archive`): `archive` (rename file + remove from index, recoverable) or `delete` (rename + remove + final deletion is still left to the user — this skill never unlinks files)
33
+ - **reason** (optional): short string recorded in the log for auditability
34
+
35
+ This skill never hard-deletes a file. `delete` mode still renames with
36
+ `.archived-{timestamp}` — actual `rm` is a human decision.
37
+
38
+ ## Process
39
+
40
+ ### Step 1: Resolve id and path
41
+
42
+ If only `id` is given, find the path by calling search or reading the id (ids
43
+ are of the form `{path}` or `{path}::{fragment}` in this brain). If only `path`
44
+ is given, the id is normally the same string for top-level documents.
45
+
46
+ ### Step 2: Confirm the document exists
47
+
48
+ Read the file at `{brain_path}/{path}` to verify it is present and (if it is a
49
+ memory) inspect its frontmatter so the log entry can record type/tier.
50
+
51
+ ### Step 3: Remove from index
52
+
53
+ ```bash
54
+ curl -s -X POST http://localhost:{port}/api \
55
+ -H "Content-Type: application/json" \
56
+ -d '{"action":"remove","params":{"id":"{id}"}}'
57
+ ```
58
+
59
+ ### Step 4: Archive the file
60
+
61
+ Rename the file in-place with an `.archived-{unix-ms}` suffix.
62
+
63
+ macOS / Linux:
64
+ ```bash
65
+ mv "{brain_path}/{path}" "{brain_path}/{path}.archived-$(date +%s)"
66
+ ```
67
+
68
+ Windows (PowerShell):
69
+ ```powershell
70
+ Rename-Item "{brain_path}/{path}" "{path}.archived-$([DateTimeOffset]::UtcNow.ToUnixTimeSeconds())"
71
+ ```
72
+
73
+ Prefer the agent-native Bash tool on the current platform; both forms produce a
74
+ recoverable archive marker.
75
+
76
+ ### Step 5: Log the forget event
77
+
78
+ Append to `{brain_path}/_meta/log.jsonl`:
79
+
80
+ ```json
81
+ {"ts":"{ISO}","op":"memory_forget","path":"{path}","id":"{id}","mode":"{mode}","reason":"{reason}","author":"agent:forget"}
82
+ ```
83
+
84
+ ### Step 6: Report
85
+
86
+ Report: path, id, previous frontmatter type/tier (if memory), archive filename,
87
+ and whether index removal succeeded. Always surface the archive path so the
88
+ user can restore it by renaming back.
89
+
90
+ ## Recovery
91
+
92
+ To restore an archived memory, rename the `.archived-{ts}` file back to its
93
+ original name. The file watcher will pick it up and re-index automatically.
@@ -69,9 +69,28 @@ directory, not a project subdirectory), push back: explain the per-project
69
69
  convention and suggest `~/.wicked-brain/projects/{project_name}` instead.
70
70
  Only accept the flat path if the user explicitly insists.
71
71
 
72
- ### Step 2: Check for existing brain
72
+ ### Step 2: Check for existing brains
73
73
 
74
- If `{brain_path}/_meta/config.json` already exists, tell the user:
74
+ #### 2a: Detect a flat brain at the parent path
75
+
76
+ If `~/.wicked-brain/brain.json` exists (note: `brain.json` at the flat parent
77
+ path, NOT inside a `projects/` subdirectory), this is a legacy flat brain from
78
+ before v0.4.7. Stop and tell the user:
79
+
80
+ "I found an existing flat brain at `~/.wicked-brain/`. The current layout puts
81
+ each project under `~/.wicked-brain/projects/{name}/`. I can migrate the flat
82
+ brain with `wicked-brain:migrate` before creating the new one. Migrate now?"
83
+
84
+ If yes, invoke `wicked-brain:migrate` with `flat_path=~/.wicked-brain` and
85
+ wait for it to complete before continuing.
86
+
87
+ If no, confirm the user wants to keep the flat brain and proceed (accept the
88
+ tradeoff: the new project brain will live under `projects/` but the old one
89
+ stays at the flat path).
90
+
91
+ #### 2b: Check target path
92
+
93
+ If `{brain_path}/_meta/config.json` already exists at the chosen target, tell the user:
75
94
  "A brain already exists at `{brain_path}`. Do you want to re-initialize it (keeps existing chunks) or pick a different path?"
76
95
 
77
96
  Stop and wait for their answer before continuing.
@@ -123,9 +142,12 @@ Write to `{brain_path}/_meta/config.json`:
123
142
  }
124
143
  ```
125
144
 
126
- `server_port: 4242` is the *preferred* port. The server will find a free port starting
127
- from this value on startup and write the actual port back to this file. You do not
128
- need to find a free port manually.
145
+ `server_port: 4242` is the *preferred* starting port, not the guaranteed port.
146
+ When the server starts in Step 7, it probes from this value upward until it
147
+ finds a free port, then writes the **actual** port back to this same file.
148
+ If multiple project brains run at once, each gets a distinct port (4242, 4243,
149
+ 4244, ...). Always re-read `_meta/config.json` after the server starts to get
150
+ the real port — never hardcode `4242` in downstream calls.
129
151
 
130
152
  ### Step 6: Initialize the event log
131
153
 
@@ -140,7 +162,22 @@ The server will pick a free port and write it back to `_meta/config.json`.
140
162
  npx wicked-brain-server --brain {brain_path} &
141
163
  ```
142
164
 
143
- Wait for the health check to confirm it's up before continuing.
165
+ Do NOT pass `--port` unless the user specifies one let the server pick a
166
+ free port. After the process starts, **re-read `{brain_path}/_meta/config.json`**
167
+ to get the actual `server_port` the server bound to. Use that port for the
168
+ health check and all subsequent API calls.
169
+
170
+ Then health-check to confirm it's up before continuing:
171
+
172
+ ```bash
173
+ curl -s -X POST http://localhost:{actual_port}/api \
174
+ -H "Content-Type: application/json" \
175
+ -d '{"action":"health"}'
176
+ ```
177
+
178
+ Verify the response includes `"brain_id"` matching this brain's id — this
179
+ confirms you're talking to the right server (not an unrelated brain on the
180
+ same machine).
144
181
 
145
182
  ### Step 8: Ingest the project
146
183
 
@@ -53,12 +53,14 @@ Read `{brain_path}/_meta/config.json`. Look for a `source_path` key:
53
53
 
54
54
  ```json
55
55
  {
56
- "brain_path": "/Users/me/.wicked-brain",
57
- "server_port": 4243,
56
+ "brain_path": "/Users/me/.wicked-brain/projects/my-project",
57
+ "server_port": 4242,
58
58
  "source_path": "/Users/me/Projects/my-project"
59
59
  }
60
60
  ```
61
61
 
62
+ (`server_port` is the preferred starting port — the server writes the actual bound port back to this field on startup.)
63
+
62
64
  If `source_path` is **present** — LSP is configured. Proceed with calls.
63
65
 
64
66
  If `source_path` is **missing** — LSP will fail. Fix it before continuing.
@@ -108,6 +110,78 @@ Symptoms that indicate missing or wrong `source_path`:
108
110
 
109
111
  Check `source_path` in config. If it points at the brain directory (e.g., `~/.wicked-brain/...`) instead of the source project, that is wrong — the brain dir has no `tsconfig.json` or language config. Set it to the project root and restart.
110
112
 
113
+ ## Warming LSP (important — read this before calling lsp-workspace-symbols)
114
+
115
+ **Language servers start lazily.** The brain server does NOT spawn language
116
+ servers on startup — they only spawn when a file-specific LSP action runs
117
+ against a file in `source_path` (`lsp-symbols`, `lsp-definition`, `lsp-hover`,
118
+ `lsp-references`, `lsp-implementation`, `lsp-call-hierarchy-in`,
119
+ `lsp-call-hierarchy-out`).
120
+
121
+ `lsp-workspace-symbols` and `lsp-health` do NOT trigger server spawn. They
122
+ only report on servers that are already running. If you call
123
+ `lsp-workspace-symbols` on a fresh brain server, you will get:
124
+
125
+ ```json
126
+ {"symbols":[],"error":"no_running_server"}
127
+ ```
128
+
129
+ This does NOT mean "no symbols exist in the project." It means "no language
130
+ server has been spawned yet." Do not report empty results to the user as
131
+ authoritative when you see this error code.
132
+
133
+ ### Warm-up procedure
134
+
135
+ Before calling `lsp-workspace-symbols` for the first time on any brain server:
136
+
137
+ 1. Pick a real source file in `source_path`. Prefer a main entry file — e.g.,
138
+ for TypeScript: `src/index.ts`, `src/main.ts`, or whichever file exists.
139
+ Use Glob against `{source_path}` to find one if unsure. Do NOT pass a file
140
+ that does not exist — the warm-up will silently fail.
141
+
142
+ 2. Call a file-specific action to trigger `ensureReady`:
143
+ ```bash
144
+ curl -s -X POST http://localhost:{port}/api \
145
+ -H "Content-Type: application/json" \
146
+ -d '{"action":"lsp-symbols","params":{"file":"{absolute_path_to_real_file}"}}'
147
+ ```
148
+
149
+ 3. Poll `lsp-health` until a server is `ready` (not `starting`):
150
+ ```bash
151
+ curl -s -X POST http://localhost:{port}/api \
152
+ -H "Content-Type: application/json" \
153
+ -d '{"action":"lsp-health"}'
154
+ ```
155
+ Expected ready response:
156
+ ```json
157
+ {"servers":{"typescript":{"status":"ready","uptime":1234}}}
158
+ ```
159
+ Retry up to 10 times with 500ms between attempts (5 seconds total). Large
160
+ projects can take several seconds to finish indexing.
161
+
162
+ **If 10 retries are exhausted without a `ready` state**, do NOT treat this
163
+ as success-with-empty-results. Check:
164
+ - `lsp-health` response — is the server in `starting` state? Extend the
165
+ poll to 30 retries (15 seconds) for very large projects.
166
+ - Is the server in `error` state? Read the `message` field and surface it
167
+ to the user. Common causes: missing `tsconfig.json` (wrong `source_path`),
168
+ language server binary not installed, or a crash loop (see
169
+ `language_server_crashed` in Step 4 below).
170
+ - Is the `servers` object still empty? The warm-up file action likely
171
+ failed silently — verify the file path exists and is inside `source_path`.
172
+
173
+ 4. Now `lsp-workspace-symbols` will return real results.
174
+
175
+ ### Interpreting `lsp-health` states
176
+
177
+ | State | Meaning | Action |
178
+ |-------|---------|--------|
179
+ | empty `servers` object | No language server spawned yet | Follow warm-up procedure above |
180
+ | `status: "starting"` | Server process spawned, still indexing | Wait and retry |
181
+ | `status: "ready"` | Ready to serve queries | Proceed |
182
+ | `status: "crashed"` | Crashed ≤3 times | Check diagnostics for the language server's stderr |
183
+ | `status: "error"` with `No Project` message | Wrong `source_path` | See source_path prerequisites above |
184
+
111
185
  ## When to Use
112
186
 
113
187
  | You want to... | Action | Example |
@@ -0,0 +1,268 @@
1
+ ---
2
+ name: wicked-brain:migrate
3
+ description: |
4
+ Migrate a flat brain at ~/.wicked-brain/ into the per-project layout at
5
+ ~/.wicked-brain/projects/{name}/. Safe to run on already-migrated brains
6
+ (no-op). Auto-invoked by wicked-brain:init when a flat brain is detected.
7
+
8
+ Use when: "migrate my brain", "move brain to per-project layout",
9
+ "I have an old ~/.wicked-brain brain", or when init detects a flat brain.
10
+ ---
11
+
12
+ # wicked-brain:migrate
13
+
14
+ You migrate a flat brain (all data directly under `~/.wicked-brain/`) into the
15
+ per-project layout (each brain under `~/.wicked-brain/projects/{project-name}/`).
16
+ This was the layout change introduced in v0.4.7.
17
+
18
+ ## Cross-Platform Notes
19
+
20
+ Commands in this skill work on macOS, Linux, and Windows. Prefer your native
21
+ Read/Write/Glob tools over shell commands when possible.
22
+
23
+ - macOS/Linux home: `~`
24
+ - Windows home: `%USERPROFILE%`
25
+
26
+ ## When to use
27
+
28
+ - User explicitly asks to migrate
29
+ - `wicked-brain:init` detected a flat brain and invoked this skill
30
+ - Another skill hit errors caused by the flat layout (e.g. port collisions
31
+ with a second project, meta-brain federation not working)
32
+
33
+ ## Parameters
34
+
35
+ - **flat_path** (optional): path to the existing flat brain. Default: `~/.wicked-brain`
36
+ - **project_name** (optional): target project name. Default: asked interactively
37
+
38
+ ## Multiple flat brains
39
+
40
+ This skill migrates **one** flat brain per invocation. If the user has several
41
+ flat brains in different locations (e.g. `~/.wicked-brain`, `~/work-brain`,
42
+ `~/.config/wicked-brain`), run `wicked-brain:migrate` once per source, passing
43
+ a different `flat_path` each time. Each becomes its own project brain — either
44
+ under the same `~/.wicked-brain/projects/` umbrella (recommended — pick
45
+ distinct project names) or under separate containers if the user prefers
46
+ isolation.
47
+
48
+ If the user asks "migrate all my brains," enumerate the flat brains you can
49
+ find first (`find ~ -maxdepth 3 -name brain.json 2>/dev/null` on macOS/Linux)
50
+ and confirm each with the user before running migration on it.
51
+
52
+ ## Process
53
+
54
+ ### Step 1: Detect what we're dealing with
55
+
56
+ Read `{flat_path}/brain.json` — if it does not exist, there is no flat brain
57
+ to migrate. Tell the user and stop.
58
+
59
+ Read `{flat_path}/brain.json` to get the brain's `id` and `name`. These become
60
+ the defaults for the target project.
61
+
62
+ Check whether `{flat_path}/projects/` already exists:
63
+
64
+ - **If `{flat_path}` looks like a pure container** (contains only a `projects/`
65
+ subdirectory and optionally `_meta/`) — this brain is already migrated.
66
+ Report "Already using per-project layout" and stop.
67
+
68
+ - **If `{flat_path}/brain.json` exists at the root AND `{flat_path}/projects/`
69
+ also exists** — mixed state. This is a real migration case. Continue.
70
+
71
+ - **If `{flat_path}/brain.json` exists and no `projects/` subdir** — standard
72
+ flat layout. Continue.
73
+
74
+ ### Step 2: Confirm target with user
75
+
76
+ Ask the user:
77
+
78
+ 1. "What should this project brain be called?" — Default: the `id` from
79
+ `{flat_path}/brain.json`, or basename of the current working directory.
80
+ 2. "Target path?" — Default: `{flat_path}/projects/{project_name}`
81
+
82
+ If the target directory already exists AND is non-empty, stop and tell the
83
+ user: "A brain already exists at the target path. Pick a different name or
84
+ remove the existing brain manually."
85
+
86
+ ### Step 3: Stop any running server on the flat brain
87
+
88
+ **Critical on Windows** — SQLite's `.brain.db` may be locked by a running server
89
+ process. Moving a locked file silently corrupts the migration.
90
+
91
+ 1. Read `{flat_path}/_meta/server.pid` if it exists.
92
+ 2. Check if the process is alive:
93
+ - macOS/Linux: `kill -0 {pid} 2>/dev/null`
94
+ - Windows: `tasklist /FI "PID eq {pid}" 2>nul | findstr {pid}`
95
+ 3. If alive, stop it:
96
+ - macOS/Linux: `kill {pid}`
97
+ - Windows PowerShell: `Stop-Process -Id {pid}`
98
+ 4. Wait 2 seconds for file handles to release.
99
+ 5. Delete the stale PID file.
100
+
101
+ Also look for any other wicked-brain-server processes targeting this flat path
102
+ and stop them. On macOS/Linux: `pgrep -f "wicked-brain-server.*{flat_path}"`.
103
+
104
+ ### Step 4: Create the target directory structure
105
+
106
+ ```bash
107
+ # macOS/Linux
108
+ mkdir -p {target_path}/_meta
109
+ ```
110
+ ```powershell
111
+ # Windows
112
+ New-Item -ItemType Directory -Force -Path "{target_path}\_meta"
113
+ ```
114
+
115
+ Do NOT pre-create `raw/`, `chunks/`, `wiki/`, `memory/` — they'll be moved
116
+ over in Step 5.
117
+
118
+ ### Step 5: Move data from flat to target
119
+
120
+ Move each of these directories/files from `{flat_path}` to `{target_path}`
121
+ (skip any that don't exist in the source):
122
+
123
+ - `brain.json`
124
+ - `raw/`
125
+ - `chunks/`
126
+ - `wiki/`
127
+ - `memory/`
128
+ - `.brain.db`
129
+ - `.brain.db-shm` (SQLite WAL shared memory, may not exist)
130
+ - `.brain.db-wal` (SQLite WAL log, may not exist)
131
+
132
+ macOS/Linux:
133
+ ```bash
134
+ for item in brain.json raw chunks wiki memory .brain.db .brain.db-shm .brain.db-wal; do
135
+ if [ -e "{flat_path}/$item" ]; then
136
+ mv "{flat_path}/$item" "{target_path}/$item"
137
+ echo "moved $item"
138
+ fi
139
+ done
140
+ ```
141
+
142
+ Windows PowerShell:
143
+ ```powershell
144
+ $items = "brain.json","raw","chunks","wiki","memory",".brain.db",".brain.db-shm",".brain.db-wal"
145
+ foreach ($item in $items) {
146
+ $src = Join-Path "{flat_path}" $item
147
+ if (Test-Path $src) {
148
+ Move-Item $src (Join-Path "{target_path}" $item)
149
+ Write-Host "moved $item"
150
+ }
151
+ }
152
+ ```
153
+
154
+ **Do NOT move `_meta/`.** The flat brain's `_meta/config.json` references the
155
+ flat path and is about to be replaced. We'll write a fresh config in Step 6.
156
+
157
+ If the move fails partway through, stop immediately. Partial migrations leave
158
+ the brain in an unrecoverable state — tell the user what moved and what didn't,
159
+ and ask them to resolve manually before retrying.
160
+
161
+ ### Step 6: Write the target's `_meta/config.json`
162
+
163
+ ```json
164
+ {
165
+ "brain_path": "{target_path}",
166
+ "server_port": 4242,
167
+ "installed_clis": []
168
+ }
169
+ ```
170
+
171
+ `server_port: 4242` is the *preferred starting port*, not the guaranteed port.
172
+ When the server starts in Step 7 it probes from this value upward and writes
173
+ the actual bound port back to this same file. After Step 7, always re-read
174
+ `_meta/config.json` to get the real port — never hardcode `4242` in downstream
175
+ calls. This matters especially when migrating while another brain is already
176
+ running on 4242.
177
+
178
+ If the flat brain's `_meta/config.json` had a `source_path` field, copy it over
179
+ to the new config — the LSP workspace root must follow the brain.
180
+
181
+ Initialize the event log:
182
+ - macOS/Linux: `touch {target_path}/_meta/log.jsonl`
183
+ - Windows: `New-Item -ItemType File -Force -Path "{target_path}\_meta\log.jsonl"`
184
+
185
+ ### Step 7: Start the server against the new path (verification before cleanup)
186
+
187
+ **Ordering matters.** Do not delete the flat `_meta/` yet — if the new server
188
+ fails to start, you need to be able to restore. Start and verify the new
189
+ server first; only clean up the flat path after verification succeeds.
190
+
191
+ Do NOT pass `--port` — let the server pick a free port. It will write the
192
+ actual port back to `{target_path}/_meta/config.json`.
193
+
194
+ ```bash
195
+ npx wicked-brain-server --brain "{target_path}" &
196
+ ```
197
+
198
+ Wait for the server to start, then re-read `{target_path}/_meta/config.json`
199
+ to get the bound port.
200
+
201
+ Health-check:
202
+ ```bash
203
+ curl -s -X POST http://localhost:{port}/api \
204
+ -H "Content-Type: application/json" \
205
+ -d '{"action":"health"}'
206
+ ```
207
+
208
+ Verify the response includes `"brain_id"` matching the id from Step 1. If the
209
+ brain_id is different, the wrong server is responding — likely a stale process
210
+ on that port. Stop and diagnose before proceeding.
211
+
212
+ ### Step 8: Verify the index still works
213
+
214
+ Run a stats call against the new server and confirm the document count is
215
+ non-zero (assuming the flat brain had any documents):
216
+
217
+ ```bash
218
+ curl -s -X POST http://localhost:{port}/api \
219
+ -H "Content-Type: application/json" \
220
+ -d '{"action":"stats"}'
221
+ ```
222
+
223
+ If document counts are zero but `.brain.db` was moved, the SQLite file may have
224
+ been truncated during the move or the server is looking at the wrong path.
225
+ **Do NOT proceed to Step 9.** Stop the new server and investigate — the flat
226
+ `_meta/` is still intact at this point, so you can roll back by moving files
227
+ back to `{flat_path}`.
228
+
229
+ ### Step 9: Clean up the flat path (only after Steps 7 and 8 pass)
230
+
231
+ Do NOT run this step unless the health check AND stats check both succeeded.
232
+ This step is irreversible.
233
+
234
+ What's left at `{flat_path}` should now be:
235
+
236
+ - `_meta/` directory (old config, log, stale PID)
237
+ - Maybe a `projects/` subdirectory (if it existed before)
238
+
239
+ Verify this is the case before cleanup. If other files remain at the flat
240
+ path, stop and report them — they were not part of a standard flat brain and
241
+ the user needs to decide what to do with them.
242
+
243
+ Delete the flat `_meta/` directory:
244
+ - macOS/Linux: `rm -rf {flat_path}/_meta`
245
+ - Windows: `Remove-Item -Recurse -Force "{flat_path}\_meta"`
246
+
247
+ The flat path is now a pure container with only `projects/` beneath it.
248
+
249
+ ### Step 10: Report
250
+
251
+ Tell the user:
252
+
253
+ "Migrated brain `{name}` from `{flat_path}` to `{target_path}`.
254
+ - {N} documents preserved
255
+ - Server running on port {port}
256
+ - `source_path`: {source_path or 'not set'}
257
+
258
+ The flat path `{flat_path}` is now a container for per-project brains. You can
259
+ create additional project brains under `{flat_path}/projects/` with `wicked-brain:init`."
260
+
261
+ ## Rollback
262
+
263
+ If anything goes wrong before Step 9 (cleanup), the migration can be rolled
264
+ back by moving items back from `{target_path}` to `{flat_path}`. The flat
265
+ `_meta/` is still intact through Steps 1-8.
266
+
267
+ After Step 9 there is no clean rollback — the flat `_meta/` has been deleted.
268
+ Never run Step 9 until Steps 7 and 8 both pass.
@@ -0,0 +1,90 @@
1
+ ---
2
+ name: wicked-brain:review
3
+ description: |
4
+ Browse stored memories with filters on type, tier, and recency. Read-only —
5
+ use wicked-brain:forget to archive or wicked-brain:agent dispatch consolidate
6
+ to promote.
7
+
8
+ Use when: "review my memories", "browse decisions", "what have I stored",
9
+ "list recent gotchas", "brain review".
10
+ ---
11
+
12
+ # wicked-brain:review
13
+
14
+ Filtered browse over the memory store. Combines the server `memory_stats` and
15
+ `recent_memories` actions with agent-side frontmatter filtering to render a
16
+ compact, navigable list.
17
+
18
+ ## Cross-Platform Notes
19
+
20
+ - Uses `curl` for server API calls (Windows 10+, macOS, Linux)
21
+ - File reads use the agent-native Read tool
22
+ - Paths always use forward slashes
23
+
24
+ ## Config
25
+
26
+ Read `_meta/config.json` for brain path and server port.
27
+ If it doesn't exist, trigger wicked-brain:init.
28
+
29
+ ## Parameters
30
+
31
+ - **filter_type** (optional): `decision`, `pattern`, `preference`, `gotcha`, or `discovery`
32
+ - **filter_tier** (optional): `working`, `episodic`, or `semantic`
33
+ - **days** (optional, default 30): only include memories indexed within this many days
34
+ - **limit** (optional, default 20): max results
35
+ - **depth** (optional, default 0): 0=frontmatter only, 1=+summary line, 2=full content
36
+
37
+ ## Process
38
+
39
+ ### Step 1: Fetch breakdown
40
+
41
+ Start with the aggregate view so the user can see the landscape before the list:
42
+
43
+ ```bash
44
+ curl -s -X POST http://localhost:{port}/api \
45
+ -H "Content-Type: application/json" \
46
+ -d '{"action":"memory_stats"}'
47
+ ```
48
+
49
+ Render `total`, `by_type`, `by_tier`, `by_age` as a one-line header.
50
+
51
+ ### Step 2: Fetch candidates
52
+
53
+ ```bash
54
+ curl -s -X POST http://localhost:{port}/api \
55
+ -H "Content-Type: application/json" \
56
+ -d '{"action":"recent_memories","params":{"days":{days},"limit":{limit * 3}}}'
57
+ ```
58
+
59
+ Over-fetch by 3x so agent-side type/tier filtering still returns a useful page.
60
+
61
+ ### Step 3: Filter
62
+
63
+ For each returned memory, parse its frontmatter. Drop any memory whose `type`
64
+ or `tier` does not match `filter_type` / `filter_tier` when those parameters
65
+ are set. Stop once `limit` matches are collected.
66
+
67
+ ### Step 4: Render
68
+
69
+ For each matching memory, render at the requested depth:
70
+
71
+ - **Depth 0**: `{path} — type={type} tier={tier} importance={importance} age={age}`
72
+ - **Depth 1**: depth 0 line + first 3 lines of content
73
+ - **Depth 2**: depth 0 line + full content
74
+
75
+ Age is derived from `indexed_at` relative to now (`Xd` / `Xh`).
76
+
77
+ ### Step 5: Suggest next actions
78
+
79
+ After the list, suggest one of:
80
+ - `wicked-brain:forget path=…` to archive a specific entry
81
+ - `wicked-brain:agent dispatch consolidate` to promote patterns and drop expired entries
82
+ - `wicked-brain:retag` if many entries have thin `contains:` arrays
83
+
84
+ ## Notes
85
+
86
+ - Review is read-only. It never mutates the index or files.
87
+ - For semantic search (content keywords) use `wicked-brain:search` or the
88
+ `recall` mode of `wicked-brain:memory` — review is for browsing by metadata.
89
+ - If no filters are given and `days=30`, this is effectively "show me what I
90
+ have been remembering lately, grouped by type and tier".
@@ -30,18 +30,40 @@ For the brain path default:
30
30
 
31
31
  ## Process
32
32
 
33
- ### Step 1: Check current version
33
+ ### Step 1: Check current installed version
34
34
 
35
- Read the installed server version:
36
- ```bash
37
- wicked-brain-server --version 2>/dev/null || npx wicked-brain-server --version 2>/dev/null || echo "not installed"
38
- ```
35
+ The `wicked-brain-server` binary lives inside the globally installed `wicked-brain` npm package. Read its version directly from the installed package:
39
36
 
40
- If that doesn't work, check the package directly:
41
37
  ```bash
42
- npm list -g wicked-brain-server --json 2>/dev/null | grep version
38
+ npm list -g wicked-brain --json 2>/dev/null | python3 -c "
39
+ import json, sys
40
+ try:
41
+ d = json.load(sys.stdin)
42
+ deps = d.get('dependencies', {})
43
+ v = deps.get('wicked-brain', {}).get('version', 'not installed')
44
+ print(v)
45
+ except Exception:
46
+ print('not installed')
47
+ " 2>/dev/null || npm list -g wicked-brain --json 2>/dev/null | python -c "
48
+ import json, sys
49
+ try:
50
+ d = json.load(sys.stdin)
51
+ deps = d.get('dependencies', {})
52
+ v = deps.get('wicked-brain', {}).get('version', 'not installed')
53
+ print(v)
54
+ except Exception:
55
+ print('not installed')
56
+ "
43
57
  ```
44
58
 
59
+ If the result is `not installed`, the package was never globally installed (the
60
+ user may have been running via `npx` only). Treat this as "needs install" and
61
+ proceed to Step 4.
62
+
63
+ **Do NOT use `npx wicked-brain-server --version`** — it may return the version
64
+ of a stale cached npx copy, not the globally installed one that actually runs
65
+ when brain servers start.
66
+
45
67
  ### Step 2: Check latest version on npm
46
68
 
47
69
  ```bash
@@ -58,15 +80,45 @@ If an update is available, ask the user:
58
80
 
59
81
  ### Step 4: Update (if user approves)
60
82
 
61
- #### Update everything (skills + server)
83
+ **Critical:** `npx wicked-brain@latest` only runs the *installer* — it refreshes
84
+ the skill markdown files in your CLI's skills directory, but it does NOT update
85
+ the globally installed `wicked-brain-server` binary. The skills will then expect
86
+ features that the old server doesn't have, producing confusing errors.
62
87
 
63
- The server is bundled in the main package — one command updates both:
88
+ Use `npm install -g` to update the actual binary:
64
89
 
65
90
  ```bash
66
- npx wicked-brain@latest
91
+ npm install -g wicked-brain@latest 2>&1
67
92
  ```
68
93
 
69
- This re-runs the installer with the latest version, updating all skills across detected CLIs. The server binary updates automatically since it's part of the same package.
94
+ On Windows PowerShell (no change needed):
95
+ ```powershell
96
+ npm install -g wicked-brain@latest
97
+ ```
98
+
99
+ If this fails with `EACCES` / permission denied:
100
+ - macOS/Linux: `sudo npm install -g wicked-brain@latest`
101
+ - Windows: re-run the shell as Administrator, or fix npm's global prefix per
102
+ npm docs. Do NOT silently skip — report the failure to the user and stop.
103
+
104
+ After a successful `npm install -g`, also run the installer to refresh skill
105
+ files in all detected CLIs (skills are copied from the installed package, not
106
+ downloaded separately):
107
+
108
+ ```bash
109
+ npx wicked-brain
110
+ ```
111
+
112
+ ### Step 4a: Verify the update landed
113
+
114
+ Re-run the Step 1 version check. The version reported MUST match the latest
115
+ version from Step 2. If it still shows the old version:
116
+
117
+ 1. Check `which wicked-brain-server` (macOS/Linux) or `where wicked-brain-server` (Windows) — the shell may have cached a path to a different installation.
118
+ 2. Clear npm's global cache: `npm cache clean --force`
119
+ 3. Check if a different Node.js version (nvm, fnm, volta) is pinning a stale copy.
120
+
121
+ Do NOT proceed to Step 5 until version verification succeeds. Reporting a successful update while the binary is stale is the top failure mode of this skill.
70
122
 
71
123
  ### Step 5: Restart server if running
72
124