nlm-memory 0.5.21 → 0.6.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.
- package/README.md +129 -19
- package/dist/cli/classify-parity.js +1 -1
- package/dist/cli/classify-parity.js.map +1 -1
- package/dist/cli/nlm.js +152 -12
- package/dist/cli/nlm.js.map +1 -1
- package/dist/core/actions/overlay.d.ts +22 -0
- package/dist/core/actions/overlay.js +56 -0
- package/dist/core/actions/overlay.js.map +1 -1
- package/dist/core/adapters/pi.js +13 -3
- package/dist/core/adapters/pi.js.map +1 -1
- package/dist/core/classifier/prompt.d.ts +1 -1
- package/dist/core/classifier/prompt.js +1 -1
- package/dist/core/dataset/build-dataset.d.ts +14 -1
- package/dist/core/dataset/build-dataset.js +68 -11
- package/dist/core/dataset/build-dataset.js.map +1 -1
- package/dist/core/hook/miss-detect.d.ts +32 -0
- package/dist/core/hook/miss-detect.js +67 -0
- package/dist/core/hook/miss-detect.js.map +1 -0
- package/dist/core/hook/pointer-block.d.ts +12 -1
- package/dist/core/hook/pointer-block.js +25 -8
- package/dist/core/hook/pointer-block.js.map +1 -1
- package/dist/core/providers/provider-registry.js +1 -1
- package/dist/core/providers/provider-registry.js.map +1 -1
- package/dist/core/recall/miss-log.d.ts +47 -0
- package/dist/core/recall/miss-log.js +112 -0
- package/dist/core/recall/miss-log.js.map +1 -0
- package/dist/core/recall/query-log.d.ts +3 -0
- package/dist/core/recall/query-log.js +1 -0
- package/dist/core/recall/query-log.js.map +1 -1
- package/dist/core/recall/recall-service.d.ts +9 -0
- package/dist/core/recall/recall-service.js +81 -18
- package/dist/core/recall/recall-service.js.map +1 -1
- package/dist/core/recall/recency.d.ts +52 -0
- package/dist/core/recall/recency.js +98 -0
- package/dist/core/recall/recency.js.map +1 -0
- package/dist/core/recall/recent-log.d.ts +1 -0
- package/dist/core/recall/recent-log.js +1 -0
- package/dist/core/recall/recent-log.js.map +1 -1
- package/dist/core/recall/related-facts.d.ts +30 -0
- package/dist/core/recall/related-facts.js +134 -0
- package/dist/core/recall/related-facts.js.map +1 -0
- package/dist/core/recall/rewrite-cache.d.ts +25 -0
- package/dist/core/recall/rewrite-cache.js +60 -0
- package/dist/core/recall/rewrite-cache.js.map +1 -0
- package/dist/core/recall/rewrite-prompt.d.ts +23 -0
- package/dist/core/recall/rewrite-prompt.js +81 -0
- package/dist/core/recall/rewrite-prompt.js.map +1 -0
- package/dist/core/recall-facts/fact-recall-service.d.ts +11 -0
- package/dist/core/recall-facts/fact-recall-service.js +69 -4
- package/dist/core/recall-facts/fact-recall-service.js.map +1 -1
- package/dist/core/scheduler/scheduler.d.ts +7 -0
- package/dist/core/scheduler/scheduler.js +26 -0
- package/dist/core/scheduler/scheduler.js.map +1 -1
- package/dist/core/signals/aggregate.d.ts +21 -0
- package/dist/core/signals/aggregate.js +34 -0
- package/dist/core/signals/aggregate.js.map +1 -0
- package/dist/core/signals/failure-mode-recall.d.ts +18 -0
- package/dist/core/signals/failure-mode-recall.js +28 -0
- package/dist/core/signals/failure-mode-recall.js.map +1 -0
- package/dist/core/signals/ingest-signal.d.ts +19 -0
- package/dist/core/signals/ingest-signal.js +63 -0
- package/dist/core/signals/ingest-signal.js.map +1 -0
- package/dist/core/signals/install-scope.d.ts +8 -0
- package/dist/core/signals/install-scope.js +41 -0
- package/dist/core/signals/install-scope.js.map +1 -0
- package/dist/core/signals/recommend.d.ts +13 -0
- package/dist/core/signals/recommend.js +24 -0
- package/dist/core/signals/recommend.js.map +1 -0
- package/dist/core/storage/pg-fact-store.d.ts +5 -0
- package/dist/core/storage/pg-fact-store.js +29 -0
- package/dist/core/storage/pg-fact-store.js.map +1 -1
- package/dist/core/storage/pg-session-store.js +16 -1
- package/dist/core/storage/pg-session-store.js.map +1 -1
- package/dist/core/storage/pg-signal-store.d.ts +16 -0
- package/dist/core/storage/pg-signal-store.js +100 -0
- package/dist/core/storage/pg-signal-store.js.map +1 -0
- package/dist/core/storage/pg-storage.d.ts +2 -0
- package/dist/core/storage/pg-storage.js +3 -0
- package/dist/core/storage/pg-storage.js.map +1 -1
- package/dist/core/storage/pg-tx-context.d.ts +5 -0
- package/dist/core/storage/pg-tx-context.js +3 -0
- package/dist/core/storage/pg-tx-context.js.map +1 -1
- package/dist/core/storage/sqlite-fact-store.d.ts +5 -0
- package/dist/core/storage/sqlite-fact-store.js +30 -0
- package/dist/core/storage/sqlite-fact-store.js.map +1 -1
- package/dist/core/storage/sqlite-session-store.d.ts +3 -0
- package/dist/core/storage/sqlite-session-store.js +21 -1
- package/dist/core/storage/sqlite-session-store.js.map +1 -1
- package/dist/core/storage/sqlite-signal-store.d.ts +20 -0
- package/dist/core/storage/sqlite-signal-store.js +112 -0
- package/dist/core/storage/sqlite-signal-store.js.map +1 -0
- package/dist/core/storage/sqlite-storage.d.ts +2 -0
- package/dist/core/storage/sqlite-storage.js +6 -2
- package/dist/core/storage/sqlite-storage.js.map +1 -1
- package/dist/hook/pi-extension.d.ts +3 -1
- package/dist/hook/pi-extension.js +4 -2
- package/dist/hook/pi-extension.js.map +1 -1
- package/dist/hook/prompt-recall-hook.d.ts +10 -1
- package/dist/hook/prompt-recall-hook.js +11 -5
- package/dist/hook/prompt-recall-hook.js.map +1 -1
- package/dist/hook/recall-over-http.d.ts +11 -1
- package/dist/hook/recall-over-http.js +21 -6
- package/dist/hook/recall-over-http.js.map +1 -1
- package/dist/hook/session-start-hook.d.ts +2 -0
- package/dist/hook/session-start-hook.js +36 -3
- package/dist/hook/session-start-hook.js.map +1 -1
- package/dist/hook/stop-hook.js +16 -0
- package/dist/hook/stop-hook.js.map +1 -1
- package/dist/http/app.d.ts +9 -0
- package/dist/http/app.js +125 -10
- package/dist/http/app.js.map +1 -1
- package/dist/install/codex.d.ts +1 -1
- package/dist/install/codex.js +4 -4
- package/dist/install/codex.js.map +1 -1
- package/dist/install/ollama.d.ts +10 -0
- package/dist/install/ollama.js +19 -3
- package/dist/install/ollama.js.map +1 -1
- package/dist/install/pi.d.ts +1 -1
- package/dist/install/pi.js +1 -1
- package/dist/install/rules-content.d.ts +51 -0
- package/dist/install/rules-content.js +140 -0
- package/dist/install/rules-content.js.map +1 -0
- package/dist/install/rules-install.d.ts +35 -0
- package/dist/install/rules-install.js +79 -0
- package/dist/install/rules-install.js.map +1 -0
- package/dist/install/setup.js +63 -10
- package/dist/install/setup.js.map +1 -1
- package/dist/llm/classifier-box.d.ts +2 -1
- package/dist/llm/classifier-box.js +3 -0
- package/dist/llm/classifier-box.js.map +1 -1
- package/dist/llm/deepseek-client.d.ts +5 -2
- package/dist/llm/deepseek-client.js +51 -1
- package/dist/llm/deepseek-client.js.map +1 -1
- package/dist/llm/ollama-client.d.ts +3 -2
- package/dist/llm/ollama-client.js +45 -2
- package/dist/llm/ollama-client.js.map +1 -1
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +23 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/ports/fact-store.d.ts +0 -0
- package/dist/ports/fact-store.js +0 -0
- package/dist/ports/fact-store.js.map +1 -1
- package/dist/ports/llm-client.d.ts +24 -0
- package/dist/ports/signal-store.d.ts +30 -0
- package/dist/ports/signal-store.js +9 -0
- package/dist/ports/signal-store.js.map +1 -0
- package/dist/ports/storage.d.ts +2 -0
- package/dist/ports/transcript-adapter.d.ts +6 -0
- package/dist/shared/types.d.ts +76 -0
- package/dist/ui/assets/index-CgHq6xeZ.css +1 -0
- package/dist/ui/assets/index-DLDd-OM_.js +69 -0
- package/dist/ui/index.html +2 -2
- package/nlm/README.md +1 -1
- package/nlm/index.js +46 -21
- package/package.json +7 -5
- package/plugin/.codex-plugin/plugin.json +2 -2
- package/plugin/README.md +3 -3
- package/plugin/scripts/prompt-recall-hook.mjs +45 -20
- package/plugin/scripts/stop-hook.mjs +80 -6
- package/plugin-hermes-agent/README.md +49 -0
- package/plugin-hermes-agent/__init__.py +75 -0
- package/plugin-hermes-agent/plugin.yaml +15 -0
- package/dist/ui/assets/index-B7j5U42U.css +0 -1
- package/dist/ui/assets/index-BO0dvNHn.js +0 -69
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
|
|
12
|
-
<a href="https://github.com/pbmagnet4/nlm-memory
|
|
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> ·
|
|
23
23
|
<a href="#runtimes">Runtimes</a> ·
|
|
24
24
|
<a href="#how-recall-works">How recall works</a> ·
|
|
25
|
+
<a href="#agent-self-improvement-signals">Signals</a> ·
|
|
25
26
|
<a href="#mcp-tools">MCP</a> ·
|
|
26
27
|
<a href="#rest-api">REST API</a> ·
|
|
27
28
|
<a href="#daily-digest">Digest</a> ·
|
|
@@ -75,19 +76,19 @@ 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
|
|
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
|
-
| **Claude Code** | `nlm connect claude-code` | `~/.claude/projects/**/*.jsonl` |
|
|
83
|
-
| **Codex CLI** | `nlm connect codex` | `~/.codex/sessions/` | Marketplace plugin |
|
|
84
|
-
| **Hermes** | `nlm connect hermes` |
|
|
85
|
-
| **Hermes Agent** | `nlm connect hermes-agent` |
|
|
86
|
-
| **Cursor** | `nlm connect cursor` | Cursor IDE chat DB | MCP
|
|
87
|
-
| **Windsurf** | `nlm connect windsurf` | Windsurf user dir | MCP
|
|
88
|
-
| **OpenCode** |
|
|
89
|
-
| **Aider** | adapter active | `AIDER_CHAT_HISTORY_FILE` | MCP
|
|
90
|
-
| **pi.dev** | `nlm setup` (auto) or `nlm connect pi` | `~/.pi/agent/sessions/**/*.jsonl` | input (
|
|
83
|
+
| **Claude Code** | `nlm connect claude-code` | `~/.claude/projects/**/*.jsonl` | 6 hooks: UserPromptSubmit, SessionStart, SessionEnd, Stop, PreCompact, SubagentStart |
|
|
84
|
+
| **Codex CLI** | `nlm connect codex` | `~/.codex/sessions/` | Marketplace plugin (UserPromptSubmit + Stop) |
|
|
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 |
|
|
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.
|
|
93
94
|
|
|
@@ -99,19 +100,37 @@ 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.
|
|
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 |
|
|
111
|
+
| **SessionEnd** | Delete the per-conversation memo on session close so state files don't accumulate | always on |
|
|
110
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 |
|
|
111
113
|
| **PreCompact** | Flush the per-conversation surfaced-IDs memo so post-compaction recalls aren't gated | always on |
|
|
112
114
|
| **SubagentStart** | Record parent→subagent links so threads stay coherent across dispatches | always on |
|
|
113
115
|
|
|
114
|
-
|
|
116
|
+
**Hermes Agent** — six hooks installed to `~/.hermes/plugins/nlm-memory/` via `nlm connect hermes-agent`. All calls are fire-and-forget except `pre_llm_call`, which returns a context string for injection:
|
|
117
|
+
|
|
118
|
+
| Hook | What NLM does | Blocks turn? |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| **pre_llm_call** | POST prompt to daemon → inject pointer block of relevant prior sessions into context | yes (returns context string) |
|
|
121
|
+
| **post_llm_call** | POST assistant response to daemon → citation scan, `useful_hit_rate` update | no |
|
|
122
|
+
| **on_session_start** | Signal session open → daemon initialises per-session memo | no |
|
|
123
|
+
| **on_session_end** | Signal session close → daemon flushes memo | no |
|
|
124
|
+
| **on_session_finalize** | Signal transcript finalised → triggers async classifier run | no |
|
|
125
|
+
| **on_session_reset** | Signal session reset → daemon clears per-session state | no |
|
|
126
|
+
|
|
127
|
+
**pi.dev** — one `input` hook registered via `nlm connect pi`. Pi's extension API only exposes `input`, so transcript ingestion is handled separately by the passive adapter scanning `~/.pi/agent/sessions/`:
|
|
128
|
+
|
|
129
|
+
| Hook | What NLM does | Blocks turn? |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| **input** | Score user message → if relevant sessions found, prepend pointer block to the prompt text via `{ action: "transform" }` | yes (returns transformed text) |
|
|
132
|
+
|
|
133
|
+
All three fail-open: any daemon error yields a clean exit and never blocks the model. Switch Claude Code hooks to **shadow** mode (log-only, no injection) with `NLM_HOOK_MODE=shadow`.
|
|
115
134
|
|
|
116
135
|
### 2. MCP — explicit tools any agent can call
|
|
117
136
|
|
|
@@ -119,6 +138,86 @@ Container-hosted agents (Hermes WebUI, Codex CLI, etc.) hit the Streamable-HTTP
|
|
|
119
138
|
|
|
120
139
|
---
|
|
121
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
|
+
|
|
122
221
|
## MCP Tools
|
|
123
222
|
|
|
124
223
|
| Tool | What it does |
|
|
@@ -227,14 +326,25 @@ recall: prompt / query
|
|
|
227
326
|
| `NLM_USEFUL_HIT_LOG` | `~/.nlm/useful-hit-log.jsonl` | Citation/useful-hit ledger |
|
|
228
327
|
| `NLM_QUERY_LOG` | `~/.nlm/query-log.jsonl` | Recall query telemetry |
|
|
229
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. |
|
|
230
331
|
| `NLM_MCP_TOKEN` | auto-generated | 256-bit bearer for `/api/*` (non-browser) and `/mcp` |
|
|
231
332
|
| `NLM_MCP_CONFIG` | `~/.mcp.json` | Path the `connect`/`disconnect` commands modify |
|
|
232
333
|
| `NLM_CLASSIFIER` | `ollama` | `ollama` (local, default), `deepseek`, `openai`, `anthropic`, `openrouter`, or `openai-compatible` |
|
|
233
|
-
| `NLM_CLASSIFIER_MODEL` | `
|
|
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. |
|
|
234
335
|
| `NLM_OLLAMA_URL` | `http://localhost:11434` | Override Ollama endpoint |
|
|
235
336
|
| `NLM_ADAPTERS` | all | Comma-separated allowlist of adapters to enable |
|
|
236
337
|
| `DEEPSEEK_API_KEY` | — | Required only when classifier=deepseek |
|
|
237
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. |
|
|
238
348
|
| `TELEGRAM_BOT_TOKEN` / `TELEGRAM_CHAT_ID` | — | Required for `nlm digest --telegram` |
|
|
239
349
|
|
|
240
350
|
### Changing the classifier from the UI
|
|
@@ -352,8 +462,8 @@ The defining property is the editable timeline. mem0 and Letta append; NLM lets
|
|
|
352
462
|
## Development
|
|
353
463
|
|
|
354
464
|
```sh
|
|
355
|
-
git clone https://github.com/pbmagnet4/nlm-memory
|
|
356
|
-
cd nlm-memory
|
|
465
|
+
git clone https://github.com/pbmagnet4/nlm-memory
|
|
466
|
+
cd nlm-memory
|
|
357
467
|
npm install
|
|
358
468
|
npm run build # compile dist/ — commit the result, it ships in the repo
|
|
359
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" : "
|
|
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":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,KAAK,SAAS,MAAM,YAAY,CAAC;AAExC,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC9E,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAqDrD,SAAS,SAAS,CAAC,IAAc;IAC/B,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,QAAiB,EAAsB,EAAE;QACnE,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC7B,IAAI,CAAC,KAAK,CAAC,CAAC;YAAE,OAAO,QAAQ,CAAC;QAC9B,OAAO,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,QAAQ,CAAC;IACjC,CAAC,CAAC;IACF,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;IACjE,MAAM,WAAW,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,IAAI,UAAU,CAAC,CAAC,WAAW,EAAE,CAAC;IACjF,MAAM,QAAQ,GAAa,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC;IAC5E,MAAM,YAAY,GAAG,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,
|
|
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
|
-
//
|
|
99
|
-
// 2026-
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
|
|
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" ? "
|
|
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,
|
|
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" : "
|
|
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
|
|
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)")
|