nlm-memory 0.5.22 → 0.7.0

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.
Files changed (117) hide show
  1. package/README.md +106 -14
  2. package/dist/cli/classify-parity.js +1 -1
  3. package/dist/cli/classify-parity.js.map +1 -1
  4. package/dist/cli/nlm.js +152 -12
  5. package/dist/cli/nlm.js.map +1 -1
  6. package/dist/core/adapters/pi.js +13 -3
  7. package/dist/core/adapters/pi.js.map +1 -1
  8. package/dist/core/classifier/prompt.d.ts +1 -1
  9. package/dist/core/classifier/prompt.js +1 -1
  10. package/dist/core/hook/pointer-block.d.ts +12 -1
  11. package/dist/core/hook/pointer-block.js +25 -8
  12. package/dist/core/hook/pointer-block.js.map +1 -1
  13. package/dist/core/providers/provider-registry.js +1 -1
  14. package/dist/core/providers/provider-registry.js.map +1 -1
  15. package/dist/core/recall/recall-service.d.ts +9 -0
  16. package/dist/core/recall/recall-service.js +81 -18
  17. package/dist/core/recall/recall-service.js.map +1 -1
  18. package/dist/core/recall-facts/fact-recall-service.d.ts +11 -0
  19. package/dist/core/recall-facts/fact-recall-service.js +69 -4
  20. package/dist/core/recall-facts/fact-recall-service.js.map +1 -1
  21. package/dist/core/scheduler/scheduler.d.ts +7 -0
  22. package/dist/core/scheduler/scheduler.js +26 -0
  23. package/dist/core/scheduler/scheduler.js.map +1 -1
  24. package/dist/core/signals/aggregate.d.ts +21 -0
  25. package/dist/core/signals/aggregate.js +34 -0
  26. package/dist/core/signals/aggregate.js.map +1 -0
  27. package/dist/core/signals/failure-mode-recall.d.ts +18 -0
  28. package/dist/core/signals/failure-mode-recall.js +28 -0
  29. package/dist/core/signals/failure-mode-recall.js.map +1 -0
  30. package/dist/core/signals/ingest-signal.d.ts +19 -0
  31. package/dist/core/signals/ingest-signal.js +63 -0
  32. package/dist/core/signals/ingest-signal.js.map +1 -0
  33. package/dist/core/signals/install-scope.d.ts +8 -0
  34. package/dist/core/signals/install-scope.js +41 -0
  35. package/dist/core/signals/install-scope.js.map +1 -0
  36. package/dist/core/signals/recommend.d.ts +13 -0
  37. package/dist/core/signals/recommend.js +24 -0
  38. package/dist/core/signals/recommend.js.map +1 -0
  39. package/dist/core/storage/pg-fact-store.d.ts +5 -0
  40. package/dist/core/storage/pg-fact-store.js +29 -0
  41. package/dist/core/storage/pg-fact-store.js.map +1 -1
  42. package/dist/core/storage/pg-signal-store.d.ts +16 -0
  43. package/dist/core/storage/pg-signal-store.js +100 -0
  44. package/dist/core/storage/pg-signal-store.js.map +1 -0
  45. package/dist/core/storage/pg-storage.d.ts +2 -0
  46. package/dist/core/storage/pg-storage.js +3 -0
  47. package/dist/core/storage/pg-storage.js.map +1 -1
  48. package/dist/core/storage/pg-tx-context.d.ts +5 -0
  49. package/dist/core/storage/pg-tx-context.js +3 -0
  50. package/dist/core/storage/pg-tx-context.js.map +1 -1
  51. package/dist/core/storage/sqlite-fact-store.d.ts +5 -0
  52. package/dist/core/storage/sqlite-fact-store.js +30 -0
  53. package/dist/core/storage/sqlite-fact-store.js.map +1 -1
  54. package/dist/core/storage/sqlite-signal-store.d.ts +20 -0
  55. package/dist/core/storage/sqlite-signal-store.js +112 -0
  56. package/dist/core/storage/sqlite-signal-store.js.map +1 -0
  57. package/dist/core/storage/sqlite-storage.d.ts +2 -0
  58. package/dist/core/storage/sqlite-storage.js +6 -2
  59. package/dist/core/storage/sqlite-storage.js.map +1 -1
  60. package/dist/hook/prompt-recall-hook.d.ts +10 -1
  61. package/dist/hook/prompt-recall-hook.js +10 -4
  62. package/dist/hook/prompt-recall-hook.js.map +1 -1
  63. package/dist/hook/recall-over-http.d.ts +11 -1
  64. package/dist/hook/recall-over-http.js +16 -4
  65. package/dist/hook/recall-over-http.js.map +1 -1
  66. package/dist/hook/session-start-hook.d.ts +2 -0
  67. package/dist/hook/session-start-hook.js +32 -2
  68. package/dist/hook/session-start-hook.js.map +1 -1
  69. package/dist/hook/stop-hook.js +16 -0
  70. package/dist/hook/stop-hook.js.map +1 -1
  71. package/dist/http/app.d.ts +9 -0
  72. package/dist/http/app.js +124 -11
  73. package/dist/http/app.js.map +1 -1
  74. package/dist/install/codex.d.ts +1 -1
  75. package/dist/install/codex.js +4 -4
  76. package/dist/install/codex.js.map +1 -1
  77. package/dist/install/ollama.d.ts +10 -0
  78. package/dist/install/ollama.js +19 -3
  79. package/dist/install/ollama.js.map +1 -1
  80. package/dist/install/pi.d.ts +1 -1
  81. package/dist/install/pi.js +1 -1
  82. package/dist/install/setup.js +63 -10
  83. package/dist/install/setup.js.map +1 -1
  84. package/dist/llm/classifier-box.d.ts +2 -1
  85. package/dist/llm/classifier-box.js +3 -0
  86. package/dist/llm/classifier-box.js.map +1 -1
  87. package/dist/llm/deepseek-client.d.ts +5 -2
  88. package/dist/llm/deepseek-client.js +51 -1
  89. package/dist/llm/deepseek-client.js.map +1 -1
  90. package/dist/llm/ollama-client.d.ts +3 -2
  91. package/dist/llm/ollama-client.js +45 -2
  92. package/dist/llm/ollama-client.js.map +1 -1
  93. package/dist/mcp/server.d.ts +1 -0
  94. package/dist/mcp/server.js +22 -3
  95. package/dist/mcp/server.js.map +1 -1
  96. package/dist/ports/fact-store.d.ts +0 -0
  97. package/dist/ports/fact-store.js +0 -0
  98. package/dist/ports/fact-store.js.map +1 -1
  99. package/dist/ports/llm-client.d.ts +24 -0
  100. package/dist/ports/signal-store.d.ts +30 -0
  101. package/dist/ports/signal-store.js +9 -0
  102. package/dist/ports/signal-store.js.map +1 -0
  103. package/dist/ports/storage.d.ts +2 -0
  104. package/dist/ports/transcript-adapter.d.ts +6 -0
  105. package/dist/shared/types.d.ts +76 -0
  106. package/dist/ui/assets/index-BTj60bAH.js +11 -0
  107. package/dist/ui/assets/index-Baw2qIKc.css +1 -0
  108. package/dist/ui/index.html +2 -2
  109. package/nlm/README.md +1 -1
  110. package/nlm/index.js +40 -17
  111. package/package.json +19 -18
  112. package/plugin/.codex-plugin/plugin.json +2 -2
  113. package/plugin/README.md +3 -3
  114. package/plugin/scripts/prompt-recall-hook.mjs +40 -17
  115. package/plugin/scripts/stop-hook.mjs +80 -6
  116. package/dist/ui/assets/index-BR1BHwew.js +0 -69
  117. package/dist/ui/assets/index-CgHq6xeZ.css +0 -1
