@hmanlab/memo 0.5.0 → 0.5.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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Local-first MCP server for persistent, persona-aware memory across projects.
4
4
 
5
- `memo` ships two surfaces: an MCP server (33 tools) for AI clients like
5
+ `memo` ships two surfaces: an MCP server (35 tools) for AI clients like
6
6
  Claude Code, and a Node CLI (`hmanlab-memory`) for power users. Both
7
7
  share the same backend — the CLI is a thin wrapper, not a re-implementation.
8
8
 
@@ -10,9 +10,123 @@ Everything lives under `~/.hmanlab/`: one root SQLite DB + a `personas/`
10
10
  directory of YAML files + one DB per registered project. No cloud, no
11
11
  account, no telemetry.
12
12
 
13
+ ## What `npx -y @hmanlab/hl-plugins install memo` does
14
+
15
+ It's the one-line path to a working setup. It runs five steps, in order:
16
+
17
+ 1. **Pre-flight.** Node ≥ 18, an `~/.opencode/` config dir. Auto-creates
18
+ the dir if missing.
19
+ 2. **Install Bun.** Memo is built with `--target=bun`, so Bun is a hard
20
+ requirement. The installer auto-installs it via
21
+ `curl -fsSL https://bun.sh/install | bash` if it isn't on PATH yet.
22
+ 3. **Stage the plugin CLI.** Copies `dist/cli.js` to
23
+ `~/.local/share/hl-plugins/memo/` so the next step can invoke plugin
24
+ subcommands by absolute path (no PATH dependency yet).
25
+ 4. **Prompt about MiniLM.** *See the section below.* Your answer is
26
+ persisted during install — there's no "run later" step.
27
+ 5. **Copy + register.** Ships the MCP server bundle to
28
+ `~/.local/share/hl-plugins/memo/memo-mcp-server.js`, drops the skill
29
+ markdown at `~/.claude/skills/memo/SKILL.md`, and registers the server
30
+ in your Claude Code config. Then prints
31
+ *"Restart opencode to use the new tools."*
32
+
33
+ That's it. No auth, no account, no telemetry, no daemon. The server runs
34
+ on stdio only when Claude Code invokes it.
35
+
36
+ ### Optional: the MiniLM embedder
37
+
38
+ After Bun is confirmed and before any files are copied, the installer asks
39
+ once whether you want the optional MiniLM-L6-v2 model. The model powers
40
+ semantic search — paraphrase and typo queries still hit the right memory
41
+ even when the words don't match the stored content literally.
42
+
43
+ ```
44
+ ? MiniLM-L6-v2 (~25 MB) powers semantic search so paraphrase and typo queries
45
+ still hit the right memory.
46
+
47
+ With it: 75.2% recall@5 (62.9% recall@1)
48
+ Without it: paraphrase queries drop to ~30%, typo queries to ~25%
49
+ (105-query eval across coding, glossary, and preferences)
50
+
51
+ Enable? [Y/n]:
52
+ ```
53
+
54
+ Your answer is committed during install — no follow-up step:
55
+
56
+ - **Y (default):** writes `embedder_mode: minilm` to
57
+ `~/.hmanlab/config.yaml`. The model downloads lazily on the next
58
+ `memory_save` / `memory_search` call (~25 MB, ~2 s warmup, then ~50 ms
59
+ per query).
60
+ - **n:** writes `embedder_mode: hash`. `loadExtractor()` short-circuits
61
+ on every subsequent call. The model is **never** downloaded or
62
+ referenced — the embedder uses the deterministic trigram fallback.
63
+
64
+ Non-interactive installs (CI, scripts piped via `| sh`) treat the prompt
65
+ as Yes so the install never blocks.
66
+
67
+ Change your mind any time:
68
+
69
+ ```bash
70
+ hmanlab-memory embedder status # show current mode
71
+ hmanlab-memory embedder install # switch to minilm (lazy download on next memory call)
72
+ hmanlab-memory embedder disable # switch to hash (no download, ever)
73
+ ```
74
+
75
+ The mode is stored under `embedder_mode` in `~/.hmanlab/config.yaml`. Three
76
+ values: `minilm` (require the real model), `hash` (use the deterministic
77
+ trigram fallback), `auto` (try MiniLM, fall back to hash on failure —
78
+ default if the key is absent).
79
+
80
+ ### With MiniLM vs without — what actually changes
81
+
82
+ Same 105 positive + 20 negative queries, same memory corpus. Two
83
+ columns: **Hash** (no MiniLM, no model download) and **MiniLM +
84
+ trigram** (what ships by default — semantic embedder + the trigram
85
+ FTS5 mirror that catches 3-char substring overlap).
86
+
87
+ **Headline metrics:**
88
+
89
+ | Metric | Hash fallback | MiniLM + trigram | Δ |
90
+ |---|---|---|---|
91
+ | Recall@1 | 41.0% | **62.9%** | **+21.9 pp** |
92
+ | Recall@5 | 68.6% | **75.2%** | +6.6 pp |
93
+ | MRR | 0.516 | **0.679** | **+0.163** |
94
+
95
+ The biggest win is Recall@1 — the trigram FTS5 mirror lifts it from
96
+ 45.7% (MiniLM alone) to 62.9% (MiniLM + trigram). When the query
97
+ shares even one 3-char substring with the right memory, that memory
98
+ now lands at rank 1 instead of being lost in the top-5 noise.
99
+
100
+ **By domain (R@5):**
101
+
102
+ | Domain | Hash | MiniLM + trigram | Δ |
103
+ |---|---|---|---|
104
+ | glossary | 64.5% | 100.0% | **+35.5** |
105
+ | preferences | 97.4% | 100.0% | +2.6 |
106
+
107
+ **By query kind (R@5):**
108
+
109
+ | Kind | Hash | MiniLM + trigram | Δ |
110
+ |---|---|---|---|
111
+ | literal | 93.3% | 96.7% | +3.4 |
112
+ | paraphrase | 60.0% | 66.7% | +6.7 |
113
+ | typo | 53.3% | 66.7% | +13.3 |
114
+ | negation | 70.0% | 60.0% | **−10.0** |
115
+ | broad | 60.0% | 80.0% | +20.0 |
116
+
117
+ If your memory is mostly short, literal preferences, hash fallback is
118
+ competitive. If your memory is glossary definitions or fuzzy
119
+ paraphrases, MiniLM + trigram dominates — particularly on
120
+ broad queries where the user types a vague prompt and expects the
121
+ right memory to surface.
122
+
123
+ Raw eval data:
124
+ - `~/Desktop/memo-eval/results-2026-06-25-bigeval.json` (MiniLM + trigram, current ship state)
125
+ - `~/Desktop/memo-eval/results-2026-06-25-bigeval-hash.json` (hash fallback, what you get if you decline MiniLM at install)
126
+
13
127
  ## What's in the box (v1.0.0)
