nlm-memory 0.11.1 → 0.13.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 (130) hide show
  1. package/README.md +1 -1
  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 +323 -62
  5. package/dist/cli/nlm.js.map +1 -1
  6. package/dist/core/classifier/prompt.d.ts +70 -0
  7. package/dist/core/classifier/prompt.js +71 -2
  8. package/dist/core/classifier/prompt.js.map +1 -1
  9. package/dist/core/exemplars/extract-exemplar.d.ts +36 -0
  10. package/dist/core/exemplars/extract-exemplar.js +177 -0
  11. package/dist/core/exemplars/extract-exemplar.js.map +1 -0
  12. package/dist/core/exemplars/ingest-exemplar.d.ts +30 -0
  13. package/dist/core/exemplars/ingest-exemplar.js +71 -0
  14. package/dist/core/exemplars/ingest-exemplar.js.map +1 -0
  15. package/dist/core/exemplars/recall-code.d.ts +25 -0
  16. package/dist/core/exemplars/recall-code.js +49 -0
  17. package/dist/core/exemplars/recall-code.js.map +1 -0
  18. package/dist/core/facts/backfill-facts.d.ts +4 -2
  19. package/dist/core/facts/backfill-facts.js +18 -27
  20. package/dist/core/facts/backfill-facts.js.map +1 -1
  21. package/dist/core/hook/strip-injected-context.d.ts +24 -0
  22. package/dist/core/hook/strip-injected-context.js +73 -0
  23. package/dist/core/hook/strip-injected-context.js.map +1 -0
  24. package/dist/core/ingest/ingest-session.d.ts +4 -2
  25. package/dist/core/ingest/ingest-session.js +12 -4
  26. package/dist/core/ingest/ingest-session.js.map +1 -1
  27. package/dist/core/providers/provider-registry.d.ts +31 -11
  28. package/dist/core/providers/provider-registry.js +27 -13
  29. package/dist/core/providers/provider-registry.js.map +1 -1
  30. package/dist/core/recall/metadata-tiebreaker.d.ts +32 -0
  31. package/dist/core/recall/metadata-tiebreaker.js +59 -0
  32. package/dist/core/recall/metadata-tiebreaker.js.map +1 -0
  33. package/dist/core/recall/recall-service.js +11 -3
  34. package/dist/core/recall/recall-service.js.map +1 -1
  35. package/dist/core/recall-facts/fact-recall-service.js +75 -28
  36. package/dist/core/recall-facts/fact-recall-service.js.map +1 -1
  37. package/dist/core/scheduler/scheduler.d.ts +4 -2
  38. package/dist/core/scheduler/scheduler.js +26 -15
  39. package/dist/core/scheduler/scheduler.js.map +1 -1
  40. package/dist/core/sources/source-registry.d.ts +36 -12
  41. package/dist/core/sources/source-registry.js +59 -44
  42. package/dist/core/sources/source-registry.js.map +1 -1
  43. package/dist/core/storage/backup-rotation.d.ts +38 -0
  44. package/dist/core/storage/backup-rotation.js +82 -0
  45. package/dist/core/storage/backup-rotation.js.map +1 -0
  46. package/dist/core/storage/null-code-exemplar-store.d.ts +21 -0
  47. package/dist/core/storage/null-code-exemplar-store.js +19 -0
  48. package/dist/core/storage/null-code-exemplar-store.js.map +1 -0
  49. package/dist/core/storage/pg-fact-store.d.ts +2 -0
  50. package/dist/core/storage/pg-fact-store.js +34 -3
  51. package/dist/core/storage/pg-fact-store.js.map +1 -1
  52. package/dist/core/storage/pg-session-store.d.ts +14 -4
  53. package/dist/core/storage/pg-session-store.js +123 -2
  54. package/dist/core/storage/pg-session-store.js.map +1 -1
  55. package/dist/core/storage/pg-storage.d.ts +11 -2
  56. package/dist/core/storage/pg-storage.js +14 -2
  57. package/dist/core/storage/pg-storage.js.map +1 -1
  58. package/dist/core/storage/pg-tx-context.d.ts +2 -0
  59. package/dist/core/storage/pg-tx-context.js +12 -0
  60. package/dist/core/storage/pg-tx-context.js.map +1 -1
  61. package/dist/core/storage/sqlite-code-exemplar-store.d.ts +40 -0
  62. package/dist/core/storage/sqlite-code-exemplar-store.js +247 -0
  63. package/dist/core/storage/sqlite-code-exemplar-store.js.map +1 -0
  64. package/dist/core/storage/sqlite-fact-store.d.ts +2 -0
  65. package/dist/core/storage/sqlite-fact-store.js +30 -2
  66. package/dist/core/storage/sqlite-fact-store.js.map +1 -1
  67. package/dist/core/storage/sqlite-session-store.d.ts +21 -0
  68. package/dist/core/storage/sqlite-session-store.js +23 -0
  69. package/dist/core/storage/sqlite-session-store.js.map +1 -1
  70. package/dist/core/storage/sqlite-storage.d.ts +6 -0
  71. package/dist/core/storage/sqlite-storage.js +14 -2
  72. package/dist/core/storage/sqlite-storage.js.map +1 -1
  73. package/dist/core/storage/supersedence-log.d.ts +8 -0
  74. package/dist/core/storage/supersedence-log.js +19 -0
  75. package/dist/core/storage/supersedence-log.js.map +1 -1
  76. package/dist/hook/prompt-recall-hook.d.ts +5 -4
  77. package/dist/hook/prompt-recall-hook.js +12 -7
  78. package/dist/hook/prompt-recall-hook.js.map +1 -1
  79. package/dist/http/app.d.ts +7 -1
  80. package/dist/http/app.js +226 -83
  81. package/dist/http/app.js.map +1 -1
  82. package/dist/install/codex.d.ts +29 -1
  83. package/dist/install/codex.js +117 -15
  84. package/dist/install/codex.js.map +1 -1
  85. package/dist/install/cursor.d.ts +4 -4
  86. package/dist/install/cursor.js +7 -7
  87. package/dist/install/cursor.js.map +1 -1
  88. package/dist/install/health.d.ts +56 -0
  89. package/dist/install/health.js +133 -0
  90. package/dist/install/health.js.map +1 -0
  91. package/dist/install/setup.js +1 -1
  92. package/dist/install/setup.js.map +1 -1
  93. package/dist/install/windsurf.d.ts +4 -4
  94. package/dist/install/windsurf.js +7 -7
  95. package/dist/install/windsurf.js.map +1 -1
  96. package/dist/llm/classifier-box.d.ts +4 -0
  97. package/dist/llm/classifier-box.js +9 -2
  98. package/dist/llm/classifier-box.js.map +1 -1
  99. package/dist/llm/ollama-client.d.ts +19 -1
  100. package/dist/llm/ollama-client.js +14 -6
  101. package/dist/llm/ollama-client.js.map +1 -1
  102. package/dist/llm/ollama-code-embedder.d.ts +28 -0
  103. package/dist/llm/ollama-code-embedder.js +73 -0
  104. package/dist/llm/ollama-code-embedder.js.map +1 -0
  105. package/dist/mcp/server.d.ts +10 -0
  106. package/dist/mcp/server.js +132 -1
  107. package/dist/mcp/server.js.map +1 -1
  108. package/dist/ports/code-embedder.d.ts +27 -0
  109. package/dist/ports/code-embedder.js +15 -0
  110. package/dist/ports/code-embedder.js.map +1 -0
  111. package/dist/ports/code-exemplar-store.d.ts +45 -0
  112. package/dist/ports/code-exemplar-store.js +11 -0
  113. package/dist/ports/code-exemplar-store.js.map +1 -0
  114. package/dist/ports/fact-store.d.ts +0 -0
  115. package/dist/ports/fact-store.js.map +1 -1
  116. package/dist/ports/storage.d.ts +8 -0
  117. package/dist/shared/types.d.ts +35 -0
  118. package/dist/ui/assets/{index-CJgVgxlM.js → index-CVo7KKw2.js} +2 -2
  119. package/dist/ui/assets/{index-D5lKaksH.css → index-DCuQrzJH.css} +1 -1
  120. package/dist/ui/index.html +2 -2
  121. package/migrations/021_code_exemplars.sql +51 -0
  122. package/migrations/022_facts_retired_at.sql +17 -0
  123. package/migrations/pg/021_code_exemplars.sql +45 -0
  124. package/migrations/pg/022_facts_retired_at.sql +12 -0
  125. package/nlm/index.js +6 -1
  126. package/package.json +3 -2
  127. package/plugin/.codex-plugin/plugin.json +2 -2
  128. package/plugin/hooks/hooks.json +13 -2
  129. package/plugin/scripts/prompt-recall-hook.mjs +7 -1
  130. package/plugin/scripts/session-start-hook.mjs +275 -0