package/README.md CHANGED
@@ -8,8 +8,8 @@
8
8
 
9
9
  <p align="center">
10
10
  <a href="https://www.npmjs.com/package/nlm-memory"><img src="https://img.shields.io/npm/v/nlm-memory?color=CB3837&label=npm&logo=npm" alt="npm version" /></a>
11
- <a href="https://github.com/pbmagnet4/nlm-memory-ts/actions/workflows/ci.yml"><img src="https://github.com/pbmagnet4/nlm-memory-ts/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI status" /></a>
12
- <a href="https://github.com/pbmagnet4/nlm-memory-ts/blob/main/LICENSE"><img src="https://img.shields.io/github/license/pbmagnet4/nlm-memory-ts?color=blue" alt="License: Apache 2.0" /></a>
11
+ <a href="https://github.com/pbmagnet4/nlm-memory/actions/workflows/ci.yml"><img src="https://github.com/pbmagnet4/nlm-memory/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI status" /></a>
12
+ <a href="https://github.com/pbmagnet4/nlm-memory/blob/main/LICENSE"><img src="https://img.shields.io/github/license/pbmagnet4/nlm-memory?color=blue" alt="License: Apache 2.0" /></a>
13
13
  <a href="https://nodejs.org"><img src="https://img.shields.io/node/v/nlm-memory?color=brightgreen" alt="Node 20+" /></a>
14
14
  <img src="https://img.shields.io/badge/tests-726%20passing-success" alt="726 tests passing" />
15
15
  <img src="https://img.shields.io/badge/MCP-9%20runtimes-8A2BE2" alt="MCP across 9 runtimes" />
@@ -22,6 +22,7 @@
22
22
  <a href="#quick-start">Quick Start</a> &middot;
23
23
  <a href="#runtimes">Runtimes</a> &middot;
24
24
  <a href="#how-recall-works">How recall works</a> &middot;
25
+ <a href="#agent-self-improvement-signals">Signals</a> &middot;
25
26
  <a href="#mcp-tools">MCP</a> &middot;
26
27
  <a href="#rest-api">REST API</a> &middot;
27
28
  <a href="#daily-digest">Digest</a> &middot;
@@ -75,18 +76,18 @@ nlm --version
75
76
 
76
77
  ## Runtimes
77
78
 
78
- One corpus across every adapter. MCP works against all nine. **Automatic context injection via hooks** ships on three (Claude Code, Hermes Agent, pi.dev); on the others the agent must call `recall_sessions` explicitly via MCP. `nlm connect` wires whichever surface the runtime supports:
79
+ One corpus across every adapter. MCP works against all nine. **Automatic context injection via hooks** ships on four (Claude Code, Codex CLI, Hermes Agent, pi.dev). Cursor, Windsurf, and OpenCode receive an optional static rules-file nudge (`nlm connect <runtime> --with-rules`) that instructs the agent to call `recall_sessions` on history-flavored prompts — those runtimes don't expose a pre-prompt hook today, so a rules nudge is the closest equivalent. Aider is ingest-only. `nlm connect` wires whichever surface the runtime supports:
79
80
 
80
81
  | Runtime | Connect | Sessions read from | Hooks |
81
82
  |---|---|---|---|
82
83
  | **Claude Code** | `nlm connect claude-code` | `~/.claude/projects/**/*.jsonl` | 6 hooks: UserPromptSubmit, SessionStart, SessionEnd, Stop, PreCompact, SubagentStart |
83
84
  | **Codex CLI** | `nlm connect codex` | `~/.codex/sessions/` | Marketplace plugin (UserPromptSubmit + Stop) |