14
128
 
15
- ### MCP tools (33)
129
+ ### MCP tools (35)
16
130
  - **Persona (11):** `persona_list`, `persona_get`, `persona_create`,
17
131
  `persona_update`, `persona_delete`, `persona_clone`,
18
132
  `persona_reload`, `user_persona_get`, `user_persona_update`
@@ -70,9 +184,11 @@ Full CLI reference: [`docs/USAGE.md`](./docs/USAGE.md).
70
184
 
71
185
  ```
72
186
  ~/.hmanlab/
73
- ├── config.yaml # cwd_auto_detect, persona_filter_mode, decay knobs
187
+ ├── config.yaml # cwd_auto_detect, persona_filter_mode, embedder_mode
74
188
  ├── root.db # user_persona, ai_personas, projects,
75
189
  │ # global_memories (+ _fts + _edges), schema migrations
190
+ ├── models/ # MiniLM-L6-v2 q8 (~25 MB), lazy-downloaded on first use
191
+ │ └── Xenova/all-MiniLM-L6-v2/...
76
192
  ├── personas/ # persona YAML files (built-in + user)
77
193
  │ ├── default.yaml
78
194
  │ ├── work.yaml # parent: default
@@ -5,25 +5,82 @@ description: Use when the user wants persistent memory across projects, persona-
5
5
 
6
6
  # memo — hmanlab-memo (local-first MCP memory)
7
7
 
8
- The `memo` MCP server exposes 9 tools that give an AI coding assistant
9
- persistent, persona-aware memory on the user's machine. Everything lives under
10
- `~/.hmanlab/` (one root SQLite DB + a `personas/` directory of YAML files).
11
- No cloud, no account, no telemetry.
12
-
13
- This is the Phase 01 slice: persona + user-persona CRUD only. Projects,
14
- memories, embeddings, and hybrid search land in later phases.
15
-
16
- | Tool | What it does |
17
- | --------------------- | ----------------------------------------------------------- |
18
- | `persona_list` | List all personas (built-in + user) |
19
- | `persona_get` | Read one persona (resolves `parent` chain) |
20
- | `persona_create` | Write a new YAML persona + DB row |
21
- | `persona_update` | Edit a persona, bump version |
22
- | `persona_delete` | Soft-delete (archive) YAML stays |
23
- | `persona_clone` | Duplicate a persona as a starting point |
24
- | `persona_reload` | Re-scan `~/.hmanlab/personas/` and resync the DB |
25
- | `user_persona_get` | Read the user's persona singleton |
26
- | `user_persona_update` | Edit the user's persona |
8
+ The `memo` MCP server exposes 35 tools that give an AI coding assistant
9
+ persistent, persona-aware memory on the user's machine. Everything lives
10
+ under `~/.hmanlab/` (one root SQLite DB + a `personas/` directory of YAML
11
+ files + one DB per registered project). No cloud, no account, no telemetry.
12
+
13
+ The MCP bundle is `--target=bun` and lives at
14
+ `~/.local/share/hl-plugins/memo/memo-mcp-server.js`. Claude Code launches
15
+ it on stdio.
16
+
17
+ ## Search strategy normalize the query before calling `memory_search`
18
+
19
+ `memory_search` is hybrid (FTS + recency + vector). Vector search lifts
20
+ Recall@5 from 68.6% 73.3% on the standard eval, but only if the query
21
+ isn't too distorted. **You are better at query rewriting than the local
22
+ embedder.** Before calling `memory_search`, normalize the query yourself:
23
+
24
+ 1. **Fix typos** before searching vector similarity on `"indenation with
25
+ tabs"` lands on the right memory; `"indsnation with tabx"` might not.
26
+ 2. **Drop conversational filler** ("can you", "do you know", "what was
27
+ that thing about"). The filler dilutes the cosine signal.
28
+ 3. **Strip negation of the question, keep negation of the memory.** When
29
+ the user asks "should I commit secrets to a private repo", search for
30
+ `"Never commit secrets to git"` (the actual memory), not the literal
31
+ question. The embedder can't distinguish "should I commit" from
32
+ "Never commit" — but if your query contains the *memory's* words,
33
+ FTS catches it.
34
+ 4. **Prefer the user's own phrasing** when you remember it from the
35
+ conversation. If the user said "tabs not spaces" earlier and the
36
+ memory says "Use tabs for indentation in this project", search for
37
+ the latter — it matches both FTS and vector better.
38
+ 5. **One query, not several.** Don't fan out: a single, well-chosen query
39
+ outperforms three noisy ones.
40
+
41
+ Concretely, before calling `memory_search("query")`:
42
+
43
+ ```
44
+ raw user question: "wait what was my rule about not committing api keys"
45
+ rewrite to: "Never commit secrets to git"
46
+ then call: memory_search("Never commit secrets to git")
47
+ ```
48
+
49
+ Don't rewrite for `memory_recent` — that's recency-only and your input
50
+ doesn't matter.
51
+
52
+ ## When to use the tools
53
+
54
+ - **User asks a question whose answer is in memory** → `memory_search`
55
+ with a normalized query. Skim top-5; if none match, fall back to
56
+ answering from your own knowledge (don't claim a memory hit when
57
+ there isn't one).
58
+ - **User states a preference / rule / decision worth keeping** →
59
+ `memory_save`. Use `importance: 0.9` for durable rules, `0.5` for
60
+ context, `0.3` for one-off notes. Add a `category` (e.g. "preferences",
61
+ "code-style", "glossary").
62
+ - **User asks "what do you know about me / this project"** → `memory_search`
63
+ with `scope: "project"` for project-specific, `scope: "all"` for
64
+ everything.
65
+ - **User asks to switch hats / "talk like X"** → `persona_list` to see
66
+ options, `persona_get` to read the full prompt, then continue as that
67
+ persona.
68
+ - **User asks to remember a global preference** → `user_persona_update`.
69
+ - **Long conversation, context getting heavy** → `memory_compact_prep`
70
+ to get the pre-selected subset worth re-injecting after compaction.
71
+ - **Storage getting messy** → `memory_hygiene all` for the stale/cold/
72
+ duplicate report, `memory_status` for the headline counts.
73
+ - **Want to back up / move a project** → `project_export <name>` /
74
+ `project_import <archive>`.
75
+
76
+ ## Save rules
77
+
78
+ - **Be specific.** "Use tabs for indentation" beats "code style matters".
79
+ - **One fact per memory.** Splitting lets each one rank on its own.
80
+ - **Use the user's own words** when possible. They're more searchable
81
+ later.
82
+ - **Pick importance honestly.** `0.9` = durable rule, `0.5` = context,
83
+ `0.3` = ephemeral.
27
84
 
28
85
  ## Setup (one-time, on the machine)
29
86
 
@@ -32,37 +89,20 @@ memories, embeddings, and hybrid search land in later phases.
32
89
  ```bash
33
90
  hl-plugins install memo
34
91
  ```
35
- 3. Restart Claude Code. The 9 tools above appear under the `memo` MCP server.
36
-
37
- The CLI auto-installs Bun if missing and registers the MCP bundle under
38
- `~/.local/share/hl-plugins/memo/`, then wires it into `~/.claude.json`.
92
+ 3. The installer prompts once about MiniLM-L6-v2 (a local embedder that
93
+ powers semantic search). Default is Yes — ~25 MB download on first
94
+ memory call.
95
+ 4. Restart Claude Code. The 35 tools appear under the `memo` MCP server.
39
96
 
40
97
  ## On-disk layout
41
98
 
42
99
  ```
43
100
  ~/.hmanlab/
44
- ├── config.yaml # paths + embedding defaults (phase-01 reads/writes subset)
45
- ├── root.db # WAL-mode SQLite: user_persona, ai_personas
46
- └── personas/
47
- ├── default.yaml # built-in (warm, balanced)
48
- ├── work.yaml # built-in (parent: default)
49
- ├── creative.yaml # built-in (parent: default)
50
- └── <user-defined>.yaml
51
- ```
52
-
53
- YAML is the source of truth. Editing a file on disk and calling `persona_reload`
54
- updates the DB to match. The starter pack is extracted only on first boot;
55
- existing YAMLs are never overwritten.
56
-
57
- ## When to use these tools
58
-
59
- - **User asks to switch hats / "talk like X" / use a persona** → `persona_list`
60
- to see options, `persona_get` to read the full prompt, then continue the
61
- conversation as that persona.
62
- - **User asks to remember a preference** → `user_persona_update` with the
63
- preference text.
64
- - **User asks to create / edit a persona** → `persona_create` or
65
- `persona_update`.
66
- - **User edits a persona YAML directly** → `persona_reload` to make the DB
67
- match.
68
- - **User wants to fork an existing persona** → `persona_clone`.
101
+ ├── config.yaml # paths, embedder_mode, persona_filter_mode
102
+ ├── root.db # user_persona, ai_personas, projects, global_memories
103
+ ├── models/ # MiniLM-L6-v2 q8 (lazy-downloaded on first embed call)
104
+ ├── personas/ # persona YAML files (built-in + user)
105
+ └── projects/<name>/
106
+ ├── project.yaml
107
+ └── hmanlab.db # memories + FTS5
108
+ ```