package/README.md CHANGED
@@ -335,7 +335,7 @@ recall: prompt / query
335
335
  | `NLM_MCP_TOKEN` | auto-generated | 256-bit bearer for `/api/*` (non-browser) and `/mcp` |
336
336
  | `NLM_MCP_CONFIG` | `~/.mcp.json` | Path the `connect`/`disconnect` commands modify |
337
337
  | `NLM_CLASSIFIER` | `ollama` | `ollama` (local, default), `deepseek`, `openai`, `anthropic`, `openrouter`, or `openai-compatible` |
338
- | `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. |
338
+ | `NLM_CLASSIFIER_MODEL` | `qwen3.5:4b` | Model id for the chosen provider. `qwen3.5:4b` (Q4_K_M, 4.7B) is the recommended local default — scored 74.4% decision-precision vs 58.7% for `qwen3:4b-instruct-2507-q4_K_M` (task #320, 2026-06-12). |
339
339
  | `NLM_OLLAMA_URL` | `http://localhost:11434` | Override Ollama endpoint |
340
340
  | `NLM_ADAPTERS` | all | Comma-separated allowlist of adapters to enable |
341
341
  | `DEEPSEEK_API_KEY` | — | Required only when classifier=deepseek |
@@ -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" : "qwen3:4b-instruct-2507-q4_K_M";
32
+ const defaultModel = provider === "deepseek" ? "deepseek-v4-flash" : "qwen3.5:4b";
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":"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,YAAY,CAAC;IAClF,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;QACvD,MAAM,EACJ,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,EAAE,uBAAuB,CAAC,CAAC;YACvF,OAAO,CAAC,OAAO,EAAE,EAAE,uBAAuB,CAAC;QAC7C,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,wBAAwB,CAAC,IAAI,wBAAwB;QAClH,aAAa,EAAE,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,IAAI,YAAY;QAC5D,QAAQ;QACR,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC;KACpC,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,IAAsE;IACzF,IAAI,IAAI,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;QACjC,WAAW,EAAE,CAAC;QACd,OAAO,IAAI,cAAc,CAAC,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,IAAI,YAAY,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;AAC1F,CAAC;AAED,SAAS,OAAO,CAAC,CAAwB,EAAE,CAAwB;IACjE,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC3D,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC3D,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;IAC1C,OAAO,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;AACjC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAgB;IAC9C,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACzD,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEnB,MAAM,IAAI,GAAG,EAAE;SACZ,OAAO,CACN;;;;eAIS,CACV;SACA,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAEnB,MAAM,aAAa,GAAG,IAAI,GAAG,EAAmC,CAAC;IACjE,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,QAAQ,GAAG,EAAE;aAChB,OAAO,CACN,oEAAoE,CACrE;aACA,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;aACT,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC;QAClC,MAAM,OAAO,GAAG,EAAE;aACf,OAAO,CACN,uEAAuE,CACxE;aACA,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACb,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YACtB,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,QAAQ;YACR,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;YAC1E,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;SAClE,CAAC,CAAC;IACL,CAAC;IACD,EAAE,CAAC,KAAK,EAAE,CAAC;IAEX,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAEjC,MAAM,KAAK,GAAkB,EAAE,CAAC;IAChC,IAAI,cAAc,GAAG,CAAC,CAAC;IACvB,IAAI,eAAe,GAAG,CAAC,CAAC;IAExB,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,GAAG,IAAI,CAAC,CAAC;QACT,MAAM,EAAE,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACnC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI;YAAE,SAAS;QAE7B,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACtB,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACzC,MAAM,OAAO,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACtD,MAAM,UAAU,GAAG,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;YACnF,MAAM,IAAI,GAAG,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC;YAC/C,MAAM,IAAI,GAAG,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC;YACjD,MAAM,KAAK,GAAG,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;YACxC,KAAK,CAAC,IAAI,CAAC;gBACT,SAAS,EAAE,CAAC,CAAC,EAAE;gBACf,UAAU;gBACV,OAAO,EAAE,EAAE,CAAC,KAAK;gBACjB,OAAO,EAAE,EAAE,CAAC,KAAK;gBACjB,aAAa,EAAE,IAAI;gBACnB,eAAe,EAAE,IAAI;gBACrB,WAAW,EAAE,KAAK;gBAClB,iBAAiB,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM;gBACxD,aAAa,EAAE,KAAK;aACrB,CAAC,CAAC;YACH,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBACjB,MAAM,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;gBACxC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,MAAM,GAAG,IAAI,IAAI,CAAC,MAAM,KAAK,OAAO,KAAK,GAAG,IAAI,CAAC,CAAC,EAAE,SAAS,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CACjI,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,OAAO,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACtD,MAAM,OAAO,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YAC3D,IAAI,CAAC,YAAY,qBAAqB;gBAAE,cAAc,IAAI,CAAC,CAAC;iBACvD,IAAI,CAAC,YAAY,mBAAmB;gBAAE,eAAe,IAAI,CAAC,CAAC;YAChE,KAAK,CAAC,IAAI,CAAC;gBACT,SAAS,EAAE,CAAC,CAAC,EAAE;gBACf,UAAU,EAAE,KAAK;gBACjB,OAAO,EAAE,EAAE;gBACX,OAAO,EAAE,EAAE,CAAC,KAAK;gBACjB,aAAa,EAAE,CAAC;gBAChB,eAAe,EAAE,CAAC;gBAClB,WAAW,EAAE,CAAC;gBACd,iBAAiB,EAAE,CAAC;gBACpB,aAAa,EAAE,CAAC,YAAY,qBAAqB;gBACjD,YAAY,EAAE,OAAO;aACtB,CAAC,CAAC;YACH,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBACjB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,MAAM,GAAG,IAAI,IAAI,CAAC,MAAM,KAAK,OAAO,UAAU,CAAC,CAAC,EAAE,QAAQ,OAAO,IAAI,CACtE,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;IACvD,MAAM,IAAI,GAAG,CAAC,EAAyB,EAAU,EAAE,CACjD,EAAE,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAE9F,OAAO;QACL,SAAS,EAAE,KAAK,CAAC,MAAM;QACvB,SAAS,EAAE,SAAS,CAAC,MAAM;QAC3B,cAAc;QACd,eAAe;QACf,mBAAmB,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACvE,iBAAiB,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;QAC9D,mBAAmB,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;QAClE,eAAe,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;QAC1D,KAAK;KACN,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI;IACxB,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9C,OAAO,CAAC,KAAK,CAAC,wBAAwB,IAAI,CAAC,KAAK,kBAAkB,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACjF,OAAO,CAAC,KAAK,CACX,eAAe,IAAI,CAAC,QAAQ,YAAY,IAAI,CAAC,aAAa,EAAE;QAC1D,CAAC,IAAI,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,aAAa,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CACpE,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC;IAErC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClB,OAAO,CAAC,KAAK,CAAC,wBAAwB,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;IAC1D,OAAO,CAAC,KAAK,CAAC,wBAAwB,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;IAC1D,OAAO,CAAC,KAAK,CAAC,wBAAwB,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC;IAC/D,OAAO,CAAC,KAAK,CAAC,wBAAwB,MAAM,CAAC,eAAe,EAAE,CAAC,CAAC;IAChE,OAAO,CAAC,KAAK,CAAC,wBAAwB,CAAC,MAAM,CAAC,mBAAmB,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACxF,OAAO,CAAC,KAAK,CAAC,wBAAwB,MAAM,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC7E,OAAO,CAAC,KAAK,CAAC,wBAAwB,MAAM,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC/E,OAAO,CAAC,KAAK,CAAC,wBAAwB,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AAC7E,CAAC;AAED,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,UAAU,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;AAC/D,IAAI,MAAM,EAAE,CAAC;IACX,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;QACjB,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,CAAC,CAAC,CAAC;QAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
package/dist/cli/nlm.js CHANGED
@@ -26,7 +26,7 @@
26
26
  import { fileURLToPath } from "node:url";