84
- | **Hermes** | `nlm connect hermes` | `~/.hermes/sessions/` | MCP only |
85
- | **Hermes Agent** | `nlm connect hermes-agent` | `~/.hermes/state.db` | 6 hooks: pre_llm_call, post_llm_call, on_session_start/end/finalize/reset |
86
- | **Cursor** | `nlm connect cursor` | Cursor IDE chat DB | MCP only |
87
- | **Windsurf** | `nlm connect windsurf` | Windsurf user dir | MCP only |
88
- | **OpenCode** | adapter active | `~/.local/share/opencode/` | MCP only |
89
- | **Aider** | adapter active | `AIDER_CHAT_HISTORY_FILE` | MCP only |
85
+ | **Hermes** (WebUI) | `nlm connect hermes` | `~/.hermes/sessions/` | MCP only (writes the MCP server block to `~/.hermes/config.yaml`) |
86
+ | **Hermes Agent** (NousResearch CLI) | `nlm connect hermes-agent` | `~/.hermes/state.db` | 6 hooks: pre_llm_call, post_llm_call, on_session_start/end/finalize/reset (Python plugin in `~/.hermes/plugins/nlm-memory/`) |
87
+ | **Cursor** | `nlm connect cursor [--with-rules]` | Cursor IDE chat DB | MCP + optional rules nudge (workspace `.cursor/rules/nlm-recall.mdc`) |
88
+ | **Windsurf** | `nlm connect windsurf [--with-rules]` | Windsurf user dir | MCP + optional rules nudge (`~/.codeium/windsurf/memories/global_rules.md`) |
89
+ | **OpenCode** | `nlm connect opencode [--with-rules]` | `~/.local/share/opencode/` | MCP + optional rules nudge (`~/.config/opencode/AGENTS.md`) |
90
+ | **Aider** | adapter active (ingest only) | `AIDER_CHAT_HISTORY_FILE` | No native MCP, no hooks — sessions are read into the corpus but Aider cannot call recall back |
90
91
  | **pi.dev** | `nlm setup` (auto) or `nlm connect pi` | `~/.pi/agent/sessions/**/*.jsonl` | 1 hook: input (prepend via transform) |
91
92
 
92
93
  `nlm disconnect <runtime>` reverses any of the above.
@@ -99,14 +100,14 @@ Two delivery paths. They share the same index.
99
100
 
100
101
  ### 1. Hooks — automatic context injection
101
102
 
102
- Hooks fire on user input and prepend a pointer block of likely-relevant prior sessions to the model's context. Three runtimes ship hooks today: Claude Code (six-hook lifecycle), Hermes Agent (six parallel hooks), and pi.dev (one `input` hook via [nlm/](nlm/README.md)). On all other runtimes the agent calls `recall_sessions` explicitly via MCP. Full lifecycle, modes, logging surface, and the daily liveness canary documented in [docs/hooks.md](docs/hooks.md).
103
+ Hooks fire on user input and prepend a pointer block of likely-relevant prior sessions to the model's context. Four runtimes ship hooks today: Claude Code (six-hook lifecycle), Codex CLI (UserPromptSubmit + Stop via the marketplace plugin), Hermes Agent (six parallel hooks), and pi.dev (one `input` hook via [nlm/](nlm/README.md)). Cursor, Windsurf, and OpenCode don't expose a pre-prompt hook today, so the `--with-rules` install path drops a static rules snippet that asks the agent to call `recall_sessions` itself on history-flavored prompts (see [docs/hooks.md](docs/hooks.md) for the snippet). Full lifecycle, modes, logging surface, and the daily liveness canary documented in [docs/hooks.md](docs/hooks.md).
103
104
 
104
105
  **Claude Code** — six hooks written to `~/.claude/settings.json` via `nlm connect claude-code`:
105
106
 
106
107
  | Event | What NLM does | Mode |
107
108
  |---|---|---|
108
109
  | **UserPromptSubmit** | Score the prompt, silently prepend pointer block listing 0–3 most likely-relevant prior sessions | live by default |
109
- | **SessionStart** | Cold-start agents (cron, background) hit this; same pointer-block delivery without a user prompt | live by default |
110
+ | **SessionStart** | Cold-start agents (cron, background) hit this; same pointer-block delivery without a user prompt, plus a "Known failure modes for this repo" block when signals data exists (see Signals below) | live by default |
110
111
  | **SessionEnd** | Delete the per-conversation memo on session close so state files don't accumulate | always on |
111
112
  | **Stop** | Scan the model's response for citations of surfaced session IDs → updates `useful_hit_rate` and builds the reranker training substrate | always on |
112
113
  | **PreCompact** | Flush the per-conversation surfaced-IDs memo so post-compaction recalls aren't gated | always on |
@@ -137,6 +138,86 @@ Container-hosted agents (Hermes WebUI, Codex CLI, etc.) hit the Streamable-HTTP
137
138
 
138
139
  ---
139
140
 
