nlm-memory 0.5.0 → 0.5.1

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 (247) hide show
  1. package/README.md +72 -34
  2. package/dist/cli/nlm.js +2 -1
  3. package/dist/cli/nlm.js.map +1 -1
  4. package/dist/http/app.js +2 -1
  5. package/dist/http/app.js.map +1 -1
  6. package/dist/mcp/server.js +20 -1
  7. package/dist/mcp/server.js.map +1 -1
  8. package/dist/ui/assets/{index-C8cpwbYJ.css → index-Beo8psd-.css} +1 -1
  9. package/dist/ui/assets/{index-CB50QnL-.js → index-CSPTTeeM.js} +8 -8
  10. package/dist/ui/index.html +2 -2
  11. package/package.json +26 -1
  12. package/.agents/plugins/marketplace.json +0 -20
  13. package/.github/workflows/ci.yml +0 -30
  14. package/docs/methodology/re-derivation-rate.md +0 -112
  15. package/docs/methodology/useful-hit-rate.md +0 -79
  16. package/docs/plans/2026-05-20-fts5-lexical-recall.md +0 -1088
  17. package/docs/plans/2026-05-20-recall-daemon-wedge-fix.md +0 -662
  18. package/docs/plans/2026-05-20-recall-hook-design.md +0 -131
  19. package/docs/plans/2026-05-20-recall-hook-implementation.md +0 -1222
  20. package/docs/plans/desktop-product.md +0 -69
  21. package/docs/plans/factstore-design.md +0 -236
  22. package/logs/CHANGELOG/CHANGELOG-2026.md +0 -1575
  23. package/logs/CHANGELOG/CHANGELOG.md +0 -209
  24. package/migrations/000_initial_schema.sql +0 -174
  25. package/migrations/001_entity_type_rename.sql +0 -17
  26. package/migrations/002_adapter_state_extend.sql +0 -12
  27. package/migrations/003_session_embeddings.sql +0 -11
  28. package/migrations/004_facts.sql +0 -46
  29. package/migrations/005_sources.sql +0 -31
  30. package/migrations/006_providers.sql +0 -33
  31. package/migrations/007_source_tokens.sql +0 -17
  32. package/migrations/008_fts_rebuild.sql +0 -9
  33. package/migrations/009_session_embedding_chunks.sql +0 -46
  34. package/migrations/010_sources_opencode.sql +0 -30
  35. package/migrations/011_sources_hermes_agent.sql +0 -30
  36. package/migrations/012_sources_aider.sql +0 -30
  37. package/migrations/013_adapter_state_failure_count.sql +0 -12
  38. package/migrations/014_sources_cursor.sql +0 -30
  39. package/migrations/015_sources_windsurf.sql +0 -30
  40. package/plugin-hermes-agent/README.md +0 -49
  41. package/plugin-hermes-agent/__init__.py +0 -75
  42. package/plugin-hermes-agent/plugin.yaml +0 -15
  43. package/scripts/backfill-citations.mjs +0 -0
  44. package/scripts/build-codex-plugin.mjs +0 -61
  45. package/scripts/deepseek-probe.mjs +0 -67
  46. package/scripts/extract-triples.mjs +0 -207
  47. package/scripts/longmemeval/embedding-cache.ts +0 -77
  48. package/scripts/longmemeval/fetch-dataset.sh +0 -25
  49. package/scripts/longmemeval/run-harness.ts +0 -315
  50. package/scripts/longmemeval/scorer.ts +0 -99
  51. package/scripts/longmemeval/tsconfig.json +0 -9
  52. package/scripts/longmemeval/types.ts +0 -35
  53. package/scripts/nlm-daily-digest.py +0 -239
  54. package/scripts/nlm-daily-digest.sh +0 -28
  55. package/src/cli/classify-parity.ts +0 -257
  56. package/src/cli/launchctl-helpers.ts +0 -49
  57. package/src/cli/nlm.ts +0 -1078
  58. package/src/core/actions/actions-log.ts +0 -118
  59. package/src/core/actions/overlay.ts +0 -117
  60. package/src/core/adapters/aider.ts +0 -205
  61. package/src/core/adapters/claude-code.ts +0 -293
  62. package/src/core/adapters/common.ts +0 -54
  63. package/src/core/adapters/cursor.ts +0 -486
  64. package/src/core/adapters/from-source.ts +0 -67
  65. package/src/core/adapters/hermes-agent.ts +0 -240
  66. package/src/core/adapters/hermes.ts +0 -277
  67. package/src/core/adapters/jsonl-generic.ts +0 -208
  68. package/src/core/adapters/opencode.ts +0 -281
  69. package/src/core/adapters/pi.ts +0 -264
  70. package/src/core/adapters/windsurf.ts +0 -386
  71. package/src/core/classifier/prompt.ts +0 -200
  72. package/src/core/dataset/build-dataset.ts +0 -463
  73. package/src/core/embedding/chunk-body.ts +0 -76
  74. package/src/core/embedding/embed-backfill.ts +0 -210
  75. package/src/core/embedding/embed-normalize.ts +0 -135
  76. package/src/core/facts/backfill-facts.ts +0 -254
  77. package/src/core/facts/extract-facts.ts +0 -50
  78. package/src/core/hook/citation-detect.ts +0 -124
  79. package/src/core/hook/cite-memo.ts +0 -68
  80. package/src/core/hook/claude-settings.ts +0 -187
  81. package/src/core/hook/gate.ts +0 -25
  82. package/src/core/hook/hook-log.ts +0 -41
  83. package/src/core/hook/memo-sweep.ts +0 -164
  84. package/src/core/hook/memo.ts +0 -67
  85. package/src/core/hook/pointer-block.ts +0 -26
  86. package/src/core/hook/select.ts +0 -32
  87. package/src/core/hook/transcript.ts +0 -121
  88. package/src/core/ingest/ingest-session.ts +0 -111
  89. package/src/core/providers/provider-models.ts +0 -100
  90. package/src/core/providers/provider-registry.ts +0 -196
  91. package/src/core/recall/citation-log.ts +0 -108
  92. package/src/core/recall/filter.ts +0 -27
  93. package/src/core/recall/index.ts +0 -6
  94. package/src/core/recall/match-fields.ts +0 -40
  95. package/src/core/recall/query-log.ts +0 -149
  96. package/src/core/recall/query-shape.ts +0 -66
  97. package/src/core/recall/recall-service.ts +0 -320
  98. package/src/core/recall/recent-log.ts +0 -59
  99. package/src/core/recall/tokenize.ts +0 -18
  100. package/src/core/recall/useful-scan.ts +0 -336
  101. package/src/core/recall-facts/fact-query-log.ts +0 -150
  102. package/src/core/recall-facts/fact-recall-service.ts +0 -327
  103. package/src/core/scheduler/scan-once.ts +0 -142
  104. package/src/core/scheduler/scheduler.ts +0 -225
  105. package/src/core/sources/source-registry.ts +0 -278
  106. package/src/core/storage/db-restore.ts +0 -133
  107. package/src/core/storage/live-status.ts +0 -45
  108. package/src/core/storage/migrate.ts +0 -72
  109. package/src/core/storage/sqlite-fact-store.ts +0 -304
  110. package/src/core/storage/sqlite-session-store.ts +0 -810
  111. package/src/hook/hook-auth.ts +0 -18
  112. package/src/hook/prompt-recall-hook.ts +0 -180
  113. package/src/hook/session-end-hook.ts +0 -81
  114. package/src/hook/session-start-hook.ts +0 -168
  115. package/src/hook/stop-hook.ts +0 -239
  116. package/src/http/app.ts +0 -1215
  117. package/src/install/claude-code.ts +0 -128
  118. package/src/install/codex.ts +0 -367
  119. package/src/install/cursor.ts +0 -68
  120. package/src/install/hermes-agent.ts +0 -76
  121. package/src/install/hermes.ts +0 -78
  122. package/src/install/nlm-dir-perms.ts +0 -55
  123. package/src/install/ollama.ts +0 -284
  124. package/src/install/setup.ts +0 -489
  125. package/src/install/windsurf.ts +0 -68
  126. package/src/llm/classifier-box.ts +0 -64
  127. package/src/llm/deepseek-client.ts +0 -150
  128. package/src/llm/env-autoload.ts +0 -55
  129. package/src/llm/ollama-client.ts +0 -189
  130. package/src/mcp/server.ts +0 -534
  131. package/src/ports/fact-store.ts +0 -102
  132. package/src/ports/llm-client.ts +0 -52
  133. package/src/ports/logger.ts +0 -16
  134. package/src/ports/session-store.ts +0 -45
  135. package/src/ports/transcript-adapter.ts +0 -55
  136. package/src/shared/types.ts +0 -149
  137. package/src/ui/App.tsx +0 -58
  138. package/src/ui/components/PromoteOpenButton.tsx +0 -65
  139. package/src/ui/components/SessionDrawer.tsx +0 -199
  140. package/src/ui/components/SideNav.tsx +0 -162
  141. package/src/ui/components/Skeleton.tsx +0 -107
  142. package/src/ui/index.html +0 -13
  143. package/src/ui/lib/actions.ts +0 -30
  144. package/src/ui/lib/api.ts +0 -92
  145. package/src/ui/lib/dataset.ts +0 -141
  146. package/src/ui/lib/registries.ts +0 -155
  147. package/src/ui/lib/view-settings.ts +0 -41
  148. package/src/ui/main.tsx +0 -15
  149. package/src/ui/pages/Live.tsx +0 -229
  150. package/src/ui/pages/Pulse.tsx +0 -415
  151. package/src/ui/pages/Recall.tsx +0 -190
  152. package/src/ui/pages/River.tsx +0 -354
  153. package/src/ui/pages/Search.tsx +0 -386
  154. package/src/ui/pages/Stub.tsx +0 -9
  155. package/src/ui/pages/Thread.tsx +0 -473
  156. package/src/ui/pages/settings/Classifier.tsx +0 -227
  157. package/src/ui/pages/settings/Data.tsx +0 -190
  158. package/src/ui/pages/settings/Index.tsx +0 -65
  159. package/src/ui/pages/settings/Labels.tsx +0 -224
  160. package/src/ui/pages/settings/Providers.tsx +0 -305
  161. package/src/ui/pages/settings/SettingsSubnav.tsx +0 -28
  162. package/src/ui/pages/settings/Sources.tsx +0 -326
  163. package/src/ui/pages/settings/Views.tsx +0 -96
  164. package/src/ui/styles.css +0 -1890
  165. package/src/ui/tsconfig.json +0 -21
  166. package/src/ui/vite.config.ts +0 -19
  167. package/tests/fixtures/claude_code/short_session.jsonl +0 -2
  168. package/tests/fixtures/claude_code/standard_iso.jsonl +0 -4
  169. package/tests/fixtures/claude_code/tool_heavy.jsonl +0 -8
  170. package/tests/fixtures/claude_code/with_subagent.jsonl +0 -7
  171. package/tests/fixtures/facts.ts +0 -17
  172. package/tests/fixtures/golden-corpus.ts +0 -85
  173. package/tests/fixtures/hermes/paired_request_dump.json +0 -24
  174. package/tests/fixtures/hermes/paired_session.json +0 -23
  175. package/tests/fixtures/hermes/request_dump.json +0 -28
  176. package/tests/fixtures/hermes/session_iso.json +0 -38
  177. package/tests/fixtures/hermes/session_unix.json +0 -38
  178. package/tests/fixtures/hermes/system_only.json +0 -18
  179. package/tests/fixtures/pi/error-connection-abort.jsonl +0 -8
  180. package/tests/fixtures/pi/short-successful.jsonl +0 -5
  181. package/tests/fixtures/pi/with-custom-message.jsonl +0 -6
  182. package/tests/fixtures/sessions.ts +0 -22
  183. package/tests/integration/backfill-facts.test.ts +0 -362
  184. package/tests/integration/citation-explicit.test.ts +0 -111
  185. package/tests/integration/cite-event.test.ts +0 -169
  186. package/tests/integration/cite-memo.test.ts +0 -87
  187. package/tests/integration/db-restore.test.ts +0 -153
  188. package/tests/integration/embed-backfill.test.ts +0 -176
  189. package/tests/integration/fact-supersedence.test.ts +0 -313
  190. package/tests/integration/fts-index.test.ts +0 -60
  191. package/tests/integration/getbyids-sqlite.test.ts +0 -100
  192. package/tests/integration/hermes-agent-hooks.test.ts +0 -248
  193. package/tests/integration/hook-claude-settings.test.ts +0 -218
  194. package/tests/integration/hook-log.test.ts +0 -54
  195. package/tests/integration/hook-memo.test.ts +0 -68
  196. package/tests/integration/hook-pre-compact.test.ts +0 -105
  197. package/tests/integration/hook-subagent-start.test.ts +0 -102
  198. package/tests/integration/http.test.ts +0 -401
  199. package/tests/integration/keyword-search-fts.test.ts +0 -66
  200. package/tests/integration/mcp-recall-logging.test.ts +0 -88
  201. package/tests/integration/mcp.test.ts +0 -260
  202. package/tests/integration/memo-sweep.test.ts +0 -91
  203. package/tests/integration/prompt-recall-hook.test.ts +0 -88
  204. package/tests/integration/provider-registry.test.ts +0 -107
  205. package/tests/integration/recall-golden.test.ts +0 -59
  206. package/tests/integration/recall-sqlite.test.ts +0 -169
  207. package/tests/integration/scheduler.test.ts +0 -391
  208. package/tests/integration/session-end-hook.test.ts +0 -48
  209. package/tests/integration/session-start-hook.test.ts +0 -126
  210. package/tests/integration/source-registry.test.ts +0 -122
  211. package/tests/integration/sqlite-fact-store.test.ts +0 -346
  212. package/tests/integration/stop-hook.test.ts +0 -560
  213. package/tests/integration/wal-checkpoint.test.ts +0 -49
  214. package/tests/unit/cli/launchctl-helpers.test.ts +0 -60
  215. package/tests/unit/core/adapters/aider.test.ts +0 -230
  216. package/tests/unit/core/adapters/claude-code.test.ts +0 -118
  217. package/tests/unit/core/adapters/cursor.test.ts +0 -485
  218. package/tests/unit/core/adapters/hermes-agent.test.ts +0 -329
  219. package/tests/unit/core/adapters/hermes.test.ts +0 -81
  220. package/tests/unit/core/adapters/jsonl-generic.test.ts +0 -142
  221. package/tests/unit/core/adapters/opencode.test.ts +0 -354
  222. package/tests/unit/core/adapters/pi.test.ts +0 -110
  223. package/tests/unit/core/adapters/windsurf.test.ts +0 -416
  224. package/tests/unit/core/classifier/prompt.test.ts +0 -126
  225. package/tests/unit/core/embedding/chunk-body.test.ts +0 -100
  226. package/tests/unit/core/facts/extract-facts.test.ts +0 -117
  227. package/tests/unit/core/filter.test.ts +0 -40
  228. package/tests/unit/core/hook/citation-detect-cite-session.test.ts +0 -96
  229. package/tests/unit/core/hook/citation-detect.test.ts +0 -124
  230. package/tests/unit/core/hook/gate.test.ts +0 -29
  231. package/tests/unit/core/hook/pointer-block.test.ts +0 -22
  232. package/tests/unit/core/hook/select.test.ts +0 -66
  233. package/tests/unit/core/match-fields.test.ts +0 -39
  234. package/tests/unit/core/mcp-cite-session.test.ts +0 -51
  235. package/tests/unit/core/providers/provider-models.test.ts +0 -101
  236. package/tests/unit/core/query-shape.test.ts +0 -92
  237. package/tests/unit/core/recall-facts/fact-recall-service.test.ts +0 -258
  238. package/tests/unit/core/recall-service.test.ts +0 -200
  239. package/tests/unit/core/storage/live-status.test.ts +0 -54
  240. package/tests/unit/core/tokenize.test.ts +0 -32
  241. package/tests/unit/core/useful-scan.test.ts +0 -537
  242. package/tests/unit/llm/embed.test.ts +0 -93
  243. package/tests/unit/llm/ollama-client.test.ts +0 -124
  244. package/tests/unit/scripts/longmemeval-scorer.test.ts +0 -114
  245. package/tsconfig.json +0 -31
  246. package/tsconfig.test.json +0 -11
  247. package/vitest.config.ts +0 -22