27
27
  import { dirname, resolve, join } from "node:path";
28
28
  import { homedir } from "node:os";
29
- import { mkdirSync, readFileSync, writeFileSync, existsSync, rmSync } from "node:fs";
29
+ import { mkdirSync, readFileSync, writeFileSync, existsSync, rmSync, copyFileSync } from "node:fs";
30
30
  import { execFileSync, spawn, spawnSync } from "node:child_process";
31
31
  import { Command } from "commander";
32
32
  import pkg from "../../package.json" with { type: "json" };
@@ -35,23 +35,24 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
35
35
  import { FactRecallService } from "../core/recall-facts/fact-recall-service.js";
36
36
  import { RecallService } from "../core/recall/recall-service.js";
37
37
  import { ProviderRegistry } from "../core/providers/provider-registry.js";
38
- import { SourceRegistry } from "../core/sources/source-registry.js";
39
38
  import { SqliteStorage } from "../core/storage/sqlite-storage.js";
40
39
  import { PgStorage } from "../core/storage/pg-storage.js";
41
- import { PgSourceRegistry } from "../core/sources/source-registry.js";
42
- import { PgProviderRegistry } from "../core/providers/provider-registry.js";
43
- import { applyPendingRestore } from "../core/storage/db-restore.js";
40
+ import { applyPendingRestore, stageRestore } from "../core/storage/db-restore.js";
41
+ import { listBackupDates, resolveBackup, runRollingBackup } from "../core/storage/backup-rotation.js";
44
42
  import { createApp } from "../http/app.js";
45
43
  import { createMcpServer } from "../mcp/server.js";
46
44
  import { ClassifierBox } from "../llm/classifier-box.js";
47
45
  import { DeepSeekClient } from "../llm/deepseek-client.js";
