hmem-mcp 6.3.0 → 6.3.2

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.
@@ -0,0 +1,105 @@
1
+ /**
2
+ * hmem OpenCode plugin
3
+ *
4
+ * Bridges OpenCode's plugin event system into hmem's CLI hook commands.
5
+ * Drop this file into ~/.config/opencode/plugins/ (global) or
6
+ * .opencode/plugins/ (project-local) and OpenCode will auto-load it.
7
+ *
8
+ * Mapping:
9
+ * chat.message → capture user prompt + write session marker
10
+ * event: message.part.updated → accumulate assistant text per session
11
+ * event: session.idle → spawn `hmem log-exchange` + `hmem checkpoint`
12
+ * experimental.session.compacting → inject hmem context into compaction prompt
13
+ *
14
+ * Requires `hmem` CLI on PATH and HMEM_PATH env var (auto-detected if unset).
15
+ */
16
+
17
+ import { spawn, spawnSync } from "node:child_process";
18
+
19
+ function spawnHmem(args, stdinJson) {
20
+ try {
21
+ const child = spawn("hmem", args, {
22
+ detached: true,
23
+ stdio: ["pipe", "ignore", "ignore"],
24
+ env: process.env,
25
+ });
26
+ if (stdinJson) {
27
+ child.stdin.end(stdinJson);
28
+ } else {
29
+ child.stdin.end();
30
+ }
31
+ child.unref();
32
+ } catch {
33
+ // hmem not installed or not on PATH — silently no-op
34
+ }
35
+ }
36
+
37
+ function partsToText(parts) {
38
+ if (!Array.isArray(parts)) return "";
39
+ return parts
40
+ .filter(p => p && p.type === "text" && typeof p.text === "string")
41
+ .map(p => p.text)
42
+ .join("\n")
43
+ .trim();
44
+ }
45
+
46
+ export default {
47
+ id: "hmem",
48
+ server: async () => {
49
+ const userMessages = new Map();
50
+ const assistantBuffers = new Map();
51
+
52
+ return {
53
+ "chat.message": async ({ sessionID }, { parts }) => {
54
+ const text = partsToText(parts);
55
+ if (text) userMessages.set(sessionID, text);
56
+ assistantBuffers.set(sessionID, "");
57
+ spawnHmem(["hook-startup"], JSON.stringify({ session_id: sessionID }));
58
+ },
59
+
60
+ event: async ({ event }) => {
61
+ if (!event || typeof event !== "object") return;
62
+
63
+ if (event.type === "message.part.updated") {
64
+ const part = event.properties?.part;
65
+ if (!part || part.type !== "text" || typeof part.text !== "string") return;
66
+ const sid = part.sessionID;
67
+ if (!sid) return;
68
+ assistantBuffers.set(sid, part.text);
69
+ return;
70
+ }
71
+
72
+ if (event.type === "session.idle") {
73
+ const sid = event.properties?.sessionID;
74
+ if (!sid) return;
75
+ const userMessage = userMessages.get(sid);
76
+ const assistantMessage = assistantBuffers.get(sid);
77
+ if (!userMessage || !assistantMessage) return;
78
+ userMessages.delete(sid);
79
+ assistantBuffers.delete(sid);
80
+ spawnHmem(["log-exchange"], JSON.stringify({
81
+ session_id: sid,
82
+ last_user_message: userMessage,
83
+ last_assistant_message: assistantMessage,
84
+ }));
85
+ spawnHmem(["checkpoint"], JSON.stringify({ session_id: sid }));
86
+ }
87
+ },
88
+
89
+ "experimental.session.compacting": async ({ sessionID }, output) => {
90
+ try {
91
+ const result = spawnSync("hmem", ["context-inject"], {
92
+ input: JSON.stringify({ session_id: sessionID }),
93
+ encoding: "utf8",
94
+ timeout: 5000,
95
+ env: process.env,
96
+ });
97
+ const text = (result.stdout || "").trim();
98
+ if (text) output.context.push(text);
99
+ } catch {
100
+ // hmem not available — skip
101
+ }
102
+ },
103
+ };
104
+ },
105
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hmem-mcp",
3
- "version": "6.3.0",
3
+ "version": "6.3.2",
4
4
  "description": "Humanlike memory for AI agents — MCP server with 5-level lazy-loaded SQLite memory",
5
5
  "author": "Bumblebiber",
6
6
  "license": "MIT",
@@ -32,6 +32,7 @@
32
32
  "dist",
33
33
  "skills",
34
34
  "scripts",
35
+ "opencode-plugin",
35
36
  "prebuilds",
36
37
  "server.json",
37
38
  "README.md",
@@ -183,3 +183,80 @@ npx hmem-sync connect
183
183
  | 401 Token verification failed | Passphrase has special chars — set `HMEM_SYNC_PASSPHRASE` in .mcp.json env |
184
184
  | 0 entries after pull | `HMEM_PATH` filename must match between devices |
185
185
  | Update | `npm update -g hmem-sync` (always global, never inside a project) |
186
+
187
+ ## Hook configuration on Windows (REQUIRED)
188
+
189
+ On Windows, hook execution is fragile out of the box. Two issues bite every new user:
190
+
191
+ **1. Git Bash routing** — On systems with Git for Windows installed, Claude Code may route hook commands through Git Bash (`bash.exe`). Its MSYS2 runtime crashes transiently with `add_item ("\??\C:\Program Files\Git", "/", ...) failed, errno 1` during cygheap init, killing the hook **before the command is even parsed**. Symptom: `UserPromptSubmit hook error` or `Stop hook error` with a bash.exe stacktrace.
192
+
193
+ **2. Unix inline env-var syntax** — Commands like `HMEM_PATH=C:/... node ...` work in bash but break in cmd.exe and PowerShell. Symptom: `"HMEM_PATH" is not recognized as a command`.
194
+
195
+ **The fix for Windows users (apply to every hook + statusLine):**
196
+
197
+ ```json
198
+ {
199
+ "env": {
200
+ "HMEM_PATH": "C:/Users/<you>/.hmem/Agents/<AGENT>/<AGENT>.hmem"
201
+ },
202
+ "hooks": {
203
+ "UserPromptSubmit": [
204
+ {
205
+ "matcher": "",
206
+ "hooks": [
207
+ {
208
+ "type": "command",
209
+ "command": "node C:/Users/<you>/AppData/Roaming/npm/node_modules/hmem-mcp/dist/cli.js hook-startup",
210
+ "shell": "powershell"
211
+ }
212
+ ]
213
+ }
214
+ ],
215
+ "Stop": [
216
+ {
217
+ "matcher": "",
218
+ "hooks": [
219
+ {
220
+ "type": "command",
221
+ "command": "node C:/Users/<you>/AppData/Roaming/npm/node_modules/hmem-mcp/dist/cli.js log-exchange",
222
+ "shell": "powershell"
223
+ }
224
+ ]
225
+ }
226
+ ],
227
+ "SessionStart": [
228
+ {
229
+ "matcher": "clear",
230
+ "hooks": [
231
+ {
232
+ "type": "command",
233
+ "command": "node C:/Users/<you>/AppData/Roaming/npm/node_modules/hmem-mcp/dist/cli.js context-inject",
234
+ "shell": "powershell"
235
+ }
236
+ ]
237
+ }
238
+ ]
239
+ },
240
+ "statusLine": {
241
+ "type": "command",
242
+ "command": "node C:/Users/<you>/AppData/Roaming/npm/node_modules/hmem-mcp/dist/cli.js statusline",
243
+ "shell": "powershell"
244
+ }
245
+ }
246
+ ```
247
+
248
+ Two things to notice:
249
+ - `"shell": "powershell"` on **every** hook command and on statusLine — forces native PowerShell, bypasses Git Bash entirely.
250
+ - `HMEM_PATH` lives in the top-level `env` block, **not** inline in the command. Claude Code inherits the env block to every hook subprocess, regardless of shell.
251
+
252
+ **Never use inline env-var syntax in hook commands on Windows.** `VAR=value command` is bash-only syntax and will silently break under cmd.exe or PowerShell.
253
+
254
+ **Troubleshooting matrix:**
255
+
256
+ | Symptom | Cause | Fix |
257
+ |---------|-------|-----|
258
+ | `UserPromptSubmit hook error` (no stacktrace) | Inline `VAR=value` in command + cmd.exe parses it as a command name | Move env vars to `env` block, remove inline prefix |
259
+ | `bash.exe: *** fatal error - add_item ... errno 1` | Git Bash MSYS2 runtime crashing at startup | Add `"shell": "powershell"` to every hook command |
260
+ | Hooks silently do nothing (no errors) | Wrong shell interpreting the command, or project not active for session logging | Verify `"shell": "powershell"`, call `load_project(id="P00XX")` every session |
261
+
262
+ **Note on `load_project` per-session:** The `active` flag on a P-entry persists in the database, but the "currently active project for session logging" is a per-session attribute. After every Claude Code restart, the agent must call `load_project(id="P00XX")` again, or exchanges will be logged to O0000 (no-project fallback) instead of the project's O-entry. Consider adding this to your project briefing or session-start routine.
@@ -1,206 +1,213 @@
1
1
  ---
2
2
  name: hmem-curate
3
- description: >
4
- Memory curation workflow. Use when asked to curate, audit, or clean up agent memories.
5
- Processes one agent at a time — read, fix, mark audited, summarize, terminate.
6
- Requires role: ceo.
3
+ description: Curate hmem memory — your own or a foreign .hmem file. Systematically review entries, mark obsolete/irrelevant/favorite, fix titles, consolidate duplicates. Use when asked to "aufräumen", "clean up memory", "curate", "Speicher bereinigen", "tidy up", or when memory_health() shows issues.
7
4
  ---
8
5
 
9
- # /hmem-curate — hmem Curation Workflow
6
+ # hmem Curation
10
7
 
11
- You are the memory curator. Process **one agent per run**, then terminate.
8
+ Curate hmem memory mark obsolete/irrelevant/favorite, fix titles, consolidate duplicates, fix broken links.
9
+
10
+ **Two modes:**
11
+ - **Self-curation** (default): you curate your own memory. No extra params.
12
+ - **Foreign-file curation**: pass `hmem_path=/absolute/path/to/file.hmem` to `read_memory`, `update_memory`, `memory_health`, `find_related`. Sync and session cache are disabled. All updates land in that file.
12
13
 
13
14
  ---
14
15
 
15
- ## Step-by-Step
16
+ ## Step 0: Health Check First
16
17
 
17
18
  ```
18
- 1. get_audit_queue()
19
- Empty → write a short summary to LAST_CURATION.md and terminate
20
- → Not empty → take the FIRST agent from the list
19
+ memory_health() # own store
20
+ memory_health(hmem_path="...") # foreign file
21
+ ```
22
+
23
+ Shows:
24
+ - **Broken links** — entries with refs to deleted IDs
25
+ - **Orphaned entries** — roots with no sub-nodes (likely draft stubs)
26
+ - **Stale favorites/pinned** — not accessed in >60 days (demote or verify)
27
+ - **Broken obsolete chains** — `[✓ID]` pointing to deleted entries
21
28
 
22
- 2. read_agent_memory(agent_name, depth=5)
23
- → Study all entries carefully
29
+ Useful before starting:
30
+ ```
31
+ memory_stats()
32
+ read_memory(stale_days=60) # stale entries in own store
33
+ read_memory(stale_days=60, hmem_path="...") # same, foreign file
34
+ ```
24
35
 
25
- 3. Fix every issue found (see criteria below)
36
+ ---
26
37
 
27
- 4. mark_audited(agent_name)
38
+ ## Workflow: Prefix by Prefix
28
39
 
29
- 5. Append one line to LAST_CURATION.md:
30
- "- **AGENTNAME**: N entries — [OK | fixed L0003 | marked E0002 obsolete (dup) | consolidated P0004+P0007→P0004]"
40
+ Work one prefix at a time. Load all entries of a prefix with full depth:
31
41
 
32
- 6. Terminate.
33
42
  ```
43
+ read_memory(prefix="P", show_all=true)
44
+ read_memory(prefix="P", show_all=true, hmem_path="...")
45
+ ```
46
+
47
+ `show_all=true` bypasses the bulk-read algorithm and session cache — every entry is expanded with L2+L3 children visible. Review the output directly.
48
+
49
+ **Order:** Start with the prefix with the most entries (usually P), then L, E, D, etc.
50
+
51
+ If context overflows mid-prefix, continue with the remaining entries — memory survives compression.
34
52
 
35
53
  ---
36
54
 
37
- ## Quality Criteria
55
+ ## For Each Entry: Decide and Act
38
56
 
39
- ### L1 Quality
40
- | Check | Rule |
41
- |-------|------|
42
- | Too long | Single concise sentence, ~15–20 tokens. Fix with `fix_agent_memory(agent_name, id, content="shorter")` |
43
- | Too vague | "Fixed a bug" mark obsolete. "SQLite failed due to wrong path in .mcp.json" → keep |
44
- | Factually wrong | Fix content or mark obsolete. |
45
- | Duplicate of another entry | Merge the best content from both into the keeper (see merge workflow below), then mark the weaker entry obsolete. |
57
+ | Decision | Action |
58
+ |----------|--------|
59
+ | Still valid and useful | Skip |
60
+ | Important reference (every session) | `update_memory(id="X", content="...", favorite=true)` |
61
+ | Outdated a better entry exists | Mark obsolete (see below) |
62
+ | Just noise — not wrong, but irrelevant | `update_memory(id="X", content="...", irrelevant=true)` |
63
+ | Title vague or misleading | `update_memory(id="X", content="Better wording")` |
64
+ | Sub-node has valuable reference info | `update_memory(id="X.N", content="...", favorite=true)` |
46
65
 
47
- ### Compound Node IDs
48
- Memory content lives in `memory_nodes` — not in flat `level_2/3` fields.
49
- To fix an L2 or deeper node, use the compound ID: `fix_agent_memory(agent_name, "L0003.2", content="corrected text")`.
50
- To navigate the tree: `read_memory` shows node IDs like `L0003.2`, `L0003.2.1` — use those directly.
66
+ Add `hmem_path="..."` to every call when curating a foreign file.
51
67
 
52
- ### Obsolete entries
53
- Entries marked `[!]` (or `[OBSOLETE]` in curator view) are already hidden from bulk reads — they do not need to be deleted.
54
- Leave them in place. The curator's job is to *mark* entries obsolete, not remove them.
68
+ ---
69
+
70
+ ## Marking Obsolete
71
+
72
+ Obsolete requires a correction reference. Three patterns:
55
73
 
56
- **Curator bypass:** As curator, you can mark entries obsolete **without** the `[✓ID]` correction reference that agents are required to include. Use this for stale entries where no correction exists (e.g., entries about deleted features, abandoned approaches).
74
+ **A: Replacement exists already**
75
+ ```
76
+ update_memory(id="E0023", content="Wrong approach — see [✓E0076]", obsolete=true)
77
+ ```
57
78
 
79
+ **B: No replacement exists yet**
80
+ ```
81
+ write_memory(prefix="L", content="Correct approach is XYZ\n\tDetails...") # -> L0090
82
+ update_memory(id="L0042", content="Superseded — see [✓L0090]", obsolete=true)
58
83
  ```
59
- # Curator can bypass [✓ID] enforcement:
60
- fix_agent_memory(agent_name, id, obsolete=true)
61
84
 
62
- # But prefer including a correction reference when one exists:
63
- fix_agent_memory(agent_name, id, content="Outdated — see [✓E0076]", obsolete=true)
85
+ **C: Just stale, no correction needed**
86
+ ```
87
+ update_memory(id="T0005", content="...", irrelevant=true)
64
88
  ```
65
89
 
66
- ### Merging entries (duplicates and fragmented P entries)
90
+ Foreign file: curator may mark obsolete without `[✓ID]` for stale entries where no correction exists.
67
91
 
68
- **Merge workflow:**
69
- 1. Read both entries fully (`read_memory(id=X)` for each)
70
- 2. Pick the **keeper** (usually the older/more informative one)
71
- 3. Fix the keeper's L1 if needed: `fix_agent_memory(agent_name, keeper_id, content="Broader title")`
72
- 4. Carry over the best content from the entry to be deleted:
73
- - Existing nodes with better wording → `fix_agent_memory(agent_name, "KEEPER.2", content="improved text")`
74
- - Content that only exists in the entry to be deleted → `append_agent_memory(agent_name, keeper_id, content="carried-over detail\n\tsub-detail")`
75
- 5. `fix_agent_memory(agent_name, fragment_id, obsolete=true)` once content is carried over
92
+ ---
76
93
 
77
- **For fragmented P entries** (same project, multiple entries):
78
- - Same workflow. Pick oldest as keeper.
79
- - Goal: one P entry per project, growing over time.
94
+ ## Consolidate Duplicates
80
95
 
81
- *Note: only carry over content with lasting value. Low-value session notes can be dropped.*
96
+ 1. Pick the **keeper** (more complete, usually older)
97
+ 2. Copy unique info: `append_memory(id="P0029", content="Carry-over\n\tDetail")`
98
+ 3. Mark duplicate obsolete: `update_memory(id="P0031", content="Merged into [✓P0029]", obsolete=true)`
82
99
 
83
- ### Links cross-references
100
+ **Fragmented P-entries (same project, multiple entries):** same workflow. One P per project.
101
+
102
+ ---
84
103
 
85
- When two entries have a clear causal or contextual relationship (e.g. a P entry and the L/E entries that resulted from it, or an E entry and the D entry that documents the fix decision), add links at **both** entries so they resolve each other on drill-down:
104
+ ## Links Cross-References
105
+
106
+ When two entries have a clear causal/contextual relationship (e.g. a P and the L/E entries that resulted from it), add links at **both** so drill-down resolves them:
86
107
 
87
108
  ```
88
- fix_agent_memory(agent_name, "P0001", links=["L0023", "E0009"])
89
- fix_agent_memory(agent_name, "L0023", links=["P0001"])
90
- fix_agent_memory(agent_name, "E0009", links=["P0001"])
109
+ update_memory(id="P0001", content="...", links=["L0023", "E0009"])
110
+ update_memory(id="L0023", content="...", links=["P0001"])
91
111
  ```
92
112
 
93
- `read_memory(id=X)` auto-resolves linked entries the agent sees both sides when drilling into either one.
113
+ Don't over-linkonly where navigation benefits.
94
114
 
95
- Don't over-link: only add links where the connection adds real navigational value, not just topical similarity.
115
+ ---
96
116
 
97
- ### Title/Body Quality (v5.1+)
117
+ ## Title/Body Quality
98
118
 
99
- Entries support explicit title/body separation via a blank line (like git commits). During curation:
119
+ Every node has a **title** (short, ~50 chars) and optional **body** (blank line separator). During curation:
100
120
 
101
121
  **Root entries (L1):**
102
- - Check if the auto-extracted title is meaningful. If it's truncated or vague, rewrite with explicit title + body:
122
+ - Auto-title truncated/meaningless? Rewrite with explicit title + body:
103
123
  ```
104
- fix_agent_memory(agent_name, id, content="Clear navigation title\n\nOriginal detailed L1 text that was too long for a title")
124
+ update_memory(id="L0042", content="Clear title\n\nDetailed L1 body that was too long for a title")
105
125
  ```
106
- - Old entries without body separation still work — the title is auto-extracted from `level_1`. Only rewrite if the auto-title is genuinely bad.
107
126
 
108
127
  **Child nodes (L2+):**
109
- - Same principle: if a node has dense content, split into title + body:
128
+ - Dense content? Split into title + body:
110
129
  ```
111
- fix_agent_memory(agent_name, "L0003.2", content="Short node title\n\nDetailed explanation\nspanning multiple lines")
130
+ update_memory(id="L0003.2", content="Short node title\n\nDetailed explanation")
112
131
  ```
113
- - Nodes with short, clear content don't need body separation — leave them as-is.
114
132
 
115
- **When to rewrite old entries:**
116
- - Auto-title is truncated mid-word or meaningless
117
- - Node has >200 chars of content crammed into one line
118
- - Content is valuable but hard to scan in listings (title = full text)
133
+ **Rewrite when:** auto-title is truncated mid-word, node has >200 chars crammed in one line, content is valuable but unscannable.
134
+ **Don't rewrite when:** title is already clear, or entry has low access count and marginal value.
119
135
 
120
- **When NOT to rewrite:**
121
- - Title is already clear and navigable
122
- - Entry has low access count and marginal value (not worth the effort)
136
+ ---
123
137
 
124
- ### P-Entry Standard-Schema (R0009)
138
+ ## P-Entry Standard-Schema (R0009)
125
139
 
126
- All P-entries must follow the standard L2 structure (see R0009):
140
+ P-entries follow the standard L2 structure:
127
141
  `.1 Overview`, `.2 Codebase`, `.3 Usage`, `.4 Context`, `.5 Deployment`, `.6 Bugs`, `.7 Protocol`, `.8 Open tasks`, `.9 Ideas`
128
142
 
129
- During curation, check P-entries against this schema:
130
- - **Missing sections:** Add them using `append_agent_memory(agent_name, "P00XX", content="\tOverview\n\t\tCurrent state: ...")`
131
- - **Wrong order:** Flag and restructure — order is fixed per R0009
132
- - **Empty sections:** OK to omit, but if content exists it must be in the right section
133
- - **L1 body:** Should be a one-line project summary in format `Name | Status | Stack | Description`
134
-
135
- ### O-entry curation (v5.1.2+)
136
-
137
- O-entries may need curation for titles, tags, and summaries:
143
+ Check P-entries during curation:
144
+ - **Missing section:** `append_memory(id="P00XX", content="\tOverview\n\t\tCurrent state: ...")`
145
+ - **Wrong order:** Restructure — order is fixed per R0009
146
+ - **Empty section:** OK to omit, but content must be in the right section if present
147
+ - **L1 body:** One-line project summary: `Name | Status | Stack | Description`
138
148
 
139
- **Titles:**
140
- - Old O-entries may have title "unassigned" or generic titles like "hmem-mcp". Fix with `fix_agent_memory(agent_name, id, content="Descriptive session title")`.
141
- - Good title: "Title/Body Separation design + v5.1.0 release". Bad: "hmem-mcp".
149
+ ---
142
150
 
143
- **Tags:**
144
- - O-entries should have a `#session` tag and optionally topic tags (e.g. `#npm`, `#release`, `#refactor`).
145
- - Add missing tags: `fix_agent_memory(agent_name, id, tags=["#session", "#release"])`
151
+ ## O-Entries (Session Logs)
146
152
 
147
- **Checkpoint summaries:**
148
- - O-entries with many exchanges (>10) should have a `[CP]` checkpoint summary. If missing, write one:
149
- `append_agent_memory(agent_name, "O00XX", content="\t[CP] Factual 3-8 sentence summary of the session")`
150
- Then tag it: the auto-tagger picks up `[CP]` prefixed nodes on the next checkpoint run.
153
+ O-entries accumulate via the Stop hook. They're excluded from bulk reads by default — **leave them alone**. Focus curation time on L, E, D, P.
151
154
 
152
155
  **Special tagged nodes — do not modify:**
153
- - **`#checkpoint-summary`** nodes: Auto-generated `[CP]` summaries. Leave as-is.
154
- - **`#skill-dialog`** exchange nodes: Skill activations filtered from `load_project`. Leave as-is.
156
+ - `#checkpoint-summary` auto-generated `[CP]` summaries
157
+ - `#skill-dialog` skill activation exchanges
155
158
 
156
- ### Stale entries auto-mark obsolete
159
+ **Exception old O-entries with bad titles/missing tags:**
160
+ - `update_memory(id="O0042", content="Descriptive session title")`
161
+ - `update_memory(id="O0042", content="...", tags=["#session", "#release"])`
157
162
 
158
- Entries older than 1 month with `access_count = 0` (no `(Nx accessed)` suffix in curator read) should be marked obsolete automatically.
163
+ ---
159
164
 
160
- ```
161
- fix_agent_memory(agent_name, id, obsolete=true)
162
- ```
165
+ ## Bulk Operations
163
166
 
164
- Exception: unique lessons or error patterns with no equivalent elsewhere — keep even if never accessed.
167
+ For large-scale changes across many entries:
165
168
 
166
- **Note:** The V2 algorithm uses **time-weighted scoring** (`access_count / log2(age_in_days + 2)`) for "most accessed" ranking. This means genuinely stale entries (old + low access) naturally sink — but an old entry that's still frequently accessed stays visible.
169
+ | Tool | Purpose |
170
+ |------|---------|
171
+ | `update_many(updates=[...])` | Batch flag updates across multiple IDs |
172
+ | `tag_bulk(ids=[...], add_tags=[...], remove_tags=[...])` | Add/remove tags across many entries |
173
+ | `tag_rename(old_tag, new_tag)` | Rename a tag globally |
167
174
 
168
- ### N entriesflag stale code pointers
169
- Navigator entries go stale when code moves. Check: does the file/line referenced still exist?
170
- If stale and the agent hasn't updated it: mark obsolete via `fix_agent_memory(agent_name, id, obsolete=true)`.
171
- Do NOT fix stale N entries yourself — the agent who wrote them must verify and update.
175
+ (These operate on your own store only for foreign files, iterate manually with `update_memory(hmem_path=...)`.)
172
176
 
173
177
  ---
174
178
 
175
- ### Quick audit with titles_only
179
+ ## Relocate Misplaced Nodes
176
180
 
177
- Use `read_memory(titles_only=true)` for a compact overview of an agent's memory before drilling in. Shows V2-selected entries as one line each with `(N)` child counts — useful for spotting duplicates, poor titles, or category imbalances at a glance.
181
+ `move_memory` cuts and re-inserts a sub-node under a new parent, rewriting all IDs + links + `[✓ID]` refs.
182
+
183
+ ```
184
+ move_memory(source_id="P0029.15", target_parent_id="L0074")
185
+ move_memory(source_id="P0029.15", target_parent_id="P0029.20")
186
+ ```
187
+
188
+ **Constraints:** source must be a sub-node (not root); cannot move into own subtree. Operates on own store only.
178
189
 
179
190
  ---
180
191
 
181
- ## V2 Bulk-Read Output
192
+ ## Favorite Audit
182
193
 
183
- The default `read_memory()` now returns **grouped output** by prefix category:
194
+ - **Too many?** >10% favorites demote less important: `update_memory(id="X", content="...", favorite=false)`
195
+ - **Missing?** Reference entries (API endpoints, key decisions, patterns) should be favorites.
196
+ - **Sub-node better than root?** Favorite the sub-node instead.
184
197
 
185
- ```
186
- ## Project experiences and summaries (5 entries)
198
+ ---
187
199
 
188
- P0001 02-14 Das Althing — Node.js/TS Multi-Agent-Orchestrator
189
- 2.1 Architecture: Node.js polling daemon with file-based IPC
190
- 2.2 Key decisions: SQLite for hmem, MCP for tool protocol
191
- [+7 more → P0001]
192
- Links: L0045, D0003
200
+ ## Stale Entries
193
201
 
194
- ## Lessons learned and best practices (78 entries)
195
- ...
202
+ Entries older than 1 month with `access_count = 0`: mark obsolete.
196
203
 
197
- --- 5 obsolete entries hidden (E0023, D0007, ...) — top 3 shown above ---
204
+ ```
205
+ update_memory(id="L0042", obsolete=true)
198
206
  ```
199
207
 
200
- **Expanded entries** (newest, most-accessed, favorites) show all L2 children + links.
201
- **Non-expanded entries** show latest child + `[+N more → ID]` hint.
208
+ **Exception:** unique lessons or error patterns with no equivalent keep even if never accessed.
202
209
 
203
- Use `read_memory(show_obsolete=true)` to see all obsolete entries.
210
+ The V2 algorithm uses time-weighted scoring (`access_count / log2(age_in_days + 2)`) old+low-access entries naturally sink; old+high-access stay visible.
204
211
 
205
212
  ---
206
213
 
@@ -208,22 +215,39 @@ Use `read_memory(show_obsolete=true)` to see all obsolete entries.
208
215
 
209
216
  | Store | Max entries | Action when over |
210
217
  |-------|-------------|-----------------|
211
- | Personal | 300 | Triage: duplicates → low-access old entries → generic lessons |
218
+ | Personal | 300 | Triage: duplicates → low-access old → generic lessons |
212
219
  | Company | 200 | Same |
213
220
 
214
- **Triage order (over limit):**
215
- 1. Mark exact duplicates obsolete (after merging content into keeper)
216
- 2. Mark stale entries obsolete (access_count = 0, >1 month old)
217
- 3. Consolidate fragmented P entries
218
- 4. Mark borderline entries obsolete
221
+ **Triage order:** exact duplicates → stale (access=0, >1 month) → fragmented P entries → borderline.
222
+
223
+ ---
224
+
225
+ ## Quick Reference
226
+
227
+ | Tool | When |
228
+ |------|------|
229
+ | `memory_health()` | **Start here** — broken links, orphans, stale favorites |
230
+ | `memory_stats()` | Overview before starting |
231
+ | `read_memory(stale_days=60)` | Prime curation targets |
232
+ | `read_memory(prefix="X", show_all=true)` | Load entire prefix |
233
+ | `update_memory(id, content, favorite=true)` | Always-show reference |
234
+ | `update_memory(id, content, irrelevant=true)` | Hide from bulk reads |
235
+ | `update_memory(id, content, obsolete=true)` | Mark wrong (needs [✓ID]) |
236
+ | `append_memory(id, content)` | Merge info into keeper |
237
+ | `move_memory(source_id, target_parent_id)` | Relocate misplaced sub-node |
238
+ | `update_many(updates=[...])` | Batch flag updates |
239
+ | `tag_bulk / tag_rename` | Tag maintenance |
240
+ | `read_memory(show_obsolete=true)` | Review already-obsolete |
241
+ | `find_related(id)` | Discover connections / spot duplicates |
242
+
243
+ All four read/update/health/find tools accept `hmem_path="..."` for foreign-file curation.
219
244
 
220
245
  ---
221
246
 
222
247
  ## Rules
223
248
 
224
249
  - Never invent or fabricate memories.
225
- - Never add new content only fix, consolidate, or mark obsolete. Never delete.
226
- - Obsolete entries are hidden from bulk reads they don't need to be removed.
227
- - Skip yourself (the curator agent) if you appear in the queue.
228
- - One agent per run be called again for the next agent.
229
- - Always write to LAST_CURATION.md, even for clean runs ("OK — nothing to fix").
250
+ - Prefer fix/consolidate/mark-obsolete over deletion. Obsolete entries are hidden from bulk reads anyway.
251
+ - **When in doubt, skip.** False obsolete/irrelevant is harder to undo than leaving an entry alone.
252
+ - **Preserve learning value.** E (errors) and L (lessons) about *why* something failed stay valuable even after the bug is fixed — only mark obsolete if the analysis is wrong.
253
+ - **One prefix per batch.** Don't try all 200+ entries at once.
@@ -136,7 +136,7 @@ The migration tags old O-entries as `#legacy`. These still use the old flat form
136
136
 
137
137
  1. **Keep for now** — they don't hurt anything, just take space
138
138
  2. **Mark irrelevant** — `update_memory(id="O0042", irrelevant=true)` for entries you don't need
139
- 3. **Delete later** — run `/hmem-self-curate` to review and clean up `#legacy` entries
139
+ 3. **Delete later** — run `/hmem-curate` to review and clean up `#legacy` entries
140
140
  4. **Auto-purge** — irrelevant entries older than 30 days are automatically deleted
141
141
 
142
142
  Recommendation: keep them for a week to make sure everything works, then curate.
@@ -382,7 +382,7 @@ After any `read_memory()` call, scan for:
382
382
  - Don't curate during time-critical tasks (the user is waiting for a bug fix, not curation)
383
383
  - Don't mark entries irrelevant if you're unsure — ask the user first
384
384
 
385
- For a thorough deep-clean, use the `/hmem-self-curate` skill.
385
+ For a thorough deep-clean, use the `/hmem-curate` skill.
386
386
 
387
387
  ---
388
388
 
@@ -47,8 +47,7 @@ Every code change can affect skill documentation. Check each skill against the c
47
47
  | **hmem-read** | read_memory output format, load_project display, O-entry format | Changes to `formatBulkRead`, `formatRecentOEntries`, `load_project` rendering |
48
48
  | **hmem-config** | New config parameters, changed defaults, removed options | Changes to `HmemConfig` interface, `DEFAULT_CONFIG`, `loadHmemConfig` |
49
49
  | **hmem-update** | New migration steps, new post-update checks | Any schema change, new features that need post-update setup |
50
- | **hmem-curate** | Curation rules, new node types, new tags | New tagged node types (#checkpoint-summary, #skill-dialog), schema changes |
51
- | **hmem-self-curate** | Same as curate but for agent self-curation | Same triggers as hmem-curate |
50
+ | **hmem-curate** | Curation rules (self + foreign-file), hmem_path param, new node types, new tags | New tagged node types (#checkpoint-summary, #skill-dialog), schema changes, hmem_path-capable tool changes |
52
51
  | **hmem-new-project** | P-entry schema (R0009), write_memory format | Changes to P-entry structure or write format |
53
52
  | **hmem-setup** | Hook scripts, init flow, MCP config format | Changes to hooks, CLI commands, environment variables |
54
53
  | **hmem-wipe** | Checkpoint references, context threshold | Changes to checkpointMode, contextTokenThreshold |
@@ -131,7 +130,7 @@ git push
131
130
  | Code area | Skills to check |
132
131
  |-----------|----------------|
133
132
  | hmem-store.ts (write/read) | hmem-write, hmem-read, hmem-curate |
134
- | hmem-store.ts (O-entries) | hmem-read, hmem-self-curate, hmem-curate |
133
+ | hmem-store.ts (O-entries) | hmem-read, hmem-curate |
135
134
  | hmem-config.ts | hmem-config, hmem-update, hmem-setup |
136
135
  | mcp-server.ts (tools) | hmem-write, hmem-read (tool params) |
137
136
  | mcp-server.ts (load_project) | hmem-read, hmem-new-project |