@@ -1,131 +0,0 @@
1
- # NLM Auto-Inject Recall Hook — Design
2
-
3
- **Status:** Approved design, ready for implementation planning
4
- **Date:** 2026-05-20
5
- **Tracking:** NLM open task #144
6
- **Repo:** nlm-memory-ts
7
-
8
- ## Problem
9
-
10
- NLM's write side works — the daemon ingests sessions automatically, the corpus is substantial, recall *coverage* is good (~92% hit rate). The unproven side is read adoption. The Recall page telemetry showed near-zero real agent recall traffic; fact recall had zero MCP calls ever. That number is confounded (the MCP server was broken until it was rewired on 2026-05-20), so adoption is untested rather than proven-bad — but the structural weakness is real regardless:
11
-
12
- Read-side recall depends on the agent *deciding* a prompt is backward-looking and *choosing* to call the recall tool. That is a soft contract — a model judgment call on every prompt, with no guarantee. A memory system that is only read when the agent remembers to ask is not reliably a memory system.
13
-
14
- This feature removes that dependency: relevant prior-session context is surfaced automatically, gated for relevance, via a Claude Code `UserPromptSubmit` hook.
15
-
16
- ## Scope
17
-
18
- - **In scope:** A Claude Code `UserPromptSubmit` hook that surfaces relevant prior sessions automatically.
19
- - **Out of scope (v1):** Hermes/Codex/Gemini equivalents (`UserPromptSubmit` is a Claude Code mechanism; other runtimes need their own integration — separate future work). Local-LLM relevance gating. Injecting full session content (v1 is pointer-only). Multi-machine sync.
20
-
21
- ## Design decisions
22
-
23
- Four decisions were settled during brainstorming:
24
-
25
- 1. **Rollout posture: shadow mode first.** Build the full hook now; it evaluates every prompt and logs what it *would* surface, but injects nothing. After 1–2 weeks the relevance gate is tuned on real logged data, then a single flag flips it live. This resolves the task #144 wait-vs-build tension: the feature is built now and measured by the real artifact, and going live is a low-risk one-flag change.
26
-
27
- 2. **Relevance gate: heuristic prefilter + recall-score threshold.** No per-prompt LLM call. A cheap heuristic excludes obviously generative prompts; everything else queries recall and is gated on the top hit's score.
28
-
29
- 3. **Injection payload: pointer-only.** When the gate fires, inject a short pointer block (matched session ids + labels), not full content. The agent pulls detail with the existing `recall_sessions` / `get_session` MCP tools. Rationale: the structural problem is the agent never *realizing memory is relevant* — pointer-only fixes exactly that (surfaces awareness automatically) while leaving retrieval depth to the agent, at minimal token cost. If shadow/live data later shows agents see pointers but don't pull, escalating to compact content injection is a follow-up.
30
-
31
- 4. **Repeat control: per-conversation dedup memo.** Each relevant session is surfaced at most once per conversation. Chosen over a turn-based rate limit because (a) both approaches require per-conversation state, so the rate limit is not actually simpler; (b) the real waste is *redundancy* (re-surfacing the same session), which the memo eliminates and a rate limit only throttles; (c) a rate limit can suppress a genuinely new, highly relevant hit purely for timing reasons, which a memory feature should never do.
32
-
33
- ## Components
34
-
35
- | Component | File | Responsibility |
36
- |---|---|---|
37
- | Gate (pure) | `src/core/hook/gate.ts` | `classifyPrompt(prompt) → "generative" \| "evaluate"`. Pure function, no I/O. Unit-tested. |
38
- | Hook entrypoint | `src/hook/prompt-recall-hook.ts` → built to `dist/hook/prompt-recall-hook.js` | Invoked by Claude Code per prompt. Reads hook JSON from stdin, runs the gate, queries recall, injects (live) or logs (shadow). |
39
- | CLI subcommand | `nlm hook install` / `nlm hook uninstall` (in `src/cli/nlm.ts`) | Adds/removes the `UserPromptSubmit` entry in `~/.claude/settings.json`. Idempotent and reversible. **Not** part of `nlm install`. |
40
- | Shadow log | `~/.nlm/hook-log.jsonl` | Append-only. One line per prompt seen: timestamp, conversation id, truncated prompt, gate decision, recall hits + scores, would-inject flag, estimated token cost. |
41
- | Per-conversation memo | `~/.nlm/hook-state/<conversation-id>.json` | The set of session ids already surfaced in this conversation. Drives dedup. |
42
-
43
- ## Gate logic
44
-
45
- The heuristic is a **conservative generative *excluder***, not a backward-looking detector. The default classification is `evaluate`; only obviously generative prompts short-circuit to `generative`.
46
-
47
- - `generative` signals (short-circuit, inject nothing, no API call): the prompt is dominated by generative intent — e.g. opens with or strongly centers on "write", "draft", "create", "compose", "brainstorm", "name", "generate", "design a", "come up with", "ideas for", "suggest a". The exact pattern set is seeded from the `workflows.md` recall trigger/non-trigger examples and refined against shadow-mode logs.
48
- - `evaluate` (default — everything else): proceed to a recall query.
49
-
50
- Rationale for the asymmetry: a false `evaluate` is cheap — recall returns weak hits and the score threshold discards them. A false `generative` (missing a backward-looking prompt) is the exact failure this feature exists to fix. So the heuristic is deliberately biased toward `evaluate`; the recall-score threshold does the real relevance filtering.
51
-
52
- ## Data flow (per prompt)
53
-
54
- 1. Claude Code fires `UserPromptSubmit` → runs `node dist/hook/prompt-recall-hook.js`, passing the hook payload as JSON on stdin (includes the user `prompt` and the conversation `session_id`).
55
- 2. The hook parses stdin and runs `classifyPrompt`.
56
- - `generative` → shadow: write a log line; live: nothing. Emit nothing. Exit 0.
57
- - `evaluate` → `GET http://localhost:3940/api/recall?q=<prompt>&mode=keyword&limit=5` with header `x-recall-source: hook`. (Keyword/FTS5, not hybrid: hybrid's Ollama embedding round-trip is ~5s, too slow for a hook that blocks prompt submission.)
58
- 3. Filter the returned hits to those with score ≥ the relevance threshold.
59
- 4. Load the per-conversation memo; drop hits whose session id was already surfaced.
60
- 5. Apply the per-conversation cap (see Token discipline).
61
- 6. Branch on mode:
62
- - **shadow** → append a log line (gate decision, surviving hits + scores, estimated token cost, would-inject flag). Emit nothing.
63
- - **live** → if any new hits remain, emit the pointer block to stdout and record those session ids in the memo. If none remain, emit nothing.
64
- 7. **Always exit 0.** Any error — daemon unreachable, malformed stdin, timeout — is caught and results in no output and a clean exit. The hook must never block or fail a prompt (fail open).
65
-
66
- ### Pointer block format (live mode)
67
-
68
- ```
69
- ## Possibly-relevant prior sessions (nlm-memory)
70
- - sess_a1b2 · FTS5 vs pgvector decision (2026-05-15)
71
- - sess_c3d4 · Semantic recall via sqlite-vec (2026-05-17)
72
- Pull detail with the recall_sessions / get_session MCP tools if relevant.
73
- ```
74
-
75
- ## Token discipline
76
-
77
- Context-bloat control is a first-class constraint, since the hook fires on every prompt.
78
-
79
- - **Pointer-only payload.** ~3 lines. Hard cap of **3 sessions per fire**, target ≤ ~60 tokens. One terse line per session: `sess_id · label (date)`.
80
- - **Each session surfaced at most once per conversation** (the dedup memo). `UserPromptSubmit` fires on every prompt; without dedup a long conversation would stack a pointer block every turn. The memo bounds total footprint by the count of *distinct relevant sessions*, not by conversation length.
81
- - **Suppress empty fires.** If the gate passes but no hit clears the score threshold, or every surviving hit was already surfaced, inject nothing.
82
- - **Per-conversation cap.** A hard ceiling of **10 distinct sessions surfaced per conversation**, total. A guardrail, not the primary mechanism — the memo plus the score threshold keep normal conversations well under it. Retained so a topic-roaming conversation cannot grow unbounded.
83
- - **Score threshold** starts conservative and is tuned in shadow mode against the logged score distribution. Under-injecting is preferred to poisoning the well (a noisy gate trains the agent to ignore injected context).
84
- - **Shadow log records estimated token cost per fire**, so the real per-conversation footprint is observed — not guessed — before going live.
85
-
86
- Expected steady state: most prompts cost zero tokens (excluded by the gate, or no new hits). A conversation injects at most a handful of ~60-token pointer blocks total, each once.
87
-
88
- ## Mode flag
89
-
90
- `NLM_HOOK_MODE` environment variable, read by the hook script. Default `shadow`. Values:
91
-
92
- - `shadow` — evaluate, log, inject nothing.
93
- - `live` — evaluate, log, inject pointer blocks.
94
-
95
- The env var is set in the hook's command entry in `~/.claude/settings.json` (so `nlm hook install` writes it). Flipping to live after the review window is a one-line settings edit; a future `nlm hook enable` convenience subcommand is possible but out of scope for v1.
96
-
97
- ## Distribution
98
-
99
- An explicit `nlm hook install` subcommand, **separate from `nlm install`**. Silently editing a user's `~/.claude/settings.json` during the main daemon install is too invasive. `nlm hook install`:
100
-
101
- - Reads `~/.claude/settings.json` (creates it if absent).
102
- - Adds a `UserPromptSubmit` hook entry pointing at the built hook script, with `NLM_HOOK_MODE=shadow` in its environment.
103
- - Is idempotent — re-running does not duplicate the entry.
104
-
105
- `nlm hook uninstall` removes exactly that entry and nothing else.
106
-
107
- ## Failure modes
108
-
109
- | Condition | Behavior |
110
- |---|---|
111
- | Daemon down / `/api/recall` unreachable | Catch, emit nothing, exit 0. |
112
- | Malformed or empty stdin | Catch, emit nothing, exit 0. |
113
- | Recall query slow | Bounded by a short timeout (≈1s); on timeout, emit nothing, exit 0. |
114
- | Memo file missing/corrupt | Treat as empty memo; continue. |
115
- | `~/.claude/settings.json` malformed during `nlm hook install` | Abort with a clear error; do not write a broken file. |
116
-
117
- The invariant: the hook never blocks, delays meaningfully, or fails a prompt. Every abnormal path is fail-open.
118
-
119
- ## Testing
120
-
121
- - **`gate.ts`** — unit tests over a fixture set of generative vs retrospective prompts, seeded from the `workflows.md` recall trigger/non-trigger examples.
122
- - **Hook script** — integration tests with a stubbed recall fetch: asserts shadow-mode logs and injects nothing; asserts live-mode injects the pointer block; asserts the dedup memo suppresses a repeat surfacing within one conversation; asserts fail-open on a simulated daemon-down.
123
- - **`nlm hook install` / `uninstall`** — integration tests against a temp `settings.json`: install adds the entry and is idempotent on re-run; uninstall removes exactly its own entry and leaves the rest of the file intact.
124
-
125
- ## Open calibration items (resolved in shadow mode, not now)
126
-
127
- - The exact generative-excluder pattern set.
128
- - The recall-score threshold value.
129
- - Whether the per-conversation cap of 10 is ever approached in practice.
130
-
131
- These are intentionally left to empirical tuning against `~/.nlm/hook-log.jsonl` during the shadow window.