48
46
  import { OllamaClient } from "../llm/ollama-client.js";
47
+ import { OllamaCodeEmbedder } from "../llm/ollama-code-embedder.js";
49
48
  import { autoloadEnv } from "../llm/env-autoload.js";
50
49
  import { addHook, buildHookCommand, removeHook } from "../core/hook/claude-settings.js";
51
- import { codexBinaryAvailable, connectCodex, disconnectCodex, pluginScriptsDir, } from "../install/codex.js";
50
+ import { codexBinaryAvailable, connectCodex, disconnectCodex, repairCodex, pluginScriptsDir, } from "../install/codex.js";
52
51
  import { connectClaudeCode, disconnectClaudeCode, installClaudeCodeHooks, mcpConfigPath } from "../install/claude-code.js";
52
+ import { evaluateInstallHealth, evaluateModelHealth, evaluateRecallSmoke } from "../install/health.js";
53
+ import { codexConfigPath } from "../install/codex.js";
53
54
  import { hardenNlmDirPermissions } from "../install/nlm-dir-perms.js";
54
- import { ensureMcpToken } from "../install/ollama.js";
55
+ import { embeddingModelPresent, ensureMcpToken, ollamaModelPresent } from "../install/ollama.js";
55
56
  import { connectCursor, disconnectCursor } from "../install/cursor.js";
56
57
  import { describeRemove, describeUpsert, installCursorRules, installOpencodeRules, installWindsurfRules, uninstallCursorRules, uninstallOpencodeRules, uninstallWindsurfRules, } from "../install/rules-install.js";
57
58
  import { runSupersedeCommand } from "./supersede.js";
@@ -98,17 +99,17 @@ function ollamaUrl() {
98
99
  return process.env["NLM_OLLAMA_URL"] ?? "http://localhost:11434";
99
100
  }
100
101
  function buildClassifier() {
101
- // qwen3:4b-instruct-2507-q4_K_M is the recommended local classifier per the
102
- // 2026-06-02 head-to-head bench (reports/classifier-comparison/2026-06-02-deepseek-v4-vs-qwen3.md):
103
- // statistical tie with DeepSeek V4 Flash on schema validity and entity/decision
104
- // counts, with better open-question coverage (100% vs 75%). Ollama is the
105
- // default to keep the daemon local-first and key-free; DeepSeek remains
106
- // available via NLM_CLASSIFIER=deepseek for users who prioritize speed.
102
+ // qwen3.5:4b is the production classifier as of 2026-06-12 (NLM task #320).
103
+ // Scored 74.4% decision-precision vs qwen3:4b-instruct-2507-q4_K_M's 58.7% in
104
+ // the 2026-06-11 eval. Requires think:false (handled by ClassifierBox via
105
+ // classifierNeedsThinkDisabled) to stay within the 180s classify timeout.
106
+ // Ollama is the default to keep the daemon local-first and key-free; DeepSeek
107
+ // remains available via NLM_CLASSIFIER=deepseek for users who prioritize speed.
107
108
  const provider = (process.env["NLM_CLASSIFIER"] ?? "ollama").toLowerCase();
108
109
  if (provider !== "ollama")
109
110
  autoloadEnv();
110
111
  const model = process.env["NLM_CLASSIFIER_MODEL"]
111
- ?? (provider === "ollama" ? "qwen3:4b-instruct-2507-q4_K_M" : "deepseek-v4-flash");
112
+ ?? (provider === "ollama" ? "qwen3.5:4b" : "deepseek-v4-flash");
112
113
  return new ClassifierBox({ provider, model, ollamaUrl: ollamaUrl() });
113
114
  }