141
+ ## Agent self-improvement signals
142
+
143
+ NLM can ingest structured feedback events (`nlm.signal`) from any tool in your agent stack -- quality gates, eval runners, code reviewers, test harnesses -- and surface the aggregated failure patterns back to the agent at session start. Over time the agent learns which steps tend to fail for a given repo and model, without any external service.
144
+
145
+ ### Payload contract
146
+
147
+ ```jsonc
148
+ {
149
+ "v": 1,
150
+ "kind": "gate" | "eval" | "review" | "test", // required
151
+ "outcome": "pass" | "fail" | "fix" | "exhausted", // required
152
+ "producer": "quality-gate", // defaults to "unknown"
153
+ "model": "qwen3-coder", // defaults to "unknown"
154
+ "repo": "/path/or/name", // defaults to "unknown"
155
+ "detail": { "step": "types", "files": ["a.ts"], "attempt": 2 },
156
+ "session": "<session-id-if-known>",
157
+ "ts": "2026-06-09T18:00:00.000Z" // defaults to now
158
+ }
159
+ ```
160
+
161
+ `kind` and `outcome` are the only required fields; invalid values are rejected with `400`. `install_scope` is stamped server-side -- do not send it.
162
+
163
+ ### Transports
164
+
165
+ **HTTP (any producer)**
166
+
167
+ ```js
168
+ await fetch("http://localhost:3940/api/signal", {
169
+ method: "POST",
170
+ headers: { "content-type": "application/json" },
171
+ body: JSON.stringify({
172
+ kind: "gate", producer: "my-tool", outcome: "fail",
173
+ model, repo, detail: { step: "types" }, ts: new Date().toISOString(),
174
+ }),
175
+ });
176
+ ```
177
+
178
+ Rides the standard `/api/*` loopback gate. When the daemon runs with `NLM_UI_AUTH=cookie`, send `Authorization: Bearer $NLM_MCP_TOKEN`.
179
+
180
+ **Session-embedded (pi.dev)**
181
+
182
+ Pi producers call `pi.appendEntry("nlm.signal", payload)` inside an extension. This writes:
183
+
184
+ ```json
185
+ { "type": "custom", "customType": "nlm.signal", "data": { ...payload } }
186
+ ```
187
+
188
+ to the session `.jsonl`. NLM's pi adapter recognises the `nlm.signal` customType and the scheduler drains it during normal ingest -- no HTTP call required.
189
+
190
+ ### Failure-mode recall
191
+
192
+ NLM aggregates signals into failure modes per `(repo, model)` pair. A mode surfaces when its fail-rate reaches 20% or higher over at least 10 events in a trailing 14-day window. At session start the Claude Code `SessionStart` hook injects a "Known failure modes for this repo" block into the agent prompt automatically (repo-scoped). Any harness can fetch the same data directly:
193
+
194
+ ```
195
+ GET /api/signals/failure-modes?repo=<repo>&model=<model>
196
+ ```
197
+
198
+ ### Inspection
199
+
200
+ ```sh
201
+ nlm improve # prints failure modes + recommendations for the current repo
202
+ ```
203
+
204
+ The UI **Recall** page also has a failure-modes panel. NLM surfaces findings and recommendations only -- it never acts on them.
205
+
206
+ ### Configuration and privacy
207
+
208
+ | Var | Default | What |
209
+ |---|---|---|
210
+ | `NLM_SIGNALS_ENABLED` | `1` (on) | Set to `0` to disable signal ingest entirely |
211
+ | `NLM_SIGNAL_RETENTION_DAYS` | `90` | Raw signals older than this are pruned |
212
+
213
+ Signals are local-only. They are stamped with a per-install ID from `~/.nlm/install-id` and never leave the machine.
214
+
215
+ ### Reference producer
216
+
217
+ The pi `quality-gate` extension (in the `pi-sandbox` repo) is a ~10-line integration that emits `nlm.signal` per gate step and again on retry exhaustion. It is the canonical example of the session-embedded transport.
218
+
219
+ ---
220
+
140
221
  ## MCP Tools
141
222
 
142
223
  | Tool | What it does |
@@ -245,14 +326,25 @@ recall: prompt / query
245
326
  | `NLM_USEFUL_HIT_LOG` | `~/.nlm/useful-hit-log.jsonl` | Citation/useful-hit ledger |
246
327
  | `NLM_QUERY_LOG` | `~/.nlm/query-log.jsonl` | Recall query telemetry |
247
328
  | `NLM_CITATION_LOG` | `~/.nlm/citation-log.jsonl` | Stop-hook citation events |
329
+ | `NLM_MISS_LOG` | `~/.nlm/miss-log.jsonl` | Stop-hook miss events — sessions the agent explicitly fetched via `get_session`/`cite_session` that the hook never surfaced. Reviewed via `nlm misses`. |
330
+ | `NLM_MISS_LOG_ENABLED` | `true` | Set to `0` to disable miss-log emission entirely. |
248
331
  | `NLM_MCP_TOKEN` | auto-generated | 256-bit bearer for `/api/*` (non-browser) and `/mcp` |
249
332
  | `NLM_MCP_CONFIG` | `~/.mcp.json` | Path the `connect`/`disconnect` commands modify |
250
333
  | `NLM_CLASSIFIER` | `ollama` | `ollama` (local, default), `deepseek`, `openai`, `anthropic`, `openrouter`, or `openai-compatible` |
251
- | `NLM_CLASSIFIER_MODEL` | `phi4-mini:latest` | Model id for the chosen provider |
334
+ | `NLM_CLASSIFIER_MODEL` | `qwen3:4b-instruct-2507-q4_K_M` | Model id for the chosen provider. See [classifier bench](reports/classifier-comparison/2026-06-02-deepseek-v4-vs-qwen3.md) for why this is the recommended local default. |
252
335
  | `NLM_OLLAMA_URL` | `http://localhost:11434` | Override Ollama endpoint |
253
336
  | `NLM_ADAPTERS` | all | Comma-separated allowlist of adapters to enable |
254
337
  | `DEEPSEEK_API_KEY` | — | Required only when classifier=deepseek |
255
338
  | `NLM_DISABLE_UPDATE_CHECK` | — | Set to `1` to disable the daily npm-registry update check |
339
+ | `NLM_RECALL_DECAY_HALF_LIFE_DAYS` | `180` | Half-life of the recency multiplier applied to recall scores. Older sessions score lower; defaults to 6 months. Set to `0` to disable recency weighting entirely. |
340
+ | `NLM_RECALL_DECAY_FLOOR` | `0.25` | Lower bound on the recency multiplier — even ancient sessions retain at least 25% of their raw score so a perfect-match old session can still surface. |
341
+ | `NLM_RECALL_REWRITE_DEFAULT` | `true` | Default value for the MCP `recall_sessions` `rewrite` parameter. When true, the service runs an LLM rewrite on vague natural-language queries before search. The HTTP hook caller bypasses rewrite regardless (hot-path protection). |
342
+ | `NLM_RECALL_REWRITE_TIMEOUT_MS` | `5000` | Per-call timeout for the rewrite LLM. Separate from the classifier timeout. |
343
+ | `NLM_FACT_CORROBORATION_BOOST_CAP` | `2.0` | Maximum multiplicative boost applied to fact recall scores based on how many sessions corroborate the same `(subject, predicate, value)`. Log-scale: 1 corroboration is 1.0×, 10 is 2.0× (capped). Set to `1.0` to disable the boost — the count is still returned on each hit. |
344
+ | `NLM_HOOK_INJECT_FACTS` | `true` | Whether to attach high-confidence facts about top-hit entities to the pointer block injected by the hook. Set to `0` to disable globally. |
345
+ | `NLM_HOOK_FACT_LIMIT` | `5` | Maximum number of facts in the "Known facts" section of the pointer block. |
346
+ | `NLM_HOOK_FACT_MIN_CORROBORATION` | `2` | Minimum number of sessions that must have asserted a fact before it qualifies for hook injection. Set to `1` to include single-source facts. |
347
+ | `NLM_HOOK_FACT_MIN_CONFIDENCE` | `0.7` | Minimum classifier confidence for a fact to qualify for hook injection. |
256
348
  | `TELEGRAM_BOT_TOKEN` / `TELEGRAM_CHAT_ID` | — | Required for `nlm digest --telegram` |