114
115
  async function buildAdapters(sources) {
@@ -163,17 +164,14 @@ async function buildStack() {
163
164
  const facts = storage.facts;
164
165
  const signals = storage.signals;
165
166
  const scope = installScope();
166
- // TODO(#215a): replace storage.rawDb() with port methods
167
- const sources = storage instanceof PgStorage
168
- ? new PgSourceRegistry(storage.pgPool())
169
- : new SourceRegistry(storage.rawDb());
167
+ const sources = storage.sources;
170
168
  await sources.seedDefaults();
171
- // TODO(#215a): replace storage.rawDb() with port methods
172
- const providers = storage instanceof PgStorage
173
- ? new PgProviderRegistry(storage.pgPool())
174
- : new ProviderRegistry(storage.rawDb());
169
+ const providers = storage.providers;
170
+ // Provider seeding is SQLite-only: it bridges from the local DEEPSEEK_API_KEY
171
+ // env, which is wrong for a hosted multi-tenant PG. PgProviderRegistry has no
172
+ // seedDefaults (not on ProviderRegistryPort), so this narrows to the SQLite case.
175
173
  if (providers instanceof ProviderRegistry)
176
- providers.seedDefaults();
174
+ await providers.seedDefaults();
177
175
  // Recall only uses embed(). Embeddings live on Ollama; DeepSeek doesn't
178
176
  // expose them. Classifier is wired separately for Phase D ingest.
179
177
  const embedder = new OllamaClient({ baseUrl: ollamaUrl() });
@@ -213,17 +211,19 @@ program
213
211
  classifier,
214
212
  sources,
215
213
  providers,
216
- // TODO(#215a): PgStorage ingest port; cast until then
217
- ...(!(storage instanceof PgStorage) ? {
218
- ingest: {
219
- classifier,
220
- embedder,
221
- store: store,
222
- ...(facts ? { factStore: facts } : {}),
223
- },
224
- } : {}),
214
+ ingest: {
215
+ classifier,
216
+ embedder,
217
+ store,
218
+ ...(facts ? { factStore: facts } : {}),
219
+ },
225
220
  signalStore: signals,
226
221
  installScope: scope,
222
+ // Code-exemplar lane. The store + code embedder are always wired; the
223
+ // NLM_CODE_EXEMPLARS_ENABLED flag gates capture (POST /api/signal) and
224
+ // the /api/exemplar + /api/recall-code routes at request time.
225
+ exemplarStore: storage.exemplars,
226
+ codeEmbedder: new OllamaCodeEmbedder({ baseUrl: ollamaUrl() }),
227
227
  embedderInfo: { provider: "ollama", model: "nomic-embed-text", dims: 768 },
228
228
  ...(existsSync(UI_DIST) ? { uiDist: UI_DIST } : {}),
229
229
  // Wire POST /mcp only when NLM_MCP_TOKEN is present. Absent = route never
@@ -294,15 +294,14 @@ program
294
294
  const memoSweep = new MemoSweepScheduler();
295
295
  memoSweep.start();
296
296
  console.error(" memo sweep: dormant cleanup every 5m (threshold 24h)");
297
- if (opts.scheduler !== false && !(storage instanceof PgStorage)) {
297
+ if (opts.scheduler !== false) {
298
298
  const adapters = await buildAdapters(sources);
299
299
  if (adapters.length === 0) {
300
300
  console.error(" scheduler: no adapters detected (set NLM_ADAPTERS to force-enable)");
301
301
  }
302
302
  else {
303
303
  const scheduler = new ScanScheduler({
304
- // TODO(#215a): PgStorage scheduler port; SQLite-only until then
305
- store: store,
304
+ store,
306
305
  adapters,
307
306
  classifier,
308
307
  embedder,
@@ -378,6 +377,58 @@ program
378
377
  await storage.close();
379
378
  }
380
379
  });
380
+ program
381
+ .command("recall-code")
382
+ .description("Semantic search over code exemplars (requires NLM_CODE_EXEMPLARS_ENABLED=1)")
383
+ .argument("<query>", "natural-language description of the task you are about to implement")
384
+ .option("-r, --repo <path>", "filter by repository path")
385
+ .option("-l, --lang <lang>", "filter by language (ts, py, go, ...)")
386
+ .option("-k <n>", "max results", (v) => Number.parseInt(v, 10), 5)
387
+ .option("--no-negatives", "exclude fail/exhausted exemplars from results")
388
+ .option("--json", "emit the raw JSON result")
389
+ .action(async (query, opts) => {
390
+ if (process.env["NLM_CODE_EXEMPLARS_ENABLED"] !== "1") {
391
+ process.stderr.write("recall-code requires NLM_CODE_EXEMPLARS_ENABLED=1\n");
392
+ process.exit(1);
393
+ }
394
+ const { storage } = await buildStack();
395
+ const { OllamaCodeEmbedder } = await import("../llm/ollama-code-embedder.js");
396
+ const { recallCode } = await import("../core/exemplars/recall-code.js");
397
+ const { installScope: getScope } = await import("../core/signals/install-scope.js");
398
+ try {
399
+ const embedder = new OllamaCodeEmbedder();
400
+ const result = await recallCode({
401
+ query,
402
+ installScope: getScope(),
403
+ ...(opts.repo ? { repo: opts.repo } : {}),
404
+ ...(opts.lang ? { lang: opts.lang } : {}),
405
+ k: opts.k ?? 5,
406
+ includeNegatives: opts.negatives !== false,
407
+ }, storage.exemplars, embedder, null);
408
+ if (opts.json) {
409
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
410
+ }
411
+ else {
412
+ const printHits = (label, hits) => {
413
+ if (hits.length === 0)
414
+ return;
415
+ process.stdout.write(`\n── ${label} ──\n`);
416
+ for (const h of hits) {
417
+ process.stdout.write(`[${h.outcome}] ${h.taskContext} (${h.repo}, dist=${h.distance.toFixed(4)})\n`);
418
+ process.stdout.write(h.code.slice(0, 400) + (h.code.length > 400 ? "\n…" : "") + "\n\n");
419
+ }
420
+ };
421
+ printHits("Positives (pass / fix)", result.positives);
422
+ printHits("Negatives (fail / exhausted) — avoid these patterns", result.negatives);
423
+ if (result.positives.length === 0 && result.negatives.length === 0) {
424
+ process.stdout.write("(no exemplars found)\n");
425
+ }
426
+ }
427
+ }
428
+ finally {
429
+ await storage.close();
430
+ }
431
+ });
381
432
  program
382
433
  .command("misses")
383
434
  .description("Show sessions the agent explicitly fetched but the hook never surfaced (recall miss log)")
@@ -430,15 +481,17 @@ program
430
481
  process.stdout.write(`${JSON.stringify({ ...result, perSource, unmeasurableSources: unmeasurable }, null, 2)}\n`);
431
482
  return;
432
483
  }
484
+ console.log(`Recall precision@k — last ${opts.days} day(s)`);
433
485
  if (result.precisionAtK === null) {
434
- console.log("No scoreable conversations in the last " + opts.days + " day(s).");
435
- console.log(" Precision joins hook-surfaced sessions (hook-log.jsonl) against explicit");
436
- console.log(" citations (citation-log.jsonl). If citations are empty, run: nlm help close-loop");
437
- return;
486
+ console.log(" Blended: no hook-lane conversations to score (hook-log.jsonl empty for this window).");
487
+ if (citationEntries.length === 0) {
488
+ console.log(" No citations recorded — run: nlm help close-loop");
489
+ }
490
+ }
491
+ else {
492
+ const pct = (result.precisionAtK * 100).toFixed(1);
493
+ console.log(` Blended: ${pct}% (${result.conversationCount} conversations scored)`);
438
494
  }
439
- const pct = (result.precisionAtK * 100).toFixed(1);
440
- console.log(`Recall precision@k — last ${opts.days} day(s)`);
441
- console.log(` Blended: ${pct}% (${result.conversationCount} conversations scored)`);
442
495
  if (perSource.length > 0) {
443
496
  console.log("\n Per source:");
444
497
  for (const row of perSource) {
@@ -478,11 +531,11 @@ program
478
531
  .description("Run TS classifier against ~/.nlm/canonical.sqlite and diff vs persisted Python output")
479
532
  .option("-l, --limit <n>", "sessions to sample", (v) => Number.parseInt(v, 10), 10)
480
533
  .option("-p, --provider <name>", "deepseek | ollama", "deepseek")
481
- .option("-m, --model <name>", "model tag (default: deepseek-v4-flash for deepseek, qwen3:4b-instruct-2507-q4_K_M for ollama)")
534
+ .option("-m, --model <name>", "model tag (default: deepseek-v4-flash for deepseek, qwen3.5:4b for ollama)")
482
535
  .option("-v, --verbose", "per-session diff lines on stderr")
483
536
  .action(async (opts) => {
484
537
  const provider = opts.provider === "ollama" ? "ollama" : "deepseek";
485
- const defaultModel = provider === "deepseek" ? "deepseek-v4-flash" : "qwen3:4b-instruct-2507-q4_K_M";
538
+ const defaultModel = provider === "deepseek" ? "deepseek-v4-flash" : "qwen3.5:4b";
486
539
  const report = await runParity({
487
540
  limit: opts.limit,
488
541
  dbPath: dbPath(),
@@ -530,8 +583,7 @@ program
530
583
  const { storage, store, facts, embedder, classifier } = await buildStack();
531
584
  try {
532
585
  const report = await backfillFacts({
533
- // TODO(#215a): PgStorage backfill port; SQLite-only until then
534
- store: store,
586
+ store,
535
587
  factStore: facts,
536
588
  classifier,
537
589
  embedder: opts.embed === false ? null : embedder,
@@ -574,8 +626,16 @@ program
574
626
  .command("mcp")
575
627
  .description("Run as an MCP stdio server (for ~/.mcp.json)")
576
628
  .action(async () => {
577
- const { recall, store, facts, factRecall } = await buildStack();
578
- const server = createMcpServer({ recall, store, factStore: facts, factRecall });
629
+ const { recall, store, facts, factRecall, storage, scope } = await buildStack();
630
+ const server = createMcpServer({
631
+ recall,
632
+ store,
633
+ factStore: facts,
634
+ factRecall,
635
+ exemplarStore: storage.exemplars,
636
+ codeEmbedder: new OllamaCodeEmbedder({ baseUrl: ollamaUrl() }),
637
+ installScope: scope,
638
+ });
579
639
  const transport = new StdioServerTransport();
580
640
  await server.connect(transport);
581
641
  });
@@ -1027,6 +1087,7 @@ connect
1027
1087
  .option("--source <source>", "marketplace source (owner/repo, git URL, or local path)", "pbmagnet4/nlm-memory")
1028
1088
  .option("--local", "shortcut for --source <repo-root>; use during dev")
1029
1089
  .option("--with-hooks", "additionally write absolute paths to ~/.codex/hooks.json (Codex Desktop fallback for openai/codex#16430)")
1090
+ .option("--repair", "first strip a stale pre-rename nlm-memory-ts install (config block + plugin + marketplace), then connect")
1030
1091
  .option("--dry-run", "print what would happen without invoking codex")
1031
1092
  .action((opts) => {
1032
1093
  if (!opts.dryRun && !codexBinaryAvailable()) {
@@ -1034,8 +1095,19 @@ connect
1034
1095
  process.exit(1);
1035
1096
  }
1036
1097
  const source = opts.local ? REPO_ROOT : opts.source;
1037
- const report = connectCodex({ source, withHooks: Boolean(opts.withHooks), dryRun: Boolean(opts.dryRun) }, pluginScriptsDir(REPO_ROOT));
1098
+ const connectOpts = { source, withHooks: Boolean(opts.withHooks), dryRun: Boolean(opts.dryRun) };
1099
+ const repair = opts.repair
1100
+ ? repairCodex(connectOpts, pluginScriptsDir(REPO_ROOT))
1101
+ : null;
1102
+ const report = repair ? repair.connect : connectCodex(connectOpts, pluginScriptsDir(REPO_ROOT));
1103
+ if (repair && !repair.dryRun) {
1104
+ console.error(repair.staleMcpRemovedFromConfig
1105
+ ? "nlm connect codex --repair: stripped stale nlm-memory-ts MCP block from config.toml"
1106
+ : "nlm connect codex --repair: no stale nlm-memory-ts MCP block found");
1107
+ }
1038
1108
  if (report.dryRun) {
1109
+ if (repair)
1110
+ console.error(" --repair: would strip any stale nlm-memory-ts block, then:");
1039
1111
  console.error("nlm connect codex (dry run):");
1040
1112
  console.error(` codex plugin marketplace add ${report.source}`);
1041
1113
  console.error(` codex plugin add ${report.pluginName}@${report.marketplaceName}`);
@@ -1063,6 +1135,9 @@ connect
1063
1135
  if (report.mcpServerWritten) {
1064
1136
  console.error(` Wrote [mcp_servers.nlm-memory] to ${report.mcpServerWritten}`);
1065
1137
  }
1138
+ else if (report.mcpServerAlreadyPresent) {
1139
+ console.error(" Left your existing [mcp_servers.nlm-memory] block in place (not overwriting it)");
1140
+ }
1066
1141
  if (report.legacyHooksWritten) {
1067
1142
  console.error(` Wrote hooks.json fallback to ${report.legacyHooksWritten}`);
1068
1143
  }
@@ -1148,9 +1223,7 @@ connect
1148
1223
  const storage = SqliteStorage.create({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
1149
1224
  await storage.init();
1150
1225
  try {
1151
- // TODO(#215a): replace storage.rawDb() with port methods
1152
- const registry = new SourceRegistry(storage.rawDb());
1153
- const report = connectCursor(registry, {
1226
+ const report = await connectCursor(storage.sources, {
1154
1227
  ...(opts.dbPath ? { dbPath: opts.dbPath } : {}),
1155
1228
  dryRun: Boolean(opts.dryRun),
1156
1229
  });
@@ -1182,9 +1255,7 @@ connect
1182
1255
  const storage = SqliteStorage.create({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
1183
1256
  await storage.init();
1184
1257
  try {
1185
- // TODO(#215a): replace storage.rawDb() with port methods
1186
- const registry = new SourceRegistry(storage.rawDb());
1187
- const report = connectWindsurf(registry, {
1258
+ const report = await connectWindsurf(storage.sources, {
1188
1259
  ...(opts.userDir ? { userDir: opts.userDir } : {}),
1189
1260
  dryRun: Boolean(opts.dryRun),
1190
1261
  });
@@ -1347,9 +1418,7 @@ disconnect
1347
1418
  const storage = SqliteStorage.create({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
1348
1419
  await storage.init();
1349
1420
  try {
1350
- // TODO(#215a): replace storage.rawDb() with port methods
1351
- const registry = new SourceRegistry(storage.rawDb());
1352
- const report = disconnectCursor(registry, { dryRun: Boolean(opts.dryRun) });
1421
+ const report = await disconnectCursor(storage.sources, { dryRun: Boolean(opts.dryRun) });
1353
1422
  if (opts.dryRun) {
1354
1423
  console.error("nlm disconnect cursor (dry run): disable Cursor source in registry");
1355
1424
  if (opts.withRules)
@@ -1377,9 +1446,7 @@ disconnect
1377
1446
  const storage = SqliteStorage.create({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
1378
1447
  await storage.init();
1379
1448
  try {
1380
- // TODO(#215a): replace storage.rawDb() with port methods
1381
- const registry = new SourceRegistry(storage.rawDb());
1382
- const report = disconnectWindsurf(registry, { dryRun: Boolean(opts.dryRun) });
1449
+ const report = await disconnectWindsurf(storage.sources, { dryRun: Boolean(opts.dryRun) });
1383
1450
  if (opts.dryRun) {
1384
1451
  console.error("nlm disconnect windsurf (dry run): disable Windsurf source in registry");
1385
1452
  if (opts.withRules)
@@ -1496,9 +1563,69 @@ program
1496
1563
  process.exit(1);
1497
1564
  }
1498
1565
  });
1566
+ async function gatherInstallProbe() {
1567
+ autoloadEnv();
1568
+ let reachable = false;
1569
+ let version = null;
1570
+ try {
1571
+ const ctrl = new AbortController();
1572
+ const timer = setTimeout(() => ctrl.abort(), 1_500);
1573
+ const res = await fetch(`http://localhost:${port()}/api/health`, { signal: ctrl.signal });
1574
+ clearTimeout(timer);
1575
+ if (res.ok) {
1576
+ const body = (await res.json());
1577
+ reachable = true;
1578
+ version = typeof body.version === "string" ? body.version : null;
1579
+ }
1580
+ }
1581
+ catch {
1582
+ // unreachable — reported as a FAIL by the evaluator
1583
+ }
1584
+ const mcpPath = mcpConfigPath();
1585
+ let claudeMcp = false;
1586
+ try {
1587
+ if (existsSync(mcpPath)) {
1588
+ const cfg = JSON.parse(readFileSync(mcpPath, "utf8"));
1589
+ claudeMcp = Boolean(cfg.mcpServers && "nlm-memory" in cfg.mcpServers);
1590
+ }
1591
+ }
1592
+ catch {
1593
+ // malformed config → treat as unconfigured (connect will rewrite it)
1594
+ }
1595
+ const codexPath = codexConfigPath();
1596
+ let codexPresent = false;
1597
+ let codexMcp = false;
1598
+ let codexStale = false;
1599
+ if (existsSync(codexPath)) {
1600
+ codexPresent = true;
1601
+ const txt = readFileSync(codexPath, "utf8");
1602
+ // Recognize the MCP table whether it's our managed (sentineled) block or a
1603
+ // hand-authored bare one — both wire the server.
1604
+ codexMcp = txt.split("\n").some((l) => l.trim() === "[mcp_servers.nlm-memory]");
1605
+ // Match the marketplace suffix, not the bare name: a [projects."…/nlm-memory-ts"]
1606
+ // trust entry is Codex's registry (not nlm's), so it must not trip this check.
1607
+ codexStale = txt.includes("@nlm-memory-ts");
1608
+ }
1609
+ const envPath = join(homedir(), ".nlm", ".env");
1610
+ return {
1611
+ daemon: { reachable, version, expectedVersion: pkg.version },
1612
+ env: {
1613
+ path: envPath,
1614
+ exists: existsSync(envPath),
1615
+ hasMcpToken: Boolean(process.env["NLM_MCP_TOKEN"]),
1616
+ },
1617
+ claudeCode: { configPath: mcpPath, mcpConfigured: claudeMcp },
1618
+ codex: {
1619
+ configPath: codexPath,
1620
+ configPresent: codexPresent,
1621
+ mcpConfigured: codexMcp,
1622
+ staleNlmMemoryTs: codexStale,
1623
+ },
1624
+ };
1625
+ }
1499
1626
  program
1500
1627
  .command("doctor")
1501
- .description("Check database integrity invariants and optionally repair safe violations")
1628
+ .description("Check database integrity invariants and install/runtime health, optionally repair safe violations")
1502
1629
  .option("--fix", "repair mechanically safe violations: delete self-loop edges (I1), restore orphaned superseded/replaced sessions to closed (I2)")
1503
1630
  .action(async (opts) => {
1504
1631
  const storage = await buildStorage(dbPath());
@@ -1535,6 +1662,7 @@ program
1535
1662
  const ALL_CHECKS = ["I1", "I2", "I3", "I4", "I5a", "I5b", "I6"];
1536
1663
  const byId = new Map(violations.map((v) => [v.id, v]));
1537
1664
  let anyFail = false;
1665
+ console.log("Database integrity:");
1538
1666
  for (const id of ALL_CHECKS) {
1539
1667
  const v = byId.get(id);
1540
1668
  if (v) {
@@ -1546,9 +1674,142 @@ program
1546
1674
  console.log(`PASS ${id}`);
1547
1675
  }
1548
1676
  }
1677
+ console.log("\nInstall & runtime health:");
1678
+ if (printHealthChecks(evaluateInstallHealth(await gatherInstallProbe())))
1679
+ anyFail = true;
1680
+ if (anyFail)
1681
+ process.exit(1);
1682
+ });
1683
+ function printHealthChecks(checks) {
1684
+ let anyFail = false;
1685
+ for (const c of checks) {
1686
+ if (c.status === "fail")
1687
+ anyFail = true;
1688
+ const label = c.status === "ok" ? "PASS" : c.status === "warn" ? "WARN" : "FAIL";
1689
+ const fix = c.fix ? `\n → fix: ${c.fix}` : "";
1690
+ console.log(`${label} ${c.id} ${c.detail}${fix}`);
1691
+ }
1692
+ return anyFail;
1693
+ }
1694
+ async function dbIntegrityCheck() {
1695
+ const storage = await buildStorage(dbPath());
1696
+ try {
1697
+ const violations = storage instanceof PgStorage
1698
+ ? await runChecksOnPg(storage.pgPool())
1699
+ : runChecksOnSqlite(storage.rawDb());
1700
+ if (violations.length === 0)
1701
+ return { id: "db-integrity", status: "ok", detail: "all invariants hold" };
1702
+ return {
1703
+ id: "db-integrity",
1704
+ status: "fail",
1705
+ detail: `violations: ${violations.map((v) => v.id).join(", ")}`,
1706
+ fix: "nlm doctor --fix",
1707
+ };
1708
+ }
1709
+ finally {
1710
+ await storage.close();
1711
+ }
1712
+ }
1713
+ async function recallSmokeCheck() {
1714
+ let reachable = false;
1715
+ let status = null;
1716
+ let wellFormed = false;
1717
+ try {
1718
+ const ctrl = new AbortController();
1719
+ const timer = setTimeout(() => ctrl.abort(), 3_000);
1720
+ const res = await fetch(`http://localhost:${port()}/api/recall?q=verify&limit=1`, { signal: ctrl.signal });
1721
+ clearTimeout(timer);
1722
+ reachable = true;
1723
+ status = res.status;
1724
+ const body = (await res.json().catch(() => null));
1725
+ wellFormed = Boolean(body && Array.isArray(body.results));
1726
+ }
1727
+ catch {
1728
+ // unreachable — evaluateRecallSmoke reports the fail
1729
+ }
1730
+ return evaluateRecallSmoke(reachable, status, wellFormed);
1731
+ }
1732
+ program
1733
+ .command("verify")
1734
+ .description("Release-gate: verify the install is wired and recall works end-to-end. Exit 1 on any failure.")
1735
+ .action(async () => {
1736
+ const provider = (process.env["NLM_CLASSIFIER"] ?? "ollama").toLowerCase();
1737
+ const classifierModel = process.env["NLM_CLASSIFIER_MODEL"] ?? (provider === "ollama" ? "qwen3.5:4b" : "deepseek-v4-flash");
1738
+ const classifierPresent = provider === "ollama" ? ollamaModelPresent(classifierModel) : true;
1739
+ const checks = [
1740
+ ...evaluateInstallHealth(await gatherInstallProbe()),
1741
+ evaluateModelHealth(embeddingModelPresent(), classifierModel, classifierPresent),
1742
+ await dbIntegrityCheck(),
1743
+ await recallSmokeCheck(),
1744
+ ];
1745
+ const anyFail = printHealthChecks(checks);
1746
+ const warns = checks.filter((c) => c.status === "warn").length;
1747
+ console.log(anyFail ? "\nVERIFY: FAIL" : warns > 0 ? `\nVERIFY: PASS (${warns} warning(s))` : "\nVERIFY: PASS");
1549
1748
  if (anyFail)
1550
1749
  process.exit(1);
1551
1750
  });
1751
+ program
1752
+ .command("backup")
1753
+ .description("Write a dated snapshot to ~/.nlm/backups/ and prune ones past the retention window")
1754
+ .option("--retention <days>", "keep snapshots from the last N days", "7")
1755
+ .action(async (opts) => {
1756
+ const retention = Number.parseInt(opts.retention, 10);
1757
+ if (!Number.isFinite(retention) || retention < 1) {
1758
+ console.error("nlm backup: --retention must be a positive integer");
1759
+ process.exit(1);
1760
+ }
1761
+ const today = new Date().toISOString().slice(0, 10);
1762
+ const storage = await buildStorage(dbPath());
1763
+ try {
1764
+ if (storage instanceof PgStorage) {
1765
+ console.error("nlm backup: only the SQLite backend is supported (Postgres: use pg_dump)");
1766
+ process.exit(1);
1767
+ }
1768
+ const result = runRollingBackup(storage.rawDb(), dbPath(), today, retention);
1769
+ console.log(`Wrote ${result.written} (${(result.bytes / 1_048_576).toFixed(1)} MiB)`);
1770
+ if (result.pruned.length > 0)
1771
+ console.log(`Pruned ${result.pruned.length} snapshot(s) older than ${retention}d`);
1772
+ }
1773
+ finally {
1774
+ await storage.close();
1775
+ }
1776
+ });
1777
+ program
1778
+ .command("restore")
1779
+ .description("Stage a dated backup for restore on next daemon start (does not overwrite the live DB in place)")
1780
+ .option("--from <date>", "backup date to restore (YYYY-MM-DD)")
1781
+ .option("--list", "list available backup dates")
1782
+ .action((opts) => {
1783
+ if (opts.list || !opts.from) {
1784
+ const dates = listBackupDates(dbPath());
1785
+ if (dates.length === 0) {
1786
+ console.log("No backups found. Run `nlm backup` (or wait for the daily job).");
1787
+ return;
1788
+ }
1789
+ console.log("Available backups:");
1790
+ for (const d of dates)
1791
+ console.log(` ${d}`);
1792
+ if (!opts.from)
1793
+ console.log("\nRestore one with: nlm restore --from <date>");
1794
+ return;
1795
+ }
1796
+ const backup = resolveBackup(dbPath(), opts.from);
1797
+ if (!backup) {
1798
+ console.error(`nlm restore: no backup for ${opts.from}. Run \`nlm restore --list\` to see options.`);
1799
+ process.exit(1);
1800
+ }
1801
+ // Copy (not move) the snapshot to a scratch candidate so the backup is
1802
+ // preserved; stageRestore renames the candidate into the pending slot.
1803
+ const candidate = `${dbPath()}.restore-candidate`;
1804
+ copyFileSync(backup, candidate);
1805
+ const validation = stageRestore(dbPath(), candidate);
1806
+ if (!validation.ok) {
1807
+ console.error(`nlm restore: ${opts.from} is not a usable backup: ${validation.error}`);
1808
+ process.exit(1);
1809
+ }
1810
+ console.log(`Staged ${opts.from} (${validation.sessions} sessions, schema v${validation.schemaVersion}).`);
1811
+ console.log("Run `nlm restart` to apply — the current DB is archived aside, not deleted.");
1812
+ });
1552
1813
  program.parseAsync().catch((e) => {
1553
1814
  console.error("nlm: fatal", e);
1554
1815
  process.exit(1);