257
349
 
258
350
  ### Changing the classifier from the UI
@@ -370,8 +462,8 @@ The defining property is the editable timeline. mem0 and Letta append; NLM lets
370
462
  ## Development
371
463
 
372
464
  ```sh
373
- git clone https://github.com/pbmagnet4/nlm-memory-ts
374
- cd nlm-memory-ts
465
+ git clone https://github.com/pbmagnet4/nlm-memory
466
+ cd nlm-memory
375
467
  npm install
376
468
  npm run build # compile dist/ — commit the result, it ships in the repo
377
469
  npm run dev # hot-reload daemon
@@ -29,7 +29,7 @@ function parseArgs(argv) {
29
29
  const limit = Number.parseInt(flag("--limit", "10") ?? "10", 10);
30
30
  const providerRaw = (flag("--provider", "deepseek") ?? "deepseek").toLowerCase();
31
31
  const provider = providerRaw === "ollama" ? "ollama" : "deepseek";
32
- const defaultModel = provider === "deepseek" ? "deepseek-v4-flash" : "phi4-mini:latest";
32
+ const defaultModel = provider === "deepseek" ? "deepseek-v4-flash" : "qwen3:4b-instruct-2507-q4_K_M";
33
33
  return {
34
34
  limit: Number.isFinite(limit) && limit > 0 ? limit : 10,
35
35
  dbPath: flag("--db", process.env["NLM_DB_PATH"] ?? resolve(homedir(), ".nlm/canonical.sqlite")) ??
@@ -1 +1 @@
1
- {"version":3,"file":"classify-parity.js","sourceRoot":"","sources":["../../src/cli/classify-parity.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"classify-parity.js","sourceRoot":"","sources":["../../src/cli/classify-parity.ts"],"names":[],"mappings":"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"}
package/dist/cli/nlm.js CHANGED
@@ -53,6 +53,7 @@ import { connectClaudeCode, disconnectClaudeCode, installClaudeCodeHooks, mcpCon
53
53
  import { hardenNlmDirPermissions } from "../install/nlm-dir-perms.js";
54
54
  import { ensureMcpToken } from "../install/ollama.js";
55
55
  import { connectCursor, disconnectCursor } from "../install/cursor.js";
56
+ import { describeRemove, describeUpsert, installCursorRules, installOpencodeRules, installWindsurfRules, uninstallCursorRules, uninstallOpencodeRules, uninstallWindsurfRules, } from "../install/rules-install.js";
56
57
  import { runSupersedeCommand } from "./supersede.js";
57
58
  import { getUpdateStatus } from "../core/update-check/check.js";
58
59
  import { connectHermes, disconnectHermes, hermesConfigPath } from "../install/hermes.js";
@@ -72,6 +73,7 @@ import { isDevBuild, updateCheckCachePath } from "./upgrade-helpers.js";
72
73
  import { applyEnvAssignment } from "./config-env.js";
73
74
  import { adapterFromSource } from "../core/adapters/from-source.js";
74
75
  import { runDigest } from "./digest.js";
76
+ import { installScope } from "../core/signals/install-scope.js";
75
77
  const __filename = fileURLToPath(import.meta.url);
76
78
  const __dirname = dirname(__filename);
77
79
  const MIGRATIONS_DIR = resolve(__dirname, "../../migrations");
@@ -95,15 +97,17 @@ function ollamaUrl() {
95
97
  return process.env["NLM_OLLAMA_URL"] ?? "http://localhost:11434";
96
98
  }
97
99
  function buildClassifier() {
98
- // DeepSeek V4 Flash is the default for the ingest classifier per the
99
- // 2026-05-19 parity run: ~5s/session, 90% first-try success vs Ollama
100
- // phi4-mini's 0% on the same first three sessions. Override with
101
- // NLM_CLASSIFIER=ollama if you need offline-only operation.
102
- const provider = (process.env["NLM_CLASSIFIER"] ?? "deepseek").toLowerCase();
100
+ // qwen3:4b-instruct-2507-q4_K_M is the recommended local classifier per the
101
+ // 2026-06-02 head-to-head bench (reports/classifier-comparison/2026-06-02-deepseek-v4-vs-qwen3.md):
102
+ // statistical tie with DeepSeek V4 Flash on schema validity and entity/decision
103
+ // counts, with better open-question coverage (100% vs 75%). Ollama is the
104
+ // default to keep the daemon local-first and key-free; DeepSeek remains
105
+ // available via NLM_CLASSIFIER=deepseek for users who prioritize speed.
106
+ const provider = (process.env["NLM_CLASSIFIER"] ?? "ollama").toLowerCase();
103
107
  if (provider !== "ollama")
104
108
  autoloadEnv();
105
109
  const model = process.env["NLM_CLASSIFIER_MODEL"]
106
- ?? (provider === "ollama" ? "phi4-mini:latest" : "deepseek-v4-flash");
110
+ ?? (provider === "ollama" ? "qwen3:4b-instruct-2507-q4_K_M" : "deepseek-v4-flash");
107
111
  return new ClassifierBox({ provider, model, ollamaUrl: ollamaUrl() });
108
112
  }
109
113
  async function buildAdapters(sources) {
@@ -156,6 +160,8 @@ async function buildStack() {
156
160
  // FactStore shares the SessionStore's connection so session+facts ingest
157
161
  // can commit in one transaction. Phase B.1 wires it in; no callers yet.
158
162
  const facts = storage.facts;
163
+ const signals = storage.signals;
164
+ const scope = installScope();
159
165
  // TODO(#215a): replace storage.rawDb() with port methods
160
166
  const sources = storage instanceof PgStorage
161
167
  ? new PgSourceRegistry(storage.pgPool())
@@ -171,9 +177,9 @@ async function buildStack() {
171
177
  // expose them. Classifier is wired separately for Phase D ingest.
172
178
  const embedder = new OllamaClient({ baseUrl: ollamaUrl() });
173
179
  const classifier = buildClassifier();
174
- const recall = new RecallService({ store, llm: embedder });
180
+ const recall = new RecallService({ store, llm: embedder, factStore: facts });
175
181
  const factRecall = new FactRecallService({ factStore: facts, llm: embedder });
176
- return { storage, store, facts, sources, providers, recall, factRecall, embedder, classifier };
182
+ return { storage, store, facts, signals, scope, sources, providers, recall, factRecall, embedder, classifier };
177
183
  }
178
184
  const program = new Command();
179
185
  program
@@ -193,7 +199,7 @@ program
193
199
  // non-browser callers. Idempotent: re-reads persisted token first.
194
200
  autoloadEnv();
195
201
  ensureMcpToken();
196
- const { storage, store, facts, sources, providers, recall, factRecall, embedder, classifier } = await buildStack();
202
+ const { storage, store, facts, signals, scope, sources, providers, recall, factRecall, embedder, classifier } = await buildStack();
197
203
  const { existsSync } = await import("node:fs");
198
204
  const hasMcpToken = Boolean(process.env["NLM_MCP_TOKEN"]);
199
205
  const app = createApp({
@@ -215,6 +221,8 @@ program
215
221
  ...(facts ? { factStore: facts } : {}),
216
222
  },
217
223
  } : {}),
224
+ signalStore: signals,
225
+ installScope: scope,
218
226
  embedderInfo: { provider: "ollama", model: "nomic-embed-text", dims: 768 },
219
227
  ...(existsSync(UI_DIST) ? { uiDist: UI_DIST } : {}),
220
228
  // Wire POST /mcp only when NLM_MCP_TOKEN is present. Absent = route never
@@ -268,6 +276,16 @@ program
268
276
  return t;
269
277
  })()
270
278
  : null;
279
+ // Signal retention prune. Best-effort, every 6h, default 90d. Runs on
280
+ // both SQLite and Pg backends since both expose pruneOlderThan().
281
+ const parsedRetentionDays = Number.parseInt(process.env["NLM_SIGNAL_RETENTION_DAYS"] ?? "90", 10);
282
+ const SIGNAL_RETENTION_DAYS = Number.isFinite(parsedRetentionDays) && parsedRetentionDays > 0 ? parsedRetentionDays : 90;
283
+ const SIGNAL_PRUNE_INTERVAL_MS = 6 * 60 * 60_000;
284
+ const signalPruneTimer = setInterval(() => {
285
+ const cutoff = new Date(Date.now() - SIGNAL_RETENTION_DAYS * 86_400_000).toISOString();
286
+ void signals.pruneOlderThan(cutoff).catch(() => { });
287
+ }, SIGNAL_PRUNE_INTERVAL_MS);
288
+ signalPruneTimer.unref();
271
289
  // Memo sweep runs independently of the transcript scheduler — it's the
272
290
  // backstop for SessionEnd hook unreliability (crashes, kill -9, IDE
273
291
  // force-close don't fire SessionEnd, so memo files would otherwise
@@ -288,6 +306,8 @@ program
288
306
  classifier,
289
307
  embedder,
290
308
  factStore: facts ?? null,
309
+ signalStore: signals,
310
+ installScope: scope,
291
311
  intervalMs: opts.intervalMin * 60_000,
292
312
  });
293
313
  scheduler.start();
@@ -343,6 +363,36 @@ program
343
363
  await storage.close();
344
364
  }
345
365
  });
366
+ program
367
+ .command("misses")
368
+ .description("Show sessions the agent explicitly fetched but the hook never surfaced (recall miss log)")
369
+ .option("-d, --days <n>", "lookback window", (v) => Number.parseInt(v, 10), 7)
370
+ .option("--json", "emit JSON instead of a human-readable table")
371
+ .action(async (opts) => {
372
+ const { missStats } = await import("../core/recall/miss-log.js");
373
+ const stats = await missStats(opts.days);
374
+ if (opts.json) {
375
+ process.stdout.write(`${JSON.stringify(stats, null, 2)}\n`);
376
+ return;
377
+ }
378
+ if (!stats.logPresent) {
379
+ console.error(`No miss log at ${process.env["NLM_MISS_LOG"] ?? "~/.nlm/miss-log.jsonl"}.`);
380
+ console.error("Misses are recorded by the Stop hook when the agent explicitly fetches or cites a session NLM didn't surface.");
381
+ return;
382
+ }
383
+ console.log(`Recall misses — last ${stats.days} day(s)`);
384
+ console.log(` Total miss events: ${stats.total}`);
385
+ console.log(` Distinct missed session IDs: ${stats.distinctIds}`);
386
+ if (stats.topIds.length === 0) {
387
+ console.log(" (no misses in this window)");
388
+ return;
389
+ }
390
+ console.log("");
391
+ console.log(" Top missed session IDs:");
392
+ for (const row of stats.topIds) {
393
+ console.log(` ${row.id} ×${row.count} (in ${row.conversations} conv${row.conversations === 1 ? "" : "s"})`);
394
+ }
395
+ });
346
396
  program
347
397
  .command("supersede")
348
398
  .description("Retroactively mark a session as superseded by a newer one")
@@ -363,11 +413,11 @@ program
363
413
  .description("Run TS classifier against ~/.nlm/canonical.sqlite and diff vs persisted Python output")
364
414
  .option("-l, --limit <n>", "sessions to sample", (v) => Number.parseInt(v, 10), 10)
365
415
  .option("-p, --provider <name>", "deepseek | ollama", "deepseek")
366
- .option("-m, --model <name>", "model tag (default: deepseek-v4-flash for deepseek, phi4-mini:latest for ollama)")
416
+ .option("-m, --model <name>", "model tag (default: deepseek-v4-flash for deepseek, qwen3:4b-instruct-2507-q4_K_M for ollama)")
367
417
  .option("-v, --verbose", "per-session diff lines on stderr")
368
418
  .action(async (opts) => {
369
419
  const provider = opts.provider === "ollama" ? "ollama" : "deepseek";
370
- const defaultModel = provider === "deepseek" ? "deepseek-v4-flash" : "phi4-mini:latest";
420
+ const defaultModel = provider === "deepseek" ? "deepseek-v4-flash" : "qwen3:4b-instruct-2507-q4_K_M";
371
421
  const report = await runParity({
372
422
  limit: opts.limit,
373
423
  dbPath: dbPath(),
@@ -897,7 +947,7 @@ const connect = program
897
947
  connect
898
948
  .command("codex")
899
949
  .description("Install nlm-memory as a Codex CLI plugin (marketplace + plugin add)")
900
- .option("--source <source>", "marketplace source (owner/repo, git URL, or local path)", "pbmagnet4/nlm-memory-ts")
950
+ .option("--source <source>", "marketplace source (owner/repo, git URL, or local path)", "pbmagnet4/nlm-memory")
901
951
  .option("--local", "shortcut for --source <repo-root>; use during dev")
902
952
  .option("--with-hooks", "additionally write absolute paths to ~/.codex/hooks.json (Codex Desktop fallback for openai/codex#16430)")
903
953
  .option("--dry-run", "print what would happen without invoking codex")
@@ -1015,6 +1065,7 @@ connect
1015
1065
  .command("cursor")
1016
1066
  .description("Register Cursor as an nlm source (reads state.vscdb directly — no files installed)")
1017
1067
  .option("--db-path <path>", "override path to globalStorage/state.vscdb")
1068
+ .option("--with-rules", "also install workspace rules nudge at .cursor/rules/nlm-recall.mdc")
1018
1069
  .option("--dry-run", "print what would happen without changing files")
1019
1070
  .action(async (opts) => {
1020
1071
  const storage = SqliteStorage.create({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
@@ -1028,10 +1079,17 @@ connect
1028
1079
  });
1029
1080
  if (opts.dryRun) {
1030
1081
  console.error(`nlm connect cursor (dry run): register source at ${report.adapterDbPath}${report.adapterExists ? "" : " (not found yet)"}`);
1082
+ if (opts.withRules)
1083
+ console.error(" also install workspace rules nudge at ./.cursor/rules/nlm-recall.mdc");
1031
1084
  return;
1032
1085
  }
1033
1086
  const suffix = report.adapterExists ? "" : " (DB not found — will activate when Cursor is installed)";
1034
1087
  console.error(`nlm: Cursor source ${report.action} → ${report.adapterDbPath}${suffix}`);
1088
+ if (opts.withRules) {
1089
+ const rules = installCursorRules();
1090
+ console.error(` ${describeUpsert("Cursor", rules)}`);
1091
+ console.error(" Note: workspace-scoped. Re-run inside each project where you want the nudge.");
1092
+ }
1035
1093
  }
1036
1094
  finally {
1037
1095
  await storage.close();
@@ -1041,6 +1099,7 @@ connect
1041
1099
  .command("windsurf")
1042
1100
  .description("Register Windsurf as an nlm source (reads state.vscdb files directly — no files installed)")
1043
1101
  .option("--user-dir <path>", "override path to Windsurf User directory")
1102
+ .option("--with-rules", "also install global rules nudge at ~/.codeium/windsurf/memories/global_rules.md")
1044
1103
  .option("--dry-run", "print what would happen without changing files")
1045
1104
  .action(async (opts) => {
1046
1105
  const storage = SqliteStorage.create({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
@@ -1054,15 +1113,43 @@ connect
1054
1113
  });
1055
1114
  if (opts.dryRun) {
1056
1115
  console.error(`nlm connect windsurf (dry run): register source at ${report.userDir}${report.dirExists ? "" : " (not found yet)"}`);
1116
+ if (opts.withRules)
1117
+ console.error(" also install global rules nudge at ~/.codeium/windsurf/memories/global_rules.md");
1057
1118
  return;
1058
1119
  }
1059
1120
  const suffix = report.dirExists ? "" : " (User dir not found — will activate when Windsurf is installed)";
1060
1121
  console.error(`nlm: Windsurf source ${report.action} → ${report.userDir}${suffix}`);
1122
+ if (opts.withRules) {
1123
+ const rules = installWindsurfRules();
1124
+ console.error(` ${describeUpsert("Windsurf", rules)}`);
1125
+ }
1061
1126
  }
1062
1127
  finally {
1063
1128
  await storage.close();
1064
1129
  }
1065
1130
  });
1131
+ connect
1132
+ .command("opencode")
1133
+ .description("Register OpenCode as an nlm source (reads opencode.db directly) and optionally install rules nudge")
1134
+ .option("--with-rules", "also install global rules nudge at ~/.config/opencode/AGENTS.md")
1135
+ .option("--dry-run", "print what would happen without changing files")
1136
+ .action((opts) => {
1137
+ if (opts.dryRun) {
1138
+ console.error("nlm connect opencode (dry run):");
1139
+ console.error(" OpenCode adapter is already wired via migrations/010_sources_opencode.sql — no source-registry mutation required");
1140
+ if (opts.withRules)
1141
+ console.error(" install global rules nudge at ~/.config/opencode/AGENTS.md");
1142
+ return;
1143
+ }
1144
+ console.error("nlm: OpenCode source already registered (see migration 010). No source-registry changes needed.");
1145
+ if (opts.withRules) {
1146
+ const rules = installOpencodeRules();
1147
+ console.error(` ${describeUpsert("OpenCode", rules)}`);
1148
+ }
1149
+ else {
1150
+ console.error(" Pass --with-rules to install the recall nudge at ~/.config/opencode/AGENTS.md");
1151
+ }
1152
+ });
1066
1153
  connect
1067
1154
  .command("pi")
1068
1155
  .description("Register the nlm-memory prompt-recall extension in ~/.pi/agent/settings.json")
@@ -1177,6 +1264,7 @@ disconnect
1177
1264
  disconnect
1178
1265
  .command("cursor")
1179
1266
  .description("Disable the Cursor source in the nlm registry (leaves Cursor untouched)")
1267
+ .option("--with-rules", "also remove workspace rules nudge at .cursor/rules/nlm-recall.mdc")
1180
1268
  .option("--dry-run", "print what would happen without changing files")
1181
1269
  .action(async (opts) => {
1182
1270
  const storage = SqliteStorage.create({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
@@ -1187,11 +1275,17 @@ disconnect
1187
1275
  const report = disconnectCursor(registry, { dryRun: Boolean(opts.dryRun) });
1188
1276
  if (opts.dryRun) {
1189
1277
  console.error("nlm disconnect cursor (dry run): disable Cursor source in registry");
1278
+ if (opts.withRules)
1279
+ console.error(" also remove ./.cursor/rules/nlm-recall.mdc");
1190
1280
  return;
1191
1281
  }
1192
1282
  console.error(report.action === "disabled"
1193
1283
  ? "nlm: Cursor source disabled"
1194
1284
  : "nlm: no Cursor source found in registry");
1285
+ if (opts.withRules) {
1286
+ const rules = uninstallCursorRules();
1287
+ console.error(` ${describeRemove("Cursor", rules)}`);
1288
+ }
1195
1289
  }
1196
1290
  finally {
1197
1291
  await storage.close();
@@ -1200,6 +1294,7 @@ disconnect
1200
1294
  disconnect
1201
1295
  .command("windsurf")
1202
1296
  .description("Disable the Windsurf source in the nlm registry (leaves Windsurf untouched)")
1297
+ .option("--with-rules", "also remove global rules nudge at ~/.codeium/windsurf/memories/global_rules.md")
1203
1298
  .option("--dry-run", "print what would happen without changing files")
1204
1299
  .action(async (opts) => {
1205
1300
  const storage = SqliteStorage.create({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
@@ -1210,16 +1305,35 @@ disconnect
1210
1305
  const report = disconnectWindsurf(registry, { dryRun: Boolean(opts.dryRun) });
1211
1306
  if (opts.dryRun) {
1212
1307
  console.error("nlm disconnect windsurf (dry run): disable Windsurf source in registry");
1308
+ if (opts.withRules)
1309
+ console.error(" also strip rules nudge from ~/.codeium/windsurf/memories/global_rules.md");
1213
1310
  return;
1214
1311
  }
1215
1312
  console.error(report.action === "disabled"
1216
1313
  ? "nlm: Windsurf source disabled"
1217
1314
  : "nlm: no Windsurf source found in registry");
1315
+ if (opts.withRules) {
1316
+ const rules = uninstallWindsurfRules();
1317
+ console.error(` ${describeRemove("Windsurf", rules)}`);
1318
+ }
1218
1319
  }
1219
1320
  finally {
1220
1321
  await storage.close();
1221
1322
  }
1222
1323
  });
1324
+ disconnect
1325
+ .command("opencode")
1326
+ .description("Strip the rules nudge from ~/.config/opencode/AGENTS.md (leaves OpenCode source registered)")
1327
+ .option("--with-rules", "remove rules nudge (default behavior — flag is for symmetry with connect)")
1328
+ .option("--dry-run", "print what would happen without changing files")
1329
+ .action((opts) => {
1330
+ if (opts.dryRun) {
1331
+ console.error("nlm disconnect opencode (dry run): strip rules nudge from ~/.config/opencode/AGENTS.md");
1332
+ return;
1333
+ }
1334
+ const rules = uninstallOpencodeRules();
1335
+ console.error(`nlm: ${describeRemove("OpenCode", rules)}`);
1336
+ });
1223
1337
  disconnect
1224
1338
  .command("pi")
1225
1339
  .description("Remove the nlm-memory pi extension from ~/.pi/agent/settings.json")
@@ -1258,6 +1372,32 @@ program
1258
1372
  buildHookCommand,
1259
1373
  });
1260
1374
  });
1375
+ program
1376
+ .command("improve")
1377
+ .description("Report known failure modes + recommended actions from captured signals")
1378
+ .option("--days <n>", "trailing window in days (default 14)", (v) => Number.parseInt(v, 10), 14)
1379
+ .action(async (opts) => {
1380
+ const storage = await buildStorage(dbPath());
1381
+ const scope = installScope();
1382
+ const sinceTs = new Date(Date.now() - opts.days * 86_400_000).toISOString();
1383
+ const rows = await storage.signals.listForAggregation({ installScope: scope, sinceTs });
1384
+ const { aggregateFailureModes } = await import("../core/signals/aggregate.js");
1385
+ const { recommendActions } = await import("../core/signals/recommend.js");
1386
+ const modes = aggregateFailureModes(rows);
1387
+ if (modes.length === 0) {
1388
+ console.error(`nlm improve: no failure modes above threshold in the last ${opts.days}d (${rows.length} signals).`);
1389
+ await storage.close();
1390
+ return;
1391
+ }
1392
+ console.error(`Failure modes (last ${opts.days}d, ${rows.length} signals):`);
1393
+ for (const m of modes) {
1394
+ console.error(` ${m.model} ${m.repo} ${m.kind}/${m.step ?? "-"}: ${Math.round(m.failRate * 100)}% of ${m.total}`);
1395
+ }
1396
+ console.error("\nRecommendations:");
1397
+ for (const r of recommendActions(modes))
1398
+ console.error(` [${r.kind}] ${r.text}`);
1399
+ await storage.close();
1400
+ });
1261
1401
  program
1262
1402
  .command("digest")
1263
1403
  .description("Compose a daily-activity digest from the running daemon (optionally post to Telegram)")